从一次报警,走到每月都能检查的设备
S07解决探头状态和整插头断线资格,这篇把它变成可维护原型:断网时仍有本地声光、湿态RAM锁存、实体TEST和干燥ACK;HA负责有资格的通知,不承担本地采样。自检不向系统伪造一场漏水。
指定低压器材和台架,无备用电源,也不控制市电阀门。尚未实水、听测、实板或运行HA,不能替代合规漏水/财产保护装置。下载包包含完整配置、状态机、HA包、许可与空维护记录。
参考:[1] DFRobot SEN0121厂家电压/AO与感应说明[2] 官方v1.1板卡引脚与供电[6] Murata P37E-24 2012-02-01:p9型号表/p13外部驱动及DC注意事项
偏置电阻的位置,比一个阈值更重要
SEN0121供3V3,AO经RA100kΩ到SIG,3V3经RB100kΩ到SIG;RA/RB都放可拆探头侧。控制器保留RC100kΩ到GND与100nF到GND,SIG接GPIO7/J1-7 ADC1_CH6。三个电阻±1%。理想SIG=(AO+3.3)/3,干约1.1、湿约2.1、整插头或SIG断开约0V。不是任意单线故障都可分辨。
GPIO4经330Ω到WP7113ID阳极/阴极地;GPIO5 TEST、GPIO6 ACK,各外接10kΩ到3V3,按钮到地。GPIO10经330Ω和无极性100nF串联到PKM13EPYH4002-B0压电端A,端B地,A/B并2kΩ。电容额定≥16V,电阻0.25W;4kHz音门内50%占空比。
这是指定外驱动压电元件,不能换电磁/大电流有源蜂鸣器直挂GPIO。串联电容隔直流、并2k泄放参照厂家外驱动注意事项;名义边沿电流由330Ω约束约10mA,实际端电压/声压受电路和安装影响,不能照抄目录3Vp-p测试音量。先断电装配,开发板/USB/压电器留在干燥处。
参考:[1] DFRobot SEN0121厂家电压/AO与感应说明[2] 官方v1.1板卡引脚与供电[6] Murata P37E-24 2012-02-01:p9型号表/p13外部驱动及DC注意事项[11] Kingbright WP7113ID官方V.14A器件资料
本地状态机不等HA来作决定
固定ESPHome2026.9.1/IDF5.5.5/Python3.12.13,完整pip锁随包。GPIO7每秒16次平均、12db衰减;0.90–1.35V判DRY,1.60–2.20V判WET,其余含NaN为FAULT。DRY/WET连续3次才确认;中间CHECKING。超过5秒无输入FAULT,所有年龄用IDF完整64位时钟。窗口须按全回路实际电压核查,不是湿度百分数。
湿态确认后锁存,回干仍报警;只有近期有效DRY且TEST结束才接受ACK。FAULT/CHECKING不等于干燥。状态机每20ms更新输出,250ms发布状态;Wi-Fi/API的reboot_timeout=0,网络离线不会单独触发重启,但仍受真实电源/程序故障影响。
esphome:
name: hixinwu-h10
friendly_name: Hixinwu H10
min_version: 2026.9.1
includes:
- h10_alarm.h
esp32:
board: esp32-s3-devkitc-1
variant: ESP32S3
flash_size: 8MB
flash_mode: qio
flash_frequency: 80MHz
toolchain: esp-idf
framework:
type: esp-idf
version: 5.5.5
psram:
mode: octal
speed: 80MHz
logger:
baud_rate: 115200
hardware_uart: UART0
level: DEBUG
api:
encryption:
key: !secret api_key
reboot_timeout: 0s
ota:
- platform: esphome
encryption:
key: !secret api_key
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
reboot_timeout: 0s
ap:
ssid: "hixinwu-h10 Recovery"
password: !secret fallback_password
ap_timeout: 90s
captive_portal:
switch:
- platform: gpio
id: indicator
pin: GPIO4
internal: true
restore_mode: ALWAYS_OFF
output:
- platform: ledc
id: piezo_signal
pin: GPIO10
frequency: 4000Hz
zero_means_zero: true
sensor:
- platform: adc
id: loop_voltage
name: "Loop Voltage"
pin: GPIO7
attenuation: 12db
samples: 16
sampling_mode: avg
update_interval: 1s
on_value:
then:
- lambda: |-
h10::device().update(x,h10::now_ms());
ESP_LOGI("H10","v=%.3f circuit=%s; qualify actual complete circuit",x,h10::device().circuit_label());
binary_sensor:
- platform: gpio
id: self_test_button
name: "Self Test Physical"
pin:
number: GPIO5
inverted: true
mode: INPUT
filters:
- delayed_on_off: 30ms
on_press:
then:
- lambda: |-
if(!h10::device().start_test(h10::now_ms()))ESP_LOGW("H10","Self-test refused: require confirmed dry and no latched alarm");
- platform: gpio
id: acknowledge_button
name: "Acknowledge Physical"
pin:
number: GPIO6
inverted: true
mode: INPUT
filters:
- delayed_on_off: 30ms
on_press:
then:
- lambda: |-
if(!h10::device().acknowledge(h10::now_ms()))ESP_LOGW("H10","Ack refused: require confirmed dry and test finished");
- platform: template
id: input_valid
name: "Input Valid"
entity_category: diagnostic
- platform: template
id: wet_now
name: "Wet Now Raw"
device_class: moisture
- platform: template
id: alarm_latched
name: "Alarm Latched Raw"
- platform: template
id: self_test_active
name: "Self Test Active"
- platform: template
id: tone_command
name: "Tone Command"
entity_category: diagnostic
button:
- platform: template
name: "Start Self Test"
on_press:
then:
- lambda: |-
if(!h10::device().start_test(h10::now_ms()))ESP_LOGW("H10","Self-test refused: require confirmed dry and no latched alarm");
- platform: template
name: "Acknowledge Dry Alarm"
on_press:
then:
- lambda: |-
if(!h10::device().acknowledge(h10::now_ms()))ESP_LOGW("H10","Ack refused: still wet/fault/checking or test active");
text_sensor:
- platform: template
id: circuit_state
name: "Circuit State"
- platform: template
id: device_phase
name: "Device Phase"
interval:
- interval: 20ms
then:
- lambda: |-
auto &d=h10::device();const auto now=h10::now_ms();d.tick(now);
if(d.led(now))id(indicator).turn_on();else id(indicator).turn_off();
id(piezo_signal).set_level(d.tone(now)?0.5f:0.0f);
- interval: 250ms
then:
- lambda: |-
auto &d=h10::device();const auto now=h10::now_ms();
id(input_valid).publish_state(d.valid());
id(wet_now).publish_state(d.circuit==h10::Circuit::WET);
id(alarm_latched).publish_state(d.latched);
id(self_test_active).publish_state(d.testing(now));
id(tone_command).publish_state(d.tone(now));
id(circuit_state).publish_state(d.circuit_label());
id(device_phase).publish_state(d.phase(now));
参考:[3] ESPHome2026.9.1 S3 ADC采样/衰减实现[4] ESPHome2026.9.1 LEDC频率/占空比[5] ESPHome2026.9.1 FloatOutput的zero_means_zero[10] IDF5.5.5完整64位单调时钟
TEST要靠人看灯听声,ACK要先确认现场
干态3次确认、没有锁存时按TEST,5秒闪灯和间歇4kHz;该功能检查输出命令,再由人看灯/听声,不证明传感器已经被水触发。真实湿态或故障输入取消测试,不能被测试遮住。
在独立小水盘只湿润感应区,3次WET后LED常亮、200ms/2s音门;仍湿或FAULT时ACK拒绝。干燥并连续3次DRY后ACK才清锁存。断线故障另用100ms/5s音门与闪灯;历史湿锁存保留时优先报警,状态为ALARM_AND_FAULT。
锁存只在RAM:RESET、断电、OTA会清掉历史。仍湿会重新3次确认;如果已干,可重新进入DRY而无需ACK。复位不是现场消警证据,必须记录原因、前后电压/现场和人工确认;不能承诺跨掉电报警史。
- 做到这里,你应该看到
- 先在真实干燥台架完成TEST,再做受控湿态;不为了消声按RESET掩盖维护记录。
参考:[1] DFRobot SEN0121厂家电压/AO与感应说明[4] ESPHome2026.9.1 LEDC频率/占空比[6] Murata P37E-24 2012-02-01:p9型号表/p13外部驱动及DC注意事项[10] IDF5.5.5完整64位单调时钟
先做有资格的HA报警,再选择手机推送
HA集成后实查Input Valid、Alarm Latched Raw、Circuit State、Self Test Active的真实实体ID,逐一替换ha-package.yaml。H10 Alarm Active以Input Valid为availability;无效时原始Wet Now Raw的false不能解释成干燥。
默认只用固定notification_id=hixinwu_h10_water创建/更新同一卡;普通重连不会为每个旧状态增一张卡,HA重启可能重建卡,它不是持久事件数据库。FAULT用另一张卡,修复核对后人工关闭。有效干态且锁存已清(正常ACK或重启)才关闭水卡并清phone helper;因此要结合维护表区分消警原因。
把HA包放/config/packages并合并packages配置,HA先检查配置成功再重启。本轮仅做实际YAML解析/Jinja分支夹具与固定源码核对,没有运行HA检查器、引擎或手机通知。
# HA Core2026.10.2 source-checked only; replace all actual source entity IDs.
# Main package creates one fixed-ID HA notification card; no phone push enabled.
input_boolean:
h10_phone_sent:
name: "H10 Phone Sent For Current Incident"
# No initial value: restore across HA restarts instead of rearming each time.
template:
- binary_sensor:
- name: "H10 Alarm Active"
unique_id: hixinwu_h10_alarm_active_v1
device_class: moisture
availability: "{{ is_state('binary_sensor.hixinwu_h10_input_valid','on') }}"
state: "{{ is_state('binary_sensor.hixinwu_h10_alarm_latched_raw','on') }}"
automation:
- id: hixinwu_h10_card_v1
alias: "H10 create or update the same alarm card"
triggers:
- trigger: state
entity_id: binary_sensor.h10_alarm_active
to: "on"
- trigger: homeassistant
event: start
conditions:
- condition: template
value_template: "{{ is_state('binary_sensor.h10_alarm_active','on') }}"
actions:
- action: persistent_notification.create
data:
notification_id: hixinwu_h10_water
title: "H10 漏水报警待确认"
message: "现场可能已湿或曾湿后锁存。先检查真实探头/电压/设备状态;确认干燥后按实体 ACK;重启也会清除设备RAM锁存,不能作为历史消警证据。网络恢复只更新这张卡,不补发每条旧状态。"
mode: single
- id: hixinwu_h10_rearm_v1
alias: "H10 rearm on confirmed valid dry with latch cleared"
triggers:
- trigger: state
entity_id: binary_sensor.h10_alarm_active
to: "off"
conditions:
- condition: template
value_template: >-
{{ is_state('binary_sensor.hixinwu_h10_input_valid','on')
and is_state('sensor.hixinwu_h10_circuit_state','DRY')
and is_state('binary_sensor.hixinwu_h10_alarm_latched_raw','off')
and not is_state('binary_sensor.hixinwu_h10_self_test_active','on') }}
actions:
- action: input_boolean.turn_off
target:
entity_id: input_boolean.h10_phone_sent
- action: persistent_notification.dismiss
data:
notification_id: hixinwu_h10_water
mode: single
- id: hixinwu_h10_fault_card_v1
alias: "H10 input fault has a separate card"
triggers:
- trigger: state
entity_id: sensor.hixinwu_h10_circuit_state
to: "FAULT"
for: "00:00:05"
actions:
- action: persistent_notification.create
data:
notification_id: hixinwu_h10_fault
title: "H10 探头回路不可用"
message: "检查整只插头/SIG支路、偏置电阻位置、实际GPIO7电压;此状态不等于干燥。修复后核对三次有效输入,并手动关闭故障卡。"
mode: single
参考:[7] HA2026.10.2 fixed notification_id更新语义[8] HA2026.10.2 helper恢复语义[9] HA2026.10.2模板实体可用资格
网络恢复发布当前状态,不编造每一次旧通知
断Wi-Fi时本地采样/声光继续,HA可能不可用;没有离线通知全量队列。同次供电中离线湿过的锁存仍可在重连后看到;期间掉电丢掉的历史无法补回。默认下载不会发送手机消息。
可选phone-automation.example.yaml初始禁用,替换真实Companion App服务,先手动动作验证手机/权限/网络再启用。它在notify动作返回后才置helper,helper无initial恢复旧值,普通重连按该标记去重。发送与写标记之间崩溃、动作超时仍可能重复或漏达;动作返回也不是手机/人收到的确认。不要写成exactly-once报警承诺。
# OPTIONAL, disabled until the real Companion App service is replaced and tested.
# Merge this automation list into HA; it relies on the main package helper/rearm.
automation:
- id: hixinwu_h10_optional_phone_v1
alias: "H10 optional phone once per acknowledged incident"
initial_state: false
triggers:
- trigger: state
entity_id: binary_sensor.h10_alarm_active
to: "on"
conditions:
- condition: template
value_template: >-
{{ is_state('binary_sensor.h10_alarm_active','on')
and is_state('input_boolean.h10_phone_sent','off') }}
actions:
- action: notify.mobile_app_replace_with_real_device
data:
title: "H10 漏水提醒"
message: "检查现场与设备锁存;普通重连按helper去重;HA崩溃或通知响应失败仍可能重复,发送也不证明手机收到。"
- action: input_boolean.turn_on
target:
entity_id: input_boolean.h10_phone_sent
mode: single
参考:[7] HA2026.10.2 fixed notification_id更新语义[8] HA2026.10.2 helper恢复语义[9] HA2026.10.2模板实体可用资格
每月按同一张空表检查,先修回路再消警
维护流程:备份版本/记录→检查腐蚀与三电阻位置→干态3次→TEST听看→小水盘湿态3次→湿ACK拒绝→干ACK→SIG断线FAULT→恢复→断网本地报警→重连同一卡→手机实测(若启用)。换探头、线束、电源或搬动后也重做。maintenance.csv与observations.csv都只保留表头,待真实执行。
断线实验预先断电安装信号SPST,运行时只操作该低压信号开关,不挪带电电源线。始终FAULT先量SIG/电源与RA-RB-RC位置,不能盲改窗口。无声先看Tone Command,再在干燥台架量4kHz/50%及压电端Vpp,不能只看软件开关。固件故障用桥USB刷回保存版本;先保存现场/报警并记复位,它会丢RAM历史。
| 试验 | 预期软件路径 | 仍需物理确认 |
|---|---|---|
| 干态TEST | 5s输出、真实湿/故障取消 | 灯亮/声音/实际GPIO波形 |
| 湿态后回干 | 锁存保持、干ACK才清 | 真实探头状态和电压 |
| SIG/整插头断开 | FAULT,不当干燥 | 连接器与实际电压 |
| 断网后重连 | 本地继续、HA当前状态/固定卡 | 实际本地报警与HA收件 |
| RESET/OTA | RAM清零,仍湿重新确认 | 维护表记复位前后现场 |
参考:[1] DFRobot SEN0121厂家电压/AO与感应说明[3] ESPHome2026.9.1 S3 ADC采样/衰减实现[4] ESPHome2026.9.1 LEDC频率/占空比[6] Murata P37E-24 2012-02-01:p9型号表/p13外部驱动及DC注意事项
通过的是构建与同源逻辑,现场结果仍空白
本轮config与固件编译Exit0;同源Device主机覆盖3次确认、湿锁存、湿/故障/测试ACK拒绝、自检优先级、NaN/窗口空隙、完整32位周期后的陈旧及恢复。HA包实际Jinja分支/fixed ID/helper门在主机夹具检查,不能替代HA自动化或实际声光。
指定低压回路、SEN0121、压电器、LED、HA和手机的观察均pending。只有按空表验证现场干湿/断线/声光与恢复后,才可另加硬件记录;软件音门、输出电平命令和通知调用不自动升级为实物成功。
参考:[3] ESPHome2026.9.1 S3 ADC采样/衰减实现[4] ESPHome2026.9.1 LEDC频率/占空比[5] ESPHome2026.9.1 FloatOutput的zero_means_zero[7] HA2026.10.2 fixed notification_id更新语义[8] HA2026.10.2 helper恢复语义[9] HA2026.10.2模板实体可用资格[10] IDF5.5.5完整64位单调时钟
资料来源与版本说明
已实际阅读文末固定版本的关键实现与指定厂商资料,来源/构建/主机夹具分别留证。具体板卡、固件和依赖范围见版本记录;指定实板、HA与现场结果尚未执行。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。
- DFRobot SEN0121厂家电压/AO与感应说明
- 官方v1.1板卡引脚与供电
- ESPHome2026.9.1 S3 ADC采样/衰减实现
- ESPHome2026.9.1 LEDC频率/占空比
- ESPHome2026.9.1 FloatOutput的zero_means_zero
- Murata P37E-24 2012-02-01:p9型号表/p13外部驱动及DC注意事项
- HA2026.10.2 fixed notification_id更新语义
- HA2026.10.2 helper恢复语义
- HA2026.10.2模板实体可用资格
- IDF5.5.5完整64位单调时钟
- Kingbright WP7113ID官方V.14A器件资料