M6-T11: document M6 (energy pricing + DSMR + Tibber) across README/roadmap/arch

- README: M6 section + feature list + new tables/config items.
- roadmap: M6 row (graduated) + detail section; M6 in milestone index.
- architecture-overview: M6 models/services/integrations/routes/jobs/config.
- design/README: index m6, drop stale '三个里程碑' wording.
- OpenAPI already in sync (no diff).
This commit is contained in:
2026-06-23 23:52:07 +02:00
parent 96e88861d4
commit 78f3cc4776
5 changed files with 124 additions and 11 deletions
+70 -2
View File
@@ -17,6 +17,10 @@
- **Modbus 设备采集**:通过 YAML profile(首个:SDM120 电表)按设备周期轮询 Modbus-TCP 网关,解码工程量并落通用读数表(`modbus_device` + `modbus_reading`
- **MQTT + Home Assistant Discovery**:以可勾选方式把 Modbus 设备/工程量注册为 HA device/entity(含 binary_sensor online),state 周期发布;配置变更可重连重发
- **前端侧边栏 + Energy 视图**:侧边导航替换顶栏;Energy 页管理 Modbus 设备、展示最新读数与 Recharts 走势图;Config 页 Accordion 分区展开;Expose 设置勾选 HA 可暴露实体
- **DSMR 实时电表接入**:订阅 DSMR Reader 的 `dsmr/json`(每秒一帧)、整帧 JSON blob 按 10 秒降采样落库(`dsmr_reading`
- **通用电价合同层**YAML profile 定合同结构(manual 固定/双费率 / tibber 动态电价);`EnergyContract`+`EnergyContractVersion` 存 UI 可填的数值,改价加新版本旧版本保留;price strategy 按 kind 出价
- **实时买卖电费计算**:每 15 分钟按寄存器差值(`_1`=dal/低、`_2`=normal/高)× 买/卖价算计量电费,快照不可变;日/月/年汇总加固定费减 heffingskorting
- **反哺 Home Assistant Energy**:当前买/卖价 + 累计买电支出/卖电收入(`total_increasing`)发成 HA 实体,可直接挂 HA Energy 仪表盘
- pytest 测试与 OpenAPI 导出脚本
- Docker / Compose 部署入口
@@ -36,6 +40,10 @@
- Modbus 设备定义(`modbus_device` 表)
- Modbus 通用读数(`modbus_reading` 表,JSON payload
- HA 实体暴露开关(`exposed_entity_toggle` 表)
- DSMR 电表实时读数(`dsmr_reading` 表,整帧 JSON blob10s 降采样)
- 电价合同(`energy_contract` 表)与版本(`energy_contract_version` 表,values JSON
- Tibber 15 分钟电价缓存(`tibber_price` 表,不可变)
- 每 15 分钟计量电费(`energy_cost_period` 表,快照价,不可变)
配置层只保留一个数据库环境变量:
@@ -47,7 +55,7 @@
python -m scripts.run_migrations
```
该命令会通过 Alembic 将 `app.db` 初始化或升级到最新 head(含全部表,包括 M5 新增的 `modbus_device``modbus_reading``exposed_entity_toggle`)。
该命令会通过 Alembic 将 `app.db` 初始化或升级到最新 head(含全部表,包括 M5 新增的 `modbus_device``modbus_reading``exposed_entity_toggle`,以及 M6 新增的 `dsmr_reading``energy_contract``energy_contract_version``tibber_price``energy_cost_period`)。
## 当前目录
@@ -55,7 +63,7 @@ python -m scripts.run_migrations
- `app/`: FastAPI 应用代码(包含 JSON API、业务服务、数据模型)
- `frontend/`: React SPA 前端(Vite + React + TypeScript + Mantine
- `alembic_app/`: App DB 的 Alembic migration 环境(管理所有表,含 M5 新增的 `modbus_device``modbus_reading``exposed_entity_toggle`
- `alembic_app/`: App DB 的 Alembic migration 环境(管理所有表,含 M5 新增的 `modbus_device``modbus_reading``exposed_entity_toggle`,以及 M6 新增的 `dsmr_reading``energy_contract``energy_contract_version``tibber_price``energy_cost_period`
- `tests/`: pytest 测试
- `docs/`: 当前系统说明文档
- `scripts/`: 辅助脚本,例如 OpenAPI 导出
@@ -366,6 +374,64 @@ CLI 工具为受控手工验证而设(设备需接市电),仅暴露读功
- **Config 页 Accordion**:各大 config section 可独立折叠/展开;「Home Assistant Expose」面板按设备分组勾选可暴露实体、显示 MQTT/Discovery 连接状态、「重新发布 discovery」按钮。
- SPA 路由新增 `/energy`
## M6 DSMR 接入 / 电价合同 / 实时电费计算 / HA Energy 反哺
M6 在 M5 IoT 基建之上接入 DSMR 实时智能电表数据,建立通用电价合同层,按每 15 分钟算出实际买卖电费并反哺 HA Energy。
### 依赖
M6 **不新增任何 Python 依赖**,复用 M5 已有的 `httpx`Tibber GraphQL)、`paho-mqtt`DSMR 订阅)、`pyyaml`pricing profile 加载)、`apscheduler`(抓价 job、计费 job)。
### DSMR 实时电表接入
订阅 DSMR Reader 的 `dsmr/json` topic(每秒一帧完整 telegram),整帧存为 JSON blob、按 `dsmr_sample_interval_s`(默认 10 秒)降采样落 `dsmr_reading``source_id` 幂等去重)。`dsmr_ingest_enabled`(默认 falseopt-in)。
### 电价合同层
- **YAML profile 定结构**(仓库内,不放数值):`manual.yaml`(固定/双费率:buy_normal/dal、sell_normal/dal、energy_tax、ode、固定费、heffingskorting);`tibber.yaml`(动态:source=tibber_apienergy_tax、sell_adjust
- **`EnergyContract` + `EnergyContractVersion`**(UI 填数值):改价 = 加新版本行(带 `effective_from`),旧版本保留(审计链);一次只有一个 active 合同
- **price strategy**`manual` 用双费率常数(`buy = energy_buy_档 + energy_tax``sell = sell_档`);`tibber``tibber_price.total` 作买价(已含税,demo 确认 `total=energy+tax`),`total energy_tax sell_adjust` 作卖价(卖价残差 `sell_adjust` 默认 0,待真实账单核定)
### 每 15 分钟计量电费(不可变)
APScheduler 1 分钟 tick,取每个闭合 15 分钟窗口的 DSMR 寄存器差值(`delivered_1/2``returned_1/2``_1`=dal/低,`_2`=normal/高,NL 惯例)× 当时合同版本的 strategy 出价,upsert `energy_cost_period`(快照当时价 + `contract_version_id`)。缺价/缺数据时标 `degraded``POST /api/energy/costs/recompute` 显式重算。
日/月/年汇总 = Σnet + 固定费(network_fee + management_fee 按月→天 × 天数)- heffingskorting(按年→天 × 天数),读时计算、不落表。能源税 `energy_tax` 参考值约 0.1108 EUR/kWh2026 第一档含 VAT,待真实账单核定;该值由 UI 填入合同版本,YAML profile 仅声明字段 unit,代码无写死默认数值)。
### Tibber 动态电价
`app/integrations/tibber/client.py` httpx POST GraphQL`priceInfoRange(QUARTER_HOURLY, first=96)`),解析 `startsAt`/`total`/`energy`/`tax`/`level`,按 `starts_at` upsert `tibber_price`(幂等)。启动 + 每小时抓取今明两天 15 分钟价、幂等 upserthourly trigger,确保每日刷新且可补重试);仅当 active 合同 kind=tibber 且 `tibber_api_token` 存在时运行。`POST /api/energy/tibber/test` 试连三态(success 带当前价 / config-error / failed)。
### 反哺 Home Assistant Energy
`_energy_cost_provider` 向 expose 框架注册 4 个实体:`buy_price_now``sell_price_now`(€/kWh sensor)、`import_cost_total``export_revenue_total``total_increasing` monetary,可直接挂 HA Energy 仪表盘)。默认未勾选,在 Expose 面板启用。
### API 端点(M6 新增)
| 端点 | 用途 |
| --- | --- |
| `GET /api/energy/contracts` | 列出合同 + active 标记 |
| `POST /api/energy/contracts` | 新建合同(kind + 首版本值,按 profile 校验)|
| `GET /api/energy/contracts/{id}` | 单个合同 + 版本历史 |
| `PATCH /api/energy/contracts/{id}` | 改名 / 激活 |
| `POST /api/energy/contracts/{id}/versions` | 加新版本(改价,带生效日期)|
| `GET /api/energy/profiles` | 列出 pricing profile 结构(前端按它渲染表单)|
| `GET /api/energy/prices` | 区间价格点(曲线)|
| `GET /api/energy/costs` | 区间 `energy_cost_period`(走势/明细)|
| `GET /api/energy/costs/summary` | 区间汇总(计量电费 + 固定费 − 抵扣)|
| `POST /api/energy/costs/recompute` | 幂等重算 |
| `GET /api/energy/dsmr/latest` | 最新 `dsmr_reading` |
| `POST /api/energy/tibber/test` | 试连 Tibber + 拉当前价,三态 |
DSMR/Tibber 标量配置复用现有 `GET/PUT /api/config`(新增 `dsmr_ingest_enabled``dsmr_mqtt_topic``dsmr_sample_interval_s``tibber_api_token`secret)、`tibber_home_id`)。
### 前端视图(M6 新增,并入 Energy 视图)
- **Contracts Tab**:合同列表 + 新建/编辑(表单按 `/api/energy/profiles` 结构渲染,不 hardcode 字段)+ 激活 + 改价加版本 + 版本历史只读。
- **Prices Tab**15 分钟价格曲线(tibber 动态或 manual 档位),复用 Recharts。
- **Costs Tab**:费用走势/明细 + 汇总卡片(含固定费/抵扣)。
- **Config 页 Tibber 测试**:三态(success/config-error/failed)。
## Config 持久化
当前 config 页面不会把修改写回 `.env`
@@ -394,6 +460,8 @@ CLI 工具为受控手工验证而设(设备需接市电),仅暴露读功
- MQTT broker 配置(`MQTT_ENABLED``MQTT_BROKER_HOST/PORT/USERNAME/PASSWORD``MQTT_TLS_ENABLED`
- Home Assistant Discovery 配置(`HA_DISCOVERY_ENABLED``HA_DISCOVERY_PREFIX`
- Modbus 采集配置(`MODBUS_POLLING_ENABLED`
- DSMR 接入配置(`DSMR_INGEST_ENABLED``DSMR_MQTT_TOPIC``DSMR_SAMPLE_INTERVAL_S`
- Tibber 凭据(`TIBBER_API_TOKEN`secret)、`TIBBER_HOME_ID`
其中 SMTP password 与其他 secret 字段一致: