Przeglądaj źródła

refactor(backend): 重构CPU和内存使用率获取逻辑

1. 抽离为独立工具函数并添加降级方案,无psutil时读取/proc文件系统计算
2. 修复原代码中psutil导入位置不当的问题
3. 更新AGENTS.md文档,修正RK3562设备的系统和网络信息
wenhongquan 8 godzin temu
rodzic
commit
331433f0f3
3 zmienionych plików z 66 dodań i 26 usunięć
  1. 19 21
      AGENTS.md
  2. BIN
      backend/__pycache__/app.cpython-313.pyc
  3. 47 5
      backend/app.py

+ 19 - 21
AGENTS.md

@@ -18,27 +18,25 @@
   - `mosquitto.service` → 本地 MQTT Broker(:1883)
   - `nginx.service`、`mysql.service` 等
 
-## 第二块板:RK3562(192.168.199.235)
-
-> ⚠️ 这块板**不是 Ubuntu/Linux,而是 Android 13**(`rk3562_t-userdebug`,内核 5.10.157,Toybox shell)。
-> 没有 `/etc/os-release`、`apt`、`systemctl`、`bash`,也没有任何 sshd/dropbear。**本仓库的 `dzxj_dtu`(systemd+Python+Flask)无法直接跑在 Android 上**;要跑 DTU 应用需刷厂商 Ubuntu 镜像。
-
-- **系统**:Android 13,userdebug(`getprop ro.build.flavor` = `rk3562_t-userdebug`),SELinux = Permissive。
-- **网络**:eth1 = `192.168.199.235`。
-- **adb**:USB 连接,`adb devices` 可见;`adb root` 可切 root(重启后失效,需重跑)。
-- **SSH(已配好)**:`ssh root@192.168.199.235`,密码 `123456`,登录即 root shell,**开机自启**。
-  - Android 自带无 sshd,官方 Android dropbear 构建把密码认证编译掉了(`DROPBEAR_SVR_PASSWORD_AUTH 0`,因 Android 无 `crypt()`),所以自建了一个 **Go 静态 SSH 服务 `rkssh`**(密码认证 + PTY + exec)。
-  - 设备上文件:
-    - `/data/dropbear/rkssh` —— 静态 aarch64 二进制(`-addr -user -pass -hostkey -shell` 参数)
-    - `/data/dropbear/go_hostkey` —— ed25519 主机密钥(首次运行自动生成)
-    - `/data/dropbear/start.sh` —— 手动启动脚本
-    - `/system/etc/init/rkssh.rc` —— init 服务,开机自启(`class main`, `user root`)
-    - 日志:`/data/dropbear/rkssh.log`(手动启动时)
-  - 持久化说明:`/system` 通过 `adb remount`(overlayfs)变可写后才写入 rc;overlay 重启后仍生效(已实测重启后 22 端口自动监听)。
-  - 源码(Go)在本仓库 `tools/rkssh/`(main.go/go.mod/go.sum/rkssh.rc/start.sh),构建:
-    `cd tools/rkssh && GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o rkssh-arm64 .`
-    然后 `adb push rkssh-arm64 /data/dropbear/rkssh && adb shell chmod 755 /data/dropbear/rkssh`。
-- **改密码/端口**:改 `/system/etc/init/rkssh.rc` 里的 `-pass`/`-addr`,然后 `adb reboot`;或临时 `pkill rkssh` 后用 `/data/dropbear/start.sh` 重启。
+## 第二块板:RK3562(192.168.31.167,Ubuntu)
+
+> 该板原为 Android 13,2026-09 已**重刷为 Ubuntu 20.04 原厂镜像**,sshd 自带可用,无需再自建 SSH。
+
+- **系统**:Ubuntu 20.04.3 LTS (Focal),内核 5.10.226 aarch64,`hostname=ido`。
+- **网络**:**wlan0** = `192.168.31.167/24`(WiFi,eth 口未启用),docker0 = `172.17.0.1`。
+- **登录**:`ssh root@192.168.31.167`,密码 `123456`,直接 root shell。
+- **自带服务**:`mosquitto`(:1883)、`mysql`(:3306 仅本地)、`smbd`(:139/445)、`adbd`(:5555 网络 adb)。
+- **已部署 `dzxj_dtu`(2026-09)**:
+  - 代码 `/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`。
+  - MQTT:`mqtt_config.json` 仍是占位(`localhost:1883`),需在网页里配 `xt.wenhq.top:8581` 之类并连接,连上后才会持久化自动重连。
+- **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`)。
+  - http:`167-http`(local 80),subDomain `pxj167`(如子域被占/改名,改 toml 后 `systemctl restart frpc`)。
+- 附:`tools/rkssh/` 是此前为 Android 系统写的 Go 版 SSH 服务,Ubuntu 下用不到,保留仅作历史记录(旧 Android 记录:192.168.199.235、密码 123456)。
 
 ## 远程访问方式
 

BIN
backend/__pycache__/app.cpython-313.pyc


+ 47 - 5
backend/app.py

@@ -1159,15 +1159,13 @@ def dtu_publish_status(force=False):
         screen_connected = screen_display.get_status().get('connected', False)
 
         # CPU/内存使用率 (Linux 才有意义)
-        cpu_usage = None
-        memory_usage = None
+        cpu_usage = _get_cpu_usage()
+        memory_usage = _get_memory_usage()
         dtu_temperature = None
         try:
-            import psutil
-            cpu_usage = psutil.cpu_percent(interval=None)
-            memory_usage = psutil.virtual_memory().percent
             # 主板温度: 优先 psutil.sensors_temperatures
             try:
+                import psutil
                 temps = psutil.sensors_temperatures(fahrenheit=False) or {}
                 for chip_name, entries in temps.items():
                     for entry in entries:
@@ -3935,19 +3933,63 @@ def manual_dtu_register():
         return jsonify({'success': False, 'message': str(e)}), 500
 
 
+_cpu_last_times = None
+
+
 def _get_cpu_usage():
+    """CPU 使用率 (psutil 优先,无 psutil 时回退读 /proc/stat 计算瞬时差值)"""
+    global _cpu_last_times
     try:
         import psutil
         return psutil.cpu_percent(interval=None)
     except Exception:
+        pass
+    try:
+        with open('/proc/stat') as f:
+            fields = f.readline().split()
+        if len(fields) < 5 or not fields[0].startswith('cpu'):
+            return None
+        vals = [int(x) for x in fields[1:]]
+        idle = vals[3] + (vals[4] if len(vals) > 4 else 0)
+        total = sum(vals)
+        if _cpu_last_times:
+            dtotal = total - _cpu_last_times[0]
+            didle = idle - _cpu_last_times[1]
+            if dtotal > 0:
+                pct = round((1.0 - didle / dtotal) * 100, 1)
+                _cpu_last_times = (total, idle)
+                return pct
+        _cpu_last_times = (total, idle)
+        return None
+    except Exception:
         return None
 
 
 def _get_memory_usage():
+    """内存使用率 (psutil 优先,无 psutil 时回退读 /proc/meminfo)"""
     try:
         import psutil
         return psutil.virtual_memory().percent
     except Exception:
+        pass
+    try:
+        meminfo = {}
+        with open('/proc/meminfo') as f:
+            for line in f:
+                parts = line.split(':')
+                if len(parts) == 2:
+                    try:
+                        meminfo[parts[0].strip()] = int(parts[1].split()[0])
+                    except (ValueError, IndexError):
+                        continue
+        total = meminfo.get('MemTotal', 0)
+        available = meminfo.get('MemAvailable')
+        if available is None:
+            available = meminfo.get('MemFree', 0)
+        if total > 0:
+            return round((1.0 - available / total) * 100, 1)
+        return None
+    except Exception:
         return None