wenhongquan před 1 dnem
rodič
revize
d04ac93c0f
3 změnil soubory, kde provedl 73 přidání a 25 odebrání
  1. 8 0
      .commandcode/taste/taste.md
  2. 18 1
      AGENTS.md
  3. 47 24
      backend/app.py

+ 8 - 0
.commandcode/taste/taste.md

@@ -0,0 +1,8 @@
+# Taste
+
+## Communication
+- Communicates in Chinese and expects replies in Chinese. Confidence: 0.6
+
+## Workflow
+- Expects the agent to read and follow the project's AGENTS.md / agent instructions before acting (reminds the agent to check it). Confidence: 0.7
+- Project convention that documentation (AGENTS.md) be updated alongside each code/hardware change; expects the agent to do this proactively. Confidence: 0.6

+ 18 - 1
AGENTS.md

@@ -30,8 +30,25 @@
   - 代码 `/root/dzxj_dtu/`(`backend` + `frontend/dist` + `venv`,venv 内 gpiod==1.5.4)。服务 `dzxj_dtu.service`(`/root/dzxj_dtu/venv/bin/python app.py`,WorkingDirectory `backend`,开机自启),Flask 监听 `0.0.0.0:5001`。
   - nginx 80:站点 `/etc/nginx/sites-available/dzxj_dtu.conf`(`root /var/www/html`,代理 `/api`、`/socket.io` → `:5001`);前端**同时**放了 `frontend/dist` 和 `/var/www/html`(别忘两处都更)。
   - 环境变量 `/etc/default/dzxj_dtu`:本板 **DHT11 已接在 gpiochip0 line19**(GPIO0_C3_d,同 149),`DHT11_ENABLED=true`,5s 轮询;C 读取器 `backend/scripts/dht11_reader` 已编译,实测 27°C/14% 正常(偶发校验失败属正常抖动)。
-  - 串口:`serial_config.json` 指向 `/dev/ttyS8 @9600`(本板 RS485,实测在发广播轮询);Nextion 屏在 `/dev/ttyS9`。
+  - 串口:`serial_config.json` 指向 `/dev/ttyS8 @9600`(本板 RS485,实测在发广播轮询);串口屏端口见下方「串口屏(Nextion)接线与刷新」。
   - MQTT:`mqtt_config.json` 仍是占位(`localhost:1883`),需在网页里配 `xt.wenhq.top:8581` 之类并连接,连上后才会持久化自动重连。
+
+### 串口屏(Nextion)接线与刷新(2026-10-10 重点)
+
+- **当前网络**:本板 wlan0 为 DHCP,现为 `192.168.10.189`(eth 口未启用)。
+- **HMI 默认文字 ≠ 实际 IP**:屏工程默认 `t1.txt="192.168.100.123"`、`t2.txt="终端 0"`、`t4.txt="V2.0"`、`g0.txt="MQTT:OK,在线终端:10"`、`t3.txt="备注:"`。**屏上一直显示 192.168.100.123 就说明 App 从未成功写过屏**,不是 App 算错 IP。
+- **屏供电必须 5V**(VCC 接 5V,GND 与板子共地),3.3V 供电会导致屏显示正常但串口不工作。
+- **接线方案(实测)**:
+  - ✅ **USB-TTL(CH341)转接**:屏接 CH341 模块,模块插板子 USB 口 → 板子上是 `/dev/ttyUSB0`,读写控件全部正常(已实测)。配置 `NEXTION_SCREEN_PORT=/dev/ttyUSB0`。
+  - ✅ **直连板子 GPIO 排针 UART9(当前方案)**:`UART9_TX_M1/GPIO3_C2_d`(pin114)、`UART9_RX_M1/GPIO3_C3_d`(pin115) 即 SoC 的 TTL UART9(= `/dev/ttyS9`,3.3V),屏 RX←板 TX、屏 TX→板 RX、GND 共地、屏 VCC 接 5V。**已实测全通**(读写控件正常,`ttyS9` 接收计数正常增长)。
+  - ⚠️ **直连 UART3 不通**:`UART3_TX_M1/GPIO3_B7_d`(pin111)、`UART3_RX_M1/GPIO3_C0_d`(pin112)(= `/dev/ttyS3`)短接回环测试通过(收发正常),但接该屏实测不通,原因未查清(不排除当时接触不良;该口也接 RS232 收发器)。
+  - ❌ `ttyS8` 是 RS485(A/B,配线架总线,接屏不通);`ttyS1` 被蓝牙占用。`ttyS0/2/4/5/6/7` 在设备树里是 disabled(无设备节点,要用需改 DTB 并确认引脚引到了排针)。
+- **配置**:屏端口在 `/etc/default/dzxj_dtu`(`NEXTION_SCREEN_PORT=`),改完 `systemctl restart dzxj_dtu`。
+- **屏幕刷新逻辑修复(app.py,2026-10-10)**:
+  - 原逻辑:`refresh_screen_panel()` 在无 panel(`panel_count=0`,未发现配线架)时直接返回,IP 监测线程也要求 panels 非空 → **App 永远不写屏**。
+  - 现逻辑:抽出 `_send_screen_header()`(t1 IP / t2 终端名 / t4 版本 / g0 MQTT 状态),无 panel 时也会下发:开机连屏后、IP 变化时、MQTT 状态变化时。刷新队列协议扩展:`screen_refresh_queue.put((None,))` = 只刷静态信息。
+  - 验证:IP 变化日志出现后,屏 `t1.txt` 会更新为实际 IP(`/tmp/screen_poke.py <port>` 可手工读写验证)。
+
 - **frpc(2026-09 装)**:`/opt/frp/frpc`(v0.71.0)+ `/opt/frp/frpc.toml` + `frpc.service`(开机自启,已连上 frps)。
   - frps:`device.wenhq.top:8580`,token `gx@12345678`。
   - stcp:`167-ssh`(local 22),secretKey `167-ssh-sk` → 访问端配 visitor(`serverName=167-ssh`)。

+ 47 - 24
backend/app.py

@@ -391,6 +391,9 @@ def _network_ip_monitor_loop():
                             idx = screen_current_panel_index
                         if 0 <= idx < len(panels):
                             screen_refresh_queue.put((panels[idx][0],))
+                    else:
+                        # 没有配线架也要刷新 IP,避免屏幕停留在 HMI 默认文字
+                        screen_refresh_queue.put((None,))
         except Exception as e:
             logger.error(f"网络 IP 监测异常: {e}")
         time.sleep(3)
@@ -404,7 +407,12 @@ def _screen_refresh_worker():
             if item is None:
                 break
             panel_id = item[0]
-            refresh_screen_panel(panel_id)
+            if panel_id is None:
+                # 没有配线架时也要把 IP/状态刷到屏幕
+                if screen_display.get_status().get('connected'):
+                    _send_screen_header()
+            else:
+                refresh_screen_panel(panel_id)
         except Exception as e:
             logger.error(f"屏幕刷新工作线程异常: {e}")
         finally:
@@ -414,6 +422,32 @@ def _screen_refresh_worker():
                 pass
 
 
+def _send_screen_header(panel_name=None):
+    """下发与具体 panel 无关的屏幕信息:本机 IP、终端名、版本、MQTT 状态。
+
+    即使一个配线架都没发现(panel_count=0)也调用,否则屏幕会一直停留在
+    HMI 工程的默认文字(例如默认占位 IP)。
+    """
+    online_count = sum(
+        1 for pid, _ in get_sorted_panels()
+        if device_last_seen.get(pid, 0) > time.time() - 60
+    )
+    mqtt_st = mqtt_client.get_status()
+    mqtt_connected = mqtt_st.get('connected', False) if isinstance(mqtt_st, dict) else bool(mqtt_st)
+
+    delay_ms = NEXTION_CMD_DELAY_MS
+    if panel_name is None:
+        panel_name = f'{NEXTION_PANEL_NAME_PREFIX}0'
+
+    screen_display.send_cmd(f't2.txt="{panel_name}"', delay_ms=delay_ms)
+    screen_display.send_cmd(f't1.txt="{_get_display_ip()}"', delay_ms=delay_ms)
+    screen_display.send_cmd(f't4.txt="{dtu_config.get("firmware_version", "v1.0.0")}"', delay_ms=delay_ms)
+
+    mqtt_str = '已连接' if mqtt_connected else '未连接'
+    screen_display.send_cmd(f'g0.txt="MQTT:{mqtt_str} 当前终端数:{online_count}"', delay_ms=delay_ms)
+    screen_display.send_cmd(f'g0.pco={0 if mqtt_connected else 63488}', delay_ms=delay_ms)
+
+
 def refresh_screen_panel(panel_id):
     """将指定 panel 的数据下发到 Nextion 屏幕。"""
     global screen_current_panel_index
@@ -427,30 +461,13 @@ def refresh_screen_panel(panel_id):
         if not screen_display.get_status().get('connected'):
             return False, '屏幕串口未连接'
 
-        ps = port_state.get(panel_id, {})
-        now = time.time()
-        online_count = sum(1 for pid in panel_ids if device_last_seen.get(pid, 0) > now - 60)
         idx = panel_ids.index(panel_id)
         screen_current_panel_index = idx
 
-        mqtt_st = mqtt_client.get_status()
-        mqtt_connected = mqtt_st.get('connected', False) if isinstance(mqtt_st, dict) else bool(mqtt_st)
-
-        delay_ms = NEXTION_CMD_DELAY_MS
-
-        # 获取本机 IP 地址显示在 t1.txt
-        ip_address = _get_display_ip()
-
         # 当前 panel 对应的设备地址
         device_address = panel_config.get(panel_id, {}).get('address', 1)
 
-        screen_display.send_cmd(f't2.txt="{NEXTION_PANEL_NAME_PREFIX}{idx + 1}"', delay_ms=delay_ms)
-        screen_display.send_cmd(f't1.txt="{ip_address}"', delay_ms=delay_ms)
-        screen_display.send_cmd(f't4.txt="{dtu_config.get("firmware_version", "v1.0.0")}"', delay_ms=delay_ms)
-
-        mqtt_str = '已连接' if mqtt_connected else '未连接'
-        screen_display.send_cmd(f'g0.txt="MQTT:{mqtt_str} 当前终端数:{online_count}"', delay_ms=delay_ms)
-        screen_display.send_cmd(f'g0.pco={0 if mqtt_connected else 63488}', delay_ms=delay_ms)
+        _send_screen_header(panel_name=f'{NEXTION_PANEL_NAME_PREFIX}{idx + 1}')
 
         for port_id in range(1, 25):
             # 屏幕网口图片显示 LED:由端口真实连接状态推导(连接/断开/非法/未知)
@@ -840,12 +857,15 @@ def mqtt_status_handler(status):
 
             # MQTT 状态变化时刷新当前屏幕显示
             panels = get_sorted_panels()
-            if panels and screen_display.get_status().get('connected'):
+            if screen_display.get_status().get('connected'):
                 try:
-                    with screen_lock:
-                        current_idx = screen_current_panel_index
-                    if 0 <= current_idx < len(panels):
-                        screen_refresh_queue.put((panels[current_idx][0],))
+                    if panels:
+                        with screen_lock:
+                            current_idx = screen_current_panel_index
+                        if 0 <= current_idx < len(panels):
+                            screen_refresh_queue.put((panels[current_idx][0],))
+                    else:
+                        screen_refresh_queue.put((None,))
                 except Exception as e:
                     logger.error(f"MQTT 状态变化刷新屏幕失败: {e}")
     except Exception as e:
@@ -4922,6 +4942,9 @@ if __name__ == '__main__':
                     panels = get_sorted_panels()
                     if panels:
                         refresh_screen_panel(panels[0][0])
+                    else:
+                        # 没有配线架时也要把 IP/版本/MQTT 状态刷到屏幕
+                        _send_screen_header()
                 else:
                     logger.warning(f"Nextion 屏幕连接失败: {msg}")
             except Exception as e: