M5-T13: document Modbus/Energy/MQTT, mark M5 done, finalize OpenAPI

This commit is contained in:
2026-06-22 16:10:11 +02:00
parent 45d87f36f7
commit 4767a7af60
4 changed files with 192 additions and 15 deletions
+69 -6
View File
@@ -19,31 +19,36 @@
- `main.py`
- FastAPI app factory
- lifespan
- lifespanAPScheduler 启停、MQTT 客户端起停、连接后触发 HA Discovery 发布)
- 基础路由注册
- `config.py`
- 环境变量驱动的 settings
- 环境变量驱动的 settings(含 M5 新增的 MQTT/HA Discovery/Modbus 配置项)
- `db.py`
- 统一数据层:一个 `Base`、一个绑定 `app_database_url` 的 cached engineSQLite WAL)、`get_engine` / `get_session_local` / `reset_db_caches` / `get_db_session`
- `dependencies.py`
- 通用依赖注入
- `api/`
- HTTP routes
- `api/routes/api/`JSON API`/api/*` 前缀),供 React SPA 调用:会话/鉴权、配置读写、数据查询、记录 CRUD
- `api/routes/api/`JSON API`/api/*` 前缀),供 React SPA 调用:会话/鉴权、配置读写、数据查询、记录 CRUD、Modbus 设备 CRUD + readings + metrics + test`/api/modbus/*`)、Expose 勾选 + 重发 discovery`/api/expose`)、MQTT 测试连接(`/api/config/mqtt/test`
- 裸 ingestion 端点:`GET /public-ip/check``POST /homeassistant/publish``POST /poo/record``GET /poo/latest`、TickTick OAuth 等
- `models/`
- SQLAlchemy models
- 所有模型(auth / config / public_ip / location / poo)共用同一个 `Base`,均落在单一 `app.db`
- 所有模型(auth / config / public_ip / location / poo / modbus / expose)共用同一个 `Base`,均落在单一 `app.db`
- M5 新增:`ModbusDevice`(设备部署层)、`ModbusReading`(通用遥测,JSON payload)、`ExposedEntityToggle`HA 实体暴露开关)
- `schemas/`
- Pydantic schemas
- Pydantic schemasM5 新增 `modbus.py``expose.py`
- `services/`
- 业务服务层
- 当前已迁入 config page 的 DB 持久化逻辑
- 当前已迁入 public IPv4 检查、状态持久化与变化通知逻辑
- 当前已迁入 SMTP 发信与测试发信逻辑
- M5 新增:`modbus_poll.py`(采集 service,逐设备 poll + 落库 + 推 MQTT state)、`ha_discovery.py`(构建 HA Discovery payload、发布 retained config、发布 state
- `integrations/`
- 外部系统适配层
- 当前已迁入 Home Assistant outbound adapter
- Home Assistant outbound adapterREST 通道,原有)
- M5 新增:`modbus/`pymodbus 薄封装:`driver.py` 块读 + float32 解码;`profiles.py` YAML profile 加载/校验/解码;`profiles/sdm120.yaml` SDM120 协议声明)
- M5 新增:`mqtt.py`paho-mqtt 长连接 `MqttManager`:lifespan 起/停、配置变更重连、`publish(topic, payload, retain)`
- M5 新增:`expose.py`(通用 expose 框架:`ExposableEntity`、provider 注册表、`build_catalog`Modbus provider 从 YAML profile 派生 sensor/binary_sensor 实体目录)
- `static/`
- 极简静态资源
@@ -76,6 +81,7 @@ React SPA 前端(M2 引入)。Vite + React + TypeScript + Mantine,由 Fast
- `app_db_adopt.py`App DB 接管 / 初始化
- `migrate_legacy_data.py`:一次性历史数据搬迁脚本
- `admin_cli.py`Admin CLI 逃生通道(M4),见下方"登录加固"说明
- `modbus_cli.py`M5):Modbus 手工试读 CLI`read`/`probe` 两个只读子命令),不依赖 DB,供受控手工验证链路连通性
### `openapi/`
@@ -93,12 +99,69 @@ M4 在基础 Argon2 + server-side session 鉴权之上叠加了三层防御:
详细说明:[`docs/auth.md`](./auth.md)
## M5 — 通用 Modbus 采集链路与 MQTT 通道
### Modbus 采集链路
```
YAML profile(协议知识) modbus_device 行(部署信息,DB
- 寄存器块/地址/类型 - host / port(网关 IP
- 每量:key/unit/device_class - unit_idModbus slave 地址)
- ha_component - friendly_name / profile / poll_interval_s / enabled
│ │
└────────────┬───────────────────────┘
│ APScheduler job(轮询所有 enabled 设备)
│ driver.py: ModbusTcpClient → FC04 块读 → 大端 float32 解码
│ profiles.py: decode(profile, registers) → dict[key → value]
modbus_reading 行
device_id · recorded_at · payload(JSON)
{"voltage": 230.2, "current": 1.3, "active_power": 295.0, ...}
```
**关键设计决策**
- 命名分层:存储/采集/API 全部通用 `modbus_*``/api/modbus/devices`);面向用户的领域视图叫 **Energy**(第一个)。接入新设备型号只需新增 YAML profile,不改表/不改 API。
- 读数为 JSON `payload`(无固定列),SQLite `json_extract` 在 DB 端做 AVG/GROUP BY(被 `(device_id, recorded_at)` 索引圈住)。
- FK `ON DELETE RESTRICT`:有读数的设备拒删,引导改为 `enabled=false`
- 设备 `uuid`(uuid4 内部生成)是**稳定身份锚点**API 路径键、HA Discovery `unique_id` 来源,不随 friendly_name 改变。
### 第二条 MQTT 通道(HA Discovery 发布)
与已有 REST 通道(`app/integrations/homeassistant.py``POST /homeassistant/publish`**并行、不冲突**
```
MQTT 通道(M5 新增):
paho-mqtt MqttManagerlifespan 长连接,配置变更可重连)
├─► Discovery configretained
│ topic: <prefix>/<component>/<node>/<object>/config
│ 内容:device 块(identifiers=uuid)、state_topic、unique_iduuid+key)、
│ namefriendly_name)、device_class、unit_of_measurement、availability
│ 时机:连接成功时 / 目录或勾选变更时(全量重发);
│ 取消勾选时发空 payload(清除 entity
└─► State / Availability(非 retained
时机:每次轮询成功后推该设备所有 enabled entity 的最新值;
周期兜底 job 重推所有 enabled entity + online topic
expose 框架:
provider 动态产出 ExposableEntity 目录(元数据从 YAML profile 派生)
ExposedEntityToggle 表:只存逐 key 开关(default=disabled
build_catalog(session) → 目录 + 勾选状态(合并所有 provider)
```
**HA entity 身份模型(Z2M 语义)**
- `unique_id` = `f"{device.uuid}_{metric.key}"`(稳定,改名不变)
- `name` = `friendly_name`(改名重发 discovery,HA 显示名跟着变、历史不丢)
- 每设备除各 sensor entity 外,另有 `binary_sensor` `online`(取 `last_poll_ok`)——这是"不止 sensor"的体现
## 当前约束
- 当前数据库继续使用 SQLite
- ~~当前不引入前后端分离~~ **已退役(M2**:现为 React SPA + JSON `/api` 层,由 FastAPI 同源托管
- 当前不设计 Notion 模块
- 当前通知能力仍保持极小范围,不引入独立通知中心或多渠道抽象
- Modbus 当前**仅 TCP**Waveshare RTU↔TCP 网关),**只读**FC03/04),不写设备寄存器
## 关于 Notion