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

111 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_config``app.db`、旧数据库、Docker volume 或既有 source。禁用/解绑/回滚配置不是删除历史的替代方式。
- 维护前停止写入窗口,使用宿主机的备份流程复制 `./data/app.db` 到受保护的备份位置;确认备份可用后才运行 migration。不要把生产库复制到开发机或用于测试。
## 识别稳定串口并配置 Compose
在宿主机(不是容器)找出稳定 symlink;不要使用会在重启后变化的 `/dev/ttyUSB*` 名称:
```bash
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`(不提交)设置:
```bash
WARMTELINK_DEVICE_PATH=/dev/serial/by-id/<stable-by-id-name>
WARMTELINK_SERIAL_GID=<host-serial-gid>
```
production 使用基础 composelocal dev 使用 base 与 dev 合并文件。两种环境都会从本地 `.env` 读取这两个变量:
```bash
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/warmtelink``rw` 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,绝不删除历史表或配置:
```bash
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:heating`GJ`)和 hot water`m³`);受 P1 CRC 限制,正常可接纳的质量可能显示为 `unverifiable`,这不是被错误提升为 `valid`
**Meters** 分别创建或选择 `heating``hot_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`、非 `m`device 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。核对 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、`rw``m`pyserial 可打开,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 后数据完整 | 待填写 | 待填写 | 待填写 | 未执行 |