# ControlBOX **Repository Path**: cnfu/control-box ## Basic Information - **Project Name**: ControlBOX - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-12-18 - **Last Updated**: 2026-06-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ControlBOX 控制盒 ControlBOX 是运行于 RK3506 平台(ATK-DLRK3506)的嵌入式主备控制应用,负责发射机主备切换、继电器电源控制、本地人机界面、Web 配置,以及通过 RS-485 与 TCP **统一协议** 的外部管理。 目标程序:`lvgl_web_app` --- ## 1. 项目功能 ### 主备控制 - **手动模式**:通过按键、Web、RS-485 或 TCP 切换主机/备机角色。 - **自动模式**:当输出功率持续低于阈值且超过可配置延时(`AUTO_DELAY`、`AUTO_POWER_TH`)时,自动切换到备机。 - **维修模式**:停止切换流程,关闭主备发射机电源继电器。 ### 电源与继电器 | 继电器 | 功能 | |--------|------| | Relay_1 | 备机发射机电源 | | Relay_2 | 主机发射机电源 | | Relay_3 | 电机主机转动电源 | | Relay_4 | 电机备机转动电源 | 切换遵循安全时序:TCP 发送关机指令(若已连接)→ 关闭电源继电器 → 切换电机继电器 → 等待到位信号 → 上电。 ### 支持的发射机型号 BGTD-V3163、PANDA、DEXIN、JIZHAO、KAITENG、ADC — 各型号在 ETH1 上使用独立的 TCP 二进制协议。 ### 人机界面 - **LVGL 本地 UI**(DRM 显示 + evdev 触摸/按键)。 - **串口屏**(`/dev/ttyS1`):周期推送主备状态、功率、IP。 - **面板按键与 LED**:模式/角色指示及手动操作。 - **Web 界面**(Boa HTTP + CGI):配置读写与状态展示。 ### 网络 - **ETH0**:管理网 / Web 访问(默认 `192.168.1.188`)。 - **ETH1**:连接主备发射机(默认 `192.168.1.189`,主机 `192.168.1.101:9001`,备机 `192.168.1.102:9002`)。 - **TCP 服务端口 9898**:外部管理口(帧格式与 RS-485 相同)。 ### 配置与持久化 | 文件 | 格式 | 说明 | |------|------|------| | `/root/lvgl_web_config` | `KEY=VALUE` | 运行时网络与控制参数 | | `/root/lvgl_web_config.bak` | 备份 | 写配置断电时恢复 | | `/root/device_config.json` | JSON | Web 页面保存的配置(同步到 `lvgl_web_config`) | | `/root/device_status.json` | JSON | 后台线程写入的设备状态 | ### 安全 - 启动前校验设备序列号与授权密钥(`/etc/.device_key`)。 --- ## 2. 软件架构 ### 分层结构 ``` ┌─────────────────────────────────────────────────────────────┐ │ main.c LVGL 主循环(约 100 ms) │ │ APP_SOC_Update / APP_Relay_Update / APP_LED_Update / ADC │ └─────────────────────────────────────────────────────────────┘ ▲ ▲ ▲ │ │ │ ┌────────┴────────┐ ┌────────┴────────┐ ┌───────┴──────────┐ │ app/ │ │ bsp/ │ │ app_src/ + lvgl8 │ │ 业务逻辑 │ │ 板级驱动 │ │ UI 界面 │ └────────┬────────┘ └────────┬────────┘ └───────────────────┘ │ ┌────┴────────────────────────────────────────────┐ │ 后台线程(pthread) │ │ • serial_recv_thread ×3 (ttyS1/S2/S3) │ │ • lcd_send_thread (ttyS1 周期发送) │ │ • config_read_thread (device_config.json) │ │ • status_write_thread (device_status.json) │ │ • tcp_server_thread (端口 9898) │ │ • power_collect_thread (主备 TCP 客户端) │ └─────────────────────────────────────────────────┘ ``` ### 核心模块 | 模块 | 路径 | 职责 | |------|------|------| | SOC 状态机 | `app/app_soc.c` | 模式、角色、继电器时序、自动切换 | | 配置管理 | `app/app_config.c` | 解析/写入 `lvgl_web_config`、网卡配置 | | Web 同步 | `app/app_web.c` | JSON 配置/状态、CGI 数据源 | | 外部管理协议 | `app/app_ext_master_protocol.c` | RS-485 / TCP 共用帧解析与应答 | | TCP 服务端 | `app/app_tcp_server.c` | 网络外部管理 | | 发射机协议 | `app/app_device_protocol.c` | 对发射机的 TCP(功率查询、开关机) | | 串口 BSP | `bsp/bsp_serial.c` | 串口初始化、接收线程、LCD 推送 | | 继电器/ADC/按键/LED/电机 | `bsp/bsp_*.c` | GPIO 与硬件抽象 | ### 启动流程(`main.c` → `APP_SOC_Init`) 1. LVGL / UI 初始化 2. 设备授权校验 3. 加载 `lvgl_web_config`,启动 Web 后台线程 4. 启动发射机功率采集(TCP 客户端) 5. 配置 ETH0 / ETH1 地址 6. 初始化继电器、ADC、电机、按键、LED 7. 初始化串口:LCD(115200)、RS-485 外部口(PORT_1)、RS-485 主备口(PORT_2) 8. 启动 TCP 管理服务(端口 **9898**) ### 主循环(约每 100 ms) ```c lv_task_handler(); APP_SOC_Update(&Soc_t); // 状态机 → 继电器状态 APP_LED_Update(&Soc_t); APP_Relay_Update(&Soc_t.Dev_relay); // 写入继电器 GPIO APP_SIGNAL_ADC_Update(&Soc_t.Signal_ADC_t); BSP_MOTOR_Update(&Soc_t.Motor_t); ``` > **说明**:继电器状态由 `APP_SOC_Update` 统一管理。外部指令若直接改继电器会被覆盖,应通过共享协议修改 `mode` / `pre_state_t` / `Changing_enable`。 ### 目录结构 ``` control-box/ ├── main.c # 程序入口 ├── app/ # 应用层 ├── bsp/ # 板级支持(串口、继电器、GPIO、ADC…) ├── app_src/ # LVGL 界面 ├── lvgl8/ # LVGL 移植(DRM、evdev) ├── sys/ common/ # 平台辅助代码 ├── web_boa_install/ # Boa Web 资源与部署脚本 │ ├── boa_file/ # boa.conf、index.html、CGI │ ├── app/ # 默认配置模板 │ └── init/ # S99lvgl_web_app └── CMakeLists.txt # RK3506 交叉编译 ``` ### 编译 ```bash mkdir -p build && cd build cmake .. make -j$(nproc) # 输出:lvgl_web_app ``` 部署脚本:`web_boa_install/app_file_update.sh`(拷贝到 Buildroot overlay)。 --- ## 3. 通信协议 ### 3.1 外部管理 — RS-485 与 TCP(统一协议) 外部上位机通过 **同一套二进制帧** 访问: - **RS-485**:`/dev/ttyS2`(`UART_ID_485_1_EXT`),波特率取自配置 `PORT_1`(默认 115200) - **TCP**:ETH0 IP 端口 **9898**(默认 `192.168.1.188:9898`) 实现:`app/app_ext_master_protocol.c`(`bsp/bsp_serial.c` 与 `app/app_tcp_server.c` 共用)。 所有帧中的 **CRC 均为 CRC16-Modbus**,占 2 字节,**低字节在前**(Little-Endian)。 #### 查询流程 **查询指令(主机 → 从机,5 字节)** | 字段 | 长度 | 取值 | |------|------|------| | 帧头 | 1 字节 | `0xAB` | | 指令 | 1 字节 | `0x01` | | CRC | 2 字节 | CRC16-Modbus | | 帧尾 | 1 字节 | `0xBA` | **查询反馈(从机 → 主机,14 字节)** | 字段 | 长度 | 取值 / 说明 | |------|------|-------------| | 帧头 | 1 字节 | `0xA5` | | 当前模式 | 1 字节 | `0x00` 手动,`0x01` 自动 | | 当前状态 | 1 字节 | `0x00` 主机,`0x01` 备机 | | 主机信号强度 | 2 字节 | uint16,低字节在前 | | 备机信号强度 | 2 字节 | uint16,低字节在前 | | IP 地址 | 4 字节 | ETH0 IPv4 四段(如 `192.168.0.101` → `C0 A8 00 65`) | | CRC | 2 字节 | CRC16-Modbus | | 帧尾 | 1 字节 | `0x5A` | #### 控制流程 **控制指令(主机 → 从机,7 字节)** | 字段 | 长度 | 取值 / 说明 | |------|------|-------------| | 帧头 | 1 字节 | `0xAB` | | 指令 | 1 字节 | `0x02` | | 设置模式 | 1 字节 | `0x00` 手动,`0x01` 自动 | | 设置主备机状态 | 1 字节 | `0x00` 主机,`0x01` 备机 | | CRC | 2 字节 | CRC16-Modbus | | 帧尾 | 1 字节 | `0xBA` | **控制反馈(从机 → 主机,14 字节)** 格式与 **查询反馈完全一致**(见上表)。 #### 注意事项 1. **查询与控制流程的反馈信息协议一致**(均为 14 字节 `0xA5 … 0x5A` 帧)。 2. **控制指令执行约需 3 s**;控制结果需由主机 **再次发送查询指令**,根据反馈中的模式/状态字段确认是否生效。 3. **查询指令可作为心跳**使用,周期性发送即可维持链路活性并获取最新状态。 手动控制会设置 `pre_state_t`;若与 `cur_state_t` 不同,则 `Changing_enable = 1` 启动切换流程(内部状态机,约 3 s 完成)。 #### 协议 Demo **Demo 1 — 主机查询从机状态** ``` 主机 TX: AB 01 3B 96 BA 从机 RX: A5 00 00 01 23 01 24 C0 A8 00 65 3E 8B 5A ``` 解析反馈帧: | 字段 | 字节 | 含义 | |------|------|------| | 控制器模式 | `0x00` | 手动模式 | | 控制器状态 | `0x00` | 主机 | | 主机信号强度 | `0x23 0x01` | 291(uint16 小端) | | 备机信号强度 | `0x24 0x01` | 292(uint16 小端) | | IP 地址 | `C0 A8 00 65` | 192.168.0.101 | | CRC | `3E 8B` | CRC16-Modbus | **Demo 2 — 主机控制:手动模式 + 备机开机** ``` 主机 TX: AB 02 00 01 3B 96 BA 从机 RX: A5 00 00 01 23 01 24 C0 A8 00 65 3E 8B 5A ``` 解析反馈帧(字段含义同 Demo 1)。控制完成后,应 **间隔约 3 s 再发查询** 确认模式/状态是否已切换到位。 #### 测试方法 — TCP(推荐) **前提**:设备 ETH0 可达,`lvgl_web_app` 已运行,9898 端口监听中。 **Demo — Python** ```python #!/usr/bin/env python3 """ext_master_tcp_demo.py — ControlBOX 外部管理 TCP 测试""" import socket import sys HOST = sys.argv[1] if len(sys.argv) > 1 else "192.168.1.188" PORT = 9898 def crc16_modbus(data: bytes) -> int: crc = 0xFFFF for b in data: crc ^= b for _ in range(8): crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1 return crc def build_request(cmd: int, mode: int = 0, state: int = 0) -> bytes: """组装 AB … CRC … BA 请求帧(CRC 覆盖指令及后续载荷字节)。""" if cmd == 0x01: body = bytes([0x01]) elif cmd == 0x02: body = bytes([0x02, mode, state]) else: raise ValueError("unsupported cmd") c = crc16_modbus(body) return bytes([0xAB]) + body + bytes([c & 0xFF, (c >> 8) & 0xFF, 0xBA]) def send_frame(sock, frame: bytes, label: str): print(f"\n>>> {label}") print("TX:", frame.hex(" ")) sock.sendall(frame) rsp = sock.recv(64) print("RX:", rsp.hex(" ")) if len(rsp) >= 14 and rsp[0] == 0xA5 and rsp[-1] == 0x5A: print(f" 模式={rsp[1]} 状态={rsp[2]} " f"主机强度={rsp[3]|(rsp[4]<<8)} 备机强度={rsp[5]|(rsp[6]<<8)} " f"IP={rsp[7]}.{rsp[8]}.{rsp[9]}.{rsp[10]} " f"CRC={rsp[11]:02X} {rsp[12]:02X}") return rsp with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(5) s.connect((HOST, PORT)) print(f"已连接 {HOST}:{PORT}") # 查询(Demo 固定帧: AB 01 3B 96 BA) send_frame(s, bytes([0xAB, 0x01, 0x3B, 0x96, 0xBA]), "查询") # 控制:手动 + 备机(Demo 固定帧: AB 02 00 01 3B 96 BA) send_frame(s, bytes([0xAB, 0x02, 0x00, 0x01, 0x3B, 0x96, 0xBA]), "手动 + 备机") import time time.sleep(3) # 控制结果确认:再次查询 send_frame(s, bytes([0xAB, 0x01, 0x3B, 0x96, 0xBA]), "查询确认") ``` 运行: ```bash python3 ext_master_tcp_demo.py 192.168.1.188 ``` **Demo — netcat + xxd(Linux 命令行)** ```bash # 查询 printf '\xAB\x01\x3B\x96\xBA' | nc -w 2 192.168.1.188 9898 | xxd # 手动 + 备机 printf '\xAB\x02\x00\x01\x3B\x96\xBA' | nc -w 2 192.168.1.188 9898 | xxd # 约 3 s 后查询确认 sleep 3 printf '\xAB\x01\x3B\x96\xBA' | nc -w 2 192.168.1.188 9898 | xxd ``` **预期设备串口日志**: ``` [TCP服务端] recv收到 5 bytes, 当前缓冲累计=5 bytes [TCP服务端] 本次recv原始数据 (5 bytes): AB 01 3B 96 BA [EXT协议解析] 解析成功: 指令=查询, ... [TCP服务端] 发送应答帧 (14 bytes): A5 00 00 ... 3E 8B 5A ``` #### 测试方法 — RS-485 **硬件**:USB 转 RS-485 接 ttyS2,共地,A/B 极性正确。 **参数**:波特率 = `/root/lvgl_web_config` 中 `PORT_1`(默认 115200),8N1。 **工具**:sscom、串口助手,或 Python `pyserial`: ```python import serial import time ser = serial.Serial("/dev/ttyUSB0", 115200, timeout=1) # 查询 ser.write(bytes([0xAB, 0x01, 0x3B, 0x96, 0xBA])) print("query:", ser.read(20).hex(" ")) # 手动 + 备机 ser.write(bytes([0xAB, 0x02, 0x00, 0x01, 0x3B, 0x96, 0xBA])) print("ctrl:", ser.read(20).hex(" ")) time.sleep(3) # 查询确认 ser.write(bytes([0xAB, 0x01, 0x3B, 0x96, 0xBA])) print("confirm:", ser.read(20).hex(" ")) ser.close() ``` 帧格式与应答与 TCP 完全一致。 --- ### 3.2 串口屏 — `/dev/ttyS1` | 项目 | 取值 | |------|------| | 设备 | `/dev/ttyS1` | | 波特率 | 115200,8N1 | | 方向 | 控制盒 → 串口屏(每 500 ms 推送) | | 帧长 | 11 字节 | | 偏移 | 字段 | 说明 | |------|------|------| | 0 | 帧头 | `0xA5` | | 1 | 主备状态 | `0` 主机,`1` 备机 | | 2-3 | 主机功率 | uint16 小端 | | 4-5 | 备机功率 | uint16 小端 | | 6-9 | ETH0 IP | 4 字节 | | 10-11 | CRC | 0(保留) | | 12 | 帧尾 | `0x5A` | **测试**:在 ttyS1 接逻辑分析仪或串口抓包,上电后应每 500 ms 收到一帧。 --- ### 3.3 RS-485 主备互联 — `/dev/ttyS3` | 项目 | 取值 | |------|------| | 设备 | `/dev/ttyS3` | | 波特率 | 配置项 `PORT_2`(默认 115200) | | 状态 | 已初始化;主备盒间协议待扩展 | --- ### 3.4 Web — HTTP(Boa)+ CGI | 项目 | 取值 | |------|------| | 服务 | Boa(`/etc/boa/boa.conf`) | | 网页根目录 | `/var/www/` | | CGI | `/var/www/cgi-bin/save_config.cgi`、`read_status.cgi` | | 配置写入 | `/root/device_config.json` | | 状态读取 | `/root/device_status.json` | `app_web.c` 中后台线程每秒轮询 `device_config.json`,变更同步到 `Soc_t` 与 `lvgl_web_config`。 **测试**: ```bash # 读取状态(需已部署 CGI) curl http://192.168.1.188/cgi-bin/read_status.cgi # 检查 Web 服务 curl -I http://192.168.1.188/ ``` 浏览器修改模式、IP、型号后,在 `lvgl_web_app` 日志中应看到 `[INFO] Update config item`。 --- ### 3.5 发射机设备协议 — TCP 客户端(ETH1) 控制盒作为 **TCP 客户端** 连接 ETH1 上的发射机(与 9898 管理端口不同)。 | 角色 | 默认地址 | |------|----------| | 主机 | `192.168.1.101:9001` | | 备机 | `192.168.1.102:9002` | `app_device_protocol.c` 按型号实现组帧(BGTD、DEXIN、JIZHAO 等): - 周期性查询功率/状态 - 切换前后发送开关机(`APP_Device_Try_Send_PowerOn/Off_By_Role`) **测试**:确认发射机 ETH1 在线,观察日志中 `[PROTO]` 及 `device_status.json` 中的功率字段。 --- ## 4. 默认网络与串口汇总 | 接口 | 默认 IP | 用途 | |------|---------|------| | ETH0 | 192.168.1.188 | Web + 外部 TCP(9898) | | ETH1 | 192.168.1.189 | 发射机链路 | | 主机发射机 | 192.168.1.101:9001 | 功率控制 / 遥测 | | 备机发射机 | 192.168.1.102:9002 | 功率控制 / 遥测 | | 串口 | 设备 | 波特率 | 用途 | |------|------|--------|------| | LCD | `/dev/ttyS1` | 115200 | 串口屏状态推送 | | RS-485 外部 | `/dev/ttyS2` | 可配置 | 外部管理 | | RS-485 主备 | `/dev/ttyS3` | 可配置 | 盒间互联(预留) | --- ## 5. 部署说明 1. 将 `lvgl_web_app`、`lvgl_web_config`、`device_config.json` 拷贝到 `/root/`。 2. 部署 Boa 到 `/etc/boa/`、`/var/www/`。 3. 配置文件须使用 **LF** 换行(Windows 的 CRLF 会导致 Boa 与配置解析失败)。 4. 在 Windows 编辑后可在设备上执行:`dos2unix /etc/boa/boa.conf`。 5. 通过 `/etc/init.d/S99lvgl_web_app` 启动,或手动运行 `/root/lvgl_web_app`。 --- ## 6. 贡献 1. Fork 本仓库 2. 创建功能分支(`Feat_xxx`) 3. 提交代码 4. 发起 Pull Request