Files
home-automation/docs/warmtelink-energy.md
T

10 KiB
Raw Blame History

WarmteLink、数据源与热力计费运维手册

本手册说明如何在不改变既有 DSMR、Modbus 和 electricity 功能的前提下,部署只读的 WarmteLink P1 采集、绑定热量/生活热水 Meter、配置热力合同,并按需暴露给 Home Assistant。所有示例均使用占位符;不要把设备标识、GID、数据库路径、token 或真实合同金额提交到仓库。

安全边界与开始前备份

  • 应用容器继续使用基础 docker-compose.yml 中的非 root user: "1000:1000";基础文件和 dev 合并配置都不设置 privileged 或额外 capability。
  • Docker device cgroup 必须以 rw 映射,容器内固定为 /dev/warmtelink:这是 pyserial 3.5 在 POSIX 上以 O_RDWR 打开串口所需的最小系统权限,并不表示业务可写。规则绝不包含 m,且不授予 root、privileged 或额外 capability。WarmteLink worker 仍只调用 serial read / close,绝不调用 write 或发送写命令。
  • 不删除或覆盖 app_configapp.db、旧数据库、Docker volume 或既有 source。禁用/解绑/回滚配置不是删除历史的替代方式。
  • 维护前停止写入窗口,使用宿主机的备份流程复制 ./data/app.db 到受保护的备份位置;确认备份可用后才运行 migration。不要把生产库复制到开发机或用于测试。

识别稳定串口并配置 Compose

在宿主机(不是容器)找出稳定 symlink;不要使用会在重启后变化的 /dev/ttyUSB* 名称:

ls -l /dev/serial/by-id/
stable_path=/dev/serial/by-id/<stable-by-id-name>
stat -c '%g %n' "$stable_path"

记录输出的数字 GID,而不是猜测 dialout 的数值。确认 path 指向预期的字符设备后,在部署机本地 .env(不提交)设置:

WARMTELINK_DEVICE_PATH=/dev/serial/by-id/<stable-by-id-name>
WARMTELINK_SERIAL_GID=<host-serial-gid>

production 使用基础 composelocal dev 使用 base 与 dev 合并文件。两种环境都会从本地 .env 读取这两个变量:

docker compose -f docker-compose.yml up -d
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build

启动前可用相同文件组合运行 docker compose ... config。结果的 app 必须仍显示 user: "1000:1000",并只出现从 stable by-id path 到 /dev/warmtelinkrw device mapping 与 group_add GIDmigration 不得有 device 或 group。不得出现 privileged、root user 或 m device permission。rw 仅满足 pyserial 的 POSIX O_RDWR 打开,不改变 worker 的只读业务行为。不得把设备路径或 GID 写入仓库的 .env.example 或文档。

同一物理串口在任意时刻只能有一个 owner。运行 Pre-M8 p1_probe.py 前,必须先停止 app(包括 dev stack),并在 probe 结束后再启动 app;不要让 probe 与 worker 同时打开该串口。

Migration 与 source 配置

先在维护窗口运行 migration;它只升级 schema,绝不删除历史表或配置:

docker compose -f docker-compose.yml run --rm migration

登录 Energy 页面,在 Sources 创建 warmtelink_serial source

字段
Path /dev/warmtelink
Baud rate 115200
Data bits / parity / stop bits 7 / N / 1
Enabled 先关闭,保存并检查配置后再开启

这些参数是固定 P1 profile。不要录入 telegram 或 equipment id;应用不会把原始 frame、设备身份或 serial 设置以外的协议标识保存到 source 配置中。开启后选择 Discover。成功发现后应有两个只读 channel:heatingGJ)和 hot water);受 P1 CRC 限制,正常可接纳的质量可能显示为 unverifiable,这不是被错误提升为 valid

Meters 分别创建或选择 heatinghot_water Meter,并各自选择与单位相符的 channel 创建 binding。source switch 只关闭旧 binding、在相同 Meter 上创建新 binding,不是换表;真正的物理换表才使用 Meter swap。跨 binding 或 Meter 边界的 15 分钟成本周期会明确标为 degraded,不能相减伪造成本。

运行检查与排障

启用 source 后,latest 通常约每 10 秒更新,持久化 history 最多每分钟一条。检查 source 状态、channel latest/quality 和绑定时间线,而不是从日志中寻找原始 telegram。

现象 安全排查
offline 或无法 open serial 核对 stable symlink 是否仍存在、stat 的 GID 是否等于 WARMTELINK_SERIAL_GID,再用 docker compose ... config 检查 non-root rw(非 r、非 mdevice rule;不要用 root/privileged 绕过权限。
无 channels / discover 超时 确认 source 启用、/dev/warmtelink path 和固定 115200 7N1;检查电缆供电后重试 Discover。
短暂拔线 worker 标记 offline 并以退避重连;插回后应恢复。检查 history 的 (channel, recorded_at) 唯一性,不能手工补重复行。
成本 degraded 查两端读数 freshness120 秒)、quality、Meter epoch 与 binding;不要通过修改累计值清除 degraded。

若需要停采集,在 UI 禁用该 source,确认 worker 关闭串口后再维护电缆。删除有 channel、binding 或历史的 source 会被 API 拒绝;保留记录以保证审计和成本重算。

热力合同、成本与 Home Assistant

Contracts 选择 Thermal scope,创建 district_heating 合同及版本。费率由 operator 按合同人工录入,字段为 heatingEUR/GJ)、hot-water heating / water / taxEUR/m³)与五个年固定费字段;仓库不含任何真实默认金额。thermal 和 electricity 各可有一个 active 合同,彼此不互斥。

成本页的 15 分钟 ledger 分开显示 heating 与 hot-water 三项 variable breakdownfixed 费只在合同级 summary 按本地自然日计提一次,all-in = variable + fixed。用显式 recompute 来验证测试时间窗时,应手算并核对 Decimal 金额,保留原有 electricity 合同和数字不变。

在 Config 的 HA Expose 中只开启需要的 source、Meter 与 thermal entities。Thermal Heating Total/Today 显示 heating variable 加上 heating network、metering、delivery set 与 other 固定费;Thermal Hot Water Total/Today 显示 hot-water heating、water、water tax variable 加上 hot-water network 固定费。两条合计 恰好等于 Thermal All-in Total/Todaycomponent entities 仍仅显示各自 variableFixed 仍显示全部 standing,避免重复计费。数值仍来自在 01:05 后结算的 summary。核对 unit、state class、availability、today reset 和换表后 identity;关闭 toggle 后应用会清理 retained discovery。不要把 source secret、设备 identity 或合同金额放进 HA entity 名称、日志或截图。

安全回滚

  1. 在 UI 禁用 WarmteLink source,确认状态离线且 worker 已停止;保留 channels、bindings、history、contracts 与成本账本。
  2. 停止 app 后,在 UI 保持 source 禁用;这不会删除 ./data、数据库、配置或 volumes。需要恢复 WarmteLink 时,确认本地 .env 的 stable by-id/GID 后再重新启用 source。
  3. 确认 DSMR、Modbus、电价、既有 electricity 成本和前端正常。
  4. schema migration 不应以 production downgrade 回滚;只有经过验证的备份恢复流程才处理灾难恢复,且必须由 operator 在隔离维护窗口执行。

上线验收清单

  • production 与 base+dev Compose 都从本地 .env 获取 serial path/GIDapp/migration 均为非 root,只有 app 有 /dev/warmtelink:rw 和 serial GID,绝无 m、root 或 privileged。该 rw 仅为 pyserial 的 O_RDWR 打开,worker 业务仍只读,且没有真实设备/GID 被记录在仓库。
  • 下列项目是交付后由用户在备份数据库、可回滚部署、真实 serial 设备和真实 HA 环境执行的人工验收;自动化技术验收不能替代这些观察,也不得把它们伪称为已执行。

用户人工验收记录(交付后填写)

本模板在交付后由用户填写;它不描述当前宿主环境,也不代表任何项目已通过。每项均填写日期、隔离部署 标识、§12 项号、预期观察、实际观察、脱敏日志/截图引用、回滚结果和结果状态。不得记录 stable device id、 GID、secret、数据库路径或合同金额;交付时所有项目默认均为“未执行”,不得填造真实环境结果。

§12 项号 日期 隔离部署标识 预期观察 实际观察 脱敏日志/截图引用 回滚结果 结果状态
1. 默认 stack 回归 待填写 待填写 DSMR、Modbus、电价/电费、前端均无回归 待填写 待填写 待填写 未执行
2. stable by-id 与权限 待填写 待填写 非 root、非 privileged、rwmpyserial 可打开,worker 无 write 待填写 待填写 待填写 未执行
3. Discover 与隐私 待填写 待填写 两 channel/unit/quality;日志、DB、API、UI 均完成脱敏检查 待填写 待填写 待填写 未执行
4. history/拔插重连 待填写 待填写 latest、分钟 history、拔插恢复且无重复记录 待填写 待填写 待填写 未执行
5. source switch / Meter swap 待填写 待填写 两条时间线正确、边界 degraded、HA identity 按设计变化 待填写 待填写 待填写 未执行
6. thermal 合同/成本 待填写 待填写 脱敏测试费率手算一致;15 分钟与 01:05 fixed 正确 待填写 待填写 待填写 未执行
7. 双 active scope 待填写 待填写 electricity 数字对照一致;UI/成本/HA 不串 scope 待填写 待填写 待填写 未执行
8. HA toggles 待填写 待填写 unit、state class、availability、today、identity、retained cleanup 正确 待填写 待填写 待填写 未执行
9. 重启与默认 compose 回滚 待填写 待填写 历史恢复;回默认 compose 后数据完整 待填写 待填写 待填写 未执行