瀏覽代碼

fix(app): 修复uptime统计不准确的问题,新增开机时间记录

新增全局开机时间变量_boot_time,替换原有的直接取当前时间戳的计算方式,现在uptime将正确显示设备实际运行时长,同时新增了AGENTS.md项目维护文档用于记录项目关键信息和维护注意事项
wenhongquan 2 天之前
父節點
當前提交
58403bda9b
共有 3 個文件被更改,包括 550 次插入359 次删除
  1. 174 0
      AGENTS.md
  2. 3 1
      backend/app.py
  3. 373 358
      线架系统功能需求与MQTT协议设计.md

+ 174 - 0
AGENTS.md

@@ -0,0 +1,174 @@
+# DZXJ DTU 项目 AGENTS.md
+
+本文件记录本项目开发/维护时需要注意的关键信息,方便下次快速上手。
+
+## 项目概览
+
+- **设备**:Rockchip RK3588 嵌入式开发板(`hostname=ido`,aarch64),作为 DTU(数据传输单元)运行。
+- **应用**:`dzxj_dtu` —— 串口(MODBUS-RTU/RS485) ↔ MQTT 网关,带 Web 管理界面(Flask + Socket.IO + Vue 前端)。
+- **代码部署位置(设备上)**:`/root/dzxj_dtu/`,代码库在本仓库。
+- **设备 IP**:eth1 = `192.168.199.149`(远程管理用),wlan0 = `192.168.10.3`,实际 IP 见串口屏显示。
+- **系统服务**:
+  - `dzxj_dtu.service` → `/root/dzxj_dtu/backend/app.py`(venv Python)
+  - `frpc.service` → FRP 客户端(stcp 映射 SSH + http 域名映射 80 端口)
+  - `easytier.service` → EasyTier VPN(组网)
+  - `mosquitto.service` → 本地 MQTT Broker(:1883)
+  - `nginx.service`、`mysql.service` 等
+
+## 远程访问方式
+
+### 方式 1:FRP stcp 映射 SSH(推荐)
+
+149 上跑 **frpc**(仅客户端,不跑 frps),通过 stcp 模式把本地 22 端口映射到公网 frps 服务器。
+
+- frps 服务器:`device.wenhq.top:8580`,token = `gx@12345678`(frps 部署在外部,不在本设备)。
+- 配置:`/opt/frp/frpc.toml`
+- 服务:`systemctl status frpc` / `systemctl restart frpc`
+
+frpc 配置摘要:
+```toml
+serverAddr = "device.wenhq.top"
+serverPort = 8580
+auth.method = "token"
+auth.token = "gx@12345678"
+
+[[proxies]]
+name = "149-ssh"
+type = "stcp"
+localIP = "127.0.0.1"
+localPort = 22
+secretKey = "149-ssh-sk"
+```
+
+**访问端(你的电脑)需要配置 visitor** 才能连:
+```toml
+serverAddr = "device.wenhq.top"
+serverPort = 8580
+auth.method = "token"
+auth.token = "gx@12345678"
+
+[[visitors]]
+name = "149-ssh"
+type = "stcp"
+serverName = "149-ssh"
+sk = "149-ssh-sk"
+bindPort = 6000
+```
+然后 `ssh root@127.0.0.1 -p 6000`(密码 `123456`)即可连到 149 的 22 端口。
+
+### 方式 2:FRP http 域名映射 80 端口
+
+frpc 里已有 http 代理,把本地 80 端口映射成 `pxj` 子域名:
+```toml
+[[proxies]]
+name = "149-http"
+type = "http"
+localIP = "127.0.0.1"
+localPort = 80
+subDomain = "pxj"
+```
+通过 `http://pxj.<frps主域名>` 访问(具体子域解析规则由 frps 端配置决定)。
+
+### 方式 3:EasyTier VPN 组网
+
+- 配置:`/etc/systemd/system/easytier.service`
+- 网络名 `pxjdtu` / secret `123456`,peer 服务器 `remote-mange.dnnbuild.com:25700`
+- **必须 `--no-tun` 模式**(本设备内核无 tun 模块,`/dev/net/tun` 不可用,`modprobe tun` 失败)。
+- 用 `--port-forward tcp://0.0.0.0:2222/10.11.21.0:22` 做端口转发,访问 `127.0.0.1:2222` 可到 MacBook 的 SSH(仅演示,10.11.21.0 是当时 peer 的虚拟 IP,会变)。
+- 状态查询:`/home/admin/easytier-cli node` / `peer` / `peer-center`。
+- **已知限制**:`--no-tun` 模式下虚拟 IP(10.11.21.0/24)不会加到系统路由表,`ping`/直接 `ssh 10.11.21.x` 不通;要访问 peer 服务只能走 port-forward。想要完整虚拟网需在有 TUN 支持的设备上跑 easytier。
+
+## DHT11 温湿度读取
+
+### 工作方式(重点!)
+
+读取优先走 **C 语言读取器**,Python gpiod 只作回退。
+
+1. **C 二进制读取器**:`/root/dzxj_dtu/backend/scripts/dht11_reader`
+   - 源码:`backend/scripts/dht11_reader.c`(用 libgpiod C API,open-drain + 高速采样)
+   - 编译命令:`gcc -O2 -o dht11_reader dht11_reader.c -lgpiod`(设备已装 `libgpiod-dev`)
+   - 用法:`dht11_reader [chip] [line]`,默认 `gpiochip0 19`
+   - 输出 JSON:`{"valid":true,"humidity":43,"temperature":31,"raw":[...],"checksum":76}`(温湿度为整数,无小数)
+   - **二进制必须存在**,否则回退到 Python gpiod,而 venv 里的 gpiod API 不兼容,会读取失败(症状:日志报 `module 'gpiod' has no attribute 'Chip'` 或读到 0.0)
+
+2. **Python gpiod 回退**:`backend/modules/dht11_sensor.py`
+   - 设备 venv 装的是 pip `gpiod 1.5.4`,**API 与系统 apt 的 python3-libgpiod 1.4.x 完全不同**:
+     - `gpiod.Chip(...)`(1.4.x) vs `gpiod.chip(...)`(1.5.x)
+     - `gpiod.LINE_REQ_DIR_OUT` vs `gpiod.line_request.DIRECTION_OUTPUT`
+     - `line.request(consumer=..., type=...)` vs `line.request(type=...)`(1.5.x 无 consumer 参数)
+     - 1.5.x chip 对象无 `close()` 方法
+   - 代码已加了兼容层(`_gpiod_chip_class`、常量映射),但**优先靠 C 二进制**,尽量别依赖这个回退。
+
+### 配置(环境变量,无默认值则使用 config.py 默认)
+
+| 变量 | 默认 | 说明 |
+|---|---|---|
+| `DHT11_ENABLED` | `true` | 是否启用 |
+| `DHT11_GPIO_PIN` | 无 | GPIO line 号(设备上为 `19`)|
+| `DHT11_GPIO_CHIP` | `gpiochip0` | GPIO chip |
+| `DHT11_POLL_INTERVAL` | `5` | 读取间隔秒 |
+| `DHT11_SIMULATE` | `false` | 是否模拟数据 |
+
+- GPIO 19(RK 平台 GPIO0_C3 系)接 DHT11 data 脚,DHT11 需上拉电阻(模块自带)。
+- 若 `DHT11_ENABLED=true` 但未配 GPIO_PIN 且非模拟,会跳过传感器启动并打 warning。
+
+### 验证
+
+```bash
+/root/dzxj_dtu/backend/scripts/dht11_reader gpiochip0 19
+# 期望输出: {"valid":true,"humidity":xx,"temperature":xx,...}
+journalctl -u dzxj_dtu | grep -E 'DHT11|环境传感器'
+# 正常: "DHT11 本地传感器数据已处理: 温度=31.0°C, 湿度=43.0%"
+```
+
+## MQTT 相关(重点)
+
+- 本地 mosquitto broker 跑在 `:1883`。
+- 应用连接的是**外部 MQTT**(经网页配置):当前 `xt.wenhq.top:8581`。
+- **MQTT 配置会持久化**到 `/root/dzxj_dtu/backend/mqtt_config.json`:
+  - 通过 Web `/api/mqtt/connect` 连接成功后自动 `save_mqtt_config()`。
+  - 服务重启后 `__main__` 里会 `auto_connect_mqtt()` 读取该文件自动重连。
+  - 若没有该文件或连接失败,MQTT 不会自动连接,需在网页手动连一次。
+- 关键函数(`backend/app.py`):`save_mqtt_config()` / `load_mqtt_config()` / `auto_connect_mqtt()` / `mqtt_connect()`(API `/api/mqtt/connect`)。
+- MQTT 客户端封装:`backend/modules/mqtt_client.py`(paho-mqtt==1.6.1,重连无限尝试,`max_reconnect_attempts=0`)。
+- 主题前缀默认 `线架系统`,DTU 协议主题格式:`{topic_prefix}/{customer_id}/dtu/{dtu_id}/{topic}`。
+
+## 串口 / RS485
+
+- 设备串口:`/dev/ttyS8 @ 9600`,连接配置持久化在 `/root/dzxj_dtu/backend/serial_config.json`。
+- 服务启动时 `auto_connect_serial()` 自动重连。
+- MODBUS-RTU 协议实现在 `backend/modules/modbus_rtu.py`;串口封装在 `backend/modules/serial_port.py`。
+- 注意:硬件通信固定 6 字节(12位) 卡号,MQTT 边界拼成 8 字节(16位) `jumper_uid`(前 4 位 DTU 号 + 后 12 位卡号),适配逻辑在 `app.py` 的 `_to_mqtt_jumper_uid` / `_from_mqtt_jumper_uid`。
+
+## 部署与更新
+
+- 代码在 `/root/dzxj_dtu/`,后端在 `backend/`,前端构建产物在 `backend/static/`。
+- 服务使用 venv:`/root/dzxj_dtu/venv/bin/python`(依赖见 `backend/requirements.txt`,注意 gpiod 是 pip 1.5.4)。
+- 常用操作:
+  ```bash
+  # 通过 frp 连上后
+  systemctl restart dzxj_dtu
+  journalctl -u dzxj_dtu -f
+  systemctl status frpc easytier mosquitto
+  ```
+- 更新代码后用 scp 覆盖对应文件再 `systemctl restart dzxj_dtu`。
+- 本地工作目录 = 本仓库;改代码后注意同步到设备。
+
+## 排障速查
+
+| 症状 | 排查 |
+|---|---|
+| SSH 连不上 149 | 先 `ping 192.168.199.149`;再试 `ssh -p 6000 root@127.0.0.1`(frp stcp);若直接 IP 通但 6000 不通,查 `systemctl status frpc` |
+| frpc 起不来 | `journalctl -u frpc`;注意 stcp 配置字段是 `secretKey`(不是 `sk`),老版本报 `unknown field "sk"` |
+| 温湿度读到 0.0 或报 gpiod 错 | 确认 `dht11_reader` 二进制存在且可执行;`dht11_reader gpiochip0 19` 手测 |
+| easytier 组网后虚拟 IP 不通 | `--no-tun` 模式限制,虚拟 IP 不进路由表,只能走 port-forward |
+| MQTT 重启后没自动连 | 确认 `/root/dzxj_dtu/backend/mqtt_config.json` 存在;存在则看 `auto_connect_mqtt` 日志 |
+
+## 注意事项(重要)
+
+- **不要在本设备上装/启用 tun**:内核无 tun 模块,easytier 必须 `--no-tun`。
+- **frps 不在本设备**:只跑 frpc,别在本机部署 frps 或删除 frpc。
+- **stcp 配置字段是 `secretKey`**,不是 `sk`(frp 0.70 报错)。
+- 设备的 root 密码是 `123456`(本机/测试环境)。
+- 改 `app.py` 后必须重启 `dzxj_dtu` 才生效。
+- Web 管理页面端口 `5001`(Flask),前端由 nginx 提供 80 端口。

+ 3 - 1
backend/app.py

@@ -74,6 +74,8 @@ if LOG_FILE:
 logging.basicConfig(**logging_config)
 logger = logging.getLogger('serial_mqtt_gateway')
 
+_boot_time = time.time()
+
 from modules.serial_port import SerialPort
 from modules.mqtt_client import MQTTClient
 from modules.network_config import network_manager
@@ -912,7 +914,7 @@ def dtu_register(discovery_request_id=None):
                 'panel_count': len(panels),
                 'network_type': network_type,
                 'signal_strength': signal_strength,
-                'uptime': int(time.time() * 1000),
+                'uptime': int((time.time() - _boot_time) * 1000),
                 'panels': panels,
                 'discovery_request_id': discovery_request_id
             }

文件差異過大導致無法顯示
+ 373 - 358
线架系统功能需求与MQTT协议设计.md


部分文件因文件數量過多而無法顯示