家庭自动化 / 项目实战发布 内容更新

可维护漏水设备:本地报警、自检与 HA 通知

在断线可辨的S07回路上增加实体TEST/ACK、低压声光、湿态锁存和有资格的HA提醒,按月维护并如实记录重启丢失历史。

读完能做什么
得到可构建的本地声光漏水原型、实体自检/干燥ACK和HA通知配置,按空维护表逐项检查与恢复,区分故障、湿态和历史锁存。
开始之前
先完成所列前置,能检查低压接线、串口与工具版本;准备独立台架和备份。
所需器材
官方S3 v1.1 N8R8、SEN0121、三只100kΩ±1%、100nF回路滤波、TEST/ACK按钮各10kΩ上拉、WP7113ID红LED/330Ω;Murata PKM13EPYH4002-B0、压电串330Ω/100nF≥16V,并2kΩ;现有HA台架
版本组合
开发框架:ESPHome2026.9.1ESP-IDF:5.5.5板卡:DevKitC-1 v1.1 / WROOM-1-N8R8Python / HA参考源码:3.12.13 / Core2026.10.2压电资料:Murata P37E-24 / 2012-02-01
适用范围

仅3.3V低压台架与指定三电阻回路;SEN0121非百分比,整插头/SIG断线覆盖不等于任意故障;报警锁存仅RAM,HA/水/声光待实测。

这篇验证到了哪一步

资料核对已通过
作者逐章核查及固定源码比对见 docs/esp32-evidence/home-station.md 与 home-station-sources.json;不是独立复核结论。验证日期:
构建 / 配置已通过
实际 config/固件编译原日志与当前源/生成CPP/固件SHA见 docs/esp32-evidence/home-station-builds.json。对象:esp32s3 / N8R8版本:ESPHome2026.9.1 / IDF5.5.5验证日期:
仿真已通过
同源主机模型及明确范围内的本机服务/模板夹具通过;不是芯片仿真、HA引擎或硬件,见 home-station-host.json。对象:macOS arm64 host fixture验证日期:
本站实板待验证
尚未完成指定板、模块、电源、HA、OTA或长时间现场实验;空观察表保留待填。

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 项目实战

从一次报警,走到每月都能检查的设备

S07解决探头状态和整插头断线资格,这篇把它变成可维护原型:断网时仍有本地声光、湿态RAM锁存、实体TEST和干燥ACK;HA负责有资格的通知,不承担本地采样。自检不向系统伪造一场漏水。

指定低压器材和台架,无备用电源,也不控制市电阀门。尚未实水、听测、实板或运行HA,不能替代合规漏水/财产保护装置。下载包包含完整配置、状态机、HA包、许可与空维护记录。

参考:[1] DFRobot SEN0121厂家电压/AO与感应说明[2] 官方v1.1板卡引脚与供电[6] Murata P37E-24 2012-02-01:p9型号表/p13外部驱动及DC注意事项

02 / 项目实战

偏置电阻的位置,比一个阈值更重要

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器件资料

03 / 项目实战

本地状态机不等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位单调时钟

04 / 项目实战

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位单调时钟

05 / 项目实战

先做有资格的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模板实体可用资格

06 / 项目实战

网络恢复发布当前状态,不编造每一次旧通知

断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模板实体可用资格

07 / 项目实战

每月按同一张空表检查,先修回路再消警

维护流程:备份版本/记录→检查腐蚀与三电阻位置→干态3次→TEST听看→小水盘湿态3次→湿ACK拒绝→干ACK→SIG断线FAULT→恢复→断网本地报警→重连同一卡→手机实测(若启用)。换探头、线束、电源或搬动后也重做。maintenance.csv与observations.csv都只保留表头,待真实执行。

断线实验预先断电安装信号SPST,运行时只操作该低压信号开关,不挪带电电源线。始终FAULT先量SIG/电源与RA-RB-RC位置,不能盲改窗口。无声先看Tone Command,再在干燥台架量4kHz/50%及压电端Vpp,不能只看软件开关。固件故障用桥USB刷回保存版本;先保存现场/报警并记复位,它会丢RAM历史。

试验预期软件路径仍需物理确认
干态TEST5s输出、真实湿/故障取消灯亮/声音/实际GPIO波形
湿态后回干锁存保持、干ACK才清真实探头状态和电压
SIG/整插头断开FAULT,不当干燥连接器与实际电压
断网后重连本地继续、HA当前状态/固定卡实际本地报警与HA收件
RESET/OTARAM清零,仍湿重新确认维护表记复位前后现场

参考:[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注意事项

08 / 项目实战

通过的是构建与同源逻辑,现场结果仍空白

本轮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位单调时钟

BACK TO THE SOURCE

资料来源与版本说明

已实际阅读文末固定版本的关键实现与指定厂商资料,来源/构建/主机夹具分别留证。具体板卡、固件和依赖范围见版本记录;指定实板、HA与现场结果尚未执行。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. DFRobot SEN0121厂家电压/AO与感应说明
  2. 官方v1.1板卡引脚与供电
  3. ESPHome2026.9.1 S3 ADC采样/衰减实现
  4. ESPHome2026.9.1 LEDC频率/占空比
  5. ESPHome2026.9.1 FloatOutput的zero_means_zero
  6. Murata P37E-24 2012-02-01:p9型号表/p13外部驱动及DC注意事项
  7. HA2026.10.2 fixed notification_id更新语义
  8. HA2026.10.2 helper恢复语义
  9. HA2026.10.2模板实体可用资格
  10. IDF5.5.5完整64位单调时钟
  11. Kingbright WP7113ID官方V.14A器件资料