From 950e9e6c9ef7b91a51d7c704767d243e292dba4c Mon Sep 17 00:00:00 2001 From: xu <1012118331@qq.com> Date: Wed, 14 Oct 2026 09:59:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=80=9A=E8=AE=AF=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 165 +- app/appcontext.cpp | 166 +- app/appcontext.h | 33 +- app/global.cpp | 36 +- app/global.h | 70 +- .../alarmbanner.cpp.7B2684B6DBA3A864.idx | Bin 1474 -> 1474 bytes .../index/appcontext.cpp.F2C226493E133C0A.idx | Bin 5234 -> 9486 bytes .../index/appcontext.h.7A52BC01A1ABA6DF.idx | Bin 4540 -> 6200 bytes .../clangd/index/arm.cpp.0DCD863C28C5B365.idx | Bin 9974 -> 10472 bytes .../index/chassis.cpp.5E5AFF472027CFA3.idx | Bin 8428 -> 10640 bytes .../index/chassis.h.CC14EDF7DD3DFCF1.idx | Bin 2892 -> 4224 bytes .../confirmdialog.cpp.A07B26C68894B406.idx | Bin 4012 -> 5064 bytes .../confirmdialog.h.17775D0EB3DA3BF8.idx | Bin 1542 -> 1782 bytes .../index/datacard.cpp.8C16BDD4A28E146F.idx | Bin 2954 -> 3380 bytes .../index/datacard.h.2DC6950C11B86AC1.idx | Bin 1722 -> 1790 bytes .../debugmaintain.cpp.6C986D704C3FD524.idx | Bin 10934 -> 12608 bytes .../debugmaintain.h.DBACFCC750E76262.idx | Bin 3602 -> 5454 bytes .../index/faultquery.cpp.D9860DBD08A6DCF3.idx | Bin 4156 -> 5480 bytes .../index/faultquery.h.E07AD20275B9FFA8.idx | Bin 2104 -> 2176 bytes .../index/faulttable.cpp.F570C8FDC58BD6A7.idx | Bin 4862 -> 8192 bytes .../index/faulttable.h.0048E583560E828F.idx | Bin 2402 -> 3208 bytes .../gasdetector.cpp.331923672C01D6FE.idx | Bin 6036 -> 6052 bytes .../index/global.cpp.3110D8C4024B8807.idx | Bin 4256 -> 4256 bytes .../index/global.h.04FEB1AA1BE5CBA9.idx | Bin 9356 -> 10680 bytes .../index/jobselect.cpp.5D8C791ACB1269D6.idx | Bin 5630 -> 6404 bytes .../ledindicator.cpp.9844B6B5BDCBA69A.idx | Bin 4180 -> 4356 bytes .../index/ledindicator.h.736A1ED2611C89E9.idx | Bin 2856 -> 3032 bytes .../index/mainwindow.cpp.939E08F5B3723232.idx | Bin 6324 -> 6720 bytes .../mockvehicle.cpp.CEE2274AD5F0B599.idx | Bin 10440 -> 10530 bytes .../index/mockvehicle.h.D216B3FA554E1321.idx | Bin 3750 -> 4002 bytes .../numpaddialog.cpp.4EC97A520E700E9E.idx | Bin 5228 -> 9096 bytes .../index/numpaddialog.h.348535E0CD997F5C.idx | Bin 1876 -> 3606 bytes .../paramcontrol.cpp.A1AF0D8315118832.idx | Bin 4026 -> 5232 bytes .../index/paramcontrol.h.92C14F36E598CA61.idx | Bin 2340 -> 2982 bytes .../index/selfcheck.cpp.776FCF2AC614C2A9.idx | Bin 8346 -> 8624 bytes .../statusstrip.cpp.A222ACFC7F328D41.idx | Bin 3896 -> 2878 bytes .../index/statusstrip.h.0FA03DAF03776B1B.idx | Bin 2034 -> 1698 bytes .../index/titlebar.cpp.C884912AA7BAF5D8.idx | Bin 4724 -> 4074 bytes .../index/titlebar.h.846D9FE96FC3F9AB.idx | Bin 1922 -> 1864 bytes .../udpcommunication.cpp.BD56AC874B6B7F45.idx | Bin 12338 -> 26832 bytes .../udpcommunication.h.1076B38082689E9B.idx | Bin 4234 -> 8998 bytes .../index/washbase.cpp.36D02F9D325A3B2C.idx | Bin 11650 -> 11794 bytes .../.qtc_clangd/compile_commands.json | 2 +- .../Makefile.Debug | 1365 +++++++++-------- .../Makefile.Release | 1365 +++++++++-------- .../debug/config.ini | 2 +- .../object_script.wuhan.Debug | 14 +- .../object_script.wuhan.Release | 14 +- cfg_test.ini | 3 + comm/car.cpp | 868 +++++++++++ comm/car.h | 152 ++ comm/mockvehicle.cpp | 223 --- comm/mockvehicle.h | 58 - comm/uart.cpp | 151 ++ comm/uart.h | 73 + comm/udpcommunication.cpp | 242 --- comm/udpcommunication.h | 70 - main.cpp | 1 + mainwindow.cpp | 44 +- pages/arm.cpp | 132 +- pages/chassis.cpp | 105 +- pages/chassis.h | 6 + pages/debugmaintain.cpp | 210 ++- pages/debugmaintain.h | 16 +- pages/faultquery.cpp | 84 +- pages/faultquery.h | 4 + pages/gasdetector.cpp | 16 +- pages/home.cpp | 149 ++ pages/home.h | 30 + pages/jobselect.cpp | 69 +- pages/manualclean.cpp | 28 - pages/manualclean.h | 23 - pages/manualdisinfect.cpp | 49 - pages/manualdisinfect.h | 25 - pages/selfcheck.cpp | 22 +- pages/washbase.cpp | 7 +- pages/washbase.h | 2 +- pages/washpage.cpp | 369 +++++ pages/washpage.h | 86 ++ tools/__pycache__/can_sender.cpython-311.pyc | Bin 0 -> 23152 bytes tools/__pycache__/rc_sender.cpython-311.pyc | Bin 0 -> 4913 bytes tools/can_sender.py | 350 +++++ tools/rc_sender.py | 79 + widgets/alarmbanner.cpp | 2 +- widgets/confirmdialog.cpp | 70 +- widgets/confirmdialog.h | 6 + widgets/datacard.cpp | 33 +- widgets/datacard.h | 1 + widgets/faulttable.cpp | 123 +- widgets/faulttable.h | 13 + widgets/ledindicator.cpp | 23 +- widgets/ledindicator.h | 2 + widgets/numpaddialog.cpp | 203 ++- widgets/numpaddialog.h | 29 + widgets/paramcontrol.cpp | 171 ++- widgets/paramcontrol.h | 22 +- widgets/statusstrip.cpp | 73 +- widgets/statusstrip.h | 15 +- widgets/titlebar.cpp | 90 +- widgets/titlebar.h | 17 +- wuhan.pro | 22 +- 洗消机器人遥控器通信协议20260810.csv | 324 ++++ 102 files changed, 5538 insertions(+), 2679 deletions(-) create mode 100644 cfg_test.ini create mode 100644 comm/car.cpp create mode 100644 comm/car.h delete mode 100644 comm/mockvehicle.cpp delete mode 100644 comm/mockvehicle.h create mode 100644 comm/uart.cpp create mode 100644 comm/uart.h delete mode 100644 comm/udpcommunication.cpp delete mode 100644 comm/udpcommunication.h create mode 100644 pages/home.cpp create mode 100644 pages/home.h delete mode 100644 pages/manualclean.cpp delete mode 100644 pages/manualclean.h delete mode 100644 pages/manualdisinfect.cpp delete mode 100644 pages/manualdisinfect.h create mode 100644 pages/washpage.cpp create mode 100644 pages/washpage.h create mode 100644 tools/__pycache__/can_sender.cpython-311.pyc create mode 100644 tools/__pycache__/rc_sender.cpython-311.pyc create mode 100644 tools/can_sender.py create mode 100644 tools/rc_sender.py create mode 100644 洗消机器人遥控器通信协议20260810.csv diff --git a/README.md b/README.md index b750b41..4ba50b4 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # 图数一体机遥控端 QT 界面(wuhan) 依据《QT界面详细设计说明书.md》实现的“玉兔工业无线遥控器(YTZT6K8EART2-aL25)”遥控端界面, -覆盖 ③开机密码(含密码警告/密码管理登录/密码管理/密码重置)④自检 ⑤作业选择 ⑥机器人底盘 -⑦机械臂 ⑧气体检测仪 ⑨手动消除 ⑩手动消毒 ⑪调试维护 ⑫故障查询 共 10 个界面, -并配套 **UDP 收包 / 发包通信** 与内置模拟车载端。 +覆盖 ③开机密码(含密码警告/密码管理登录/密码管理/密码重置)④自检 ⑤系统首页面 ⑥作业选择 ⑦机器人底盘 +⑧机械臂 ⑨气体检测仪 ⑩消除作业 ⑪消毒作业 ⑫调试维护 ⑬故障查询 共 11 个界面, +并配套 **CAN 报文收发**的通信层(19 条报文经 `/dev/ttyS7` 串口收发,遥控器另占 `/dev/ttyS5`)。 ## 环境与构建 - Qt 5.15.2 MinGW 32-bit(`F:\QT\5.15.2\mingw81_32`),编译套件 `Desktop Qt 5.15.2 MinGW 32-bit`。 -- 用 Qt Creator 打开 `wuhan.pro` 直接构建运行即可(qmake 工程,`QT += network`)。 +- 用 Qt Creator 打开 `wuhan.pro` 直接构建运行即可(qmake 工程,`QT += serialport`)。 命令行构建示例: @@ -23,35 +23,141 @@ mingw32-make -j4 ## 运行说明 -- 开机密码默认 **6789**(可在“忘记密码/修改密码 → 密码管理登录(管理员密码 6789)→ 密码管理 → 重置密码”中修改)。 +- 开机密码默认 **6789**(可在“忘记密码/修改密码 → 密码管理登录(管理员密码 **9527**)→ 密码管理 → 重置密码”中修改);管理员密码为固定值 **9527**,不可修改。 - 密码正确后进入自检,自检完成 2 秒自动进入“作业选择”;自检异常时可“忽略故障”继续。 -- 底部状态条上的**模式徽标可点击**(或按键盘 **M**)在“行车模式 / 机械臂模式”间切换; - 行车模式操作底盘,机械臂模式操作机械臂。 -- 软急停:各页顶条右上角“急停”按钮,或按键盘 **E**;急停后需点击“复位”恢复。 +- 底部状态条的模式徽标只作**显示**(不响应点击);行车模式操作底盘,机械臂模式操作机械臂。 + 演示用快捷键 **M** 可切换模式,但**遥控器在线时模式由遥控器报文决定,M 被忽略**。 +- 软急停由**遥控器 bit4** 下发:上升沿触发急停、下降沿复位,面板不提供急停/复位控件 + (顶条原有的“急停”按钮已移除)。触发后页面之上出现全屏红条“急停已触发,请用遥控器复位!”。 - ⑨⑩ 运行中“返回”无效(需先停止);调试维护页常显红色警告。 -## 通信(UDP 收包 / 发包) +## 通信(19 条 CAN 报文,经 /dev/ttyS7 串口收发) -通信层位于 `comm/udpcommunication.cpp`,协议为 **JSON 文本**(`QJsonDocument`): +通信层位于 `comm/car.cpp`,按《洗消机器人遥控器通信协议20260810》sheet **RC** 实现全部 +**19 条**报文(分类四标准帧 `DIDO/PWM/AI/BMS/MCU` 未实现)。所有报文均为 **29 位扩展帧、 +信号一律 Intel(小端)位序**。 -- 遥控端绑定本地端口 `9000`,向 `127.0.0.1:9001` 发包; -- 内置模拟车载端 `comm/mockvehicle.cpp` 绑定 `9001`,向 `9000` 回传遥测,并对命令作出响应。 +串口是字节流,故线上帧统一为 **18 字节**,收发同一格式: -上行命令(UI→车载端,`{"t":...}`):`chassis_move` `arm_move` `arm_return` `arm_pause` -`gas_start` `gas_stop` `wash_start` `wash_stop` `ball_valve` `pump_power` `pump_rpm` -`set_param` `selfcheck_start` `estop` `estop_reset`。 +| 偏移 | 长度 | 含义 | +| --- | --- | --- | +| 0 | 4 | 帧头 `0x5AA5FF00`(线上字节序 `00 FF A5 5A`,用于在字节流中定位报文起始) | +| 4 | 4 | CAN ID(29 位扩展帧),直接用于分流 | +| 8 | 1 | DLC,固定 `8` | +| 9 | 8 | CAN 数据段 | +| 17 | 1 | 校验和:canId 四字节 + DLC + 数据八字节求和取低 8 位 | -下行遥测(车载端→UI):`heartbeat` `chassis` `arm` `wash` `tank` `gas` `valve` `pump` -`estop` `selfcheck` `selfcheck_done`。 +收包时逐字节找帧头、校验不过右移一字节继续找,容忍粘包、拆包与杂包。 -可在 `config.ini`(程序目录)中配置 `net/local_port`、`net/remote_host`、`net/remote_port`、 -`net/mock`(true 启用内置模拟端,false 连接真实车载端)、`net/selfcheck_fault`(自检故障演示)。 +**上行 7 条**(遥控器 → 车端)按协议「报文周期时间」列分三组重发:**50ms** 底盘 / 机械臂1 / +机械臂2 / 急停,**100ms** 洗消指令 / 洗消调试,**1000ms** 气体检测仪。界面上层只更新控制状态, +编码发送由定时器完成。 + +`Chassis_ControlCmd 0x18208081`、`Arm_ControlCmd1 0x18218081`、`Arm_ControlCmd2 0x18228081`、 +`Decontam_ControlCmd 0x18238081`、`Decontam_DebugCmd 0x18248081`、`Detector_ControlCmd 0x18258081`、 +`Emergency_Cmd 0x18268081`。 + +**下行 12 条**(车端 → 遥控器)按 canId 分流写入 `Telemetry` 供各页刷新: + +| CAN ID | 报文 | 周期 | 主要信号 | +| --- | --- | --- | --- | +| `0x18308180` | 底盘状态1(BMS) | 100 | 总电压、SOC、MCU 故障码与等级、10 路 BMS 故障等级 | +| `0x18318180` | 底盘状态2(BMS) | 100 | 7 路故障等级、4 路继电器粘连、手刹、车速、ready、整系统通信故障码 | +| `0x18328180` | 机械臂状态1 | 100 | 末端位置 X/Y/Z | +| `0x18338180` | 机械臂状态2 | 100 | 末端姿态 RX/RY/RZ、末端速度 | +| `0x18348180` | 机械臂状态3 | 100 | 关节角 J1~J4 | +| `0x18358180` | 机械臂状态4 | 100 | 关节角 J5~J6 | +| `0x18368180` | 洗消工作状态1 | 200 | 模式、启停、三泵上电、D1/D2 到位、液位、A/B 流量、气体流量、发泡倍数 | +| `0x18378180` | 洗消工作状态2 | 200 | 管路/空压机压力、三泵流量反馈、空压机与柱塞泵故障码 | +| `0x18388180` | 检测仪结果状态1 | 1000 | 主机状态、毒物种类与等级、告警位 | +| `0x18398180` | 检测仪结果状态2 | 1000 | 40 位有毒物浓度(ppb) | +| `0x183A8180` | 底盘状态3 | 100 | EPS 22 位 + 制动 24 位故障标志、EPS 故障码 | +| `0x183B8180` | 底盘状态4 | 100 | 挡位、驻车、四驱模式、方向盘角度与对中、左右制动电机故障 | + +故障码按协议“信号值描述”列查表转中文(MCU 29 条、空压机 31 条、柱塞泵 7 条、EPS/制动 46 个 +标志位、检测仪 4 个告警位等),页面显示中文描述,**首次出现**的故障才写入故障历史, +避免周期报文重复刷屏。 + +各信号的起始位/位长/精度与协议表一致;界面量程与组帧取值统一按协议「信号值描述」列收口: +车速 0~5.00 km/h、发泡倍数 6~12 倍、泡沫比例 0~10%、空压机流量 0~340 L/min、柱塞泵流量 +0~24 L/min;调试维护页三泵转速上限各按协议(空压机 3000 / 柱塞泵 1450 / 叶片泵 2000 rpm)。 + +顶部「通信正常/异常」按收帧判定:连续 **2s** 未收到任何合法帧即判为异常(最慢的下行周期 +1000ms)。串口打开失败(如开发机 Windows 上没有 `/dev/ttyS7`)时不启动定时器、不崩溃, +界面显示「通信异常」。 + +可在 `config.ini`(程序目录)中配置 `car/device`(默认 `/dev/ttyS7`)、`car/baud`(默认 +`500000`)、`rc/device`(默认 `/dev/ttyS5`)、`rc/baud`(默认 `115200`)、`net/selfcheck_fault` +(自检故障演示)。 参数(车速、柱塞泵流量、发泡倍数、泡沫比例、空压机流量)与密码经 `QSettings` 持久化,上电自动读取。 +`tools/can_sender.py` / `tools/rc_sender.py` 是旧的 UDP 联调工具,**已不适用于当前串口通道**, +留待需要时另写。 + +### 已知缺口 + +- **自检 10 项无对应 CAN 报文**,`sendSelfCheckStart()` 暂由本端跑完序列(见 `onSelfCheckTick` + 的 TODO),仅为维持 ④自检 → ⑤作业选择 流程不中断;拿到真实报文后替换该本地驱动。 +- **行车/机械臂模式**没有对应的下行信号:协议 12 条反馈里没有该字段,`telemetry.driveMode` + 无处可填,故模式只由遥控器报文决定(遥控器离线时保持上次值,可用演示快捷键 **M** 切换)。 +- **`Vehi_spd` 按协议取 8 位**(bit24、精度 0.01 → 0~2.55 km/h)。协议表里该信号与其后的 + `Vehi_RdyStatus`(32)/`Reserve`(34)/`All_Comm_FaultCode`(40) 起始位自洽,按 8 位读才对齐; + 若实测车速需要 0~5 km/h,说明该字段实为 16 位、其后信号整体后移 8 位,需同步调整。 +- 分类四(标准帧,`DIDO/PWM/AI/BMS/MCU`)整体未实现。其 `Motorola LSB` 排列格式 = + **大端字节序 + 起始位为 LSB 位置**:16 位信号起始位 8/24/40/56 表示占字节对 + (0,1)/(2,3)/(4,5)/(6,7),`value = data[起/8-1]<<8 | data[起/8]`,恰好占满 64 位、 + 无溢出。实现分类四时勿按 Intel 连续位序去读。 + +## 遥控器(串口 /dev/ttyS5,24 字节二进制帧) + +遥控器不走车端 CAN 通道,独立占一路串口:`comm/uart.cpp` 打开 `rc/device` +(默认 **`/dev/ttyS5`**,115200 / 8 数据位 / 1 停止位 / 无校验),与车端 `/dev/ttyS7` +(见 `comm/car.cpp`)互不干扰。 + +帧长 **24 字节**、1 字节对齐、多字节字段**小端**,发送周期 100ms。帧头 `BYTE0=0x11`、 +帧尾 `BYTE22=0x22`、`BYTE23 = BYTE0~BYTE22 之和的低 8 位`;校验不过即丢弃。 +串口是字节流,收包在缓冲区里按帧长滑窗重新对齐,容忍粘包与拆包。 + +| 字节 | 含义 | +| --- | --- | +| 0 | 帧头 `0x11` | +| 1 | 标志位:bit0 行车模式 / bit1 机械臂模式 / bit2 机械臂使能 / bit3 底盘使能 / bit4 急停 | +| 2 | 预留 `0x00` | +| 3-4 | 整车档位(底盘 Y):0 后 / 2048 中 / 4096 前 | +| 5-6 | 整车转向(底盘 X):0 左 / 2048 中 / 4096 右 | +| 7-8 | 末端位置上下(臂 Z) | +| 9-10 | 末端位置左右(臂 X) | +| 11-12 | 末端位置前后(臂 Y) | +| 13-14 | 末端姿态横滚 roll | +| 15-16 | 末端姿态俯仰 pitch | +| 17-18 | 末端姿态偏航 yaw | +| 19 | 遥控器电量 0~100 | +| 20-21 | 预留 `0x00` | +| 22 | 帧尾 `0x22` | +| 23 | 和校验 | + +分工:`RcCommunication` 只负责收包、校验、解析与链路超时(连续 500ms 无合法帧判为断开); +模式/急停/动作的判定集中在 `AppContext`。 + +- **模式**:bit1 机械臂模式须与 bit2 机械臂使能**同时为 1** 才切到机械臂模式,否则按 bit0 + 走行车模式。遥控器在线时模式由遥控器决定,离线时才回落到车载端 `chassis` 报文的 `mode` 字段。 +- **急停**:bit4 **上升沿触发急停、下降沿复位**,两者都由遥控器下发(一并上行 `estop` / + `estop_reset`),面板只显示红条提示、不提供复位控件。链路中断不会触发复位——超时导致的 + bit4 读为 0 不算遥控器指令。 +- **动作**:摇杆以 ±10%(1638~2458)为中立死区,方向**变化时才下发**,命令复用现有 + `chassis_move` / `arm_move`,并与界面按钮同一套安全联锁(模式 + 使能 + 未急停)。 + 档位与转向可同时下发(边前进/后退边转向);链路中断会立即撤销所有遥控器动作, + 避免最后一条指令被一直挂住。 +- **优先级**:遥控器与界面都能操作,同时操作时**遥控器优先**——遥控器正在操作(链路在线且 + 有轴偏离中位)时,底盘页与机械臂页的动作按钮变灰,强行按下会提示“遥控器操作中”。 +- **显示**:顶条右侧为遥控器链路(●正常/断开)与电量;顶条不再显示时钟与急停按钮。 +- 底盘页的方向按钮同样支持多键同按:按住“前进”再按住“左转向”,会下发 `dir=fwd` + + `turn=left`,两键都松开才停车。 + ## 目录结构 - `app/` 全局常量、配色、应用上下文(AppContext)、QSS 样式、闪烁工具 -- `comm/` UDP 通信层 + 内置模拟车载端 +- `comm/` 车端 CAN 通信层(`car`,19 条报文经 `/dev/ttyS7`)+ 遥控器串口通道(`uart`,`/dev/ttyS5`) - `widgets/` 公共组件:TitleBar / StatusStrip / LedIndicator / NumPadDialog / ConfirmDialog / AlarmBanner / ParamControl / DataCard / FaultTable - `pages/` 各界面(继承 BasePage),对应关系如下: @@ -65,15 +171,14 @@ mingw32-make -j4 | ③ 密码管理 | passwordmgmt | PasswordMgmtPage | | ③ 密码重置 | passwordreset | PasswordResetPage | | ④ 自检 | selfcheck | SelfCheckPage | -| ⑤ 作业选择 | jobselect | JobSelectPage | -| ⑥ 机器人底盘 | chassis | ChassisPage | -| ⑦ 机械臂 | arm | ArmPage | -| ⑧ 气体检测仪 | gasdetector | GasDetectorPage | -| ⑨/⑩ 共用基类 | washbase | WashBasePage | -| ⑨ 手动消除 | manualclean | ManualCleanPage | -| ⑩ 手动消毒 | manualdisinfect | ManualDisinfectPage | -| ⑪ 调试维护 | debugmaintain | DebugMaintainPage | -| ⑫ 故障查询 | faultquery | FaultQueryPage | +| ⑤ 系统首页面 | home | HomePage | +| ⑥ 作业选择 | jobselect | JobSelectPage | +| ⑦ 机器人底盘 | chassis | ChassisPage | +| ⑧ 机械臂 | arm | ArmPage | +| ⑨ 气体检测仪 | gasdetector | GasDetectorPage | +| ⑩/⑪ 消除/消毒作业 | washpage | WashPage | +| ⑫ 调试维护 | debugmaintain | DebugMaintainPage | +| ⑬ 故障查询 | faultquery | FaultQueryPage | -- `mainwindow.h/.cpp` 主窗口(QStackedWidget 页面路由 + 全局急停横幅) +- `mainwindow.h/.cpp` 主窗口(QStackedWidget 页面路由 + 全局急停横幅,横幅内含“复位”按钮) diff --git a/app/appcontext.cpp b/app/appcontext.cpp index 7a3aa8c..f9a0c83 100644 --- a/app/appcontext.cpp +++ b/app/appcontext.cpp @@ -1,9 +1,26 @@ #include "appcontext.h" -#include "udpcommunication.h" -#include "mockvehicle.h" +#include "car.h" #include #include +namespace { + +// 遥控器摇杆满量程 0~4096、中位 2048;取 ±10% 死区,即 1638~2458 视为中立 +const int kAxisMid = 2048; +const int kAxisDead = 410; + +int axisDir(int v) +{ + if (v > kAxisMid + kAxisDead) return +1; + if (v < kAxisMid - kAxisDead) return -1; + return 0; +} + +// 六个机械臂轴,顺序与 sendArmMove 的轴名一致(见 pages/arm.cpp) +const char* const kArmAxes[6] = {"z", "x", "y", "roll", "pitch", "yaw"}; + +} // namespace + AppContext* AppContext::instance() { static AppContext* s = nullptr; @@ -25,18 +42,31 @@ AppContext::AppContext(QObject* parent) if (!m_settings->contains(keyFoamPercent()))m_settings->setValue(keyFoamPercent(), 3); if (!m_settings->contains(keyAirFlow())) m_settings->setValue(keyAirFlow(), 100); if (!m_settings->contains("param/pwd")) m_settings->setValue("param/pwd", "6789"); - if (!m_settings->contains("param/admin_pwd"))m_settings->setValue("param/admin_pwd", "6789"); - // 通信层(UDP 收包发包) - m_comm = new UdpCommunication(&telemetry, this); + // 通信层(19 条 CAN 报文经 /dev/ttyS7 收发,见 comm/car.h) + m_comm = new CarCommunication(&telemetry, this); - // 内置模拟车载端(离线演示:回环 UDP 收发) - m_vehicle = new MockVehicle(this); + // 遥控器(串口 /dev/ttyS5,24 字节二进制帧,见 comm/uart.h) + m_rc = new RcCommunication(this); // 通信层故障信号 → 写入故障历史 - connect(m_comm, &UdpCommunication::faultAppended, + connect(m_comm, &CarCommunication::faultAppended, this, &AppContext::appendFault); + // 遥控器报文 → 模式 / 急停 / 动作 / 显示 + connect(m_rc, &RcCommunication::frameReceived, this, &AppContext::onRcFrame); + connect(m_rc, &RcCommunication::linkChanged, this, &AppContext::onRcLinkChanged); + +#if 0 + // 行车/机械臂模式改由底盘报文的 mode 字段决定(遥控器在线时让位给遥控器报文)。 + // 停用原因:协议 sheet RC 的 12 条下行报文里没有该 mode 字段,telemetry.driveMode + // 无处可填,此连接只会一直把模式按 0(行车)写。待协议补充该信号后恢复。 + connect(m_comm, &CarCommunication::telemetryUpdated, this, [this]() { + if (m_rc->linkOk()) return; + setMode(telemetry.driveMode == 0 ? Mode::Drive : Mode::Arm); + }); +#endif + seedFaultHistory(); } @@ -117,9 +147,10 @@ void AppContext::setPassword(const QString& pwd) m_settings->sync(); } +// 管理员密码为固定值,不持久化、不可修改 QString AppContext::adminPassword() const { - return m_settings->value("param/admin_pwd", "6789").toString(); + return "9527"; } void AppContext::resetEstop() @@ -144,4 +175,121 @@ void AppContext::setMode(Mode m) if (mode == m) return; mode = m; emit modeChanged(m); + if (m_comm) m_comm->setArmMode(m == Mode::Arm); // 同步到 Arm_ControlCmd1 的 Arm_modeCmd/Arm_enable +} + +// ===================== 遥控器(24 字节二进制帧) ===================== +void AppContext::onRcFrame(const RcFrame& f) +{ + // 存入遥测快照,供界面显示 + telemetry.rcBattery = f.battery; + telemetry.rcDriveMode = f.driveMode; + telemetry.rcArmMode = f.armMode; + telemetry.rcArmEnabled = f.armEnabled; + telemetry.rcChassisEnabled = f.chassisEnabled; + telemetry.rcEstop = f.estop; + telemetry.rcGear = f.gear; + telemetry.rcSteer = f.steer; + telemetry.rcPosZ = f.posZ; + telemetry.rcPosX = f.posX; + telemetry.rcPosY = f.posY; + telemetry.rcRoll = f.roll; + telemetry.rcPitch = f.pitch; + telemetry.rcYaw = f.yaw; + + // 模式:bit1 机械臂模式须与 bit2 机械臂使能同时为 1 才视为机械臂 + setMode((f.armMode && f.armEnabled) ? Mode::Arm : Mode::Drive); + + // 急停与复位都由遥控器 bit4 下发:上升沿触发,下降沿复位。面板只显示横幅提示。 + if (f.estop != m_rcEstopPrev) { + m_rcEstopPrev = f.estop; + if (f.estop) triggerEstop(); + else resetEstop(); + } + + // 动作:与界面按钮同一套安全联锁(模式 + 使能 + 未急停) + applyRcChassis(f.gear, f.steer, f.chassisEnabled); + applyRcArm(f); + + updateRcDriving(); +} + +void AppContext::applyRcChassis(int gear, int steer, bool enabled) +{ + const bool allowed = enabled && !estopTriggered && (mode == Mode::Drive); + + const int g = allowed ? axisDir(gear) : 0; + const int s = allowed ? axisDir(steer) : 0; + + if (g == m_rcGearDir && s == m_rcSteerDir) return; // 方向未变,不重复发包 + m_rcGearDir = g; + m_rcSteerDir = s; + sendChassisCommand(); +} + +void AppContext::applyRcArm(const RcFrame& f) +{ + const bool allowed = f.armEnabled && !estopTriggered && (mode == Mode::Arm); + const int vals[6] = {f.posZ, f.posX, f.posY, f.roll, f.pitch, f.yaw}; + + for (int i = 0; i < 6; ++i) { + const int d = allowed ? axisDir(vals[i]) : 0; + if (d == m_rcAxisDir[i]) continue; // 方向未变,不重复发包 + m_rcAxisDir[i] = d; + m_comm->sendArmMove(QString::fromLatin1(kArmAxes[i]), d); + } +} + +// 档位与转向可同时下发(边前进/后退边转向);两者都回中才停车。 +// 仅转向(档位回中)时发 stop + turn,由车载端决定是否原地转向。 +void AppContext::sendChassisCommand() +{ + QString dir = "stop"; + if (m_rcGearDir > 0) dir = "fwd"; + else if (m_rcGearDir < 0) dir = "back"; + + QString turn; + if (m_rcSteerDir > 0) turn = "right"; + else if (m_rcSteerDir < 0) turn = "left"; + + if (dir == "stop" && turn.isEmpty()) { + m_comm->sendChassisMove("stop", 0); + return; + } + const double spd = (dir == "stop") ? 0.0 : getDouble(keySpeed(), 4.5); + m_comm->sendChassisMove(dir, spd, turn); +} + +void AppContext::updateRcDriving() +{ + // 遥控器正在操作:链路在线且任一已生效轴偏离中位 + bool driving = (m_rcGearDir != 0) || (m_rcSteerDir != 0); + for (int i = 0; i < 6; ++i) { + if (m_rcAxisDir[i] != 0) { driving = true; break; } + } + + if (driving == m_rcDriving) return; + m_rcDriving = driving; + emit rcDrivingChanged(driving); +} + +void AppContext::onRcLinkChanged(bool ok) +{ + telemetry.rcLinkOk = ok; + + if (!ok) { + // 链路中断:撤销所有遥控器动作,否则最后一条指令会被一直挂住。 + // 不动急停状态——丢包导致的 bit4 读为 0 不应被当成遥控器下发的复位。 + if (m_rcGearDir != 0 || m_rcSteerDir != 0) { + m_rcGearDir = 0; + m_rcSteerDir = 0; + m_comm->sendChassisMove("stop", 0); + } + for (int i = 0; i < 6; ++i) { + if (m_rcAxisDir[i] == 0) continue; + m_rcAxisDir[i] = 0; + m_comm->sendArmMove(QString::fromLatin1(kArmAxes[i]), 0); + } + } + updateRcDriving(); } diff --git a/app/appcontext.h b/app/appcontext.h index 1528d41..562882e 100644 --- a/app/appcontext.h +++ b/app/appcontext.h @@ -5,9 +5,9 @@ #include #include #include "global.h" +#include "uart.h" -class UdpCommunication; -class MockVehicle; +class CarCommunication; // 故障历史记录 struct FaultRecord { @@ -25,8 +25,8 @@ class AppContext : public QObject public: static AppContext* instance(); - UdpCommunication* comm() const { return m_comm; } - MockVehicle* vehicle() const { return m_vehicle; } + CarCommunication* comm() const { return m_comm; } + RcCommunication* rc() const { return m_rc; } // 遥测数据快照(由通信层填充) Telemetry telemetry; @@ -68,17 +68,38 @@ public: // 模式切换(行车/机械臂,由面板机械旋钮或演示按钮触发) void setMode(Mode m); + // 遥控器:链路状态与“正在操作”判据(遥控器优先时界面按钮让位) + bool rcDriving() const { return m_rcDriving; } + signals: void modeChanged(Mode m); void estopChanged(bool triggered); void commChanged(bool ok); + void rcDrivingChanged(bool driving); + +private slots: + void onRcFrame(const RcFrame& f); + void onRcLinkChanged(bool ok); private: explicit AppContext(QObject* parent = nullptr); void seedFaultHistory(); + + void applyRcChassis(int gear, int steer, bool enabled); + void applyRcArm(const RcFrame& f); + void sendChassisCommand(); // 按当前档位/转向方向下发(两者可同时) + void updateRcDriving(); + QSettings* m_settings = nullptr; - UdpCommunication* m_comm = nullptr; - MockVehicle* m_vehicle = nullptr; + CarCommunication* m_comm = nullptr; + RcCommunication* m_rc = nullptr; + + // 遥控器动作边沿状态:只在方向变化时下发,避免 100ms 刷屏 + int m_rcGearDir = 0; // -1=后 0=中 +1=前 + int m_rcSteerDir = 0; // -1=左 0=中 +1=右 + int m_rcAxisDir[6] = {0, 0, 0, 0, 0, 0}; // 顺序见 kArmAxes + bool m_rcEstopPrev = false; + bool m_rcDriving = false; }; // 便捷访问宏 diff --git a/app/global.cpp b/app/global.cpp index 5533058..261312e 100644 --- a/app/global.cpp +++ b/app/global.cpp @@ -1,8 +1,10 @@ #include "global.h" #include #include +#include #include #include +#include #include // 启动 500ms 闪烁:每 500ms 切换一次动态属性,QSS 据此在 40%↔100% 透明度间切换 @@ -61,7 +63,7 @@ bool ButtonFocusGlow::eventFilter(QObject* obj, QEvent* ev) if (ev->type() == QEvent::FocusIn) { if (!w->graphicsEffect()) { // 挂一圈发光(偏移 0 → 四周光晕) QGraphicsDropShadowEffect* g = new QGraphicsDropShadowEffect(w); - g->setBlurRadius(55); // 光晕大小(越大越强) + g->setBlurRadius(40); // 光晕大小(越大越强) g->setOffset(0, 0); g->setColor(m_glow); w->setGraphicsEffect(g); @@ -80,6 +82,33 @@ void installButtonFocusGlow(QApplication* app) app->installEventFilter(new ButtonFocusGlow(QColor("#7FBBFF"), app)); // 亮蓝,深色底更醒目 } +// ===================== 方向键(旋钮)隔离 ===================== +ArrowKeyGuard::ArrowKeyGuard(QObject* parent) + : QObject(parent) +{ +} + +bool ArrowKeyGuard::eventFilter(QObject* obj, QEvent* ev) +{ + if (ev->type() == QEvent::KeyPress) { + // 只在焦点落在按钮上时拦截:否则 QAbstractButton 会拿方向键去换焦 + QWidget* fw = QApplication::focusWidget(); + if (fw && qobject_cast(fw)) { + const int k = static_cast(ev)->key(); + if (k == Qt::Key_Up || k == Qt::Key_Down + || k == Qt::Key_Left || k == Qt::Key_Right) + return true; + } + } + return QObject::eventFilter(obj, ev); +} + +void installArrowKeyGuard(QApplication* app) +{ + if (app) + app->installEventFilter(new ArrowKeyGuard(app)); +} + QString appStyleSheet() { const QString s = QString(R"QSS( @@ -108,7 +137,6 @@ QPushButton { border: 1px solid BD; border-radius: 12px; font-size: 40px; - min-height: 56px; } QPushButton:hover { background-color: PRI; color: TXT; border-color: PRI; } QPushButton:pressed { background-color: PRI; color: #FFFFFF; border-color: PRI; } @@ -165,7 +193,7 @@ QHeaderView::section { font-size: 40px; } QTableWidget::item { padding: 6px; } -QTableWidget::item:selected { background-color: rgba(61,139,253,0.15); color: TXT; } +QTableWidget::item:selected { background-color: PRI; color: #FFFFFF; } /* ===== 数字键盘 ===== */ QPushButton[numpad="true"] { @@ -177,7 +205,7 @@ QPushButton[numpad="true"]:pressed { background-color: PRI; color: #FFFFFF; } QPushButton:focus { background-color: PRI; color: #FFFFFF; - border: 2px solid #8ABFFF; + border: 4px solid #8ABFFF; } QPushButton:focus:disabled { background-color: DIS; color: DIST; border: 1px solid DIS; diff --git a/app/global.h b/app/global.h index 20d29c5..549d322 100644 --- a/app/global.h +++ b/app/global.h @@ -12,9 +12,10 @@ class QApplication; // ===================== 画布与全局常量 ===================== -// 屏幕分辨率(1920×1080 横屏);若部署在更低分辨率屏,可按同一比例整体缩放 +// 屏幕分辨率(1920×1200);窗口按此固定尺寸,页面布局靠 layout 自适应, +// 富余的垂直空间由各页的 addStretch 分配(控件像素尺寸与字号不随屏幕缩放) static const int SCREEN_W = 1920; -static const int SCREEN_H = 1080; +static const int SCREEN_H = 1200; // ===================== 工业风格配色体系(QSS 十六进制) ===================== namespace C { @@ -44,25 +45,32 @@ enum PageId { PageMgmt, // 密码管理 PageReset, // 密码重置 PageSelfCheck, // ④ 自检 - PageJob, // ⑤ 作业选择 - PageChassis, // ⑥ 机器人底盘 - PageArm, // ⑦ 机械臂 - PageGas, // ⑧ 气体检测仪 - PageClean, // ⑨ 手动消除 - PageDisinfect, // ⑩ 手动消毒 - PageDebug, // ⑪ 调试维护 - PageFault, // ⑫ 故障查询 + PageHome, // ⑤ 系统首页面(自检完成后进入) + PageJob, // ⑥ 作业选择 + PageChassis, // ⑦ 机器人底盘 + PageArm, // ⑧ 机械臂 + PageGas, // ⑨ 气体检测仪 + PageClean, // ⑩ 消除作业 + PageDisinfect, // ⑪ 消毒作业 + PageDebug, // ⑫ 调试维护 + PageFault, // ⑬ 故障查询 PageCount }; // ===================== 遥测数据(车载端 → UI) ===================== -// 由通信层解析 UDP 报文后填充,页面 500ms 定时读取刷新 +// 由通信层解析车端反馈报文后填充,页面刷新时读取 struct Telemetry { // 底盘 int soc = 85; // 电量 % double volt = 360.0; // 电压 V double speed = 3.45; // 车速 km/h bool handbrake = true; // 手刹 + int driveMode = 0; // 行车/机械臂模式:0=行车 1=机械臂(遥控器拨钮→串口→底盘报文 mode) + int rdyStatus = 0; // 底盘 ready:0=初始 1=高压上电完成 2=准备就绪 + int gearPos = 0; // 挡位:0=N 1=D 2=R + int parking = 0; // 驻车:0=未驻车 1=左电机 2=右电机 3=完全驻车 + int awdMode = 0; // 四驱模式:0=二驱 1=四驱 2=四驱锁定 + double steerAngle = 0.0; // 方向盘角度 QStringList chassisFaults; // 底盘故障(多条 2s 轮换) // 机械臂 @@ -74,13 +82,17 @@ struct Telemetry { bool armHomed = false; // 是否已回位(用于底盘“机械臂未回位”告警) // 洗消 - QString washMode = "--"; // 手动消除 / 手动消毒 + QString washMode = "--"; // 消除作业 / 消毒作业 + bool washStart = false; // 洗消启停反馈 double flowA = 0.0; // A 药剂流量 L/min double flowB = 0.0; // B 药剂流量 L/min double gasFlow = 0.0; // 气体流量 L/min double press = 0.0; // 管路压力 MPa double airPress = 0.0; // 空压机压力 MPa double foam = 0.0; // 发泡倍数 + double flowPlungerFb = 0.0; // 柱塞泵流量反馈 L/min + double flowAirFb = 0.0; // 空压机流量反馈 L/min + double flowVaneFb = 0.0; // 叶片泵流量反馈 L/min QStringList washFaults; // 洗消故障代码 // 液位 @@ -106,6 +118,23 @@ struct Telemetry { bool leaf = false; // 叶片泵上电 QStringList pumpFaults; // 泵故障代码 + // 遥控器(24 字节二进制帧,见 comm/uart.h) + bool rcLinkOk = false; // 遥控器链路是否在线 + int rcBattery = 0; // 遥控器电量 % + bool rcDriveMode = false; // bit0 行车模式 + bool rcArmMode = false; // bit1 机械臂模式 + bool rcArmEnabled = false; // bit2 机械臂使能 + bool rcChassisEnabled = false;// bit3 底盘使能 + bool rcEstop = false; // bit4 急停 + int rcGear = 2048; // 整车档位(底盘 Y)0~4096,中位 2048 + int rcSteer = 2048; // 整车转向(底盘 X) + int rcPosZ = 2048; // 末端位置上下 + int rcPosX = 2048; // 末端位置左右 + int rcPosY = 2048; // 末端位置前后 + int rcRoll = 2048; // 末端姿态横滚 + int rcPitch = 2048; // 末端姿态俯仰 + int rcYaw = 2048; // 末端姿态偏航 + // 全局 bool commOk = true; // 通信状态 bool estop = false; // 急停触发 @@ -139,6 +168,23 @@ private: // 为整个应用安装按钮焦点光圈 void installButtonFocusGlow(QApplication* app); +// ===================== 方向键(旋钮)隔离 ===================== +// 方向键在本机是"旋钮"键位,只由 ParamControl 用来调值。但 QAbstractButton 会把 +// 方向键当成沿焦点链换焦(等同 Tab/Shift+Tab),停在按钮上时按方向键焦点会乱跳。 +// 这个全局过滤器把"焦点在按钮上时"的方向键吞掉,方向键只做调值、换焦只走 Tab。 +class ArrowKeyGuard : public QObject +{ + Q_OBJECT +public: + explicit ArrowKeyGuard(QObject* parent = nullptr); + +protected: + bool eventFilter(QObject* obj, QEvent* ev) override; +}; + +// 为整个应用安装方向键隔离 +void installArrowKeyGuard(QApplication* app); + // 全局 QSS 样式表 QString appStyleSheet(); diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/alarmbanner.cpp.7B2684B6DBA3A864.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/alarmbanner.cpp.7B2684B6DBA3A864.idx index 6b1d19d0fd4f33f917dfe10b55b9dbb50f7108da..c30899e4b63c988870b4db379a42aaa1b1decb37 100644 GIT binary patch delta 20 ccmX@aeTaL*WLA!Ti<`0abF-&Rp3SNb09lC$T>t<8 delta 20 ccmX@aeTaL*WLAzl{{#)zN;gzYp3SNb09qCZAOHXW diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/appcontext.cpp.F2C226493E133C0A.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/appcontext.cpp.F2C226493E133C0A.idx index 1ac3b98b26f743bad3a751c7170863288b32742a..64215ea5246c85f7b9bd562088a98989a418eb28 100644 GIT binary patch literal 9486 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 literal 4540 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 diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/arm.cpp.0DCD863C28C5B365.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/arm.cpp.0DCD863C28C5B365.idx index 73a698208432b3a3f731e9bfc8e0c8226d77bd5e..f923481a6fbc840b887a4c334474522794a48f98 100644 GIT binary patch literal 10472 zcmb`Ndt6l2_P}T9QDD!38JGjiFo&mus3-%7Vu)A4)O@5OkAM^!ae$HcOU*|<(@a#v z0w3u$>x%xuGOVtJ{PgFeOtX|!t^&T_k1Vas?9$z*+-EO8zu!NYeJH>Usy!Hut;lxqcCUsfQZb#kyb}xR({14yEUPFXi+KsmELO)UTWLX9R&nuD7%A8HH0C}1u}9hR=?Ev= z3JdL}Pi59jZ&p!ZwxhIQio=#)l=DSK)Oxvi={3%O97`IPw$FZ^^V@yV;*LaJ*-mHt6lj(e9lj+F%#B?E*iow~M!lHMrWlc5~L?uS(rJdC&%X z!EG=2YK<;NBWrL^_L(uI^X84-oq#sbyXf^=gR4PeVr3R(*Mc;ftYv54NLioNxsyni zfpHmhV-Jd?668tqd~6T|_$NF1tWvW)SHNyX*y#&03^J?eaN@YzXz~4IW>a zBb-4bhalh(^kjkqKW_F~awNz_5Q$kZi@ZNsl4VXu!1)O1*+U{Z3eHD4dydL@$vE+E zE}dl3-@U&tdrT&8gVWn!V1wRp?Zq3rPDN|!AkKjA83<(hzOQ$DaQon3Jr#I+daKVL z{Ce`?pr1kvRBe<;l##351fH9~%+$trU;MuJ$|eKIqz-)Rz`_J4CU`aVznm;lK@Ie- zfmo*DQ{lcQaebvCxC-X05Wogf^>D=pN55Pl(H?hz_YMeRs${YgymxY*_xAi{t?!)l zrVR&L2C6O~lP%!01$^0KjqXR&x>}wy(vGUZQVpSO1P9ji)*b$**+gf245E%fG#kOt z-2J+3qrNrKC!X}S-D9(l-_ywE&Walf%fOwfQru9i1QX}wAT5VL#vKP1fj%KbMGQA=4!B5*{YOJkb*a|W;k4ze2TqC41FMg=~$k}!FLs^iCK{g<( zTKp}&n0A?r3mB&wQYO;^rm3X$`8Y1`o-v4yy#@NVKnyeM{d7F{4v+$N|?w5;+*A;*>Rpd=%%lSzWQ`qD&zZQI;`Fbu5vb0^t<6vN0((7g{(U@k{*b+nlP1P7>Xhed{2a z4O$`%5Y+$!7{3)W^vm}&JtL{Hn<20n!nnp}7}^Zu8Na&XvuW!W=SEUT+6}#TLw~jk zGHHU|O`I28{BD8}vqEXO4b0mhfORF4?O@){c}-Kpp^8~OqUac#VPG@-jdi7PTrLR z2IwMFiw&uT@ywJmSp#uvAc6B*7`7H4VCz6w2NIL}JWeRs#;JSz$jst5^a<2Kw}H3~ zOx*O_LEO&yY|t;WiVhA-po_U5%=;mP#pAxE#dBUc+?GfMqXI|uW*vpNb&Xl+|7;5V z_!xv8g9v89#g5ut!bd|>=~QaKy#_ql6G~vx?cgz02~4`3nD>EUA9%Blm4O)ca~9k0 z@7RCZDUJ3xL>dyn)b9Fv*9}wM!*qJ`b~ASKXCKRCq|Zoo!^>o>JXTGAWKvrJjZ;D;cLOEX3e}ivL8!&N281=3*MhJXa~%kEpk*B?9SaSdYkvDa z^^=xoC)1#D5+Y9WAT!Htnr`N3zbxt|0|yLLvkj3Z1z}qtlIgSO{p<5H`$DtmjGDpP z4E@=nkG^!z`{IKOSu~dT34S`Z2$W<$H5iIylrRd(use0J?#>i;{8tXud=xy6@(p!0 z>H6K1hhNO28)z6A=FK$AWHD$LgBoszU3u%#yiE!DG*L|y5>bYcBqSk97LpOA2q}nc zMq4mbujDquF>q(|6X`J{9LL-YLNn)$>+ORJrXI|vhP?^)n~=vIJU7H?`o`5i7f^wF zS9dq2e?aGTCBs)=E21CY0s9@uXYJd4w{Hz!{C+XD!&>lL3t{Yu#o50szkhIG2^C{Nt_E}(Tn70vbYo_g$qkTiV7?9VZOnH;zRP*u ztl5rg*9Ehw_4k2!AM{`bD*N)>4>`wwoM!(16fHy}iWOs3w-(7D z??FM#+#zp+U7oGU;`_=QP6$W=7th!3$h0x3!`YrS6$0s1-1kcb7 zK5hHHa5=qz?$ShKqIv*|B-Jxjy}Hm0OPYwXKf7dCX++!WeLUVf{Te+c+Mrt-1T%Yz z^ximtUL6@%zj*DKQ{2#{)LtvWxRR#^=QjV+v#RJTMQ{W>j_^SFW_9a)m%6;yyGS%Q z3qA!QOqIeTPr+!;7s2Nu$egc1;5EKf63tQqZ*kr;DZF;XyQi1Y9xI_|B}6gnwlq2Q z{mES%^;vZZnif=DEpeP#;X>2mAbn5PS|r_sZfg9dgkp_IMhYX53`ZW3j1|Tr z8ID6DsSqlV3`eCi-!BW;^U11JM7ep0TS3RpV43DnVm;;^;JkyM@k&^A-if&p^o?L( zDTZ=$32TH1%+EE#XwLgU+y^@5;Yt`5_hUW;rbFER6Q)f1`O~(YtCY|YIY@>qev4 zKHhD->dZ2k?lxVeThH3&m^VLolMZgS^K3Os5^2)y(*)hwoZk$3c;ma7pVZPFa|r0m zz?>`Kg7d-RFJ4-!Om57;L_E3pEijTz?w>1<98MXwo!*L`r~+&=9xCbh`uTNqYM#VX z?Oi4TBmhx&*Y2wK$z+UfjCvuKNrGE~8slU#!)=C2SG^xPm#;LvMU!lA!CQ?iA~6a^ zL?+3k9*>J$7Eb&%_Q+d*Y=tqEki_QGxHmLAXu!D*?lMU-B&k@Edx9eb&P8dx*V(7R<9F@ zU9h7oXSR@yC`ZUalq=*SatIDYQ-!IB@`OA@`9eOT0-*p=p-_mZ#8iSJ$!QQy^9!z0 z#uCn8ZUvzgb2~V<^Zds2#<1C0HMe%q4RRQ?hq++JoF8W&h#%hIA(9@xJuJ+kl&n4a zbBgwl?#^kZJX6iZMKUvNWtkFf^9UB<~5$1c^O8@)BKM?ceY=Z zJk9?&<`W>F;K4x2d*zduTfp1`LHzkvFt=iEhi>iAi_5P-uPgkrqojMiZeqRzz3y;+ zv+lD(H@T{TdQ3m}erk-Z5Z>KC<;J>?sX32B)NvTV^t1X$SBcCebJfrwk$FI0i01>D zk9h%*1(+8CS%`TNkVTx&TiVL?v)Y>IvDVAEmwH1JX>G!1H-xZb@&4qqf49AUO=(sS zhI;Vk%g_kkjbLUCR~$@?t;#j+p|yx!UcJ;cbd7kfGO(aZgiz#*yw zjQYE8o2j;AA2m^&6sMl?55D^#>ecsp9ikcNB`{s$OD5AK$aI~vyW4wp-!NK0{}pJ>mG8ma+WY{XAKDQY8L@|KqO@G#Zfv z2my$O3&RnW3Z;mafUpD*F9m5S7@6L=D?VAZ)9cLF)U->1z6@-eF(Xko>!a7Na82~G)^v#&O-40*(ZPy+)C(IWLmw|TBRX_349UU_VMt~bt$NS0w~ULxUQIFf~Xgd56SX^^VIY}BKgLAB+C!ZN6!@)3y`cJxB$rtjfF^77+i>S&ehIE{yEoU zu9_VxQ9(NokaQk+%;WVaNeL12!Go{qe30g2ISm;LuzUeX3$UDqkcC*j5Tu2meh(ul zA!QMkF9K;1mam3@)qwnM69jGIUq~oL3LMNOTA~cu238hjBwFzf+0K&ziC!7yBRn`O zl}8->m0BDXc5A41IwRGCM0qjEVudjDbrdJPA(*J7c6&qvJ*b(uvyA;ZBs5Q zaRW!NWmk%L(OX~;Gwbit)*X>f-fcRWn21S@2{OHD%7{YUL)Dd*$spH3>iH&;oXyRqhJ2Y!cAJbS%Pk90v94H66=YJPD?wCBN|DYv9&^yeV+oKYJeJ91InbBd z`t~rtBiH){ouxsq4Z62+^KE>=ZS}ozA6}&4<_g%a@S0n4--eGqOiR5&?RE-$Pl1Ik zn8IV^$_tg z_^=L?k%?C@-vIFj=9?hi#C#jX+nDcwcn9-c5btvCSekby%&mkzL|xa~3X!biNexZC zErEGI&>}jn49QPahU6zILo&gYp}Y(CTx#9o?$N96aR@ukGhk(tg`VJFj47Ke^d#mM z=+gq>Y$VF2>e&jtIk!V(JM?3`{`su3meK7$dPu}zG^k!75pQ>I^-e7jv(Bupfkga_ zeyC+PshjGB60w*qC`OF*8i~%=QNE*43><45i?ocB$D!zzC?z7DsqU$0EEDw;RqvFD zU2jKJ;aQ=2y+o$T(~!<(AT0x=btOnEIfrg_Dh;|?|A2mvdId7BfQ?y5sqJRm#C!)b z?r{EH7(61TIrNvVwC*RQqEbPckcKE-NJli;I9WZ%m9nC60^C@K%B4y;$@$;cSL)Kd z5`LjOFg7GMjGaRp?&klzu0EuLevC`uXUbmKX}D3+L3iJir9zQ@86sEn@DcZd%ezfF z_uuLBRbZ$Bb!F(^yX4!HoVTnrtE^6WBPu`s{HJGcG#VX$>&oA~8f s39eebPIPl|brQOE>FliO%v*QmZ@cifPW-Kwzg6>cjmC|C>cZdt2mNGsr~m)} literal 9974 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 diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/chassis.cpp.5E5AFF472027CFA3.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/chassis.cpp.5E5AFF472027CFA3.idx index e217193e2074195830dd2475b778143eefb4ddec..d5265d396cfbed2e6ef13a0d74de83007517c4d6 100644 GIT binary patch literal 10640 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 XMMnOHMtC5Xo;pcimC{#7>FfUhK^bW5 literal 8428 zcmb`NdstLQmcaXv;#GZ{?xq{)ZZ2=Mc}WW*7{rkl5D4lg_~4~t)3neZ&8wRRHTVD> zi8|v0%@~c3afj!CLez+(u;x=UiN-PclF4ME**Iq1Bu+La(bbG`)@)s4-CKUU|7#JL* zB~?sGHreZ~4tJH!RM?!=;3U5m+Uso2|HoIw_Nr=Iv&&SNH>+Xg9LuT(ck|ErdcNCL zGt1rF+))2>zFz#lW#^`wS{t0Tu11U1X0oJK)L85;yXo=I>0ji}7eAI{oPS7&lpH44Ao^>*uH(>;@s8;P}DChJe8Pqa2R zJ_Bu^9`jkuzx9cOSR58-U8SYI-sXHJv!~-)8|qisopn`qi=(0XnT}j#X|`A`&Z=iR zXrskxsUtc!I~$&jf6;Ytk)Ysq38A9#nJV0DZ+6%!ErQYNEVZ_5yYtCDZKz++XtPz3 z&{JP+tCBsz1(p^9PTLBn%~d0FH=z zOjUUVkPdsme-G&8fxdw%+Tq56u62!3C393B8q(o7_#cOGxz1M?L_1`>zwrLSgKrcC zdU%iyehNRGT%*wFs0JzD{Vn$R=?8Txk3iDA9sIXLq&&zsNKd~fb9AjW$YfV0Z8b;R z>@p7yFZW*_N{8jJHP9MK>BL*--n*A;2H^1+B#l8TouGB=r@I%wYJHA;U8}2&qWwAS z;2lOvLmz)|Z_s?+OTppAXtA2U=5R48W~c;}FjR_487f0%43!(o#lbneM7LyuG-fxT zZcx+paKa02+|E;PuqoU)sIAfy2EptruocK|^K7{4WP5 z`H+Ua5WW{;X#@2XPaYeCjL}q49>?fnM74PwtB;*1wZ$3Y#0eT6yH|OQa?^jGQf&x$ zVcBxsl3(}|ORV!=r=g=C_@eyQslTMjiTI-saugz{_-}{o!~gl_OciMe2nYzFBB=d5 zce{R6ryTkz_ygx4oQeG&qxcb`%JsnjbvPe&SDk*-+U|H3N?%1ficdZKhU zq;x|XolREFdF8+hALvPc*4^%{{cvyLXlL+qWRfVrj}F7(KorPOkTyv4@4vkGQq5O0 zc%Fzj0wG5roQjw?wyO8FhbcmXBiB*z(|(r89>`DlDB2$Ih{tMPEgFZ%QDISWR3MM1 zM@&zUs3^QBo|02mcJWWQWD!Kia#S8fUvs!0_aiscX<8wT=BCDLfChLn_47rh8q?WI37wfcr zv1Do$#tM`4Nhf4?!W?Q_9*=@?6in21LMY(JAey?8{X}Sg^!H8iWP*NrKPD8k__y$M zP!6~Hw~BQBpL=R2z4}rDF=wDEP)9p?6x|qj_>t!%^12_a{ZLKkymNku*O9E>P9`F1 zQ7sb?9GpW>zvghAwl0FwJE03Me^9+63ADsjr*uFnozDLBtc!!P*km$RKTPR|Oe#d+ zMg34I@c^U@fSK|yx1DZzbyH0;(QY@$cS8UTwH=?`E<)+slF6Rh2*Dd6oQmS{IB3Q} zM_1~V@ndhhUyMod&=OH|`(ZvETHpo!u#j?sE`Nq$-kKC*5aMYA{Y|XwE^?RB z$?|v=S|!@m@FcI$&D?YSx0h#q{CO%-+^KeoM!4x{h;Y*c@R{C zpp(WM0@V=9M?iT5f~5ANpghX*Feryv9s%Wu#FOX!@!l(Ux~G$67zNEJgi(j?QM`R^ zsOYnNV!sMqh3G*%?u03wkU<s9g&y|Gqw|#G~-Bm-rt916`TE z!|$wa+^s4k$$2Ai8$nN}z!8T;7#`ObRNlee2S3UP zQ!gZ2>L4T>ghVRO=;xKbJuG`M8KxT&yCI1-eB)IXR1}(8LK-XzO8^~rlYP%AbRw&S z1Rl5AExr~OT-ga?ZsYM5P;Oy)6y&4QGM&0|>XyFmK^fT$k-Eq*Iy#Tj4e5*(3-yKJ zwCzyh`|o-kk6T8TtPdjkAdYH!;rh1IkB((5Cz@`Ckj*fGF4=GX`EAL`kFqPt7ekOV zB*ozu{aTc(3qq}=p&b(2A(?(*tNrUAubzvy66^JYxgYYV48d<^4M0BSf`7jg^x5|} zti((qD1`Bi5I#gqG@hoh3N-TjYNFU-@INfM^XCh{{N}?;<+VgiDoz!BiN`DCE5wL; zJ=44O;I8|1L`kKWQcQ_-FB1c_WVXzwtHa|~b8DeQE73|VCBu_ik~+ht76174-8ZK< z5xrP-KTmxm%l%>8Nwlg|R2t~lJYEmp>p@G`sO#7oEZGlwn6!cZluWXQ@{ zHIn!$U6tt99Lc*eV_>42_sos>id(AQb~2SgNE(!iC5_>eb8q>6+<`beGhn7z`f)f* zo5f5fSDVYwV%=hKmrxsHlF?;;jB>(hgEo?b!t>T_NmW;}YlcMb9JlCJ9Bxyz>8R;B z;z*v`K}CI#BiYJxkF;tWiE^IDK}CZgM@m!A)1ac$_89xO(ialaJBj02qpg#v$gc7) z-t#tRZ6>c90~^JOUH_MV#O`RgyqWmp0q{Q{1#O-f&;O9bFLzyQ_wB3QLUQvkOc(|u zwX(qZ!%#r^?9$15a=Sm?N(4D#9Vt})N8{d8?gei6H7WNeDJC)7(WEdjl%z;vXo_MA zLsJz~MH=te@p+T|>Nam;f;hhC&*b&=mZnXGddwt;w|8_h3(%3{#{a?#FI)QxeKOJ%k$*K zq~8lXU0ES7qfzjtmKQjD6rv;^12hKS)Dwh4G+<0BuZpUc-v7fV$9Iwq(GC&q5K9$% ztu!#K>nu@)1h~*vv1$*SnfP7Uqh-5@$^5au=rTMG#i0yEE270c$m0xU29sMB_$?53 z&C8JwZrxseat{%|7sy`?o!990cP=f?IYMUo)KP3e_jc2xD|7b|@fxfV11*oka2P`q z6ca?R;PDLQ46)$l@j}0aVyNHfyJ5)>8Sm^D{Bn+CA@!IuZ^W;NoOb;HDc3SpnPP7q zn^k6pvX$A4lpJLaLwU+PhVqs93>9mNgQ+MUmuN~DD%F%SRHi9os64h@bn2V?3s-*; zdgKt%yd4bf5JK&?s^R^I@gdMn8d^eI#8YBL=ck&EA8wNK(^E10T$6hEM?H*?bQ+K1 zRa7eu?*hXvm`J@&zdNVR`kSvth?(|-e7|(WU%BSL+AhqReax4{tHidcR0%>^Z7)c) z9sK(hgYBvrdW-vuFbAzW9A z*T4JKg?}aKDGOyOr1i>1*$m~N9ERrT=ZKd>!U-B30R@#TTtT3t5^sBRLB@_6{aK{N z@#=Uns}a)0iZ?c~nmfmg2(r?F$ZxbQdEUbS^UTz8Eq)&KSK>OODA~GfW|Zvk zZ1M6DKQ5x=E?&hguqwcv2g)@s=m2I5yr2 z)m`u~TZ23uf3@Vq95xOUY1o~-^ir_4R`ChhL+y~(4m0RPf7JGmBzp&aqU3Q94ifhq zkArbALy3ySKxzXXPgYE3C|QxrP>Ldjp){Pvj8dR1U~=~yZ8PkDvJtss4H(u}8Wy5j;M*7pBrR5Ii`3P`YdpJUD)c z32)QVae>HyTi7Vet?ZI<}JH;()JhK!F$ zK4EVWuDXhkYi>0?B8$Zyxj5`HxWtfTF81vnSE_#`-!`e6#2D(dIV?iDayhN8KH-5A zemwpIxU8`@v3=Xu6$-gp#rY|H72cj+$VaB8J!CSK^r(;?J*7u4>5={q1oFmPdMl<* RnM^Ld^^+cz(xb2R_&-}#&qn|N diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/chassis.h.CC14EDF7DD3DFCF1.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/chassis.h.CC14EDF7DD3DFCF1.idx index 909a6051a92819744d5e9b1cdc650e128c36c73e..87891d8becc98772ecb6fa42138933c0c58e5520 100644 GIT binary patch literal 4224 zcmb_ec~ldL8J`(LNFXGch#M~5C^rZJtNZ$Rx)reO<7wa1#dWRQmJZ1*q>=mJpyVbtl{brI6kIEl?g$G|U z-+leQ;l;v&g3Wq@$hQ`5wzE8=AP6E7{y5%cTOSHvq@QI^`!U^V?KO!9hlPE86T`#8 zaGUo)hqtZK-`QUAL72DqM9d7gvcl+v3ia)7?iY7-J zmt=`XtC>>v z(eF7q#0rLI@)?(vSk1T?d%n}byPV}j5qPNLiU3>{#H+S)Hpgr>7vz}NtTivm%wCeY z$ZWScN~^z`UHp|rW}CxOUS(y?YxsPp3qFci{(rVv$69!&Ys&0}9CNkPwUMh}EUcM% zuDFb`aW=C|7cI)I$j&lX^5*KQGR6_4FWHI%j}=fMXD(qlHnVKT+*tzM%d}KH?8g6_ zBKn+(EURtSQkLh;j0CX~c@1Wydh~0B9kIZU%&5}}Y2Pe?eVb9ooV>fzi=hJi+KZtl z@`Bro1FZp8u=zoD@TtJYmRk#$s&XDQtTtC(70){z96XdVTu`+PR94tr5|d*#v1@I1 zKpcFpU|Fm5`3P+6#k`F#XY&|Ip_<{!0xp=1EI0`GCY+8U$ub`nxfC1(AB(`Vm2k1I zx>&A^;8=$gGT&*pGY%^uozkywVjVnDRRMEhzu~m9F96&frG(JZD7K%512-||5$cK5yp zgqnkJ9tm!Xz2wke3EsVEI9 zsFW&NQh;|gIVYt5+LimS=U!Cms3V+*?CC&H#sG_L_YC_*i#xc6t*h572l5YP>8NKxoIoeclL?Bi+CI`P-d6V|Mg00zG!+l_cS*xt(nQCp zJ zz1yj$hW~1(sT5EcCk^A|0yt%?dm_hHH~tu+4- za6dcbl``GVf1cHES5Yu8TCdd8GLaNW_@zVMs-%bh9ZeqBQ85r8BPJtBCK!Ob{IdJk zpS?Y5v!g@_eUk})!+l%Pn^;UcDKn+mgzU7AHIXoJF_*;DnVW6i4H z*bzo#m<%86D#8c0QUVRKzv#CO{o$Rj=^;|6GE^H3L}=)EMZ5+9gUA9~2~TK?Vu&Px z3E5}9ZC$qX{8kgNNfkKZajgGFJW0z{AlvrFyuO6LRHl;}Ix;#k z9+!Vh-pZdpWBq9su%lJc5$GFR0=^|+qLYrXlkZmjAZ0NbDf_;}cmBPajJSnR4FVLk z52`w^y>q0$5PTbF8|NaR5Vzo40xfDIUcrfkQe*oS{Dgi$Jk|Ifih}6)@iewGqTSKQPQZ$G+4V|h;)!@>jTPiGYJEq({I*hT}7O~QH zy%~BDnHHIiBl~3Nv(LYI^n-m*Y)jh-+sYVAK^JLwY#3!dmHi7&H*}HIV{E)pP^NUC zFrUra{=D_=yn>U8$7iUaO^K$32u*m(G37pd%2a3S@#?H=I2Zb->%LLQIYW^Vj$ukp z?C$^7-Hx%~pm5EwRgi^r2Qdp;`})z(M|AJaQqvlxhQ=j;xC)c*VcZN&h7kkc)r57D zZY6rcC48bOI53DS-A~lUB7iO3O=RN;fi2z3DfY>2AKN#)|2HT#Q<14c<0~z?4=yhq z`NtEhdYyau)NFs{zhEaLlo3;lt)bHu>0yXnxW47g0;qRj%`5jNiw9Sbh&)6Qfzdga jg)>W?nYx7pp~FtWdnvrVDkrzlMa|M_BZ#R3L=gW02OPVk literal 2892 zcmb_eYitx%6uvXv?snVh+0r9a;cZcqTeb1DF z7DbE_Q{_=?0Fi18geo-^YCwyXrxAi+gpdFd9?BafA`fF!^vvv@QX2l4uxZcso^#K4 z&b{CHZaRH(ZSBMiLOlM-^FyK{=m;Uh;8#}S($6gXp?=2S@8kSw#Rlhv_xsMT={ncf zcfPOV;@Z_0JKny$?jz^@wvQ*(K_MQh5yD|HPO8J8wFW> zfN;LVjgIm~qYtb_#0-#-7YNm833(e6yoS8VaMGPz)khlZAm`Q+MvFJcjRgY3&hFn{Rc0TLY}okczD|Ie9d5Z#k5)aPdy)& z^K34J3(N%$&0zSbbc~K!xMZA(bwKr1jAa#LquIu66IK^qxc!EIq1CWW*O;4w`BjG-Zg+O>oSx08TtD-0 zZ{MFG6I%e`WsGGRQ$%x&IX2ASv1eb#ik+z~1{NPKnx;=1L{D2}<>B=sm6v$d3=KNX zPMc=1K-G^Gm)Ka(LY~cmaEYbFrWp)d^G@#m)_8oefz=~DM!j(mJF;NQb8Z`O=uidz#0?NpFAr^OLQeBydqQrY|<1ku&F>-Kyi*n@b646|Kwl_qoZb> znOoVsD_l4AhY*T{N!P@Iw}#??p;ASGZWIMn(!?-O$)R&_IPWM7$W`Tlxfq5@sus|_ z?Ro$BsvWIPa5HI4RuYk*I>4<;19u-glRme<`=pC8Yo|cB^tq#}_>b5l3`eats|#m$ zLCutpv;7~GgF7oND-*HtN&s8c6@skp-?Fdy<+3r%Fb(^lvhJ@ICi65ZEvYZujrsx* zRb1#sUBPZuP3Xpsz^$qX-Fr^ncyU69(lF#WNDj-~bIJsB*w^U^hL+Biw?l9^nonH_ ziNb_RsvObX-nQt}`ZFdOcB}N`RqXOis84OT&l}Rf_Ua3~d*`xw$zj1;M@<9$nwAIX zcj}xJ^^D(eF{-Gs0UTCvt71ks>NwR%HT@1i7U~K)6d6a0bVVF0lA|u2i$jHRv{+Zn z;TR(U4at|4S z^|9`vt!;mM6toD(q4_L8Es!0em`;P zE9kOJSC)amjhoxMzq_)!KS>Qv5MB<{Ro^PCYvwm?ytbY0PchOAeTErnhe|p-#Y#|s zE6c$GIA24~i(Ehf^|iwSNCUXlR}S4rmeT`=sr6H?gZu{;e=&Ig diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/confirmdialog.cpp.A07B26C68894B406.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/confirmdialog.cpp.A07B26C68894B406.idx index e432f177af97c84570f4f701a881b5d0023b514e..d5a03a1168a8c929eb65d03e9f33d750e8883ad4 100644 GIT binary patch literal 5064 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 Bn7jZ0 literal 4012 zcmcIndu&tJ8NV?%v3>4w{JL>$=h#V{gb>H^wFBf~W|IddAqj*S!Xq>U6HHF#o=Kf0!8ZI!ocYPl3$)7>|`Dk8Nd z>IY)<-@<5rB=$dOEsn10i6r7uTVvh8x|Z;>1H*|Y(QXN^jPyN;;{4%wZ{2VrF|g)I zv={w1=?0&)b|Ch2d@$S{k-`;Sz2Rs)DouQsm)8z@Jl}f<~jUa;k}hS6iC z>$gL^Z(F%Rkx7>u#YT%@(wVYg`SjVmpM3c5%fmVarpo~_kR#~SIvp&3c6;l~=kDZv zZct33%Qa$6j$qUovtgNwMOMc-%yHwVP9HvkHl-;rN?HfX7`5x#vw%WY&L?kWY_C$L z5hPiqQV_~&TE4>kR6sq6AfsT+hG~LIRi$DIxv|>}XwydStMT*QI%;W=dXW(}NblB$ zzPHqyO<7bss@))sQAb{f3xw_q@2NB+_X*w3oMWC0LIhQiisBSnLY5SR3d}036Yk&p z<|pe1U(>aTsLoKA1un82IQ6&J&;BEia_YpLCeSVY{NScnuJ|1af-sMDV2Yp?WeX!d zc4zbT{h1ApNf_Df_7V`nsK8b*C51xPP!ZULuv1RSm;)z*>Q(hR;Pn33ABqq5yk1N< zckwO(ED_}4Jq*2M%NcVLzl+C+7zpz*M)~@D77dKr_%;NaQ)Fr*fEsqLOzaOH@+5b$ zyNYDHI6UkuAe?5MNJ8K{lNkRv`o-qyR5!CQlbw~IdByWeL1*^|@#)XCADd2HYY+^K zp?-zm{9vphm`;iFgq;T?8*h^#J8zevTw|^k7-Ho2`m4Z`S6+JW*8I;Zo9Hf1mD319 zz*LQqic@J|nj%_>u!xb%?V6EV^OSpLrjS3~Z-8Ae3Zw@NP<9yAq}LcATo~2yb@~)) zOm8%R5AfBogD1aGE~RN}*Vrv!oS*_+z&t`wNF8GJcI51lo0h%zJLrM*7JVM5JeT>5 zc73zGn^uW!k*x!!F>(YQa{x7ebtn7!-7k|u#eA_ArYW))vzTsPeQ)d7=@~2SK1Uaxnb|?u;GSsSU6~PY#El@9D)%DZE8?Z}g z8P34S%$sF2<)w{L1z#aslNUNhOZX*PFiw%I>>)_B#FC8>VolI2)hsPUsrb~68RyT> zAE62C_xWof%mf9h12teFG~avXO#Z#^Q%7|vyI4$*Y-tJ&o+#f;6Ih+0mZetE63l}Q zFlyqPgcQT3tR_}@7%(`3?Wkx1&GULZ=0|y%l^a9ROWWx2RW?;27{^G@>tz)y&s|zCq^2O{nnr75K5)&Q>HcT# z`10*E5*D>32h0&vi7E-8i}x-#M@C;w3YAz(WWB1)UdHnJ!7<+pBSS}Dqv7(@dKy4i zJ^IepUp{qm2lW`v!euaxQH#0711x@J{&;Bo?A)DH*Q&O%F7ovoZRAc>T2f4q7_E0)5zpEZh(ko<2ryGQh_e)=o{~hg9h)5_ETp%Q(W_Pm$LYJ2veK5M| z);`)FuwMT^L~32h=dHU|jNCSrBur;xwE?}U<|ozD>{fs&w+oLb|9Jt>+~Ge3nIy^Sn? zFxNC)HF zwA*&YvSa)N^=2V1WcMk_JaFKLM#IOHVWuvV*`@cMaURpvXa0Kc?W5Yi(C${km&iJp zJay^Sqjxcy7?ts5GD&$_W8}&5Fw$3prK|bQ1!pJ&4X1 zrR=Avrx_ry!P5J`82R(HbCg&6uRScj*X;SE$mW_)LS`S^PCEadqXRWbx zpEBmSvHvcOwXM|_f|Jo(H{hrRbSb|0UE|H6ajM(Nce1x$%;euQtM!BLsII^ZGKS5{ zX7<#GMf$?YpCj>Dcf2b(c=Y2WIm}3YJ^TnT;JEbEP@NhoQ^VxX?}wi>^rs7Nt+;%$ O=>?^rO3zU8YVJSfdnL30 diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/confirmdialog.h.17775D0EB3DA3BF8.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/confirmdialog.h.17775D0EB3DA3BF8.idx index 2b6a6860a1419c154bd6d24d1d7aebfa738ef8d3..7fcb072c59c241056f0ba3a491c74d36133e2c31 100644 GIT binary patch literal 1782 zcmZuwYfKzf6uvX;-m=Ruon593Y~9@n@rSm;l(j{(32ABt#MB=kwem+g><%!x56TQe z6%vEQZK*AU*cMt7`yi#FZ38Lz;8APTYFe%R(Zq=2tGm#eKw^o;m{jk~?r{U@By;cF z^PO|oM&Eou)%K8&aIevdeR^8CX2q4?BC^CL&a z`+1bw6DMM~PYsA0#l*SOi6b%ckqnaJy+;xg`xDXqO_naj_ zd}wt3Sogy8B*Er~K3Ns`L_ zQdNz#rKosIkxvS%k;b;ii|ZcsNophzY7Ht*}Myy<`i>k$@5+ zAI);a+;vqCDJZOHn%t-$P0?$VcHP`f!2EZl?G4xOB8>2{7q0C-cfpfO(-}#6Fq_S~ zP-gOwU1(|9{UM9)s3 zE+?G@u)qldz-1f%KGAVy?mA1aA-8)NkJDTLzTDIK{5@&x21{oWSagW_<^u4I#e;8N z|3m!FNn0Q}&+-2!f9Ce(j>qILeqw1GiS}lDd2@ki)S@(K?~=d`?7_-SBVT7w7R*=} z=HKx^?O^%MEZAwvG(#tnP7_xy-WeDAXO?h~J^jvX4&_+kv~!lLExh-`x>aUilFBz3 zgp~VlN@agtdai)7V=9vplC&ZO#1e0VQ1Bkfy)|T?JNz)^TybT$d;Q%F50pNVVZ&Bz zWgrQI-4?eUSZ1)t;;}>5K^o)&LlSVo!1UOct*<_`nc~e0;8!6BsnU=Mc*9N;aS(Z= z?=>_EVE=*F=7xUeG>S1(f)j{`6kv!3yeGFMSm~4Ams5m5GeJwl+t3hre;9kZy7^Pz z8zi^gZGRBV(_MXuJ?i&|tPEz?vjxz42J?Bm7nH+bAy4o^Vg|c%U2fE7ihaE|2p9uMBnw4iEHz6ejT{ z>B9Ijen$i#+Zqa2_dK>*TP0-8BEcp}>U?Bt6zhbb$vLgfhk2clFlKmY&$ literal 1542 zcmZuweQXnD7=Q2j@$R~7ciWBY+Bs+z{!nmxSp{O6ECg|#GyKy9G)s)**50;@?K*ll zHxv?s#DWvLED9T71ZluD0vTcWK?lZYFabsXAR4EOAGDj8Y>6xyV}ibS?IUT!o80q$ zJkRrcK7P-2b_9d>YzS?Mbd1ClUA7{GYU!ux33Y^_2V=E9X!+((o?AGV&SgKE&m8eR z$fDJeKau?H-3i}XU;gZ={NW@$6r4ydbvS=%|5El?E_FJe8oPbvn9r~I7Lu9V>+exT zE-&`IQpOS_))fr_Ylz#)GP%Y(lpNGrgPTr6in*u#57Q-J!+$?Go-zA1z-%Ti>{&WWZ#9)xJ>6#nzCe5vf8vqJ*w0C<5D z1n_TL`}c|6*XC}z*a`~o<-Ikg0QEPE2j95)hwr-@)&}09Ai6<~5=yUzsb-DqV8^=5 zmlno~E3K9MztiEa$>6O@*jdJWhGYvym#$sBGwmL~vSg3APQ5cLGSwwU+h>KYrh7lE zD0>Dr7>~&UKpwb7+WxxwOe5pKOaYm;V9+cLCczqZ)=?nu|G37S zfpUEGmAUj!g2wPBBvc6e(gX}%!TR#%yo-F2da+0dH1osc)18B#`d_0UTm!cntY`L) z<#(#zAF}Y+S?_Fw9`jhN6Ia0u@K}&*4ZvcgC9 zEbsp8iQol$DfK+As;gQBB=J~q3oD@@8k~q5?*|v##I5&lQj@eEQt6o*fB(jviJ4-p z7oU1{G&3E(3QaE~8PsKS1%y&+jK2Un0}*L^jlqZKUztst8ji}#Wgzr_h|1NJ8t5|u c2c#v!nlY9Pe*3x6)u)eqn`Z3D4lE+{ANDxq=l}o! diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/datacard.cpp.8C16BDD4A28E146F.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/datacard.cpp.8C16BDD4A28E146F.idx index 41211df569a549cae70ae5b5da15be7db9174169..6b4e946e03bd32030db7bee59f0fac9bb381d631 100644 GIT binary patch delta 783 zcmeAY-y$U&s57MVYfU%JMOBW#*-%=9L8Hmlsdg zV$!UCx_0`rbz5I7?0Gh=9msyNyYK1tWzYA_c)n-li~X~oc5Hau(DP)++LzO}JZ_fmE6F9wDJsk$!N4E@l&&z@Js;dv9 z3}>=ivJXt@WNX&wdKMl|RTfqL-3PSOlq~Mg1@fd-)eyX7j$~n&dLGVXjbvjOukPp3 zq?;aMnLtB@c!i{6H|^C?QOy3xc5)xT&?e;qCZJu`$&N79GMs6GX>dojrTysreAK#- zQD*WL))Ypz$(C#uGCZ8|Eb;s>4M3hsygrOKxsWZIkz?{%wj@3t&REu1Ubuul`vwPRZoWgxjLiS!fd3cBO8P0g|cx9LYKxaxPtHXGc%{UTd zflfCuGO<8XZxn9C+f?*?;HeLn8BMN1(W;gZ7-U+TH?!Px{&E{=T$)Up8cc388<#C3$5j8Q39P>t7ftr& Gwg&)9jwyuz delta 278 zcmdlY)g>+)s57MVZDMW%-yUn=xsKFceqjChYwQ11s)~_P8)t3aV{ZFAwdQXpb8+UFx8mlS&{B7z{EXSi&>75 zcd{pQ5L;1dS}{Av=0(gq87CL9rcHj&8qLTw*_%y9yzb}Gq?;aMnT#@=LcBuKv77el zs3>NCoV=eckCA1v9=pZlCiZAX_R06ylP3Fdd=S^^EHsJi@m~s5Zz67@y!(K5nv%u+ zxsy9M6Zk=5MkW?;F;=dr;$OY(1=SqxoB+z1N}DRc<(_bD-MoR@mXRZ%ETY3!ZJzSv HuRQhu0!vX) diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/datacard.h.2DC6950C11B86AC1.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/datacard.h.2DC6950C11B86AC1.idx index 5b4fa7923f3239a9f21146d9cc24f3ded47121c1..06f0b8026567f6bddc3b71325aa9c6d17b578fc2 100644 GIT binary patch delta 409 zcmdnR`;S*P$kWa38yf?IbBbq0ZfZ#)3j+g#2oM*S6lLz+C|k_Pm6?~4npYB(UtT=9 zpV7IVp|~{=QrkC+5$}c%s>r5z%IZkAjrT0;keCBtUI~5)>43p7bY#l zDFm1H-L13n<*F~90!$n*X;Ds5VVLd;Q;k`k73tmrOgu1YVNPM7ZUczv%O{r{ak-q6 z%*VvZ%)l-sDkTdhVA^9u?|(Pu1PG(|bWdHz6^IIYS delta 281 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 v9+1eErw0F?ywRQt-g+Uq{2epZfmVNv`uxO;T literal 10934 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 literal 4156 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 literal 2104 zcmcIkeQXnD7=Q0p+FpCTwp-iT&up>{Ghnw*gPlwEp*7R!*q9R%P3gK@ccJZdcUML; z%r+bbg5nq=VE9Ogh#Qj$AtpMfCTe8BFn+~^gn`5i5Ft#+mS{A-ciY3CcpZDJ9 zd7tNbf4}Ehb;z3W=)m_K4CiiT;@%Dkq z!$+?VAGx;YwHpVIWIgC?1*(e21vw=1LfoI#E(fKkARAC1l#I&tNlr|y4n?>{TfcwS z(HLrua;TM$a(q(mspv7twUZ6RLbS=AO7S0#!H*>;)jNVbLIbM|>YIJ-^Yn@MbQNa70G z{_Fp;#~+lDUW=@|-lkNmVChS%@nhOsUL?_(kO8q!?F65DUsG z-FW3?OuG=TnJtoP6e5u**MdC_LPt%M4{yL5k!uw>sSQb-+%R86O2wM@5(w!!KOMN> z5|&!X3=N{Jl+~dcgyp6@LH#v>yQN>rkY*fq(XI^DK)CVJ!M@&-+8Ty5;jqu>%Tx`7 zH~thKNH?u`k|B*a>~^`As|Ld7-TmF2S9d*YAuSrDue4X@QFhAiQo@R=+3mS!mWAJX zjv*PG?X);ERRh_><@w!}ms(ddB#pyf+G|q{gs1;9#HL2JHt5JSgeardXj2VxB7|WR z)RKzSvf^@A*9#13!1*4#CtEd;FE`Zf?7FwD+d-z`%TX4}N107#H-tal{yx*a`_2{} zxkM!j_t0v!7DTB#T)t%6_<{5v4h>pJE6!hUU!P4mD2H9iSHQW5Rb@jb_1{{^bR4d; z*Lf%#WwSt7nVK|>Z=TP*URyXgo2gb>GeRRIDH=N~hJ)b2t!9xil^VOdeLkpiUk- zjh~qK{_MLw1td*bGp$~@5ALh+8K7^f`k4Vo%i%)YH$$Icf(H9WPn}72K2l29R7`!F ze|&7{m+By7DJ65Pg;3INXH}PBZ)Mp889YLxW z05>QKpH}1qZdggftb*oXU@oy9AUL|F{E4Uk{$mYcT~HE4hD$06gVT5SHs$0x*tJCZ zg3~Zvl33uL9X-$l>!RGtmAZ~YyQgEAYopJY2c7zNbWgl<#Uf$1q8}K+NYy06e*VVB zIa$3qHTYfcm}LT#foc3VI1oD)iYoH^3^+AT1~|^Wd{mm*|H`Y27#Q8sH`&f#+^J}9 zrMVP3{5*W~_PS%|_Ak-^d^Pw2%GUxYHobgr^Ox0uQ)vsSpeYaS$pDUN%B%4jVTesT zKGOYu@%krNO>T|b2)e;38jlfDnosr*o&R;;=o}3I!O9#zc6-D;hVkmPdMl8IE79&u zsC3TxLFDYZ7snRig%2AeP(Elt01PiWZM*Pz{pl~_x3E44J@F8961iwdX$wO|PzFh2 dSX!za)52N&A3py|J^Z2gRivkAEveI@e*mU=d8PmW diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/faulttable.cpp.F570C8FDC58BD6A7.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/faulttable.cpp.F570C8FDC58BD6A7.idx index f1c594dccaeac96993d5cb461538cb22230f4321..893f4e3d2fb3372e4caf76ed9953917be73f2ea7 100644 GIT binary patch literal 8192 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 diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/faulttable.h.0048E583560E828F.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/faulttable.h.0048E583560E828F.idx index cfd736bb83a6e702256ac8f918215adce86eb40e..82dc592052a874d6a964fb334808f0dd666269fa 100644 GIT binary patch literal 3208 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 R1hFKMjZcUJ(_*KK{s)Lk>jVG* literal 6036 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 delta 329 zcmZ3WxIl43KP$^^M(Nv|C$rvUWVy~LbbWFGyCKUHM(!t@m$GXyvfN{ozBlVPc|IeL_#;N?M~q4^i7Ru~JxcA6r~*pdVwAo$xt`OR zl)l5L3{$G`kmG2<@r0Q`r4JaTA56Z+b%5mtqx6l*v$*E~ox*Z` zvK3FtzNSbk1Ey>AMKO&I{+8{KYQ7rPW_mfnw;DnHjI21|aS*`=FQK{@d6XPT6EN9jSG$yVxg=}bjNhi;PHVYBOI>&E0+ zEv3a{K+#g_(iPZBUFoZVJw0Y3nfrm+oI_%pNY)cS_sZb<<*E zN}RgErW2k`8*cWsxI627EUCK6UE4O$UFm+YRhN;Kr2}nu^*UL{TeVT< z++&+OjlEL$rh)FA659r;yL+(eUcRN%b<(9hQuS8v<#x}`Dp=suS+Oy%HBF6~8KZNz zp8z?m?PlM8@AjjEo7z47XJC8W`_B#Z@4PdK>Fki|I^p|B^*d+|bT$rL-tOK5YP&ss zFSr{o-y_t)4M*MQL9xqS+X22|t=|5A&&f_ml?Gi-D!9JKoPPAm3smz&TwN?D| zJuZ$Ul0=~M;?q(Qf?-TZ22+)<@ETpq=hogou5k5GgBAE zXD%b@#zHgoPM--jn~Mz+i;PaE#VL=iW@D*5vXzy}@_FXGvO>n2XK`9e3(O{&pKL6% z%A*vcJbS*a#LN^^^Vws8Ess4H6_&_~bg)X#Z28`bd*|3 z3(5a1Z6qTLiX>%bTT0AO%ScvYW>QjG?tB~=NLEf_Vp3KX$xd3D%}8!mP6Ej`Tg^~x z9c%86V1o?tnQ5tMDa2@T(BE{2+36%uXR};utmdpDGX!RIl+Xp`6I0x{xk+=`_R2%L zeXvCOk~J3tGsR6}uPi>0rNVDHF&RruW~=;-nQa24m~J=QJtr~QN=gI?nAM9OR33PmT%T)xDss+c=)#W|2A!*#p(LR9_5Ytr4E-E z;jV<8`Ni`fZEe3~TT;p_`iw-I^%qT&D}Yn)yMcc}lsI!0Bq84XQ;O~wNQu)|*c`=9 zyU}FU8>iI2XDHu#wETnOo8rGxA>b~6FX`FRdwv6)yPRgG3(PT#FtPdf<| zj{+btEna>kQx_$<;J$*`t!1T!keGIxlm42Cot-&*S&7|u791w1O-ZH3JS!YJ_QG=8 zVM;K%Af?v;&sVGNDzVb%Q%hopz+p-;8y%p#5Skn~S`JgX#bqj@Pn&b8{8%PGo`6TD z&1!{5mdgyWIZVrp6~tk7l{reYTt=6fIBh24w3{IyShT~HKszTG+U)YE+-NN~%Y~3` zR=LfR4+P3$h0$5$TNWL%2(RyI(@A}ma7#;_TRn$1kn6h+>%3Jb+=nY+ctqzp+w19l ze(=~{`BI`69urC(JKWXvaBF#A+AnQ6N}Gx6yN>FhZy4xq8LX^vZ+`(8$LRGBeat{l zkJPirv%eX5$M3cT@Yb$#cQwM53;y6%9q2s)yccxt3)O@Bj>yJjWjwi6*J;u-vV{io7*vh}^NT zme76ZBD752)^=c?9c(%!_nZ>D!C@DqOBbXg&|1~X9VUTpM5;OC>3NBEO_wjZTepH! znC(8)2TUT>o*vxyf?i%24Y2a4wBZ1Yi!PPwo;v0^(nI7g=jmwm)NlQ#7tg+4seg}; z15iq2f9zeK<+cCH~7*$$K21g68h!0 zJ+Jq^R1eJ_4W11^1u_YmZaS#$f^`yTtncivxAPn{&{S5t2V^jS9rv8NOgzUnz&Dch zd@n4PTH2n;t?16<&=vdkTV4m*cD=k#NMvZYJUwkRU-$JxY7n{C9q8$hH%T@N z9V$Qta1P%~I&zKog&RMIGYtKGMA5g|PtD;(e*O$%Fa?-2jF4e6O8fEk zD`&cgZ{gG=BA6IL)FHu)Py8czQxCV=e>G<5wVhz1AdnbZ zIH83L^gYnbbDk#$yRXg=MBV~k<}2rG7(sFKn?h_s`v*b8_$lZ`@DqYa0DI$>41K-% zLGi7Nk>Mi$49uS+%uzFf^RKR}F8cfIs3?&y0&$8mMPvlUT_Xa&e&Mm-j8qXLnm@=5 zJ;+6pa0R!E+sGxrDxO)k;`i=D^`k|8HP~1utP5lWZS=0ba%G9W?LLuz0>s%uw#W#I zg^2U<#kWR`5&0)UoGau8F@oaXNh3>-C!QaxBEP~2wsOO^azP|~DDNLL7yR$Y{~-L_ z^~+;Lz8EZ6HP#45&_eZ_-@MoH@%Q62WCCQ0UZEElp&zge5S@coE z4XuIH03UmNt4t~W{MK_{jTx^Yqrl7r#RMfI)XY}d%=M@R?~_aR@gnbnIGz!n31S4} zP_D1-Er<`9pdqSoVi-4aoQ@IpsYc9(b*!B+*LmMJ(aY?@etqmq2mQcXnb&7!2E%@#NV7G6Jtew9+*$m zB#vYR%|AFIb#_GB`iDh+9f%hw7lbi_;v3Fg-0`C;XZVp3V1Ar1PGp3dhg<{mcJcW4 z5&uy&OXQcs{0t!@h!LDWySt(@Z)W#nB3};TwZhs!Mo>I)(a7dUmVGi;WQ2NemN)<9cjhdA_lLqH4Y?0AW+-N;7-74^%0c>s zZL>OV)-4kG67b*<99l-u!#n@A@M`sEmllWdnm~Db4UL?tk&8lwjJ(e($1deFME+5j z|G4mYFe50wJn8fU?4*?<{}hPhg?KF^DBku=%$nm8?*$nvlM z%O}r1U!WlmzycpsJg8!Xn%FNd@QsG>xGz8MEfD!N5JQDf5yS`@P~1p+HEm;yRYN9$ zfk}!(%g=;G$PtJ>^;8Cxdub`Y-;R)sQx;y)hfxNfc$)i6Ts z@0KGVn>PRP@)r%2YBCKp!qwrSj8Kh(vPSPG59AJNj%^V6d~mT;SQ^L(x_EWYqd|c~ z`!l=eb9?Z{+6zto{B(%#QU6CZj8F}MRTAAN!>-uhd}^PXM1hG2 zbwmgwRN;VZ!o7I?(SrBW530#jFrf+5gfqepJ6qzkS5lu^-@qMGkw>srwsON-xeyXQ zWJsfY`L=yI@4Mtzmba@(D7YD|9zB8)>gIXbO?~8&12wlUz990YuvEKX*D``j?VeT< z`u49QPKo?V5U*6O)G~tN@W!NPOnd*{CGtriuH*tLInnn(l_w^;|M1nJch89YB2d}D z1#IBdz6Ywzx!lz9#)+JBBL6g~WD1!YMo?@Sdf%T4z9=}aA>&{trYoka7@>=(m-D$y zapbl9!ItwPzlo;HW5Q!1BWU5n^!?+W?NMFOkg;IlVa3CNj8F?ZWeW)b>E1DxIlUrZ z2nH+x77Zh4;GrR_m%VLT)T<%UU?5fzD=wUO=A62pU-O zyQ^_ubC13&@)aOnEvyb?1jQ9I&7a?#cIJ;7G7{`hRZJBaq4u}S_AT?2S0a_4eIW7< zFiN+CI9!{Hw_0e-imJ5U&x|1Tuo+S5A+p|J;~)O-(`|4@Rj+g)&0zACmK+ zXGrg@4%Kfz6nPsMa0#wRM$kaaink)Q#j20gL<9q2>M$)M)WBZZz(Iq0_pQaJudB&J zpb@E#3}J+7JSS_+URJKT`r+42w|jG*|Ni%mOUoAsB^ zG-NdRk5R;kj8OmEWdA?b6#rw2de!G5zk&Ks6;joVpoQG+*7zjj`WqS&M%$a9abb+G zGs)VU$$wTJ))W-q6!}#!zf33#Vg%pqOBPe#53!J0GyXb=(&QJ&TUI;mWP4Na+I)oa) zrZ@u|x#;Zd4}X2yFofp}4V%&%V-dkc&FVO+c7|UzUx3$%`?0@xH%V4IFu0@xHtpf(xUFvw( zYgs|WI;mxC8+LPY7l_TFmQ`mg+FEvJ<5jLTqy(jeVpeDk^MmJyVXA5k3nCWi5amG2 z6qXi=t(4ZVJaqYJte{%MiqI7iSP9@#4_g_DcfIx}M-%PA&QZvOJBObE*c2+^K5JqU z$9&u1H*1Izz{S``ZX%)%`X1ZJ&Aj{G!YNv~;dlTMT!B4`tk4aDDqvGYg=_>-c*4jLJb^w% zP8cF0CSX&zgw_!$0h_`k)W#D4Y>JK0DPkgEQ$U19@bmzi0wL5!00eA`e$aNq_R7B= z`B>xRl)ePV>_9jK`Vz430f{^h#zJ2w@6d#oLxPeBnzgJ1~Q6bT_4 z5fHE`{6Xt@T!Bqd53&*P0Gpy6jQsYGzq`HeyO*oDsGl4z#6O@Q#^#)pLG8H#!6BdI zf_P)r?=StW4L*MpH@1nJh?$I)6%0{?gpLsz0h_`ij2f#XuqhluHXQt)EtfNuo4__=TwOf>9bm{X$i^UrIxmUziFn7^Pu^-v||+Mx`O#FIQ@SZZ;LFe-YrCN**k@m8T39@4}`VTL36 z1h*7=qFY3rz^0HBgG8taY>G2c8zCmJDZWHDCK0eHuEf*x-uH#Kn`dnQFc|u(C~Y*R zJ@i#kA<>xj&{su;Mq}DTUlkP=jcE^kRn&-ROnd07qQavw?V+!ViipOvhrTK*G8)q! z`l_g?XiR(PtD;6mW7 literal 9356 zcmcIo30PD|wywUmAolIsD9w)AQHe{`M%*E@cYbA$s^{cNAC>c^J??!-sAdJ2f>bKi_J}FgYx?Ji|y+ zetz)?&%^>tX13X37`cXW;P1akQr zmS*WpNl$xKPsc^qm2$b`h_tsE!tLqkkjr)$Qd0`;Injj<2jt#Wb4lv1S8U4l7jJiN zmku;bTP{lb_e(pQrEP~`N#ID@u@gMPI_T2m`a|-$I;rI#L@Ym%S4A|Gg%n@-F1Rj@=QnGmq6al5PQm`B@aP-I-?n-tDN(&XIJA+>Gp zX{(V~8l<*1clpD7OJ{aSm+Pd8t*-87XH7XQaO{-Gh*#^!Mof+{$jv8U9@cidbHA(V zsC#p>v+FFRN8W$Fr>o|k5|i03?P!Jlk!rWo8fe|q(_JOkf!J`7ZX z`<9iE>F-=it{J)(5o6Ng z;*yuo!odO}&WnkO6GbvNZsA-;mW%VE$y~G53~jYw-Te`yVnI||a$<42SL$M;OJFKELgb8LpyW;esHE#I6+G9x{o zk?hlKci8fYxyYR7h__fB<^qysv6`nEjmG$?#?-mS31h}i7!zsCg}kkqJa+lGNTVe$ z!&;bWHl{jaYz6S+N$USiEy-ec{BDm*dVa6N`KAJk3G(r~^`IA#HLkH0tg`2uGR#KP#N|0AI4VX)BO{~o$3_@eJB(`zb4+<2I?u9P z`GiU|W?OCPChM3Sk_8PQiqUA2Wi#cHY?D34mX|?)<7sn%-&r=;3;0cfYOvD5+;#b8 z0+ut=3rwrb!~*qYvU&>fep^{-Cvdj2fPh+*@)J*8GeFfSYGD&J~7nmJ|1$m;witrf7I35M0(oV>5bKPLTcaR+|w+z zRJsmt`B%sRavk)JIaN>b&!KX6Q^OY}rShr6z@&q^(^5$%6>@FfF7IsxA?JyDX~$vZ zR)!V1D$f$-W@gYwK6DA7fvd3@nCIN}rxa`@v6~9AAYHyF9RUbYtsp4^x2{xq*4ciA zqJ!?sa^qHT3fl6aPGAzL`iy(;3r1yOG{BOhQt1H}7hNiqJ$1}^q@5^R;cRJi)^7dP zhjVX-)K%wUfv~i5o9Dw_UhV8^AyQ{MB$6($@+^3u>0r1FY{MS*w4H*1f~TmrEBgWF z$!A7AQS08iiyG|S;%sbjwHzVtk`k%B-3X8X zYDZa$yQ$38(yijWeKu)^8iMC)~L7nBB3FkMf3 zi;^V8EFdv}#NZtEOFD9%4lq5S>Cw4YHd1w?v$EVMbzX)7GP)ZtLKcl6N0UkH>vGcv z^d)5D%g0|yul$VY$S6X@5$X{-Mo2IrTUC!ft`d9yIV9(YxhrO<1TKC85lucOUq(nD zw-zJSFak?XlnmX^B)E;yjBAS z()$syT%{>j_4E9ok>{LC+-)CD(g>Uxe8ltdzKr1XYYn4xZ(j-x7B~w`PuENr7{Tce zH&o<&IXx^);4)!4j*rtZg43si`hEAp6K@UR$uf+vO4Ykc6-t8Ds$$zr2(e@B)vNQ3 zO^*oNe6WzFNz*Zc7BnFjqE_7r8Z2-NV0xKmS${@w`cKk;yyGz!hVWz+IAR{<^%f3@}{OHq@dXpe9KU6bR#|SkK3jp)^y5m2E{Ac+Tfm;ar zb2M}MGlKdfW)EnXw&?myftv->89o`ljNtT|zH66UxV=7BM}~v>$JCGUj8OAs${~B{ z-5E>&_;YrgjywVq6V(&7jIiai{J#6oqz@}@UY--c`Sw#1B$leQrK&LWk(z$9WXM7; zRp6#V2#K0RKSpr6d*qp&jxNn&9eE5Kj8>2C#|RCBHj9@24-kUB=nFVA*eqiG`K&o7NV67C4N3S+U(0cg{v=9`IM>j zXhHO@{rmNe&u8h#NLbz|^(ZYP)IzzEVfC%#SCTjFv+BqQkQk{R*^dz_u|<(MxZnIl z?#ioHEzv_11B3y3MySMQrIStWwQqI&Zl_I4CW1tm5a!Pam8e!E8gB3Vuj1NW1v)Yq zyo^_m*D^vSHn23G9$kOx?ia0gftv@JNY*6#GJ@;);6Td_b4Ad4EfK(jUeNn8LiIPY zOpwiGA9R1ct3*r2fk3bj9KZ+_*rtTgas9F7Zr@|20=EJ@%-78C#|V0OWya(E`}OYJ zEN}%dox~^U8Nun)c~|p)nD_lwEs1~y==$k`8KL(3#ytTiA>os4qx+xyTZO2oZVF@(>7sEz&me-oD#Uj zu+*jeQavNM)ZjgF&tyFN*IiJ)^sR z-|Hvlofo()m`>58_%edimfny2HS6oF3pxV-9VkvvPtY<#qkD!GQla|DtC{Y77X&UJ z^k-;h=omr$k5l#!-Po?Zs3Su_f1G+;KSrqjdZr)klj0g|nb9F|8K9r-lkLk0>W}NS zcF{W-vpaMo9Q4Pk$7&d%`fHi~)WmJW+rD1VDR2p(zrtsQjuF&fGuiyr?FnZ;(2)V4 zAEA!WFhcd$F#VU$1l4|JO1q{d{*eBG!oUDVsK9n5{SnLF4%M&HexfBhkO&k4^^8!7 zO^U?91;Xw-bI#n*lCdBWDuntoLM18{iGN&c+&`;#TaUo4hA2f}F@zC}a^ASJ=g%x! zC<)vmm|n~;_G1L6*YBP-KXt}WE`gf_(>XplJR>;${iXVvSEu~_rj87PH5%1M9V66z z5nJOgm8 zTi|j*KaV*$Qq{o%1ysSQ61+!AoGgkR#v2s$7I<}ABOdmkmL`Z9Egw=qIPU{hR(CmSIl zuqhtIt37<~+>hTpv!EBJ5;v+h@)*6EeZ7+JdOiJGuxq4HofvWA-Cin%jXH=S!6XHc z=&4urlu6(1d9*hGzW-?wAtgvtJc-f>B~`%+g!Hrq#E&4YfJclBQ6o%H$cScM?TY(x zZSebARsUZ(ZFoZynbrJJf6hnLcxe#D5MHcOg;lABBT7W92}UWE9$jUFS27(trCMU+OI2y6-x5qKdw1U7|+=n}PnO);OxG(oTjY>Md_hrJmhKcugrS1v`zN!451jfAKCIFj4U(B*^M9knHc6m+d#RJqF zQmPt(7?gfTmZ~P-|1MXl%T;~xAR$-=dlV_78wAO~rU)6?i@$j!J7>bpnZQ={Q9&nD z{``BuFtfF6XcTylQb*}<%Xv{$i#lxUytq=mQit`5FdHmUjE%*E*c#XrSR=dW>Q8<1 zL;f1~z=^{}K{+b`8@mAlY%oL7HHMGC8rT$7qjiMSz@}Ilr4dF0n__5WBYXxn#m>k^ z*bHonnUReJ3T%p%vA-fnhR79gjFBTa26>8%F+@bfz@~5**;u&1rWhElV|4?Y0$*e! z-~~2CyJ%!rRmrDEKKHe&cvgV8sfd0-nnGV(P)C$37+=~^F0v8l0-J(d4DjwRf4aNj$Ct`gVGq11-ZK{=5 zTdvh`ylD5chhRhH#Ufo%0CvU?Uw^N>uxHv%m0pYuhz&$j*n_|%#mTr1G!1MDjxjRq zJiw-q7^SbbetB!r@ptxuZF&RZHHrusq$xZ`BZ!NkL_B!dGjmaQYs#N5f7S&1Ri+wJ zrW%38gsln;QHYFgvA+PD;$n;$Au+Hi9!B=s;%}aLEjs>eATR2@^jh3kUiA0!*J5Ml z#Q?7WEhdE*1HA&Z*qC`S$SX*Tb$npVB&Z&Q=O?nfX9pL$U1MtC%;BSJZu%IQF-Iu;kyEI zOdl1PV|-5ln?5FxjqeCx(?RQ0hRz=tq> zI94gZhcN$etWtmvVFBS-r2rqo0>iON0X~EUg=3Whd7xtT!+*Jc z@|f%G8!GNsz!)!UkfskX+;}`Cz@`r`WMc<=0A1r#45aBh45jf225kEN!X3tk7qIE8 z3#IYF1#J4-LfnI=5ZLsMh0<7aTwmo`Fa$jIz^3miGG&?0Nc}^;seSdP!{bBO`Vbc9!>iyw=1D^3bxBvhE diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/jobselect.cpp.5D8C791ACB1269D6.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/jobselect.cpp.5D8C791ACB1269D6.idx index 723baaf21f5d714fe9b5388af39b3b3d941ed157..0c25e115393d87441f9251d5be6c7f2ca1dc0ccd 100644 GIT binary patch literal 6404 zcmb_hdsI_L8qdI0nB9kS)O~2J+PZFgd~7T3x;@)gyK41VSGR6mwQg%mAMVaoXYSbk=P~C1_jkXU z@B8MP@B7U+p`vK++#ggDNv^f1yV>S4F%pS{LqASehojpYJqVw9t>*c*CY!}oW?E)@ z6d4SX21k?4pwsE*8g%6gbZJTYw4@YWv!i8M*DQT~YKqR$Vrg<)ZMt&TVuy8^&E?dU z7j!tRC8iavZr9_?E}YZa{rI!X+)n!(x69Sq@;J5^{oHneLD$vV(dcY5S!_B}dcED` za5{ANf2X7v+w{r0c9*WpZ8x<%{Jqdo{{WiW+H}jBTFs`WBs+2D@zq&co11lRYn$-b z-QuvAT#nY3C!)Cx?YmRAyw&Wyp9V>mwzenW?@zTpi5PS}Ofjp;WwMw$tWRY5SgF%x za=D#CX|z32y)K8V$!0cnAf+9)h7Oz4E^)4CHfN#Idvo{ahp()-Ss3C4yd+SjknvKv zpFB+Bd%rLxH~w>K=BCD9j0bqhk$4TaCR`c-1N^ZVX>$&ta<;6$`231+;L_H6F>6JcJUs;_2d6cBeJ`j!v@~|H$`)TX} zN)L?(fnpGZh~w7+#abE<0skSO66M3df0)K2Krup(9|ek05##b^IG8L;X7S27sF&#} zlp;%^P^v7IplROczg*$HCI@9o57Q&V3MBXRzF&`w8`0}7Wf!R(ECyls>2}lTjTI4J z$al_CpP9FD%er}J6U(G!N<4~(<O?hSh-g(b zMaLOo8N?|bE)HExa(~yoIp325ZViPyub=;?cKL7ys=TJ0ZvTAqDF$y>izb6r%Sh^boH0|lSYt-DyfP>v3{}C z-c-x0NyIn&=9BDmu|qqM-cDsFA)Eo|S@K=xsTT!`$>%C8SIuqs~~NC&xk-_T=|e;3+IzGigmAj>F-16B?psUD}Dx*KB1r)gb36b0}m~ z8Yz^g$fMSougIrRp?@J+H4lsYizrm0D4|A{DoP3JR8J(_{n7X`%E)4-m|8~(Q$nFq zrj$bSx%pI?jR0mu>>Grxjv1x#7+}Uke0ObUZ9w3vml1yyFa?CgSXjsuQmBY2qEI&@swOdFpVadoUJi zq#8;V7ZDdNZeJfz_kn4m-F~3%r|}Trhrm=(J`DI_8jphTQ4odYZsym=tk<`{i3*_~ z%;*QRu;6^zj_7CJ_B((C=2CMtwtwlYdB>7>d*4E5kAX2z1WKbN>eR?pN^GPBA|l(W#vSSguZX(zj#QQcx#`S#3lw4k1_ z9&)9?u|i!%&w`#B93vjCWL6SF47+~=#>MzSSnjHeA308=Ob-qS4#GDW7KSh(53kR{ z%}2$kD3s2mQ)mIRfI{s|JB6H_6Mb*?;PcGbfsJnjpF53G&?$A2mal7;?Z&ONeIKE< z=#}~~?DXHhUpBFA=KAwm4k|;HBw|0i9Zc02@e~fmPmPbkyMP*DHknvKjWDYxIn2Qn zHieR6O}_Hijg-`GGmmH(wQ5P8ztJy zQ{|DJ)>7)sUp*V10BwjkNViqGwjMm1H~ztua;- zU7?evnMJr^(aa*;{eF5ET;u^&xCnT}h&1!?h!JV#5n7z^5wQc{5k=EXlw3O=&Ncd7 zKzXU2u1>}-|Brj^4=Fw&7tjgCNjZ{3JWLEoB+1LebZI&zTOeIPq2==BNZ~xw^%BzSDM?l1g__cw6jU%8C zN9UV^TgQ%NeEM%RKRGCwBx>!xBid+9<{9 literal 5630 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 Gzx^M{MF5fj diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/ledindicator.cpp.9844B6B5BDCBA69A.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/ledindicator.cpp.9844B6B5BDCBA69A.idx index 518ebaf12b4c9b21884a319b261ef44fc0670933..e4741b8c703864ef389d2098c5c6c32a97357ac2 100644 GIT binary patch literal 4356 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 R<%G*y>Nf_`;=lk(^dHc9zU2S_ literal 4180 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 agcM$qhvFr0xp@0QJrC3n7y9?>X#NGZ9n8i6 delta 614 zcmca1zCuhk$kWYDft!KBImNRgH?<^@g@J)V1c-}EiZV}cl#OPZ+{|P&`6!d42t#pY zZqjuo28N|G_OK@?+pzO7iB1+|5%Fx`$^PUN-^<0vB*MbLufU@q1SS|bfW`oU+y#sC z%>EyF1(;-k(y|<~0$>6r{qRr4@A=Bl_ym~5CzmnHc?kj)DRC&KK<-xQQ9dRC zpn63fMPU#zc{^(%(`%N=T5LwlDjX`4~|3 zEaH>cwT(c2gsF0w|K`VZMF(d_9)2kiDfz`Q$4t+y`LqY-YoKP3mnY9;S5%f&lGTD4 z$_4f+%#ce{xo%4|Ib|_TzRNB%nVmyjP?SZK6J`bvzdVclW_u1XMrM$|BRC5LMHNLg zVA5P*&)?*s04o`ll>h($ diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/mainwindow.cpp.939E08F5B3723232.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/mainwindow.cpp.939E08F5B3723232.idx index dc18bfa78a66a61585b9261432cbb3507a6a45e2..0e0b9144542a5a5ebf4bd32d04331ca88e5bcba0 100644 GIT binary patch literal 6720 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 xn1%k60ZD8?8X0KPful= literal 6324 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 diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/mockvehicle.cpp.CEE2274AD5F0B599.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/mockvehicle.cpp.CEE2274AD5F0B599.idx index 6f3c68e098abb91aa4cd4464c60f9508bcd9311b..1ca2d5fbba313c8e0fba8848315dc04e876a9b31 100644 GIT binary patch delta 3398 zcmZ9OX>43q702^uo}ITPv)x%dexC6%j+@j;>c)=2g~r+Hq-ubs6@>~^A(ggBAr(<1 zmJbX3BmM7Z{`2i|X3Qc!i+agr#LIk5`Noq4umT!?#^1UqkE~ zm# zcJZ?;tqpu~>wN6(Z+)+WN4L%AdJ*YG)IZ9MAq0mIl01yiFv61a2KGtuz*MbQU1(!D;K>+P8ySlO(HHeO(8l(<LTk(*TWBBG1blOiHTDqlu$ zSzLTE*qUP{>Ou)^B|IoCtRT8V6|Et-Mq}e3=Fkv0Xmw<^FvCH&gM(6E8LUk0xrdQ* zQR`JJNA?nr$*oZ7-CO&a^f_@nr z^)_}$Hy03Ip!eSbdrQ_TGj4*`F1uH|vi{joV;SLPSx(GYL2QK@Ttj$`vJ1;a3w>5& zh2IXJ$z6ncQ3l*HERzck7 zTl`c_Quzq#NBrfQ?38>f{T&fyKL1XRF5%8%Kt3q;@^K zIWw41!ak84@{TuYNWqCdv&Y#iKjh%JJn!Oh7f<-kdd?rN@ME>-(ZS!VomYCH_3~cp zlrj!&oPTQlYX2;>S?E%Mjiikl$~$fBqWmQrUA$QLs8WPhJ+IfvY zTjAdq!fWYJ&Oz3p`Qo6{!55{{GPE*v!Ub~?ldo{G%f0jfVKfVLoc~&Ch8tKhw}! ze?Tm3>IQ$Y{*UOnain1gV$$oqviEqCULV3X-vTp+p$}ubYK zX*xq^pwCdPBF|=J{KS|?jSb6xrpLQ%*={`UvLoo02Ns|&U>onwX4=S}v1B) zq-K|)FN<5RW%uUD_9T+EguPPt3iK7KWexfo4Uq%gp^q2&?9jRp`Rw3fsZZo{nLbwJ zv)hfNpOfAKaK9_pS&xsqI3eXCp({vJ?yT_B%@4{2V7mFoq8X}Ox@8(ypkASDL$xW- zL!GC*1a*n>D%4fVE)*9*-wSW-s1-zg{)E4}VJ%P{zd=* delta 3356 zcmZXXT}&L;703Pe4lp}2@v=L2KN&BJSs*x$U1DsvxT>*(T{{x_aN5wON!+wa8>wy^ z5lj706#7udvQ!J|3+w_`YZjZ2M4BkES!@!8-8v62>j%^_vGH1}Dpg7!D)k{xNn5FR z>`w0?T_I+FoO{nXbMCq4916#mpQ=56?8uSQgYVXwfj~pBfm;D9kP4>we(_{M#-9~W zGidT(h-V_0;Q4@+O$L*(P3oq5pZ99M{%P~c(scdiP+*h3ipVOOjA$^*ZwGduhu0`y zRo0PO=ZBRSv!jTNg8jQS{m&EsdhP<#=MbAiMsfwQ3U`!ezEJ=xpve~)`V^v5*e3ZB zqL(ONM*K3eQa+97G?mXFF~d*QEU-ar9i)nfkr?I&YWo!jha5g%+bh28ba-vuOR=jE zKg8w&*a9;A)jDf)^g91tT~qR#_*MH)|EgGE`e`(t#>2h?e5r0Xy7{MdpT+$(mGsj$ z|MbJ54zad?Y+))8gi=69f!AqWeQRi1Lx&$pLwB*q#bL>}pxgqJd>iU*Bz*3F{e`!$ z{R9W0{MQZNCeLfz+7`hUk?=iRy>W|F8XAdse)A+_~~CeJ`>(Je3Pbo&c``^g7yM*r~bmpi%nKyhFMp%K*kGNzBBeiUKJ zIfQeFNX{dYhbegs;V~*7M`|1mQa%B5f~MCbLX*T^Ttq%npbDp8PJ!_gTdaZTWyGYW zX_(Vg(+pxWh)ek_BC}M{9M~M9Qf?z{qmlA1TZWVAHV)Z%RLYA{go?yfeF2dLm^>7+ zdd%z8lSM=pspS$vCAzT;R;Hem(NV@D{6uKSfO&%|T0v-qZgdcJXb2qaaAZa^-9fj5 zgVN;+ScMvLk#f=C=Ohz3yxT=5;buJH;z{|3Dl%0xQ9fAZz4ec)|4ZMg`d^Bi36D^> z@?d#+)0sYr@FYDM8yOp|(#;~mMY{j~Etc|YVAp5~x{l~|Ss>V9y@YT{-XEslK=cMR zxPtHsWfzuW}iWd4vDmNM9+imykVt;a44E-mADB>)ndRGK6KoCY8n*iG8G02x zRlXkU=sgGHoD8U;520a5o~WUZfsM(#W#}8d|KpFhy|U%@>J%*fDmt(7zIe0ZH6{K= ze21w1?SnF>EqxHj2lPjSd@oDfieoD%q>nJeT;vz@CCbZS%amQzy9oQ? zXCnNpikQzI@Yj!dP?;3Q9O{mot*2^&eNz(%``?UcV_%XY@}B7#L|yt?%@MS)cPW8B8E~3&$7u#JtMER(TW0I>#RcXDfVsDkVHx6dG zsQWU&))$5(OTRZH-EC}UL+BX7e*dKm-8%qoW?F;>mCHV9d_jY8U&C{m17a{!*ZJ== zYdCV{RMRL#vDHTKfUnVO(Ap@rNX|jaA>^|+6>Kadd1!fAg1l>_jmh;)TvctH*3t=R z6J!9`C#tzNNgXIaD^P{r#DqP*15C_?+7!aP)Z92g=7ecz(?qSfl-dlv&9l&Esn$7Y zb246DcTU={q!p1mwk#W-yvvp|j+Z@JF+$y5fVO~o{%W@IAQ^8Kp)FFgC1@qOs|>A7 z-4NMV#x7pU?iwIkR-moWT@Eyd4g?}U9h&MQKOKBczDxyLg<26w>&jWu>)Q{xau)UY zn43W_P27em(vUrswp*eqnTIQ?zHs3eDX!_|CZPKzyI2gyf23L W>)r?Y8|&Zc`#iVl*Szvz-+utSJKS&p diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/mockvehicle.h.D216B3FA554E1321.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/mockvehicle.h.D216B3FA554E1321.idx index fbd9c2ecea4c491e9761c30fcf628279719fdcfe..ab07239eeee86945fae7a6bd3bb0ca3113f784b3 100644 GIT binary patch literal 4002 zcmb`I3s6+o8OQG)R^{$pF3Tsa7;5 ziWn6`t1+ZTgKw?zg+?LTOxtO#mY8I;wNI7Zg`^EuW23cA`<=6VM4e1$oaqjHd+z^# zzVAHmZ)Z-PFkxpj$Bna3Ug44ztD57uSorrVKF7=Sfqq7P+s7Pu?bT4*Pl5+_2kUor zw(aX|YYQDd*4?@xwC{ZA+=XCkW2pIDcWa&b+mc6VI?nG6)gK8Rt~bvaKiwSIyFIvh zZ{XHPY@KXX~L?Xy-bp7TC}d zXlUy=-wZSE+`XymV2c!NIv3pYUg!F?QfTWdT{~)};K|cc@YMET%Lb`){h{Eypw#XS ztZ51zt%HT#u3gKOxZNJbsyICEV)JsP%!$dwXGTD9|pWS9T@`IHTs$~A%#!r&F&t5{hs&s4TFyKfovVG)3Jcrw-#P9 z760g}Q6#ZoTVgFH7SMii#8i3zA8r|SWH1!>>;1_>JRfhMHi6l_ysE5l_N9NuiNpli zc|-FoEMRu&$E}~nbUp$*p4usSnJl0^CT-7@{V)9{K_mvSbNC!13ut>UOv)L%AUaDV zX0X$>>G3R}y@i}I92)f~TO@k0hwwvmETBDvzr5jQ2z|Q0I3@o5sdiR6IYrhj_=*U1=_aObC6d{g}i={SQ_x7l}wyLj_7l|AYb91CbSuWKj2 z@-(a!Nix_2A_v5>fOfTI>~A~Is@CbqP_PZW!N3CA^Gm&#Mso8uh@?N*!}wuF7SR58 zR_dd&e=TUzksO%XYW?aoA(>C^y^1w&d~#;=OVw|SBoVR)@&i*@z-)Q2@VE8K(cL1^ zfnB66ie~}s6MZJ$%XD7crz3-)_*Iqusw!PD@g@`PKo6zW*!RvK8^#|ONebi-=Z7b< zfcaGq<}`R8cAtqO5s=SE@g^ZUDw<`}_w3b{q?jol%}+&=4%ud%*~9{7FKRo~-m>xY z%OVlMF60XhETElpdi3ho(p#>GBn|8gZALN+X#e?n^G7%Chpvjm2zEhEK_Lrhuk<^f z9~^(@6OqJ$-9NIwfd#ZTE`2~!?xlY!k|>%TS)gM9?a)1rO~aQxRwzm=GUE$`=z{?$_izA29>|n;DBr*P_m9i>pDcxO z&1$oV?*S4JHkFbOdn5Uv2*n=C6R)qU`>aG+0J17lWeiW9K~3`wbqGTsM}DNRvEkU|lP zPn7pXTs&-4eRTAx2_jXY2t_LPUa{)Sl|LIh;Y7rfZ-Q3HRp$!0k6NKfT_oT%5Wp}l z1us^bTmOE)yEi&p;dm`-i-0>&etg@J%b)FNi_i)}l8}MD!^0sTv_f=3v>6NI)rF~2 zeq#!v8)V9Dlo8n=Q(mKts0NvG8f8Q@$du10BbsS%;4;eXkCr^#@tykLlmGRaW}duu zz4pL=ZovgkG9_i=ozx1cDXD{yu2_+1!cgzI{jl)Iwh#dil?-OXV7!s3=Qs)Pj*^kU}!uP4vTVrpUpplPD*IoWce^>MB{Vn YzI?yP-rp8*;FkqIJT^G4-#5+w0$~^UB>(^b literal 3750 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 diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/numpaddialog.h.348535E0CD997F5C.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/numpaddialog.h.348535E0CD997F5C.idx index 8e1dd6338ce7bc54b55e9024c5026ecc08cf9e1d..787618a26ec3799280f34e3ea596c5dbcfbdf81f 100644 GIT binary patch literal 3606 zcmb7GeNa@_6@U9!7v9^)+qcUuuq!GEIts#LmoFhYtztAb#7LwxpQQ`?0^95t`ytZk z6u%Jb2Wm9I+GsFX)I>B^Q_;mxLnj)OW}vc zd++ae?>Xn5d++b=V@nq==6Q;$v@Kobl>AmDMNxYA_xZi{c?r-UpZVYS$qer~5jyr# zWOv`lzOGQPf8^*(w}Tzw!`vr(hk?uDm>kr>J|3YZvq0qkl;jZ(ygInXFGR^tr|7u%X2w0@8E&NIlI^i=L zBYS%Q8R_o3(bpq}I$sRG*};B(re~ygTj<2u(D5_G@3}4@Lp{Ba^T_57NHui4edLw3 zk?zwYyAMOc;A!MkZ)nrb7~8oHqDJ$O2W<68{z|vgV{P!y@_Pf)Y?wnW3pkfsZHw$y zhr4lHr=o(YUgEXes;sTRG!(st`qSafMi2 zBbF4HOA0Ka)9z|)nQN{qvWRw9gCk&*#A=A|g|Awv!SDWmrv1cjYn1#xu{xG023%4h zwz$2|`Cv_wXf3U4vf6!iQMMM#0*|>+Z1#&SfhMafW`At2i*;ijT>&VF%_gtEpowzU zSzQfHkejov-rDfo!%{1>8UT|Vu{K$7tQoB(46xZ7?S3CLB$x|djaCn_P(Ao^T31y` zE@-%8?mSkP5cC(h{glT6 zi(W4K@=DKoC12BnBsy&J0(lA1nvs5N(@FD@Xfn`x6l!Mow-Ru<6{AC^Rt_Ic!q@7o z&w&edtm@-02M#q*0-YJVQLKqErBg}K+chj?y`~BcaRNO9X#-=>#%Yh%@bRWV3Qubj zDAueo8)Sp@`Tj-5KJP<0Je>w~p|)_cY>;-}xjA}m{@5&mo(Xg+m#UQw($~InZTOpE zbdErqFm?`;qeuGGMb~}T#vLnoS`D)ck_!y7LAt29rP?`Wt`X=&oSnv`@kkdGmY1~t zy%OljNY7wq2uLs3sVaMBbKs`}Jr(I(CN~A?I|onawSVy=;))@@Z+? zyyw5JPo-&`txMJ!k=}K34L3(!6-cBfMXOA)jEd1xjNDVOlY+yppM8_@_G*F7#`#9Z z7>|DK$qSJWo151s(lkY}yo%S!hB8u=0{+-ZmujxO>vGm^;Ast{_W<*NPBs|7{pUx7 zBNxwh@N_cJrP|UA*&w~u^nS(tOFKJxng_aATRd4dNMG%|cffx0dN)t&fG$rePnHeR zYpxW3VBg>NDo-1MF5}8lWP^16Q&-y^>W&_sP6xVzsnE*?>3e@!S6e*i;9-Hz!Ie*8 zrWlaEG`wN#i;~xY&Okbw$&T;Z2U&M(PKO>n!P5dbx0o!`WrMRnxmLgL>A|-CM4G%L z-w<8Y?oqF6b$kPUxU!pBZmkrJj4*zE0$>08dh^JG47PVrUY>>`(e^d0! zW9%OVIt!D_WHRHs7OcC|yMD=(KMC|Sq-Qg; zdRN_bg{L{7i?l^1*&zMJp1b=?$FzSHXcaooWAfs$x7XbJd@y6jKLmOT(leQv@yl?b zGG*FOZ}n$9tq13GljaJtLFb@Bz8|W-)e41C)(AX?x%{@Jg-72{P$*eJDR7tb3Pxwn zTe0mQ^GvQlkZ@C{z@1LX-rZRDBqd=3f6Duh7a)5FU;R3^*a`f-njT zz(iFLMnO@SWFbKqg#dFqPyt~Ty4EA(t?c4{vA z>>4F5#0!m@ z1$h&}!c0)BAVH#02%|;;OtcAM)Fyz5CLxTP1ipzNA&goCqlpF~?D_1i{7+sOYgEwV z^4_(j?Rw9rf)Da0tHhe1V$n=gsbVM}$k zqKuJHk7;h)ab{&z#zCmcFqcR&!l=fuM?{4YMimB_s4v2};S)9*MHze(MaI#q*4$k^ zv~cmsN#nPcV-4wsX($qqvockUSCC_~)3T>xbdD`kmZ@=hsO?ZWS<~Z3LR5+x6z_PQl6M4Q^bd)X_XA;kI=~5eG7!cm17Px0AdF8194CE-J_kdw@`Hhc2G1s^ z-{1RXc*05WN)9Wj0A4`~$5TFUgD;!ZCib(FG^4+UNQ+``9UT4oXi!&9GfC9{0B^jQ A{{R30 literal 1876 zcmZuxe@qj16u&##gDcne3Y1@IvBYg5I5}FyD1j`3V%QiN&be&K(j4tsF6%G)gC9|o zA~M|+aVlCy)UmMuPSLr9I9HGn5?ydx)FtdkaL%QNg=K7J*&oXm_PuK#ONp2C_4|GA z-sk=JymvcmYijCJF|68N+vef}oC3qJEcEjSe8Mdp9i*r7NsmeFy%N22CU$CczCRp| zd^UgK%u?jAI6NSRI+h}Do1WxTsfu>L6ASf-;bXCm(fRQa(>hZeAa?cx@$8MI$a}GY zu~_HuqluHzu8*Sq=fv>DQsn4LD3f{pmR~|0rAQYGg~azG=ptV0iuDa5&HTXVqtQV& z+H+bQJB-x_T`zI=Z35@?IFc^q<(SRMb3Sa3;N;6$maQpg8+Nf}CFZgc3+ocxj@Hd) z>jn!exNXj$oo5>YuL^btAMmpc2{`cFQO>q{d~f=_oQ-F>P1Yt(@C&RgEtaibb1B;# zU|WMtoI4?35v+-80?{3Gc{#gX&a$Klb6I&?lL!5|xVAdpjSelGGst5ecNJ%g$3f?6 zIem|No8S-vn2+D%wtwSQlY%;{jxp>h~7A9=7TRI&H3#%zKB zvs$gr1M}?bQ?WbE&7G+Pfng*?QW>(q@-PgQjg!j#cQ<|`oC|%ZAt;o)kv3+^0{p+6 zJGlI7*~MXopaEV_=~tphtdEB}cNo8B2sN1Vsr;4Fibo^;*E_qAS%bi}lr}>a06tZn zxn_E(;h}~|L*|0C0!9{KmMHWeMYzS9?%L7|U!~v*GF_2Q&#Wt1c&4J|!1;UsT2l(c zG_E4?G(0;_)&E@Lc`}F4fx0_Ue)IiLHMmX&0X0yIKqOv44V2>fGL@hPDiO9sA*kD{ zUKq)?pD)E}GAk`h4VlaoynOqf3*J&qzuT#sh_v#sWgZSRl1D4xol{Kx%0WK<#~aWBpGjmmN5fgni;j=>FgX z#*gxrGJ(85-zsX@$)1)(zrVfAPu?t>xa4AU7Yn-I*te>KKnH;*VaK_G;vf-16`#LE zC^{+2YloiS>7BB?h0>u6)LQTvKX6=Z7JfK})arb7A(RNvtNL|Q`<&&Ay2(C*B9)|4 z3pLTCL19n>+dp4k{Oz@&nZ8vNz%meY5@9NNB_JJG3Dja~6+jJLTGgGl{eSM8uBy4J zOzsCwGP%qe`0Su?3SBzP2Tkg8^aT)xCXEVXI%L-P$%%`%7sBHSYG9~g4^d9i{y|Q# ze`t!O_7PSY|a1x diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/paramcontrol.cpp.A1AF0D8315118832.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/paramcontrol.cpp.A1AF0D8315118832.idx index 924971e09ca8060f8f4d25919948637fdde3bf86..1a1d8068bae55d3986c411bc79c2f2eaf56d0759 100644 GIT binary patch literal 5232 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 b*Hi8_)53y3xN3>q?)_ce!!IRlXZ6fK3lCv; literal 2340 zcmZuydr(wm6uaLPu> ziBJ%-sljG?8M&2}gpAWDWRo?f*h6QsvBuZrZfXrQ89CC!&Ucq%!nm`Xd%xfBJBM?= z^E=;Gm6w${j0{s!SKj7P{IY>z7(4uFexFJRywFeKoj%TBPkZR4fy)EWUf$Lo+7Y;P z=z#N1`wC6rjlte-XO=VA9{{J|=HXD^CqMTeq)11P6x{YksH^kJ&JSUI>F}$;5BkE( z@!{u#J^QX~?i?TP%yNdd><;#K2RjZ1ceK+8y2tHXmD3sblI@w^ATUQdxmiSuN$y&&{8oCwWxwy4JoO^mEo6Lo+}EZ)<)lYtx0w%lDx2{K~^$so5{Jwlv7zDf_5e zGqs$G*hF)7g>#V1mVz)*KA9?oI8SFUx#Il${)+-IhSk>nOL;Q|kpK ze4VqsC`D13C@Kri#F?zRVGV}C9i>Uv6?()gT z@9q|e5$IHNs;C>JPwua7zS?^_MI^R}umcxwjJG3w!5Ub-gda^6i2>;ZV}b+eQ~R?L zyh~1H2qXrAr}OFYxB5dekywyUG$zI%edbtc z(@%zLg#r;Ec$zuQsvD$>C%aDM9I#Zy5`x~sEFl5uac*r~)m;l$i)1#^R-@H|bl~5V zn_IqF)+!P!(oUoER%&GU?3xF&3pzv+hjg4VZZ>+CzAgMS`|R-!fkZ)SnU>5sxz*O|O`{h-Y&nz3Lequ%oDuhL&N9tEL&wzJs-+ZqBf~Y}pw2xTa`S!`* z-@LI)W689J9V!Y5(x*Kern-*Z#yNvboG@RQ3CnW!`J6hZf6pkC z!A7&O7zr;NXa>D#usXbGU^l&G5XK7zV0ybCj70!UZxw{$C(td^oUvKAt`Wc~vYp_ShN diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/selfcheck.cpp.776FCF2AC614C2A9.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/selfcheck.cpp.776FCF2AC614C2A9.idx index 0878ff6e8f8b67ce95adcdf3ad922bbcc50c1696..8e34233c7eb6d061a69e85b80bee21a32525e107 100644 GIT binary patch literal 8624 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 A9RL6T literal 8346 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 literal 2034 zcmcIkYfKzf6uvVI%iU#|eS~G%@EWwF6_8nwYP%5?h+8Q?4Az1~TU?eYtn3SBcv(zn zt)Z3j2!V*0p_*tF)L52>u}!QAF>3VA>>eVdf4#|`x#ymH z&i&3g-`S^%9FEl}gbJ&Qn!TdzWDr8Jx)zw+|v(yQl!ZHIzy z4&R{M$wZG z8;9TNC3~;GN+h2~zejdfi>O{aP%la~ND}1&66JB9v(iKA7gkG2&O|57Y4KL}QwVLD zOMUK>FHcY$tz!^QWE0J*KsdxHSI0tM>7XkjijF`CM{A;usz6qRh%<#F3u`Xv$97-0 z#?cWBio!8C9_wTD@!)^|Lkya8?akomXc9ibkYH8?n5`=>d^~U4pUKf$Voo)tT2uk% zC$H2c+lJa7Gtfp7++lL?*rYXCzHD z*vSfx=16ef$Xiqa-n08`uk9N7w!=V2D&s^JHfznXV7}R@OJmZOPjGY$@lIh=EUEx= z-7y|MM!pn*1#xART;JT_{L>(HVPKirUOD4XMGJ@HOrlgz3B@7*YI-;1X*|KVts zn3LJ$SXF>oX`(bsDh}PDbZS?i8}Oxdp>BlWBUUXv7}L4VBHQ>E5fp=Cm>4!UXm0-F zlGO$cqpTZlw`P_1oW1(}R?4K_h9J;C0zhdV9J{BIN4KoiWkhIklqSj$eqRA#)!z#* zusDH9;2^@({EpebuB2sBERIF7#H6cmreRlQ{+2rbwW z15gHLCO>UBxGk58Rej+$aFf(g;Uv5SoFvBz7vUJZHkB7BO5iH|gxlPmnsV>jvjNJu z0d8+c+rr3?28jSvItl({9Z(Y!TMtG^w zXpDe_#XQ67q1Y@=Wm5IP6krtruZ$_6{HV2mTN=e*r|1nW*qPOmamG^o+opGcj3kIM m%n*dkvB#;PfTE*yi&XEDEDAmxS8%PebpOZ@r9m11jL^SR?qE&; diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/titlebar.cpp.C884912AA7BAF5D8.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/titlebar.cpp.C884912AA7BAF5D8.idx index 8d92ef3da84d185d385f562782f9cf12d3c336c6..2363e983e383ec6fc1a962c8d7bbbc8def1ab311 100644 GIT binary patch literal 4074 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 literal 1922 zcmb7FZA@Eb6n^ie+|F|C_$rO=xb}E?ZrpcW8-rF7~WPiMbbKZNN zd(L~_=Q(fse1YA*M~=`IXF;Qz6C5&xP$K^K310pO0x!fT=RuD((lrw9=(;g}XnuTT zapFkiT&s0cc5UP0#8K;mzRyvGM&FtLpm#3N9tpI~f7o^7)sb-Ez~V&PTuU(A|8{6> zj15g3#BMIou{d#jeyl&#JrL=-6zMn{K65y7wiO3M!Hc*=xMd{LGHhLE{c&RGFdlYQ`wN6QTjwC%h!~< z{7#N76t;N0*cI_^&ig;IcJj_jPVljX(Zy2cGd8xtJ;#slQyWM5=SnEx*%G~~*%2gUt9TydzN>nBq zkRtl>>v?|g=TD!~$Vn0*N=>O5$)IF}a56!~fBaWV_2X|IS+5}($k!|NTFBpCFTb9< z?}aRel*b{;N?Y}k0rC=GgmPjqGv2TiyC(U1uMuTic^ z1lY?}_%2`|m3gfoz3yEC8Vf zNk}Cj9Npl%%eSu8`1?NMht2Ok{72ooI3=Z^6q;Beu~L%O3cS&jQD$U-v0Nx==&)tvJ@4NMN?*(Sp}-XY~W*vGXbsa&U^N~Bxh$jK~qVJBsElOogBL2-}`75 zktj6|t-=HmDrs_{2<(Bw;@ChKCI*Maae=UHb47k`$zzp-b{Q$Dot|&1jQjZ&VfL%Y zDDB!>P3V?=-E=JQZTGCkhx-=Ag1&(uyrnpTu;qAem|-sm>toXHO+VE$FmrP1)LYX- z`FH+&nN(8vPX|S4$|N&sp!>;#Z-ndknb%30QXAA3D2e5HM4JZkfXD3qSO4z^e_Ghp zJ9T;mjg9+%-}gzLeTZB>2sEWP=q(^r*@dR_U)*e+jP(qxffOtF&@$(G7seB=;-knZ zx%U21EHX<1+%E8#9r236QGkvW%ukox3eE1gc@2L}D2OF?V7zcxyogW)?jUAByn7J7 zd-8CqY*M+fqW0HkuA8@A^4ulJWvb5jc3#v+>i)*6T4h!hj2m2FTwA=jP#bwUmqR=% kNQ9OUpSRq%RwRMaO-GV(1MWoGaGyU1(gD0p8x;= diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/udpcommunication.cpp.BD56AC874B6B7F45.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/udpcommunication.cpp.BD56AC874B6B7F45.idx index 3a163d219e3969269b3d53cc12db122e0617f08c..716042ced1bbb0de30ee9a3cc59902c54a7e74f6 100644 GIT binary patch literal 26832 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 K`B#7b_5T1e-x}Ni literal 12338 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 diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/udpcommunication.h.1076B38082689E9B.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/udpcommunication.h.1076B38082689E9B.idx index 15dfb1e6d3921c7313464a224533c29819d7f296..b9f10faee3a4e06fe8db5ce3adb90c9c92912751 100644 GIT binary patch literal 8998 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 diff --git a/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/washbase.cpp.36D02F9D325A3B2C.idx b/build/Desktop_Qt_5_15_2_MinGW_32_bit-Debug/.qtc_clangd/.cache/clangd/index/washbase.cpp.36D02F9D325A3B2C.idx index bc7da53f9c6ab3f924e47c29a19c696b1c28c6df..2a3d9717a5c72bacbcfa73c31f23e2890aa96382 100644 GIT binary patch literal 11794 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 oZ?Aj!(Qrwvv9ouum4* +#include +#include +#include +#include +#include + +namespace { + +// ===================== 线格式 ===================== +// 一帧 = 32 位帧头 + 32 位 CAN ID + DLC + 8 字节数据 + 8 位校验和,共 18 字节。 +// 多字节字段按主机小端排列,与车载端网关一致;帧头用于在字节流中定位报文起始。 +#pragma pack(push, 1) +struct CanFrame { + quint32 header; // 固定帧头 0x5AA5FF00(线上字节序 00 FF A5 5A) + quint32 canId; // 29 位扩展帧 ID,直接用于分流 + quint8 dlc; // 数据长度码,固定 8 + quint8 data[8]; // 8 字节 CAN 原生数据段 + quint8 checksum; // canId 4 字节 + dlc + data[8],共 13 字节求和取低 8 位 +}; +#pragma pack(pop) + +const quint32 kFrameHeader = 0x5AA5FF00u; +const int kFrameLen = int(sizeof(CanFrame)); +const int kDataLen = 8; + +quint8 canChecksum(quint32 canId, quint8 dlc, const quint8* data) +{ + quint8 sum = 0; + for (int i = 0; i < 4; ++i) sum += quint8((canId >> (8 * i)) & 0xFF); + sum += dlc; + for (int i = 0; i < kDataLen; ++i) sum += data[i]; + return sum; +} + +// ===================== CAN ID(sheet RC) ===================== +// 分类一 遥控器控制指令(上行,7 条) +const quint32 kIdChassisCmd = 0x18208081; +const quint32 kIdArmCmd1 = 0x18218081; +const quint32 kIdArmCmd2 = 0x18228081; +const quint32 kIdDecontamCmd = 0x18238081; +const quint32 kIdDecontamDbg = 0x18248081; +const quint32 kIdDetectorCmd = 0x18258081; +const quint32 kIdEmergency = 0x18268081; +// 分类二/三 VCU 状态反馈(下行,12 条) +const quint32 kIdVehicleFb1 = 0x18308180; // 底盘状态1 BMS +const quint32 kIdVehicleFb2 = 0x18318180; // 底盘状态2 BMS +const quint32 kIdArmFb1 = 0x18328180; // 机械臂状态1 末端位置 +const quint32 kIdArmFb2 = 0x18338180; // 机械臂状态2 末端姿态/速度 +const quint32 kIdArmFb3 = 0x18348180; // 机械臂状态3 关节角 J1~J4 +const quint32 kIdArmFb4 = 0x18358180; // 机械臂状态4 关节角 J5~J6 +const quint32 kIdDecontamFb1 = 0x18368180; // 洗消工作状态1 +const quint32 kIdDecontamFb2 = 0x18378180; // 洗消工作状态2 +const quint32 kIdDetectorFb1 = 0x18388180; // 检测仪结果状态1 +const quint32 kIdDetectorFb2 = 0x18398180; // 检测仪结果状态2 +const quint32 kIdVehicleFb3 = 0x183A8180; // 底盘状态3 EPS/制动故障位 +const quint32 kIdVehicleFb4 = 0x183B8180; // 底盘状态4 挡位/驻车/方向盘/制动电机 + +// ===================== 位运算 ===================== +// 分类一/二/三全部是扩展帧、信号一律 Intel(小端):起始位即最低位序号, +// 位序由低到高、可跨字节(如 bit47 长度 2 就横跨 byte5/byte6)。 +quint64 intelBits(const quint8* d, int start, int len) +{ + quint64 v = 0; + for (int i = 0; i < len; ++i) { + const int bit = start + i; + if (d[bit >> 3] & (1u << (bit & 7))) + v |= quint64(1) << i; + } + return v; +} + +qint64 intelSigned(const quint8* d, int start, int len) +{ + const quint64 raw = intelBits(d, start, len); + const quint64 signBit = quint64(1) << (len - 1); + return qint64((raw ^ signBit) - signBit); // 符号扩展 +} + +void setIntelBits(quint8* d, int start, int len, quint64 v) +{ + for (int i = 0; i < len; ++i) { + const int bit = start + i; + const quint8 mask = quint8(1u << (bit & 7)); + if (v & (quint64(1) << i)) d[bit >> 3] |= mask; + else d[bit >> 3] &= quint8(~mask); + } +} + +// 物理值 = 总线原始整数值 × 精度 + 偏移量 +double phys(quint64 raw, double scale, double offset = 0.0) +{ + return double(raw) * scale + offset; +} + +// ===================== 协议配置文件 ===================== +// 与 AppContext 用同一份 config.ini(程序目录)。 +QString cfgPath() +{ + return QApplication::applicationDirPath() + "/config.ini"; +} + +// ===================== 故障码 → 中文 ===================== +struct CodeText { quint8 code; const char* text; }; + +const char* lookup(const CodeText* tbl, int n, quint8 code) +{ + for (int i = 0; i < n; ++i) + if (tbl[i].code == code) return tbl[i].text; + return nullptr; +} + +const CodeText kMcuFaults[] = { + {6, "电机缺相预警"}, {17, "控制器过温警告"}, {18, "电机过温警告"}, + {19, "控制器温度传感器警告"}, {20, "电机温度传感器警告"}, {21, "控制器过载警告"}, + {22, "电机超速警告"}, {23, "直流母线电压过压警告"}, {24, "直流母线电压欠压警告"}, + {35, "电流霍尔传感U相器故障"}, {36, "电流霍尔传感W相器故障"}, {37, "VCU报文丢失故障"}, + {38, "VCU循环计数错误"}, {39, "VCU校验和错误"}, {40, "CAN BussOff故障"}, + {49, "母线硬件过压故障"}, {50, "相电流硬件过流故障"}, {51, "IGBT短路故障"}, + {52, "驱动欠压故障"}, {53, "旋变故障"}, {54, "电机超速故障"}, + {55, "低压电源过压故障"}, {56, "低压电源欠压故障"}, {57, "直流母线电压过压故障"}, + {58, "直流母线电压欠压故障"}, {59, "控制器过温故障"}, {60, "电机过温故障"}, + {61, "电机缺相故障"}, {62, "驻坡失效故障"}, +}; + +const CodeText kAirCompFaults[] = { + {2, "加速过流"}, {3, "减速过流"}, {4, "恒速过流"}, {5, "加速过压"}, + {6, "减速过压"}, {7, "恒速过压"}, {8, "缓冲过载"}, {9, "欠压"}, + {10, "控制器过载"}, {11, "电机过载"}, {12, "输入缺相"}, {13, "输出缺相"}, + {14, "模块过热"}, {15, "外部故障"}, {16, "通信异常"}, {17, "接触器异常"}, + {18, "电流检测故障"}, {19, "自学习故障"}, {20, "编码器故障"}, {21, "参数读写异常"}, + {22, "变频器硬件故障"}, {23, "电机对地短路故障"}, {31, "PID反馈丢失"}, + {40, "快速限流超时故障"}, {42, "速度偏差过大"}, {43, "电机超速"}, {45, "电机过温"}, + {90, "编码器线数设定错误"}, {91, "未接编码器"}, {92, "初始位置错误"}, {94, "速度反馈错误"}, +}; + +const CodeText kPlungerFaults[] = { + {1, "短路故障"}, {2, "欠压"}, {3, "过压"}, {4, "控制器过温"}, + {5, "控制器温度传感器故障"}, {6, "电机过温"}, {7, "电机温度传感器故障"}, +}; + +const CodeText kCommFaults[] = { + {1, "底盘电机通信异常"}, {2, "底盘BMS通信异常"}, {3, "机械臂通信异常"}, + {4, "空压机通信异常"}, {5, "柱塞泵通信异常"}, {6, "叶片泵通信异常"}, + {7, "气体检测仪通信异常"}, {8, "遥控器通信异常"}, +}; + +const CodeText kDetectorWarns[] = { + {1, "进气嘴未接"}, {2, "氢气流量不足"}, {4, "仪器故障"}, {8, "浓度过高"}, +}; + +// 底盘状态3(0x183A8180)的 46 个 1 bit 故障标志(bit0~bit45) +const char* const kVsb3Bits[46] = { + "EPS转子角未对中", "EPS转子角度断开", "EPS转子角度SPI故障", "EPSEEPROM故障", + "EPS方向盘中点未对中", "EPS方向盘角度超限", "EPS方向盘小齿信号断开", "EPS方向盘中齿信号断开", + "EPS过电流故障", "EPS电机A相电流异常", "EPS电机C相电流异常", "EPS电机B相电流异常", + "EPSVCU信号丢失", "EPS电机无助力", "EPS预驱配置失败", "EPS半堵转保护", + "EPS温度热保护", "EPS电压管理保护", "EPS转角响应不到位", "EPS转子角度PWM故障", + "EPS电压校验异常", "EPS热敏电阻开路或短路", "EPS热敏电阻温差过大", + "制动器过载故障", "制动器过温或过压故障", "制动器过流故障", "制动器电源欠压故障", + "制动器电源过压故障", "制动器压力不足", "制动器缺相故障", "制动器通讯故障", + "制动器电流采样故障", "制动器驱动故障", "制动器磁编故障", "制动器压力传感器故障", + "制动器踏板位置传感器故障", "制动器机械故障", "制动器堵转故障", "制动器急停按键闭合警告", + "制动器驻车时间过长", "制动器CAN1中断警告", "制动器CAN2中断警告", "制动器点火线断开警告", + "制动器电流异常警告", "制动器温度过高警告", "制动器输入电压过高警告", +}; + +// 各路 *_FaultLevel 统一为 0 正常 / 1~3 故障等级 +const char* const kLevels[4] = {"", "一级故障", "二级故障", "三级故障"}; + +// 制动电机状态(Brake_*_Mot_fault):0x00~0x07 +const char* const kBrakeMotFaults[8] = { + "", "电机开路", "电机短路", "制动执行超时", "解除制动执行超时", + "驱动电路故障", "供电电压过低", "供电电压过高", +}; + +void addFault(QVector& out, const QString& system, const char* codePrefix, + quint8 code, const char* text) +{ + if (!text) return; + CanFault f; + f.system = system; + f.code = QString::fromLatin1(codePrefix) + QString::number(code); + f.desc = QString::fromUtf8(text); + out.append(f); +} + +// 未在码表中登记的码也上报,避免“有故障但界面无提示” +void addFaultByCode(QVector& out, const QString& system, const char* codePrefix, + const CodeText* tbl, int n, quint8 code) +{ + if (code == 0) return; + const char* t = lookup(tbl, n, code); + if (t) addFault(out, system, codePrefix, code, t); + else { + CanFault f; + f.system = system; + f.code = QString::fromLatin1(codePrefix) + QString::number(code); + f.desc = QString::fromLatin1(codePrefix) + QString::number(code); + out.append(f); + } +} + +// 故障等级:>0 才上报,描述形如“单体电压过低二级故障” +void addLevelFault(QVector& out, const QString& system, const char* codePrefix, + const char* name, quint8 level) +{ + if (level == 0 || level > 3) return; + CanFault f; + f.system = system; + f.code = QString::fromLatin1(codePrefix) + QString::number(level); + f.desc = QString::fromUtf8(name) + QString::fromUtf8(kLevels[level]); + out.append(f); +} + +const char* const kSysChassis = "机器人底盘"; +const char* const kSysWash = "洗消系统"; +const char* const kSysDetector = "气体检测仪"; + +} // namespace + +// ===================== 构造与生命周期 ===================== +CarCommunication::CarCommunication(Telemetry* tel, QObject* parent) + : QObject(parent), m_tel(tel) +{ + m_serial = new QSerialPort(this); + connect(m_serial, &QSerialPort::readyRead, this, &CarCommunication::onReadyRead); + + // 串口配置与 AppContext 共用 config.ini(默认 /dev/ttyS7,500000 8N1) + QSettings s(cfgPath(), QSettings::IniFormat); + m_device = s.value("car/device", "/dev/ttyS7").toString(); + m_baud = s.value("car/baud", 500000).toInt(); + m_selfCheckFault = s.value("net/selfcheck_fault", true).toBool(); + + // 洗消四参数按存盘值初始化,否则开机帧发的是硬编码初值而非用户设定值。 + // INI 读回的是文本("6.5"),须用 toDouble 解析;协议字段都是整数,四舍五入取整。 + m_plungerFlow = qRound(qBound(0.0, s.value("param/pump_flow", 20).toDouble(), 24.0)); + m_foamRatio = qRound(qBound(6.0, s.value("param/foam_ratio", 6).toDouble(), 12.0)); + m_foamPercent = qRound(qBound(0.0, s.value("param/foam_percent", 3).toDouble(), 10.0)); + m_airFlow = qRound(qBound(0.0, s.value("param/air_flow", 100).toDouble(), 340.0)); + + // 通信超时判定:最慢的下行报文周期 1000ms,连续 2s 无任何合法帧 → 通信异常 + m_hbTimer = new QTimer(this); + m_hbTimer->setInterval(2000); + connect(m_hbTimer, &QTimer::timeout, this, &CarCommunication::onHeartbeatTimeout); + + // 上行周期定时器,周期取自协议「报文周期时间」列 + m_fastTimer = new QTimer(this); // 50ms:底盘 / 机械臂 / 急停 + m_fastTimer->setInterval(50); + connect(m_fastTimer, &QTimer::timeout, this, &CarCommunication::onSendFast); + + m_midTimer = new QTimer(this); // 100ms:洗消指令 / 洗消调试 + m_midTimer->setInterval(100); + connect(m_midTimer, &QTimer::timeout, this, &CarCommunication::onSendMid); + + m_slowTimer = new QTimer(this); // 1000ms:气体检测仪 + m_slowTimer->setInterval(1000); + connect(m_slowTimer, &QTimer::timeout, this, &CarCommunication::onSendSlow); + + // 本端自检序列(见 onSelfCheckTick 的 TODO) + m_selfCheckTimer = new QTimer(this); + m_selfCheckTimer->setInterval(450); + connect(m_selfCheckTimer, &QTimer::timeout, this, &CarCommunication::onSelfCheckTick); + + if (m_tel) m_tel->js.resize(6); +} + +CarCommunication::~CarCommunication() +{ + stop(); +} + +bool CarCommunication::start() +{ + if (m_serial->isOpen()) + return true; + + m_serial->setPortName(m_device); + m_serial->setBaudRate(m_baud); + m_serial->setDataBits(QSerialPort::Data8); + m_serial->setStopBits(QSerialPort::OneStop); + m_serial->setParity(QSerialPort::NoParity); + m_serial->setFlowControl(QSerialPort::NoFlowControl); + + if (!m_serial->open(QIODevice::ReadWrite)) { + // 开发机(Windows)没有 /dev/ttyS7 时走这里:不启动定时器,界面显示通信异常 + qWarning() << "车端串口打开失败:" << m_device << m_serial->errorString(); + setCommOk(false); + return false; + } + m_buf.clear(); + m_hbTimer->start(); + m_fastTimer->start(); + m_midTimer->start(); + m_slowTimer->start(); + return true; +} + +void CarCommunication::stop() +{ + m_hbTimer->stop(); + m_fastTimer->stop(); + m_midTimer->stop(); + m_slowTimer->stop(); + m_selfCheckTimer->stop(); + if (m_serial->isOpen()) m_serial->close(); + m_buf.clear(); +} + +void CarCommunication::setCommOk(bool ok) +{ + if (ok == m_lastCommOk) return; + m_lastCommOk = ok; + if (m_tel) m_tel->commOk = ok; + emit commOkChanged(ok); +} + +// ===================== 收包与分流 ===================== +void CarCommunication::onReadyRead() +{ + m_buf.append(m_serial->readAll()); + + // 串口是字节流:逐字节找帧头,校验不过则右移一字节继续找(容忍粘包、拆包与杂包)。 + // 尾部不足一帧的残段留在 m_buf 里等下次数据。 + int off = 0; + while (off + kFrameLen <= m_buf.size()) { + CanFrame f; + std::memcpy(&f, m_buf.constData() + off, kFrameLen); + if (f.header == kFrameHeader && f.dlc == kDataLen + && f.checksum == canChecksum(f.canId, f.dlc, f.data)) { + handleFrame(f.canId, f.data); + off += kFrameLen; + } else { + ++off; + } + } + if (off > 0) m_buf.remove(0, off); + + // 防御:长期收不到帧头时缓冲会一直涨,超过上限只保留尾部一帧长度 + if (m_buf.size() > 4096) m_buf.remove(0, m_buf.size() - kFrameLen); +} + +void CarCommunication::handleFrame(quint32 canId, const quint8* d) +{ + if (!m_tel) return; + + // 收到任意合法帧即刷新链路(CAN 无独立心跳报文) + m_hbTimer->start(); + setCommOk(true); + + switch (canId) { + // ---------- 0x18308180 底盘状态1(BMS):电池总况与故障等级 ---------- + case kIdVehicleFb1: { + m_tel->volt = phys(intelBits(d, 0, 16), 0.1); + m_tel->soc = int(phys(intelBits(d, 16, 16), 0.1)); + + m_vsf1Faults.clear(); + addFaultByCode(m_vsf1Faults, QString::fromUtf8(kSysChassis), "MCU", + kMcuFaults, int(sizeof(kMcuFaults) / sizeof(CodeText)), + quint8(intelBits(d, 32, 8))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "MCUL", + "底盘电机", quint8(intelBits(d, 40, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "CELLL", + "单体电压过低", quint8(intelBits(d, 45, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "CELLHL", + "单体电压过高", quint8(intelBits(d, 47, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "VOLTL", + "总电压过低", quint8(intelBits(d, 49, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "VOLTHL", + "总电压过高", quint8(intelBits(d, 51, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "DLTPL", + "放电低温", quint8(intelBits(d, 53, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "DHTPL", + "放电高温", quint8(intelBits(d, 55, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "CLTPL", + "充电低温", quint8(intelBits(d, 57, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "CHTPL", + "充电高温", quint8(intelBits(d, 59, 2))); + addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), "TIL", + "温度不均衡", quint8(intelBits(d, 61, 2))); + break; + } + + // ---------- 0x18318180 底盘状态2(BMS):手刹 / 车速 / 继电器 / 通信 ---------- + case kIdVehicleFb2: { + m_vsf2Faults.clear(); + addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), "VIL", + "压差不均衡", quint8(intelBits(d, 0, 2))); + addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), "DOCL", + "放电过流", quint8(intelBits(d, 2, 2))); + addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), "COCL", + "充电过流", quint8(intelBits(d, 4, 2))); + addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), "INCL", + "内部通讯", quint8(intelBits(d, 6, 2))); + addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), "EXCL", + "外部通讯", quint8(intelBits(d, 8, 2))); + addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), "SOCL", + "SOC过低", quint8(intelBits(d, 10, 2))); + addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), "INSL", + "绝缘", quint8(intelBits(d, 12, 2))); + + // 继电器粘连:0=正常 非 0=故障 + struct { int bit; const char* name; const char* code; } relays[] = { + {14, "加热继电器粘连", "HEATW"}, + {16, "充电继电器粘连", "CHGW"}, + {18, "总负继电器粘连", "NEGW"}, + {20, "总正继电器粘连", "POSW"}, + }; + for (const auto& r : relays) { + if (intelBits(d, r.bit, 2) != 0) + addFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), r.code, 1, r.name); + } + + m_tel->handbrake = intelBits(d, 22, 2) != 0; + // Vehi_spd:协议表为 8 位、精度 0.01(0~2.55 km/h)。其后 Vehi_RdyStatus(32)、 + // Reserve(34)、All_Comm_FaultCode(40) 的起始位与 8 位布局自洽,故按协议取值。 + m_tel->speed = phys(intelBits(d, 24, 8), 0.01); + m_tel->rdyStatus = int(intelBits(d, 32, 2)); + + const quint8 comm = quint8(intelBits(d, 40, 8)); // All_Comm_FaultCode + addFaultByCode(m_vsf2Faults, QString::fromUtf8(kSysChassis), "COMM", + kCommFaults, int(sizeof(kCommFaults) / sizeof(CodeText)), comm); + break; + } + + // ---------- 0x183A8180 底盘状态3:EPS / 制动故障位 ---------- + case kIdVehicleFb3: { + m_vsf3Faults.clear(); + for (int i = 0; i < 46; ++i) { + if (intelBits(d, i, 1) != 0) + addFault(m_vsf3Faults, QString::fromUtf8(kSysChassis), "EPS", quint8(i), + kVsb3Bits[i]); + } + const quint8 eps = quint8(intelBits(d, 48, 8)); + if (eps != 0) { + CanFault f; + f.system = QString::fromUtf8(kSysChassis); + f.code = QString("EPSC") + QString::number(eps); + f.desc = QString("EPS故障码") + QString::number(eps); + m_vsf3Faults.append(f); + } + break; + } + + // ---------- 0x183B8180 底盘状态4:挡位 / 驻车 / 方向盘 / 制动电机 ---------- + case kIdVehicleFb4: { + m_vsf4Faults.clear(); + m_tel->gearPos = int(intelBits(d, 0, 2)); // 0=N 1=D 2=R + m_tel->parking = int(intelBits(d, 2, 2)); // 0=未驻车 1=左电机 2=右电机 3=完全驻车 + m_tel->awdMode = int(intelBits(d, 4, 2)); // 0=二驱 1=四驱 2=四驱锁定 + m_tel->steerAngle = double(intelBits(d, 8, 16)); + + const quint8 align = quint8(intelBits(d, 24, 8)); + if (align == 0x11) addFault(m_vsf4Faults, QString::fromUtf8(kSysChassis), "ALIGN", 1, "方向盘对中失败"); + else if (align != 0xEE && align != 0x55) + addFault(m_vsf4Faults, QString::fromUtf8(kSysChassis), "ALIGN", 0, "方向盘未对中"); + + // 注:协议表把两个信号的“信号值描述”写反了(Brake_Right 描述成左电机、 + // Brake_Left 描述成右电机),此处以信号名为准。 + const quint8 br = quint8(intelBits(d, 32, 8)); + if (br != 0) addFault(m_vsf4Faults, QString::fromUtf8(kSysChassis), "BRKR", br, + br < 8 ? kBrakeMotFaults[br] : "制动电机故障"); + const quint8 bl = quint8(intelBits(d, 40, 8)); + if (bl != 0) addFault(m_vsf4Faults, QString::fromUtf8(kSysChassis), "BRKL", bl, + bl < 8 ? kBrakeMotFaults[bl] : "制动电机故障"); + break; + } + + // ---------- 0x18328180 机械臂状态1:末端位置 ---------- + case kIdArmFb1: { + m_tel->pos = QString("X:%1 Y:%2 Z:%3") + .arg(intelSigned(d, 0, 16)) + .arg(intelSigned(d, 16, 16)) + .arg(intelSigned(d, 32, 16)); + break; + } + // ---------- 0x18338180 机械臂状态2:末端姿态与末端速度 ---------- + case kIdArmFb2: { + m_tel->pose = QString("RX:%1 RY:%2 RZ:%3") + .arg(intelSigned(d, 0, 16)) + .arg(intelSigned(d, 16, 16)) + .arg(intelSigned(d, 32, 16)); + m_tel->vel = double(intelSigned(d, 48, 8)); + break; + } + // ---------- 0x18348180 机械臂状态3:关节角 J1~J4 ---------- + case kIdArmFb3: { + if (m_tel->js.size() < 6) m_tel->js.resize(6); + for (int i = 0; i < 4; ++i) + m_tel->js[i] = double(intelSigned(d, i * 16, 16)); + break; + } + // ---------- 0x18358180 机械臂状态4:关节角 J5~J6 ---------- + case kIdArmFb4: { + if (m_tel->js.size() < 6) m_tel->js.resize(6); + m_tel->js[4] = double(intelSigned(d, 0, 16)); + m_tel->js[5] = double(intelSigned(d, 16, 16)); + break; + } + + // ---------- 0x18368180 洗消工作状态1:模式 / 泵 / 球阀 / 液位 / 流量 ---------- + case kIdDecontamFb1: { + const quint8 mode = quint8(intelBits(d, 0, 2)); + m_tel->washMode = (mode == 3) ? QString::fromUtf8("消毒作业") + : (mode == 2) ? QString::fromUtf8("消除作业") + : QString("--"); + m_tel->washStart = intelBits(d, 2, 1) != 0; + m_tel->plunger = intelBits(d, 3, 1) != 0; + m_tel->air = intelBits(d, 4, 1) != 0; + m_tel->leaf = intelBits(d, 5, 1) != 0; + + m_tel->d1open = intelBits(d, 8, 1) != 0; + m_tel->d1close = !m_tel->d1open; + m_tel->d2open = intelBits(d, 9, 1) != 0; + m_tel->d2close = !m_tel->d2open; + + m_washFaults.clear(); + if (intelBits(d, 10, 1) != 0) // Sys_fault:无分路码,只报总故障 + addFault(m_washFaults, QString::fromUtf8(kSysWash), "SYS", 1, "洗消系统故障"); + if (intelBits(d, 6, 1) != 0) + addFault(m_washFaults, QString::fromUtf8(kSysWash), "LVL", 1, "A组分/清水液位低"); + if (intelBits(d, 7, 1) != 0) + addFault(m_washFaults, QString::fromUtf8(kSysWash), "LVL", 2, "B组分液位低"); + + m_tel->levelA = double(intelBits(d, 16, 8)); + m_tel->levelFoam = double(intelBits(d, 24, 8)); + m_tel->flowA = double(intelBits(d, 32, 8)); + m_tel->flowB = double(intelBits(d, 40, 8)); + m_tel->gasFlow = double(intelBits(d, 48, 8)); + m_tel->foam = double(intelBits(d, 56, 8)); + break; + } + // ---------- 0x18378180 洗消工作状态2:压力 / 流量反馈 / 泵故障码 ---------- + case kIdDecontamFb2: { + m_tel->press = phys(intelBits(d, 0, 8), 0.1); + m_tel->airPress = phys(intelBits(d, 8, 8), 0.1); + m_tel->flowPlungerFb = double(intelBits(d, 16, 8)); + m_tel->flowAirFb = double(intelBits(d, 32, 8)); + m_tel->flowVaneFb = double(intelBits(d, 40, 8)); + + m_pumpFaults.clear(); + addFaultByCode(m_pumpFaults, QString::fromUtf8(kSysWash), "AIR", + kAirCompFaults, int(sizeof(kAirCompFaults) / sizeof(CodeText)), + quint8(intelBits(d, 48, 8))); + addFaultByCode(m_pumpFaults, QString::fromUtf8(kSysWash), "PLG", + kPlungerFaults, int(sizeof(kPlungerFaults) / sizeof(CodeText)), + quint8(intelBits(d, 56, 8))); + break; + } + + // ---------- 0x18388180 检测仪结果状态1:状态 / 毒物种类与等级 / 告警 ---------- + case kIdDetectorFb1: { + const quint8 sta = quint8(intelBits(d, 0, 8)); + const quint8 warn = quint8(intelBits(d, 24, 8)); + // 2=计算目标基线 3=等待稳定 4=检测 5=等待点火 6=确定初始基线 + m_tel->gasHost = (warn & 0x04) ? QString("fault") + : (sta == 4) ? QString("detecting") + : QString("standby"); + + // Toxic_types 是位组合:1=P类 2=S类 4=HNO类 8=CH类 16=AS类 + const quint8 types = quint8(intelBits(d, 8, 8)); + static const struct { quint8 bit; const char* name; } kTypes[] = { + {1, "P类"}, {2, "S类"}, {4, "HNO类"}, {8, "CH类"}, {16, "AS类"}, + }; + QStringList names; + for (const auto& t : kTypes) + if (types & t.bit) names << QString::fromUtf8(t.name); + m_tel->gasType = names.isEmpty() ? QString("--") : names.join("/"); + + const quint8 lvl = quint8(intelBits(d, 16, 8)); + m_tel->gasLevel = (lvl >= 1 && lvl <= 5) ? QString("%1级").arg(lvl) : QString("--"); + + m_detectorFaults.clear(); + for (const CodeText& w : kDetectorWarns) + if (warn & w.code) + m_detectorFaults.append(CanFault{QString::fromUtf8(kSysDetector), + QString::fromLatin1("W") + QString::number(w.code), + QString::fromUtf8(w.text)}); + break; + } + // ---------- 0x18398180 检测仪结果状态2:40 位浓度(ppb) ---------- + case kIdDetectorFb2: { + m_tel->gasConc = double(intelBits(d, 0, 40)); + break; + } + + default: + return; // 未知 canId 不触发刷新(分类四本次未实现) + } + + cacheFaults(); + emit telemetryUpdated(); +} + +// 把各帧的故障合并进 Telemetry,并把“新出现”的故障写入故障历史 +void CarCommunication::cacheFaults() +{ + if (!m_tel) return; + + const QVector*> groups = { + &m_vsf1Faults, &m_vsf2Faults, &m_vsf3Faults, &m_vsf4Faults, + }; + QStringList chassis; + for (const QVector* g : groups) + for (const CanFault& f : *g) chassis << f.desc; + m_tel->chassisFaults = chassis; + + QStringList wash; + for (const CanFault& f : m_washFaults) wash << f.desc; + for (const CanFault& f : m_detectorFaults) wash << f.desc; + m_tel->washFaults = wash; + + QStringList pump; + for (const CanFault& f : m_pumpFaults) pump << f.desc; + m_tel->pumpFaults = pump; + + // 周期报文会反复报到同一故障,只在首次出现时写故障历史 + const QVector*> all = { + &m_vsf1Faults, &m_vsf2Faults, &m_vsf3Faults, &m_vsf4Faults, + &m_washFaults, &m_pumpFaults, &m_detectorFaults, + }; + for (const QVector* g : all) { + for (const CanFault& f : *g) { + const QString key = f.system + "|" + f.code; + if (m_reportedFaults.contains(key)) continue; + m_reportedFaults.insert(key); + emit faultAppended(f.system, f.code, f.desc); + } + } +} + +// ===================== 发包 ===================== +void CarCommunication::sendFrame(quint32 canId, const quint8* data) +{ + if (!m_serial->isOpen()) return; + + CanFrame f; + f.header = kFrameHeader; + f.canId = canId; + f.dlc = kDataLen; + std::memcpy(f.data, data, kDataLen); + f.checksum = canChecksum(canId, f.dlc, f.data); + m_serial->write(reinterpret_cast(&f), kFrameLen); +} + +// 50ms 组:Chassis_ControlCmd / Arm_ControlCmd1 / Arm_ControlCmd2 / Emergency_Cmd +void CarCommunication::onSendFast() +{ + { + quint8 d[8] = {0}; + setIntelBits(d, 0, 1, 1); // Chassis_modeCmd 有效 + setIntelBits(d, 1, 2, 0); // MCU_modeCmd 底盘预留 + setIntelBits(d, 3, 1, 1); // MCU_enable 有效 + setIntelBits(d, 4, 4, 0); // Reserve 置 0 + setIntelBits(d, 8, 16, m_gear); + setIntelBits(d, 24, 16, m_targetSpd); + setIntelBits(d, 40, 16, m_turning); + sendFrame(kIdChassisCmd, d); + } + { + quint8 d[8] = {0}; + setIntelBits(d, 0, 1, m_armMode ? 1 : 0); // Arm_modeCmd + setIntelBits(d, 1, 1, m_armMode ? 1 : 0); // Arm_enable + setIntelBits(d, 2, 6, 0); // Reserve + setIntelBits(d, 8, 16, m_updown); + setIntelBits(d, 24, 16, m_leftright); + setIntelBits(d, 40, 16, m_frontrear); + sendFrame(kIdArmCmd1, d); + } + { + quint8 d[8] = {0}; + setIntelBits(d, 0, 16, m_roll); + setIntelBits(d, 16, 16, m_pitch); + setIntelBits(d, 32, 16, m_yaw); + setIntelBits(d, 48, 8, m_homingPause); + sendFrame(kIdArmCmd2, d); + } + { + quint8 d[8] = {0}; + setIntelBits(d, 0, 8, m_emergStop); + sendFrame(kIdEmergency, d); + } +} + +// 100ms 组:Decontam_ControlCmd / Decontam_DebugCmd +void CarCommunication::onSendMid() +{ + { + quint8 d[8] = {0}; + setIntelBits(d, 0, 2, m_decontamMode); + setIntelBits(d, 2, 1, m_decontamStart ? 1 : 0); + setIntelBits(d, 3, 5, 0); // Reserve 置 0 + + // 发泡倍数 / 泡沫比例 / 空压机流量 / 柱塞泵流量:均由页面保存时经 sendSetParam + // 写入缓存(构造时先按 config.ini 初始化),这里直接读缓存组帧,不再每帧读文件。 + // 取值范围按协议「信号值描述」列收口:发泡倍数 6-12 倍、泡沫比例 0-10%、 + // 空压机流量 0-340 L/min;界面 ParamControl 的可调范围与之一致。 + setIntelBits(d, 8, 8, quint64(qBound(6, m_foamRatio, 12))); + setIntelBits(d, 16, 8, quint64(qBound(0, m_foamPercent, 10))); + setIntelBits(d, 24, 8, quint64(qBound(0, m_airFlow, 340))); + setIntelBits(d, 32, 8, quint64(qBound(0, m_plungerFlow, 24))); + sendFrame(kIdDecontamCmd, d); + } + { + quint8 d[8] = {0}; + setIntelBits(d, 0, 1, m_debugMode ? 1 : 0); + setIntelBits(d, 1, 1, m_plungerOn ? 1 : 0); + setIntelBits(d, 2, 1, m_airOn ? 1 : 0); + setIntelBits(d, 3, 1, m_vaneOn ? 1 : 0); + setIntelBits(d, 4, 1, m_d1Open ? 1 : 0); + setIntelBits(d, 5, 1, m_d2Open ? 1 : 0); + setIntelBits(d, 6, 2, 0); // Debug_Reserve1 + setIntelBits(d, 8, 16, quint64(qBound(0, int(m_airRpm), 3000))); + setIntelBits(d, 24, 16, quint64(qBound(0, int(m_plungerRpm), 1450))); + setIntelBits(d, 40, 16, quint64(qBound(0, int(m_vaneRpm), 2000))); + // 界面每个泵只有一组“启动/停止”,上电与启动同源 + setIntelBits(d, 56, 1, m_airOn ? 1 : 0); + setIntelBits(d, 57, 1, m_plungerOn ? 1 : 0); + setIntelBits(d, 58, 1, m_vaneOn ? 1 : 0); + sendFrame(kIdDecontamDbg, d); + } +} + +// 1000ms 组:Detector_ControlCmd +void CarCommunication::onSendSlow() +{ + quint8 d[8] = {0}; + setIntelBits(d, 0, 8, m_detectorCmd); + sendFrame(kIdDetectorCmd, d); +} + +// ===================== 上行命令 → 控制状态 ===================== +void CarCommunication::sendChassisMove(const QString& dir, double speed, const QString& turn) +{ + // 档位推杆量:后 0 / 中 2048 / 前 4096 + if (dir == "fwd") m_gear = 4096; + else if (dir == "back") m_gear = 0; + else m_gear = kRcAxisMid; + + // 转向推杆量:左 0 / 中 2048 / 右 4096 + if (turn == "left") m_turning = 0; + else if (turn == "right") m_turning = 4096; + else m_turning = kRcAxisMid; + + // Vehi_TargetSpd 精度 0.01 km/h,量程 0~500(即 0~5.00 km/h) + m_targetSpd = quint16(qBound(0, int(qRound(speed * 100.0)), 500)); +} + +void CarCommunication::sendArmMove(const QString& axis, int dir) +{ + // 轴位推杆量:dir +1 / -1 / 0 → 4096 / 0 / 中位 + const quint16 v = (dir > 0) ? 4096 : (dir < 0) ? 0 : kRcAxisMid; + + if (axis == "z") m_updown = v; + else if (axis == "x") m_leftright = v; + else if (axis == "y") m_frontrear = v; + else if (axis == "roll") m_roll = v; + else if (axis == "pitch") m_pitch = v; + else if (axis == "yaw") m_yaw = v; +} + +void CarCommunication::sendArmReturn() +{ + m_homingPause = 0xAA; // 系统回位 +} + +void CarCommunication::sendArmPause() +{ + m_homingPause = 0x55; // 暂停回位 +} + +void CarCommunication::sendGasStart() +{ + m_detectorCmd = 0xAA; // 启动检测 +} + +void CarCommunication::sendGasStop() +{ + m_detectorCmd = 0x55; // 停止检测 +} + +void CarCommunication::sendWashStart(const QString& mode) +{ + m_decontamMode = (mode == "disinfect") ? 3 : 2; // 3=手动消毒 2=手动消除 + m_decontamStart = true; +} + +void CarCommunication::sendWashStop() +{ + m_decontamStart = false; +} + +void CarCommunication::sendBallValve(int valve, bool open) +{ + if (valve == 1) m_d1Open = open; + else m_d2Open = open; + m_debugMode = true; // 球阀属于调试指令 +} + +void CarCommunication::sendPumpPower(const QString& pump, bool on) +{ + if (pump == "plunger") m_plungerOn = on; + else if (pump == "air") m_airOn = on; + else if (pump == "leaf") m_vaneOn = on; // 叶片泵 → VanePump + m_debugMode = true; +} + +void CarCommunication::sendPumpRpm(const QString& pump, int rpm) +{ + if (pump == "plunger") m_plungerRpm = quint16(qBound(0, rpm, 1450)); + else if (pump == "air") m_airRpm = quint16(qBound(0, rpm, 3000)); + else if (pump == "leaf") m_vaneRpm = quint16(qBound(0, rpm, 2000)); +} + +void CarCommunication::sendSetParam(const QString& key, double value) +{ + // 参数缓存与 config.ini 同源:页面保存时先落盘再调这里,周期帧只读缓存。 + if (key == "param/speed") + m_targetSpd = quint16(qBound(0, int(qRound(value * 100.0)), 500)); + else if (key == "param/pump_flow") + // 字段是 0~24 的整数,界面刻度是 0.1:必须四舍五入而不是截断, + // 否则 0.1 累加出的尾差(面板 5.0 实际存 4.9999999999999982)会下发成 4 + m_plungerFlow = qRound(qBound(0.0, value, 24.0)); + else if (key == "param/foam_ratio") + m_foamRatio = qRound(qBound(6.0, value, 12.0)); + else if (key == "param/foam_percent") + m_foamPercent = qRound(qBound(0.0, value, 10.0)); + else if (key == "param/air_flow") + m_airFlow = qRound(qBound(0.0, value, 340.0)); +} + +void CarCommunication::sendSelfCheckStart() +{ + m_selfCheckIndex = 0; + m_selfCheckTimer->start(); +} + +void CarCommunication::sendEstop() +{ + m_emergStop = 0x55; +} + +void CarCommunication::sendEstopReset() +{ + m_emergStop = 0; +} + +// TODO:CAN 矩阵(sheet RC)没有自检报文,故暂由本端跑完 10 项序列,仅为维持 +// ④自检 → ⑤作业选择 的流程不中断(onSelfCheckDone 是唯一点亮“作业选择”的地方)。 +// 拿到真实自检报文后,改为解析该报文并删除此本地驱动。 +void CarCommunication::onSelfCheckTick() +{ + bool ok = true; + if (m_selfCheckFault && m_selfCheckIndex == 6) ok = false; // 演示:球阀D1关闭故障 + emit selfCheckItem(m_selfCheckIndex, ok); + + ++m_selfCheckIndex; + if (m_selfCheckIndex >= 10) { + m_selfCheckTimer->stop(); + emit selfCheckDone(!m_selfCheckFault); + } +} + +void CarCommunication::onHeartbeatTimeout() +{ + setCommOk(false); +} diff --git a/comm/car.h b/comm/car.h new file mode 100644 index 0000000..0db7506 --- /dev/null +++ b/comm/car.h @@ -0,0 +1,152 @@ +#ifndef CAR_H +#define CAR_H + +#include +#include +#include +#include +#include +#include +#include "global.h" + +class QSerialPort; + +// 摇杆量协议范围 0~4096,中位 2048(分类一各轴、Vehi_gear/Vehi_turning 通用) +static const int kRcAxisMid = 2048; + +// CAN 反馈只给故障码,这里带上系统与中文描述: +// system 取界面故障历史既有的四个分组名,desc 供页面直接显示,code 供故障查询追溯。 +struct CanFault { + QString system; + QString code; + QString desc; +}; + +// ===================== 车端通信层(CAN 报文经 /dev/ttyS7 串口收发) ===================== +// 依据《洗消机器人遥控器通信协议20260810》sheet RC 实现的 19 条报文: +// 上行 分类一 7 条(遥控器 → 车端):Chassis_ControlCmd / Arm_ControlCmd1 / Arm_ControlCmd2 / +// Decontam_ControlCmd / Decontam_DebugCmd / Detector_ControlCmd / Emergency_Cmd, +// 按协议「报文周期时间」列分 50 / 100 / 1000ms 三组重发; +// 下行 分类二/三 12 条(车端 → 遥控器):底盘 4 条、机械臂 4 条、洗消 2 条、气体检测仪 2 条, +// 按 canId 分流转 handleFrame() 写入 Telemetry。 +// 全部为 29 位扩展帧、信号一律 Intel(小端)位序。 +// 串口是字节流,故线上帧统一为 18 字节:帧头 0x5AA5FF00 + canId + DLC + 8 字节数据 + 校验和。 +// 通信超时判定:连续 2s 未收到任何合法帧则判为通信异常(最慢的下行周期 1000ms)。 +class CarCommunication : public QObject +{ + Q_OBJECT +public: + explicit CarCommunication(Telemetry* tel, QObject* parent = nullptr); + ~CarCommunication() override; + + bool start(); + void stop(); + QString deviceName() const { return m_device; } + + // 行车/机械臂模式(面板旋钮或遥控器报文)→ Arm_ControlCmd1 的 Arm_modeCmd / Arm_enable + void setArmMode(bool arm) { m_armMode = arm; } + + // ---------- 上行命令(分类一)---------- + // 命令只更新本端控制状态,实际报文由周期定时器编码发送(CAN 控制帧本就是周期报文)。 + // turn 非空("left"/"right")时与 dir 同时下发,支持边前进/后退边转向 + void sendChassisMove(const QString& dir, double speed, const QString& turn = QString()); + void sendArmMove(const QString& axis, int dir); // dir: +1 / -1 / 0(停止) + void sendArmReturn(); + void sendArmPause(); + void sendGasStart(); + void sendGasStop(); + void sendWashStart(const QString& mode); // "clean" / "disinfect" + void sendWashStop(); + void sendBallValve(int valve, bool open); // valve: 1=D1, 2=D2 + void sendPumpPower(const QString& pump, bool on); // "plunger"/"air"/"leaf" + void sendPumpRpm(const QString& pump, int rpm); + void sendSetParam(const QString& key, double value); + void sendSelfCheckStart(); // 启动本端自检序列(不发 CAN) + void sendEstop(); + void sendEstopReset(); + +signals: + void telemetryUpdated(); // 任意遥测包到达(页面据此刷新) + void commOkChanged(bool ok); // 通信状态变化 + void selfCheckItem(int index, bool ok); // 自检单项结果(0~9) + void selfCheckDone(bool allOk); // 自检完成 + void faultAppended(const QString& system, const QString& code, const QString& desc); + +private slots: + void onReadyRead(); + void onSendFast(); // 50ms 组:底盘 / 机械臂 / 急停 + void onSendMid(); // 100ms 组:洗消指令 / 洗消调试 + void onSendSlow(); // 1000ms 组:气体检测仪 + void onHeartbeatTimeout(); + void onSelfCheckTick(); + +private: + // 收包:按 canId 分流;发包:18 字节封帧后写串口 + void handleFrame(quint32 canId, const quint8* d); + void sendFrame(quint32 canId, const quint8* data); + void cacheFaults(); + void setCommOk(bool ok); + + Telemetry* m_tel = nullptr; + QSerialPort* m_serial = nullptr; + QByteArray m_buf; // 串口字节流缓冲:帧头滑窗重同步用 + QTimer* m_hbTimer = nullptr; + QTimer* m_fastTimer = nullptr; + QTimer* m_midTimer = nullptr; + QTimer* m_slowTimer = nullptr; + QTimer* m_selfCheckTimer = nullptr; + + QString m_device = "/dev/ttyS7"; + int m_baud = 500000; + bool m_lastCommOk = true; + + bool m_armMode = false; + + // 本地自检序列(CAN 矩阵无自检报文,见 .cpp TODO) + int m_selfCheckIndex = 0; + bool m_selfCheckFault = true; + + // ---- 分类一 上行控制状态 ---- + // Chassis_ControlCmd 0x18208081 + quint16 m_gear = kRcAxisMid; // Vehi_gear:后 0 / 中 2048 / 前 4096 + quint16 m_turning = kRcAxisMid; // Vehi_turning:左 0 / 中 2048 / 右 4096 + quint16 m_targetSpd = 0; // Vehi_TargetSpd,精度 0.01,0~500(0~5.00 km/h) + + // Arm_ControlCmd1/2 0x18218081 / 0x18228081 —— 六轴位 0~4096 + quint16 m_updown = kRcAxisMid; + quint16 m_leftright = kRcAxisMid; + quint16 m_frontrear = kRcAxisMid; + quint16 m_roll = kRcAxisMid; + quint16 m_pitch = kRcAxisMid; + quint16 m_yaw = kRcAxisMid; + quint8 m_homingPause = 0x55; // System_homing_pause:0xAA=系统回位 0x55=暂停回位 + + // Decontam_ControlCmd 0x18238081 + // 四个参数缓存与 config.ini 的 param/* 同源:构造时按存盘值初始化, + // 页面保存时经 sendSetParam 更新,周期帧只读缓存(不再每帧读文件)。 + quint8 m_decontamMode = 0; // 2=手动消除 3=手动消毒 + bool m_decontamStart = false; + int m_plungerFlow = 20; // PlungerPump_flow,L/min,0~24(param/pump_flow) + int m_foamRatio = 6; // Expansion_ratioCmd,倍,6~12(param/foam_ratio) + int m_foamPercent = 3; // Foam_ratio,%,0~10(param/foam_percent) + int m_airFlow = 100; // AirComp_flow,L/min,0~340(param/air_flow) + + // Decontam_DebugCmd 0x18248081 + bool m_debugMode = false; // 0=正常模式 1=调试模式(球阀/泵操作置 1) + bool m_plungerOn = false, m_airOn = false, m_vaneOn = false; + bool m_d1Open = false, m_d2Open = false; + quint16 m_airRpm = 0, m_plungerRpm = 0, m_vaneRpm = 0; + + // Detector_ControlCmd 0x18258081 / Emergency_Cmd 0x18268081 + quint8 m_detectorCmd = 0x55; // 0xAA=启动检测 0x55=停止检测 + quint8 m_emergStop = 0; // 0x55=急停 0=正常 + + // ---- 下行故障缓存 ---- + // 底盘故障由 VSFB1~4 四帧共同构成,各自重建后再合并,避免互相覆盖。 + // 气体检测仪告警周期 1000ms、洗消故障 200ms,分开存放,否则后到的会把先到的清掉。 + QVector m_vsf1Faults, m_vsf2Faults, m_vsf3Faults, m_vsf4Faults; + QVector m_washFaults, m_pumpFaults, m_detectorFaults; + QSet m_reportedFaults; // 已记入故障历史的“系统|码”,周期性报文只上报新增故障 +}; + +#endif // CAR_H diff --git a/comm/mockvehicle.cpp b/comm/mockvehicle.cpp deleted file mode 100644 index ca9fc04..0000000 --- a/comm/mockvehicle.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include "mockvehicle.h" -#include -#include -#include -#include -#include -#include -#include - -MockVehicle::MockVehicle(QObject* parent) - : QObject(parent) -{ - QSettings s; - m_localPort = s.value("net/local_port", 9000).toInt(); // 发送到遥控端 - m_remotePort = s.value("net/remote_port", 9001).toInt(); // 本端监听 - m_enabled = s.value("net/mock", true).toBool(); - m_plantSelfCheckFault = s.value("net/selfcheck_fault", false).toBool(); - - if (!m_enabled) - return; - - m_socket = new QUdpSocket(this); - m_socket->bind(QHostAddress::AnyIPv4, m_remotePort, - QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint); - connect(m_socket, &QUdpSocket::readyRead, this, &MockVehicle::onReadyRead); - - m_sendTimer = new QTimer(this); - connect(m_sendTimer, &QTimer::timeout, this, &MockVehicle::onSendTick); - m_sendTimer->start(500); - - m_selfCheckTimer = new QTimer(this); - connect(m_selfCheckTimer, &QTimer::timeout, this, &MockVehicle::onSelfCheckTick); -} - -void MockVehicle::sendJson(const QJsonObject& obj) -{ - QByteArray data = QJsonDocument(obj).toJson(QJsonDocument::Compact); - m_socket->writeDatagram(data, QHostAddress("127.0.0.1"), m_localPort); -} - -void MockVehicle::onReadyRead() -{ - while (m_socket->hasPendingDatagrams()) { - QNetworkDatagram dg = m_socket->receiveDatagram(); - QJsonParseError err; - QJsonDocument doc = QJsonDocument::fromJson(dg.data(), &err); - if (err.error == QJsonParseError::NoError && doc.isObject()) - handleCommand(doc.object()); - } -} - -void MockVehicle::handleCommand(const QJsonObject& o) -{ - const QString t = o.value("t").toString(); - - if (t == "chassis_move") { - const QString dir = o.value("dir").toString(); - m_speed = o.value("speed").toDouble(); - if (dir == "stop") m_speed = 0; - } else if (t == "arm_move") { - int d = o.value("dir").toInt(); - if (d == 0) return; - if (m_js.size() >= 6) - m_js[1] = qBound(-90.0, m_js[1] + d * 1.0, 90.0); - } else if (t == "arm_return") { - m_armReturn = "returning"; - m_armHomed = false; - } else if (t == "arm_pause") { - m_armReturn = "idle"; - } else if (t == "gas_start") { - m_gasRunning = true; - } else if (t == "gas_stop") { - m_gasRunning = false; - } else if (t == "wash_start") { - m_washRunning = true; - m_washMode = (o.value("mode").toString() == "disinfect") ? "手动消毒" : "手动消除"; - m_pPlunger = true; m_pAir = true; m_pLeaf = true; - } else if (t == "wash_stop") { - m_washRunning = false; - m_pPlunger = false; m_pAir = false; m_pLeaf = false; - } else if (t == "ball_valve") { - int v = o.value("valve").toInt(); - bool open = o.value("open").toBool(); - if (v == 1) m_valve1Open = open; - else m_valve2Open = open; - } else if (t == "pump_power") { - const QString p = o.value("pump").toString(); - bool on = o.value("on").toBool(); - if (p == "plunger") m_pPlunger = on; - else if (p == "air") m_pAir = on; - else if (p == "leaf") m_pLeaf = on; - } else if (t == "pump_rpm") { - const QString p = o.value("pump").toString(); - int rpm = o.value("rpm").toInt(); - if (p == "plunger") m_plungerRpm = rpm; - else if (p == "air") m_airRpm = rpm; - else if (p == "leaf") m_leafRpm = rpm; - } else if (t == "selfcheck_start") { - m_selfCheckActive = true; - m_selfCheckIndex = 0; - m_selfCheckTimer->start(450); - } else if (t == "estop") { - m_estop = true; - m_washRunning = false; m_gasRunning = false; - m_pPlunger = false; m_pAir = false; m_pLeaf = false; - QJsonObject o2; o2["t"] = "estop"; o2["triggered"] = true; - sendJson(o2); - } else if (t == "estop_reset") { - m_estop = false; - QJsonObject o2; o2["t"] = "estop"; o2["triggered"] = false; - sendJson(o2); - } - // set_param 仅用于演示,无需额外处理 -} - -void MockVehicle::onSendTick() -{ - sendHeartbeat(); - sendTelemetry(); -} - -void MockVehicle::sendHeartbeat() -{ - QJsonObject o; o["t"] = "heartbeat"; o["ok"] = true; - sendJson(o); -} - -void MockVehicle::sendTelemetry() -{ - // 底盘 - { - QJsonObject o; - QJsonArray faults; - if (m_estop) faults << QStringLiteral("急停触发"); - o["t"] = "chassis"; - o["soc"] = 85; o["volt"] = 360.0; o["speed"] = m_speed; - o["handbrake"] = true; o["faults"] = faults; - sendJson(o); - } - // 机械臂 - { - if (m_armReturn == "returning") { - bool allZero = true; - for (double& a : m_js) { if (qAbs(a) > 1.0) a *= 0.7; else a = 0; if (qAbs(a) > 0.1) allZero = false; } - if (allZero) { m_armReturn = "done"; m_armHomed = true; } - } - QJsonObject o; - QJsonArray js; - for (double a : m_js) js << a; - o["t"] = "arm"; - o["js"] = js; - o["pos"] = QString("X:%1 Y:%2 Z:%3").arg(1200).arg(300).arg(800); - o["pose"] = "RX:0 RY:0 RZ:0"; - o["vel"] = (m_armReturn == "returning") ? 0.5 : 0.0; - o["return_state"] = m_armReturn; - o["homed"] = m_armHomed; - sendJson(o); - } - // 洗消 - { - QJsonObject o; - o["t"] = "wash"; - o["mode"] = m_washRunning ? m_washMode : "--"; - o["flowA"] = m_washRunning ? 20.0 : 0.0; - o["flowB"] = m_washRunning ? 0.5 : 0.0; - o["gasFlow"] = m_washRunning ? 100.0 : 0.0; - o["press"] = m_washRunning ? 0.3 : 0.0; - o["airPress"] = m_washRunning ? 0.35 : 0.0; - o["foam"] = (m_washMode == "手动消毒") ? 6.0 : 8.0; - o["faults"] = QJsonArray(); - sendJson(o); - } - // 液位 - { - QJsonObject o; o["t"] = "tank"; o["a"] = 100.0; o["foam"] = 30.0; - sendJson(o); - } - // 气体检测仪 - { - static int step = 0; step++; - double conc = m_gasRunning ? 250.0 + 50.0 * qSin(step * 0.4) : 0.0; - QJsonObject o; - o["t"] = "gas"; - o["host"] = m_gasRunning ? "detecting" : "standby"; - o["type"] = m_gasRunning ? "S类" : "--"; - o["level"] = m_gasRunning ? "2级" : "--"; - o["conc"] = qMax(0.0, conc); - sendJson(o); - } - // 球阀 - { - QJsonObject o; - o["t"] = "valve"; - o["d1open"] = m_valve1Open; o["d1close"] = !m_valve1Open; - o["d2open"] = m_valve2Open; o["d2close"] = !m_valve2Open; - o["fault"] = ""; - sendJson(o); - } - // 泵 - { - QJsonObject o; - o["t"] = "pump"; - o["plunger"] = m_pPlunger; o["air"] = m_pAir; o["leaf"] = m_pLeaf; - o["faults"] = QJsonArray(); - sendJson(o); - } -} - -void MockVehicle::onSelfCheckTick() -{ - // 自检 10 项:通信系统/底盘通信/机械臂通信/洗消通信/传感器信号/清水液位/球阀D1开/球阀D1关/球阀D2开/球阀D2关 - bool ok = true; - if (m_plantSelfCheckFault && m_selfCheckIndex == 6) ok = false; // 球阀D1关闭故障 - QJsonObject o; o["t"] = "selfcheck"; o["item"] = m_selfCheckIndex; o["ok"] = ok; - sendJson(o); - m_selfCheckIndex++; - if (m_selfCheckIndex >= 10) { - m_selfCheckTimer->stop(); - m_selfCheckActive = false; - QJsonObject o2; o2["t"] = "selfcheck_done"; o2["all_ok"] = !m_plantSelfCheckFault; - sendJson(o2); - } -} diff --git a/comm/mockvehicle.h b/comm/mockvehicle.h deleted file mode 100644 index 296c857..0000000 --- a/comm/mockvehicle.h +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef MOCKVEHICLE_H -#define MOCKVEHICLE_H - -#include -#include -#include -#include - -// ===================== 内置模拟车载端(离线演示) ===================== -// 以独立 UDP 套接字在回环地址上与遥控端通信:向 UI 端口周期发送遥测, -// 接收并响应 UI 下发的控制命令(行驶/机械臂/球阀/泵/洗消/自检/急停等)。 -class MockVehicle : public QObject -{ - Q_OBJECT -public: - explicit MockVehicle(QObject* parent = nullptr); - bool enabled() const { return m_enabled; } - -private slots: - void onReadyRead(); - void onSendTick(); - void onSelfCheckTick(); - -private: - void sendJson(const QJsonObject& obj); - void sendTelemetry(); - void sendHeartbeat(); - void handleCommand(const QJsonObject& o); - - QUdpSocket* m_socket = nullptr; - QTimer* m_sendTimer = nullptr; - QTimer* m_selfCheckTimer = nullptr; - - int m_localPort = 9000; // 遥控端收包端口(本端发送目标) - int m_remotePort = 9001; // 本端绑定端口(遥控端发包目标) - bool m_enabled = true; - bool m_plantSelfCheckFault = false; - - // 模拟车辆状态 - bool m_estop = false; - bool m_gasRunning = false; - bool m_washRunning = false; - QString m_washMode = "手动消除"; - bool m_valve1Open = false; // D1 控制状态(开/关) - bool m_valve2Open = false; - bool m_pPlunger = false, m_pAir = false, m_pLeaf = false; - double m_speed = 3.45; - int m_airRpm = 300, m_plungerRpm = 200, m_leafRpm = 200; - QVector m_js{30, 20, 0, 90, 45, 0}; - bool m_armHomed = true; - QString m_armReturn = "idle"; - - // 自检序列 - bool m_selfCheckActive = false; - int m_selfCheckIndex = 0; -}; - -#endif // MOCKVEHICLE_H diff --git a/comm/uart.cpp b/comm/uart.cpp new file mode 100644 index 0000000..bd2e8f4 --- /dev/null +++ b/comm/uart.cpp @@ -0,0 +1,151 @@ +#include "uart.h" +#include +#include +#include +#include + +namespace { + +const int kFrameLen = 24; +const quint8 kHead = 0x11; +const quint8 kTail = 0x22; + +// 收不到帧头时缓冲上限,超过则只保留尾部一帧长度,避免长期涨下去 +const int kBufMax = 1024; + +// 小端无符号 16 位 +inline int leU16(const char* p) +{ + return int(quint8(p[0]) | (quint8(p[1]) << 8)); +} + +// 摇杆量按协议范围 0~4096 收口(外部输入,越界值不采信) +inline int axis(const char* p) +{ + return qBound(0, leU16(p), 4096); +} + +// 校验帧头、帧尾与和校验(BYTE0~BYTE22 之和的低 8 位) +bool parseFrame(const char* d, RcFrame& f) +{ + if (quint8(d[0]) != kHead || quint8(d[22]) != kTail) + return false; + + quint8 sum = 0; + for (int i = 0; i < 23; ++i) + sum += quint8(d[i]); + if (sum != quint8(d[23])) + return false; + + const quint8 flags = quint8(d[1]); + f.driveMode = (flags & 0x01) != 0; + f.armMode = (flags & 0x02) != 0; + f.armEnabled = (flags & 0x04) != 0; + f.chassisEnabled = (flags & 0x08) != 0; + f.estop = (flags & 0x10) != 0; + + f.gear = axis(d + 3); + f.steer = axis(d + 5); + f.posZ = axis(d + 7); + f.posX = axis(d + 9); + f.posY = axis(d + 11); + f.roll = axis(d + 13); + f.pitch = axis(d + 15); + f.yaw = axis(d + 17); + + f.battery = qBound(0, int(quint8(d[19])), 100); + return true; +} + +} // namespace + +RcCommunication::RcCommunication(QObject* parent) + : QObject(parent) +{ + m_serial = new QSerialPort(this); + connect(m_serial, &QSerialPort::readyRead, this, &RcCommunication::onReadyRead); + + // 与 AppContext 共用程序目录的 config.ini:默认构造读不到 rc/* 这些键 + // (本工程没有 setOrganizationName/setApplicationName) + QSettings s(QApplication::applicationDirPath() + "/config.ini", QSettings::IniFormat); + m_device = s.value("rc/device", "/dev/ttyS5").toString(); + m_baud = s.value("rc/baud", 115200).toInt(); + + // 协议发送周期 100ms,连续 500ms 无合法帧判为链路断开 + m_linkTimer = new QTimer(this); + m_linkTimer->setInterval(500); + connect(m_linkTimer, &QTimer::timeout, this, &RcCommunication::onLinkTimeout); +} + +RcCommunication::~RcCommunication() +{ + stop(); +} + +bool RcCommunication::start() +{ + if (m_serial->isOpen()) + return true; + + m_serial->setPortName(m_device); + m_serial->setBaudRate(m_baud); + m_serial->setDataBits(QSerialPort::Data8); + m_serial->setStopBits(QSerialPort::OneStop); + m_serial->setParity(QSerialPort::NoParity); + m_serial->setFlowControl(QSerialPort::NoFlowControl); + + if (!m_serial->open(QIODevice::ReadWrite)) { + qWarning() << "遥控器串口打开失败:" << m_device << m_serial->errorString(); + return false; + } + m_buf.clear(); + m_linkTimer->start(); + return true; +} + +void RcCommunication::stop() +{ + m_linkTimer->stop(); + if (m_serial->isOpen()) m_serial->close(); + m_buf.clear(); +} + +void RcCommunication::onReadyRead() +{ + m_buf.append(m_serial->readAll()); + + // 逐字节扫描合法帧:容忍前导垃圾与粘包,未对齐时向后找下一个帧头。 + // 串口是字节流,一帧可能被切开,尾部不足一帧的残段留在 m_buf 等下次数据。 + int off = 0; + while (off + kFrameLen <= m_buf.size()) { + RcFrame f; + if (parseFrame(m_buf.constData() + off, f)) { + onFrame(f); + off += kFrameLen; + } else { + ++off; + } + } + if (off > 0) m_buf.remove(0, off); + + if (m_buf.size() > kBufMax) m_buf.remove(0, m_buf.size() - kFrameLen); +} + +void RcCommunication::onFrame(const RcFrame& frame) +{ + m_linkTimer->start(); // 重置链路超时 + + if (!m_linkOk) { + m_linkOk = true; + emit linkChanged(true); + } + emit frameReceived(frame); +} + +void RcCommunication::onLinkTimeout() +{ + if (m_linkOk) { + m_linkOk = false; + emit linkChanged(false); + } +} diff --git a/comm/uart.h b/comm/uart.h new file mode 100644 index 0000000..dc248d8 --- /dev/null +++ b/comm/uart.h @@ -0,0 +1,73 @@ +#ifndef UART_H +#define UART_H + +#include +#include +#include +#include +#include + +class QSerialPort; + +// ===================== 遥控器帧(24 字节二进制协议) ===================== +// 周期 100ms,1 字节对齐,多字节字段小端。 +// BYTE0=0x11 帧头,BYTE22=0x22 帧尾,BYTE23 = BYTE0~BYTE22 之和的低 8 位。 +// 本结构只承载解析结果,不含任何控制策略。 +struct RcFrame { + // BYTE1 控制模式与使能标志位 + bool driveMode = false; // bit0 行车模式 + bool armMode = false; // bit1 机械臂模式 + bool armEnabled = false; // bit2 机械臂使能 + bool chassisEnabled = false; // bit3 底盘使能 + bool estop = false; // bit4 急停 + + // 摇杆量:0~4096,中位 2048 + int gear = 2048; // BYTE3-4 整车档位(底盘模式-左摇杆 Y 轴) + int steer = 2048; // BYTE5-6 整车转向(底盘模式-左摇杆 X 轴) + int posZ = 2048; // BYTE7-8 末端位置上下(左摇杆 Z 轴) + int posX = 2048; // BYTE9-10 末端位置左右(左摇杆 X 轴) + int posY = 2048; // BYTE11-12 末端位置前后(左摇杆 Y 轴) + int roll = 2048; // BYTE13-14 末端姿态横滚(右摇杆 X 轴) + int pitch = 2048; // BYTE15-16 末端姿态俯仰(右摇杆 Y 轴) + int yaw = 2048; // BYTE17-18 末端姿态偏航(右摇杆 Z 轴) + + int battery = 0; // BYTE19 遥控器电量 0~100 +}; +Q_DECLARE_METATYPE(RcFrame) + +// ===================== 遥控器串口接收层 ===================== +// 独占 rc/device(默认 /dev/ttyS5),115200 / 8 数据位 / 1 停止位 / 无校验, +// 与车端 /dev/ttyS7(见 car.h)互不干扰。 +// 只负责收包、校验、解析与链路超时判定,不决定遥控器数据的用途。 +class RcCommunication : public QObject +{ + Q_OBJECT +public: + explicit RcCommunication(QObject* parent = nullptr); + ~RcCommunication() override; + + bool start(); + void stop(); + QString deviceName() const { return m_device; } + bool linkOk() const { return m_linkOk; } + +signals: + void frameReceived(const RcFrame& frame); // 收到一帧合法报文 + void linkChanged(bool ok); // 链路状态变化 + +private slots: + void onReadyRead(); + void onLinkTimeout(); + +private: + void onFrame(const RcFrame& frame); + + QSerialPort* m_serial = nullptr; + QByteArray m_buf; // 串口是字节流,粘包/拆包在此缓冲里重新对齐 + QTimer* m_linkTimer = nullptr; + QString m_device = "/dev/ttyS7"; + int m_baud = 115200; + bool m_linkOk = false; +}; + +#endif // UART_H diff --git a/comm/udpcommunication.cpp b/comm/udpcommunication.cpp deleted file mode 100644 index 765a140..0000000 --- a/comm/udpcommunication.cpp +++ /dev/null @@ -1,242 +0,0 @@ -#include "udpcommunication.h" -#include -#include -#include -#include -#include -#include -#include - -UdpCommunication::UdpCommunication(Telemetry* tel, QObject* parent) - : QObject(parent), m_tel(tel) -{ - m_socket = new QUdpSocket(this); - connect(m_socket, &QUdpSocket::readyRead, this, &UdpCommunication::onReadyRead); - - // 网络配置(默认回环:本地 9000 收,向 127.0.0.1:9001 发) - QSettings s; - m_localPort = s.value("net/local_port", 9000).toInt(); - m_remoteHost = s.value("net/remote_host", "127.0.0.1").toString(); - m_remotePort = s.value("net/remote_port", 9001).toInt(); - m_mock = s.value("net/mock", true).toBool(); - - // 通信超时判定:连续 2s 无心跳 → 通信异常 - m_hbTimer = new QTimer(this); - m_hbTimer->setInterval(2000); - connect(m_hbTimer, &QTimer::timeout, this, &UdpCommunication::onHeartbeatTimeout); -} - -bool UdpCommunication::start() -{ - if (m_socket->state() != QAbstractSocket::UnconnectedState) - return true; - bool ok = m_socket->bind(QHostAddress::AnyIPv4, m_localPort, - QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint); - if (ok) m_hbTimer->start(); - return ok; -} - -void UdpCommunication::stop() -{ - m_hbTimer->stop(); - m_socket->close(); -} - -void UdpCommunication::sendJson(const QJsonObject& obj) -{ - QByteArray data = QJsonDocument(obj).toJson(QJsonDocument::Compact); - m_socket->writeDatagram(data, QHostAddress(m_remoteHost), m_remotePort); -} - -// ===================== 上行命令 ===================== -void UdpCommunication::sendChassisMove(const QString& dir, double speed) -{ - QJsonObject o; o["t"] = "chassis_move"; o["dir"] = dir; o["speed"] = speed; - sendJson(o); -} - -void UdpCommunication::sendArmMove(const QString& axis, int dir) -{ - QJsonObject o; o["t"] = "arm_move"; o["axis"] = axis; o["dir"] = dir; - sendJson(o); -} - -void UdpCommunication::sendArmReturn() -{ - QJsonObject o; o["t"] = "arm_return"; - sendJson(o); -} - -void UdpCommunication::sendArmPause() -{ - QJsonObject o; o["t"] = "arm_pause"; - sendJson(o); -} - -void UdpCommunication::sendGasStart() -{ - QJsonObject o; o["t"] = "gas_start"; - sendJson(o); -} - -void UdpCommunication::sendGasStop() -{ - QJsonObject o; o["t"] = "gas_stop"; - sendJson(o); -} - -void UdpCommunication::sendWashStart(const QString& mode) -{ - QJsonObject o; o["t"] = "wash_start"; o["mode"] = mode; - sendJson(o); -} - -void UdpCommunication::sendWashStop() -{ - QJsonObject o; o["t"] = "wash_stop"; - sendJson(o); -} - -void UdpCommunication::sendBallValve(int valve, bool open) -{ - QJsonObject o; o["t"] = "ball_valve"; o["valve"] = valve; o["open"] = open; - sendJson(o); -} - -void UdpCommunication::sendPumpPower(const QString& pump, bool on) -{ - QJsonObject o; o["t"] = "pump_power"; o["pump"] = pump; o["on"] = on; - sendJson(o); -} - -void UdpCommunication::sendPumpRpm(const QString& pump, int rpm) -{ - QJsonObject o; o["t"] = "pump_rpm"; o["pump"] = pump; o["rpm"] = rpm; - sendJson(o); -} - -void UdpCommunication::sendSetParam(const QString& key, double value) -{ - QJsonObject o; o["t"] = "set_param"; o["key"] = key; o["value"] = value; - sendJson(o); -} - -void UdpCommunication::sendSelfCheckStart() -{ - QJsonObject o; o["t"] = "selfcheck_start"; - sendJson(o); -} - -void UdpCommunication::sendEstop() -{ - QJsonObject o; o["t"] = "estop"; - sendJson(o); -} - -void UdpCommunication::sendEstopReset() -{ - QJsonObject o; o["t"] = "estop_reset"; - sendJson(o); -} - -// ===================== 收包与解析 ===================== -void UdpCommunication::onReadyRead() -{ - while (m_socket->hasPendingDatagrams()) { - QNetworkDatagram dg = m_socket->receiveDatagram(); - QJsonParseError err; - QJsonDocument doc = QJsonDocument::fromJson(dg.data(), &err); - if (err.error != QJsonParseError::NoError || !doc.isObject()) - continue; - parse(doc.object()); - } -} - -void UdpCommunication::parse(const QJsonObject& o) -{ - const QString t = o.value("t").toString(); - - if (t == "heartbeat") { - bool ok = o.value("ok").toBool(); - m_hbTimer->start(); // 重置超时计时 - if (ok != m_lastCommOk) { - m_lastCommOk = ok; - if (m_tel) m_tel->commOk = ok; - emit commOkChanged(ok); - } - return; - } - - if (t == "chassis") { - m_tel->soc = o.value("soc").toInt(); - m_tel->volt = o.value("volt").toDouble(); - m_tel->speed = o.value("speed").toDouble(); - m_tel->handbrake = o.value("handbrake").toBool(); - m_tel->chassisFaults.clear(); - for (const QJsonValue& v : o.value("faults").toArray()) - m_tel->chassisFaults << v.toString(); - } else if (t == "arm") { - const QJsonArray a = o.value("js").toArray(); - m_tel->js.clear(); - for (const QJsonValue& v : a) m_tel->js << v.toDouble(); - m_tel->pos = o.value("pos").toString(); - m_tel->pose = o.value("pose").toString(); - m_tel->vel = o.value("vel").toDouble(); - m_tel->returnState = o.value("return_state").toString("idle"); - m_tel->armHomed = o.value("homed").toBool(); - } else if (t == "wash") { - m_tel->washMode = o.value("mode").toString("--"); - m_tel->flowA = o.value("flowA").toDouble(); - m_tel->flowB = o.value("flowB").toDouble(); - m_tel->gasFlow = o.value("gasFlow").toDouble(); - m_tel->press = o.value("press").toDouble(); - m_tel->airPress = o.value("airPress").toDouble(); - m_tel->foam = o.value("foam").toDouble(); - m_tel->washFaults.clear(); - for (const QJsonValue& v : o.value("faults").toArray()) - m_tel->washFaults << v.toString(); - } else if (t == "tank") { - m_tel->levelA = o.value("a").toDouble(); - m_tel->levelFoam = o.value("foam").toDouble(); - } else if (t == "gas") { - m_tel->gasHost = o.value("host").toString("standby"); - m_tel->gasType = o.value("type").toString("--"); - m_tel->gasLevel = o.value("level").toString("--"); - m_tel->gasConc = o.value("conc").toDouble(); - } else if (t == "valve") { - m_tel->d1open = o.value("d1open").toBool(); - m_tel->d1close = o.value("d1close").toBool(); - m_tel->d2open = o.value("d2open").toBool(); - m_tel->d2close = o.value("d2close").toBool(); - m_tel->valveFault = o.value("fault").toString(); - if (!m_tel->valveFault.isEmpty()) - emit faultAppended("洗消系统", m_tel->valveFault, m_tel->valveFault); - } else if (t == "pump") { - m_tel->plunger = o.value("plunger").toBool(); - m_tel->air = o.value("air").toBool(); - m_tel->leaf = o.value("leaf").toBool(); - m_tel->pumpFaults.clear(); - for (const QJsonValue& v : o.value("faults").toArray()) - m_tel->pumpFaults << v.toString(); - } else if (t == "estop") { - m_tel->estop = o.value("triggered").toBool(); - emit estopReceived(m_tel->estop); - } else if (t == "selfcheck") { - emit selfCheckItem(o.value("item").toInt(), o.value("ok").toBool()); - } else if (t == "selfcheck_done") { - emit selfCheckDone(o.value("all_ok").toBool()); - } else { - return; // 未知类型不触发刷新 - } - - emit telemetryUpdated(); -} - -void UdpCommunication::onHeartbeatTimeout() -{ - if (m_lastCommOk) { - m_lastCommOk = false; - if (m_tel) m_tel->commOk = false; - emit commOkChanged(false); - } -} diff --git a/comm/udpcommunication.h b/comm/udpcommunication.h deleted file mode 100644 index 723a9b1..0000000 --- a/comm/udpcommunication.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef UDPCOMMUNICATION_H -#define UDPCOMMUNICATION_H - -#include -#include -#include -#include "global.h" - -// ===================== UDP 通信层(收包 / 发包) ===================== -// 与车载端经 UDP 报文通信,JSON 文本协议: -// 上行(UI→车载端)命令见各 sendXxx(); -// 下行(车载端→UI)遥测见 parse() 中 "t" 字段分支。 -// 本类同时承担“通信超时判定”:连续 2s 未收到心跳则判为通信异常。 -class UdpCommunication : public QObject -{ - Q_OBJECT -public: - explicit UdpCommunication(Telemetry* tel, QObject* parent = nullptr); - - bool start(); - void stop(); - - // 是否启用内置模拟车载端(离线演示) - bool mockEnabled() const { return m_mock; } - int localPort() const { return m_localPort; } - - // ---------- 上行命令(发包) ---------- - void sendChassisMove(const QString& dir, double speed); - void sendArmMove(const QString& axis, int dir); // dir: +1 / -1 / 0(停止) - void sendArmReturn(); - void sendArmPause(); - void sendGasStart(); - void sendGasStop(); - void sendWashStart(const QString& mode); // "clean" / "disinfect" - void sendWashStop(); - void sendBallValve(int valve, bool open); // valve: 1=D1, 2=D2 - void sendPumpPower(const QString& pump, bool on); // "plunger"/"air"/"leaf" - void sendPumpRpm(const QString& pump, int rpm); - void sendSetParam(const QString& key, double value); - void sendSelfCheckStart(); - void sendEstop(); - void sendEstopReset(); - -signals: - void telemetryUpdated(); // 任意遥测包到达(页面据此刷新) - void commOkChanged(bool ok); // 通信状态变化 - void estopReceived(bool triggered); // 收到急停 - void selfCheckItem(int index, bool ok); // 自检单项结果(0~9) - void selfCheckDone(bool allOk); // 自检完成 - void faultAppended(const QString& system, const QString& code, const QString& desc); - -private slots: - void onReadyRead(); - void onHeartbeatTimeout(); - -private: - void sendJson(const QJsonObject& obj); - void parse(const QJsonObject& obj); - - Telemetry* m_tel = nullptr; - QUdpSocket* m_socket = nullptr; - QTimer* m_hbTimer = nullptr; - int m_localPort = 9000; - QString m_remoteHost = "127.0.0.1"; - int m_remotePort = 9001; - bool m_mock = true; - bool m_lastCommOk = true; -}; - -#endif // UDPCOMMUNICATION_H diff --git a/main.cpp b/main.cpp index 72c59c1..421bee6 100644 --- a/main.cpp +++ b/main.cpp @@ -8,6 +8,7 @@ int main(int argc, char *argv[]) a.setStyleSheet(appStyleSheet()); installButtonFocusGlow(&a); // 按钮获得焦点时显示光圈 + installArrowKeyGuard(&a); // 方向键只做旋钮调值,不在按钮间换焦 MainWindow w; w.showFullScreen(); diff --git a/mainwindow.cpp b/mainwindow.cpp index 7578877..b30ff66 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,6 +1,6 @@ #include "mainwindow.h" #include "appcontext.h" -#include "udpcommunication.h" +#include "car.h" #include "confirmdialog.h" #include "base.h" @@ -10,12 +10,12 @@ #include "passwordmgmt.h" #include "passwordreset.h" #include "selfcheck.h" +#include "home.h" #include "jobselect.h" #include "chassis.h" #include "arm.h" #include "gasdetector.h" -#include "manualclean.h" -#include "manualdisinfect.h" +#include "washpage.h" #include "debugmaintain.h" #include "faultquery.h" @@ -29,7 +29,7 @@ MainWindow::MainWindow(QWidget* parent) { m_ctx = AppContext::instance(); - setWindowTitle("玉兔工业无线遥控器 YTZT6K8EART2-aL25"); + setWindowTitle("玉兔工业无线遥控器"); setWindowFlags(Qt::FramelessWindowHint | Qt::Window); setFixedSize(SCREEN_W, SCREEN_H); @@ -40,11 +40,12 @@ MainWindow::MainWindow(QWidget* parent) buildPages(); - // 全局急停横幅(覆盖于页面之上) - m_estopBanner = new QLabel("急停已触发,请复位", this); + // 全局急停横幅(覆盖于页面之上):仅作提示。 + // 急停与复位都由遥控器 bit4 下发,面板不提供复位控件。 + m_estopBanner = new QLabel("急停已触发,请用遥控器复位!", this); m_estopBanner->setAlignment(Qt::AlignCenter); m_estopBanner->setStyleSheet( - "background-color:#E5484D;color:#FFFFFF;font-size:30px;font-weight:700;"); + "background-color:#E5484D;color:#FFFFFF;font-size:50px;font-weight:700;"); m_estopBanner->setGeometry(0, 98, SCREEN_W, 64); m_estopBanner->hide(); @@ -52,11 +53,14 @@ MainWindow::MainWindow(QWidget* parent) // 启动通信层(UDP 收包发包) m_ctx->comm()->start(); + m_ctx->rc()->start(); // 遥控器接收(独立端口) // 初始页面:开机密码 // m_stack->setCurrentIndex(PageBoot); - // 免密码直接进自检页(恢复原流程时改回上面的 PageBoot) - m_stack->setCurrentIndex(PageSelfCheck); + // 免密码直接进自检页 + // m_stack->setCurrentIndex(PageSelfCheck); + // 免登录直接进作业选择页(恢复原流程时改回上面的 PageBoot) + m_stack->setCurrentIndex(PageJob); } MainWindow::~MainWindow() @@ -79,12 +83,13 @@ void MainWindow::buildPages() add(PageMgmt, new PasswordMgmtPage(m_ctx)); add(PageReset, new PasswordResetPage(m_ctx)); add(PageSelfCheck, new SelfCheckPage(m_ctx)); + add(PageHome, new HomePage(m_ctx)); add(PageJob, new JobSelectPage(m_ctx)); add(PageChassis, new ChassisPage(m_ctx)); add(PageArm, new ArmPage(m_ctx)); add(PageGas, new GasDetectorPage(m_ctx)); - add(PageClean, new ManualCleanPage(m_ctx)); - add(PageDisinfect, new ManualDisinfectPage(m_ctx)); + add(PageClean, new WashPage(m_ctx, WashPage::Clean)); + add(PageDisinfect, new WashPage(m_ctx, WashPage::Disinfect)); add(PageDebug, new DebugMaintainPage(m_ctx)); add(PageFault, new FaultQueryPage(m_ctx)); @@ -93,10 +98,13 @@ void MainWindow::buildPages() void MainWindow::onNavigate(PageId id) { - // 记录故障查询返回来源 + // 记录故障查询返回来源(自检页 / 系统首页面 进入时原路返回,其它一律回作业选择页) if (id == PageFault && m_faultPage) { PageId cur = static_cast(m_stack->currentIndex()); - m_faultPage->setReturnPage((cur == PageSelfCheck) ? PageSelfCheck : PageJob); + if (cur == PageSelfCheck || cur == PageHome) + m_faultPage->setReturnPage(cur); + else + m_faultPage->setReturnPage(PageJob); } if (id >= 0 && id < PageCount) m_stack->setCurrentIndex(id); @@ -110,14 +118,12 @@ void MainWindow::onEstopChanged(bool triggered) void MainWindow::keyPressEvent(QKeyEvent* ev) { - // 演示用快捷键:M 切换行车/机械臂模式,E 触发/复位急停 + // 演示用快捷键:M 切换行车/机械臂模式。 + // 遥控器在线时模式由遥控器报文决定,此处不抢(与底盘页 showEvent 同一规则)。 switch (ev->key()) { case Qt::Key_M: - m_ctx->setMode(m_ctx->mode == Mode::Drive ? Mode::Arm : Mode::Drive); - break; - case Qt::Key_E: - if (m_ctx->estopTriggered) m_ctx->resetEstop(); - else m_ctx->triggerEstop(); + if (!m_ctx->rc()->linkOk()) + m_ctx->setMode(m_ctx->mode == Mode::Drive ? Mode::Arm : Mode::Drive); break; default: QMainWindow::keyPressEvent(ev); diff --git a/pages/arm.cpp b/pages/arm.cpp index 48df846..15e7f36 100644 --- a/pages/arm.cpp +++ b/pages/arm.cpp @@ -1,13 +1,14 @@ + #include "arm.h" #include "titlebar.h" #include "statusstrip.h" #include "alarmbanner.h" #include "datacard.h" -#include "udpcommunication.h" +#include "car.h" #include "confirmdialog.h" #include #include -#include +//#include // 末端按钮原为 2×3 网格,改为竖列后暂不需要 #include #include #include @@ -34,8 +35,8 @@ ArmPage::ArmPage(AppContext* ctx, QWidget* parent) // 左控制列(6 运动按钮 + 系统回位/暂停) QWidget* left = new QWidget(this); QVBoxLayout* leftLay = new QVBoxLayout(left); - leftLay->setContentsMargins(0, 0, 0, 0); - leftLay->setSpacing(8); + leftLay->setContentsMargins(10,10,20,10); + leftLay->setSpacing(30); auto addMove = [&](const QString& t, const QString& axis, int dir) { QPushButton* b = makeMoveButton(t, axis, dir); @@ -49,11 +50,14 @@ ArmPage::ArmPage(AppContext* ctx, QWidget* parent) addMove("前平移", "y", +1); addMove("后平移", "y", -1); - m_returnBtn = button("系统回位/暂停", "", left); + m_returnBtn = button("系统回位/\n暂停", "", left); m_returnBtn->setCheckable(true); - m_returnBtn->setFixedSize(180, 90); - m_returnBtn->setStyleSheet(QString("font-size:22px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;") - .arg(C::PANEL_HI, C::TEXT, C::BORDER)); + m_returnBtn->setFixedSize(270, 120); // 两行文字,故加高 + m_returnBtn->setStyleSheet(QString( + "QPushButton{font-size:40px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;}" + "QPushButton:hover,QPushButton:focus,QPushButton:checked" + "{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}") + .arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY)); connect(m_returnBtn, &QPushButton::toggled, this, &ArmPage::onReturnToggled); leftLay->addWidget(m_returnBtn); @@ -63,69 +67,72 @@ ArmPage::ArmPage(AppContext* ctx, QWidget* parent) leftLay->addWidget(m_returnStatus); leftLay->addStretch(1); - left->setFixedWidth(180); + left->setFixedWidth(300); // 270 按钮 + 左右边距 30,避免按钮被裁 main->addWidget(left); - // 中央卡片区(底盘/洗消/气体检测仪) + // 中央区:左侧三张状态卡 + 机械臂参数卡,并排放在中间 QWidget* center = new QWidget(this); - QVBoxLayout* centerLay = new QVBoxLayout(center); + QHBoxLayout* centerLay = new QHBoxLayout(center); centerLay->setContentsMargins(0, 0, 0, 0); centerLay->setSpacing(12); - m_chassisCard = new DataCard("底盘", center); - m_chassisCard->addField("电量", "85%"); - m_chassisCard->addField("电压", "360V"); - m_chassisCard->addField("故障", "无"); + QWidget* cardsCol = new QWidget(center); + QVBoxLayout* cardsLay = new QVBoxLayout(cardsCol); + cardsLay->setContentsMargins(0, 0, 0, 0); + cardsLay->setSpacing(12); - m_washCard = new DataCard("洗消", center); - m_washCard->addField("A液位", "100L"); - m_washCard->addField("泡沫液位", "30L"); + m_chassisCard = new DataCard("底盘", cardsCol); + m_chassisCard->addField("底盘电量SOC", "85%"); + m_chassisCard->addField("底盘电压", "360V"); + m_chassisCard->addField("底盘故障", "无"); - m_gasCard = new DataCard("气体检测仪", center); - m_gasCard->addField("主机状态", "待机"); - m_gasCard->addField("浓度", "0 ppb"); + m_washCard = new DataCard("洗消", cardsCol); + m_washCard->addField("A组分/清水液位", "100L"); + m_washCard->addField("泡沫箱液位", "30L"); - centerLay->addWidget(m_chassisCard, 1); - centerLay->addWidget(m_washCard, 1); - centerLay->addWidget(m_gasCard, 1); + m_gasCard = new DataCard("气体检测仪", cardsCol); + m_gasCard->addField("主机状态", "点火状态"); + m_gasCard->addField("有毒物种类", "S级"); + m_gasCard->addField("有毒物等级", "二级"); + + cardsLay->addWidget(m_chassisCard, 1); + cardsLay->addWidget(m_washCard, 1); + cardsLay->addWidget(m_gasCard, 1); + + m_armCard = new DataCard("机械臂参数", center); + m_armCard->addField("末端位置", "XYZ"); + m_armCard->addField("末端姿态", "RX/RY/RZ"); + m_armCard->addField("末端速度", "0"); + m_armCard->addField("Joint1关节角", "30°"); + m_armCard->addField("Joint2关节角", "20°"); + m_armCard->addField("Joint3关节角", "0°"); + m_armCard->addField("Joint4关节角", "90°"); + m_armCard->addField("Joint5关节角", "45°"); + m_armCard->addField("Joint6关节角", "0°"); + + centerLay->addWidget(cardsCol, 1); + centerLay->addWidget(m_armCard, 1); main->addWidget(center, 1); - // 右参数区(机械臂参数卡 + 末端按钮列) + // 右控制列(末端 6 姿态按钮,与左侧按钮列对称) QWidget* right = new QWidget(this); QVBoxLayout* rightLay = new QVBoxLayout(right); - rightLay->setContentsMargins(0, 0, 0, 0); - rightLay->setSpacing(12); + rightLay->setContentsMargins(10, 20, 20, 20); + rightLay->setSpacing(15); - m_armCard = new DataCard("机械臂参数", right); - m_armCard->addField("末端位置", "XYZ"); - m_armCard->addField("姿态", "RX/RY/RZ"); - m_armCard->addField("速度", "0"); - m_armCard->addField("J1", "30°"); - m_armCard->addField("J2", "20°"); - m_armCard->addField("J3", "0°"); - m_armCard->addField("J4", "90°"); - m_armCard->addField("J5", "45°"); - m_armCard->addField("J6", "0°"); - rightLay->addWidget(m_armCard, 1); - - // 末端 6 姿态按钮(2 列 × 3 行) - QGridLayout* endGrid = new QGridLayout(); - endGrid->setSpacing(8); - auto addEnd = [&](const QString& t, const QString& axis, int dir, int r, int c) { + auto addEnd = [&](const QString& t, const QString& axis, int dir) { QPushButton* b = makeMoveButton(t, axis, dir); - b->setFixedHeight(80); m_endButtons << b; - endGrid->addWidget(b, r, c); + rightLay->addWidget(b); }; - addEnd("末端仰", "pitch", +1, 0, 0); - addEnd("末端俯", "pitch", -1, 0, 1); - addEnd("末端正偏航", "yaw", +1, 1, 0); - addEnd("末端负偏航", "yaw", -1, 1, 1); - addEnd("末端左横滚", "roll", -1, 2, 0); - addEnd("末端右横滚", "roll", +1, 2, 1); - rightLay->addLayout(endGrid); + addEnd("末端仰", "pitch", +1); + addEnd("末端俯", "pitch", -1); + addEnd("末端正偏航", "yaw", +1); + addEnd("末端负偏航", "yaw", -1); + addEnd("末端左横滚", "roll", -1); + addEnd("末端右横滚", "roll", +1); - right->setFixedWidth(430); + right->setFixedWidth(300); // 与左列同宽,按钮 270 不被裁 main->addWidget(right); root->addLayout(main, 1); @@ -141,9 +148,10 @@ ArmPage::ArmPage(AppContext* ctx, QWidget* parent) m_returnStatus->setText(""); }); - connect(ctx->comm(), &UdpCommunication::telemetryUpdated, this, &ArmPage::refresh); + connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &ArmPage::refresh); connect(ctx, &AppContext::modeChanged, this, [this](Mode) { updateModeGating(); }); connect(ctx, &AppContext::estopChanged, this, [this](bool) { updateModeGating(); }); + connect(ctx, &AppContext::rcDrivingChanged, this, [this](bool) { updateModeGating(); }); updateModeGating(); refresh(); } @@ -151,10 +159,17 @@ ArmPage::ArmPage(AppContext* ctx, QWidget* parent) QPushButton* ArmPage::makeMoveButton(const QString& text, const QString& axis, int dir) { QPushButton* b = button(text, "", this); - b->setFixedSize(180, 90); - b->setStyleSheet(QString("font-size:24px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;") - .arg(C::PANEL_HI, C::TEXT, C::BORDER)); + b->setFixedSize(270, 76); + b->setStyleSheet(QString( + "QPushButton{font-size:45px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;}" + "QPushButton:hover,QPushButton:focus,QPushButton:pressed" + "{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}") + .arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY)); connect(b, &QPushButton::pressed, this, [this, axis, dir]() { + if (ctx()->rcDriving()) { + ConfirmDialog::toast(this, "遥控器操作中"); + return; + } if (ctx()->mode != Mode::Arm) { ConfirmDialog::toast(this, "请切换至机械臂模式"); return; @@ -169,7 +184,8 @@ QPushButton* ArmPage::makeMoveButton(const QString& text, const QString& axis, i void ArmPage::updateModeGating() { - bool arm = (ctx()->mode == Mode::Arm) && !ctx()->estopTriggered; + // 非机械臂模式、急停触发或遥控器正在操作时禁用动作按钮(遥控器优先) + bool arm = (ctx()->mode == Mode::Arm) && !ctx()->estopTriggered && !ctx()->rcDriving(); for (QPushButton* b : m_moveButtons) b->setEnabled(arm); for (QPushButton* b : m_endButtons) b->setEnabled(arm); } diff --git a/pages/chassis.cpp b/pages/chassis.cpp index b8e0d1f..8cecdac 100644 --- a/pages/chassis.cpp +++ b/pages/chassis.cpp @@ -4,7 +4,7 @@ #include "alarmbanner.h" #include "datacard.h" #include "paramcontrol.h" -#include "udpcommunication.h" +#include "car.h" #include "confirmdialog.h" #include #include @@ -29,13 +29,13 @@ ChassisPage::ChassisPage(AppContext* ctx, QWidget* parent) QHBoxLayout* main = new QHBoxLayout(); main->setContentsMargins(36, 16, 36, 16); - main->setSpacing(32); + main->setSpacing(0); // 各段间距改用显式 addSpacing,便于单独调整(底盘列位置见各段注释) // 左控制列(垂直居中 + 平均分布) QWidget* left = new QWidget(this); QVBoxLayout* leftLay = new QVBoxLayout(left); - leftLay->setContentsMargins(0, 0, 0, 0); - leftLay->setSpacing(8); + leftLay->setContentsMargins(20, 20, 0, 0); + leftLay->setSpacing(30); QPushButton* fwd = makeDirButton("前进", "fwd"); QPushButton* back = makeDirButton("后退", "back"); @@ -54,6 +54,7 @@ ChassisPage::ChassisPage(AppContext* ctx, QWidget* parent) leftLay->addStretch(1); left->setFixedWidth(180); main->addWidget(left); + main->addSpacing(32); // 中央卡片区(洗消/机械臂/气体检测仪) QWidget* center = new QWidget(this); @@ -76,36 +77,49 @@ ChassisPage::ChassisPage(AppContext* ctx, QWidget* parent) centerLay->addWidget(m_washCard, 1); centerLay->addWidget(m_armCard, 1); centerLay->addWidget(m_gasCard, 1); + // 中间区域由「卡片列」与「底盘状态列」平均分配(两列拉伸因子都为 1) main->addWidget(center, 1); + main->addSpacing(47); // 中间卡片列 ↔ 底盘列:由 32 加大到 47 - // 右参数区 - QWidget* right = new QWidget(this); - QVBoxLayout* rightLay = new QVBoxLayout(right); - rightLay->setContentsMargins(0, 0, 0, 0); - rightLay->setSpacing(12); + // 底盘状态列 + QWidget* chassisCol = new QWidget(this); + QVBoxLayout* chassisLay = new QVBoxLayout(chassisCol); + chassisLay->setContentsMargins(0, 0, 0, 0); + chassisLay->setSpacing(12); - m_chassisCard = new DataCard("底盘状态", right); + m_chassisCard = new DataCard("底盘状态", chassisCol); m_chassisCard->addField("电量SOC", "85%"); m_chassisCard->addField("电压U", "360V"); m_chassisCard->addField("车速V", "3.45 km/h"); m_chassisCard->addField("手刹", "有效"); m_chassisFieldFault = m_chassisCard->addField("故障", "无"); - rightLay->addWidget(m_chassisCard, 1); + chassisLay->addWidget(m_chassisCard, 1); + main->addWidget(chassisCol, 1); // 与中间卡片列平均分配中间区域 + main->addSpacing(17); // 底盘列 ↔ 车速设置列:由 32 减小到 17(底盘列整体右移 15px) + + // 车速设置列(垂直居中) + QWidget* right = new QWidget(this); + QVBoxLayout* rightLay = new QVBoxLayout(right); + rightLay->setContentsMargins(0, 0, 0, 0); + rightLay->setSpacing(12); - // 车速设置 double speed = ctx->getDouble(ctx->keySpeed(), 4.5); - m_speed = new ParamControl("车速设置", "km/h", 0.5, 0.0, 10.0, false, false, right); + // 协议 Vehi_TargetSpd:精度 0.01、0~500 → 物理范围 0~5.00 km/h + // 每档 = 显示精度的最小单位(1 位小数 → 0.1),比协议的 0.01 粗,够用 + m_speed = new ParamControl("车速设置", "km/h", 0.1, 0.0, 5.0, false, false, right); m_speed->setValue(speed); - m_speed->setStyleSheet("background:transparent;"); + m_speed->setStyleSheet("ParamControl{background:transparent;}"); // 只作用于本控件,避免影响子控件 connect(m_speed, &ParamControl::valueChanged, this, &ChassisPage::onSpeedChanged); + rightLay->addStretch(1); rightLay->addWidget(m_speed); - + rightLay->addStretch(1); right->setFixedWidth(380); main->addWidget(right); root->addLayout(main, 1); m_statusStrip = new StatusStrip(ctx, this); + m_statusStrip->setModeVisible(false); // 本页固定行车模式,底部不显示模式徽标 connect(m_statusStrip, &StatusStrip::backClicked, this, [this]() { emit navigate(PageJob); }); root->addWidget(m_statusStrip); @@ -115,38 +129,81 @@ ChassisPage::ChassisPage(AppContext* ctx, QWidget* parent) m_saveTimer->setInterval(1000); connect(m_saveTimer, &QTimer::timeout, this, &ChassisPage::onSpeedSaved); - connect(ctx->comm(), &UdpCommunication::telemetryUpdated, this, &ChassisPage::refresh); + connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &ChassisPage::refresh); connect(ctx, &AppContext::modeChanged, this, [this](Mode) { updateModeGating(); }); connect(ctx, &AppContext::estopChanged, this, [this](bool) { updateModeGating(); }); + connect(ctx, &AppContext::rcDrivingChanged, this, [this](bool) { updateModeGating(); }); updateModeGating(); refresh(); } +void ChassisPage::showEvent(QShowEvent* ev) +{ + BasePage::showEvent(ev); + // 每次进入底盘页自动切换到行车模式;遥控器在线时模式由遥控器报文决定,不抢 + if (!ctx()->rc()->linkOk()) + ctx()->setMode(Mode::Drive); +} + QPushButton* ChassisPage::makeDirButton(const QString& text, const QString& dir) { QPushButton* b = button(text, "", this); - b->setFixedSize(180, 150); - b->setStyleSheet(QString("font-size:26px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;") - .arg(C::PANEL_HI, C::TEXT, C::BORDER)); + b->setFixedSize(140, 100); + b->setStyleSheet(QString( + "QPushButton{font-size:45px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;}" + "QPushButton:hover,QPushButton:focus,QPushButton:pressed" + "{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}") + .arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY)); connect(b, &QPushButton::pressed, this, [this, dir]() { + if (ctx()->rcDriving()) { + ConfirmDialog::toast(this, "遥控器操作中"); + return; + } if (ctx()->mode != Mode::Drive) { ConfirmDialog::toast(this, "请切换至行车模式"); return; } - ctx()->comm()->sendChassisMove(dir, ctx()->getDouble(ctx()->keySpeed(), 4.5)); + m_heldDirs.insert(dir); + sendDirCommand(); }); - connect(b, &QPushButton::released, this, [this]() { - ctx()->comm()->sendChassisMove("stop", 0); + connect(b, &QPushButton::released, this, [this, dir]() { + m_heldDirs.remove(dir); + sendDirCommand(); }); return b; } +// 同时按住前进/后退与左/右转向时一并下发;两者都松开才停车 +void ChassisPage::sendDirCommand() +{ + const QString dir = m_heldDirs.contains("fwd") ? "fwd" + : m_heldDirs.contains("back") ? "back" : QString("stop"); + const QString turn = m_heldDirs.contains("right") ? "right" + : m_heldDirs.contains("left") ? "left" : QString(); + + if (dir == "stop" && turn.isEmpty()) { + ctx()->comm()->sendChassisMove("stop", 0); + return; + } + // 仅转向(档位回中)时发 stop + turn,由车载端决定是否原地转向 + const double spd = (dir == "stop") ? 0.0 : ctx()->getDouble(ctx()->keySpeed(), 4.5); + ctx()->comm()->sendChassisMove(dir, spd, turn); +} + void ChassisPage::updateModeGating() { - // 非行车模式或急停触发时禁用方向按钮 - bool drive = (ctx()->mode == Mode::Drive) && !ctx()->estopTriggered; + // 非行车模式、急停触发或遥控器正在操作时禁用方向按钮(遥控器优先) + const bool drive = (ctx()->mode == Mode::Drive) && !ctx()->estopTriggered && !ctx()->rcDriving(); for (QPushButton* b : m_dirButtons) b->setEnabled(drive); + + // 让位时清掉本地按住状态:按钮被禁用后不会再收到 released,否则方向会被一直挂住。 + // 让位给遥控器时不补发 stop,否则会覆盖遥控器刚下发的那条指令。 + if (!drive && !m_heldDirs.isEmpty()) { + m_heldDirs.clear(); + if (!ctx()->rcDriving()) + ctx()->comm()->sendChassisMove("stop", 0); + } } void ChassisPage::refresh() diff --git a/pages/chassis.h b/pages/chassis.h index 43469ac..305c023 100644 --- a/pages/chassis.h +++ b/pages/chassis.h @@ -3,6 +3,7 @@ #include "base.h" #include +#include class TitleBar; class StatusStrip; @@ -19,6 +20,9 @@ class ChassisPage : public BasePage public: explicit ChassisPage(AppContext* ctx, QWidget* parent = nullptr); +protected: + void showEvent(QShowEvent* ev) override; // 进入页面即切至行车模式 + private slots: void refresh(); void updateModeGating(); @@ -27,6 +31,7 @@ private slots: private: QPushButton* makeDirButton(const QString& text, const QString& dir); + void sendDirCommand(); // 按当前按住的方向组合下发(档位与转向可同时) TitleBar* m_titleBar = nullptr; StatusStrip* m_statusStrip = nullptr; @@ -39,6 +44,7 @@ private: QTimer* m_saveTimer = nullptr; int m_chassisFieldFault = -1; QVector m_dirButtons; + QSet m_heldDirs; // 当前按住的方向,可多键同时按 }; #endif // CHASSISPAGE_H diff --git a/pages/debugmaintain.cpp b/pages/debugmaintain.cpp index 5fadbde..247e556 100644 --- a/pages/debugmaintain.cpp +++ b/pages/debugmaintain.cpp @@ -4,7 +4,7 @@ #include "datacard.h" #include "ledindicator.h" #include "paramcontrol.h" -#include "udpcommunication.h" +#include "car.h" #include #include #include @@ -19,7 +19,7 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent) root->setSpacing(0); m_titleBar = new TitleBar(ctx, this); - m_titleBar->setTitle("洗消调试"); + m_titleBar->setTitle("调试维护"); root->addWidget(m_titleBar); // 警告横幅(常显红闪) @@ -34,35 +34,38 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent) // 左球阀控制区 QWidget* left = new QWidget(this); QVBoxLayout* leftLay = new QVBoxLayout(left); - leftLay->setContentsMargins(0, 0, 0, 0); - leftLay->setSpacing(16); + leftLay->setContentsMargins(20, 10, 10, 10); + leftLay->setSpacing(60); leftLay->addWidget(makeValveGroup(1)); leftLay->addWidget(makeValveGroup(2)); leftLay->addStretch(1); left->setFixedWidth(300); main->addWidget(left); - // 中央卡片区 + // 中央卡片区(左右两列:左列 底盘/机械臂/气体检测仪 纵向排列,右列 洗消参数) QWidget* center = new QWidget(this); - QVBoxLayout* centerLay = new QVBoxLayout(center); + QHBoxLayout* centerLay = new QHBoxLayout(center); centerLay->setContentsMargins(0, 0, 0, 0); - centerLay->setSpacing(10); + centerLay->setSpacing(28); // 与 main 的列间距一致 - QHBoxLayout* miniRow = new QHBoxLayout(); - miniRow->setSpacing(10); - m_chassisCard = new DataCard("底盘", center); + QWidget* centerLeft = new QWidget(center); + QVBoxLayout* centerLeftLay = new QVBoxLayout(centerLeft); + centerLeftLay->setContentsMargins(0, 0, 0, 0); + centerLeftLay->setSpacing(10); + + m_chassisCard = new DataCard("底盘", centerLeft); m_chassisCard->addField("电量", "85%"); m_chassisCard->addField("电压", "360V"); - m_armCard = new DataCard("机械臂", center); + m_armCard = new DataCard("机械臂", centerLeft); m_armCard->addField("末端位置", "XYZ"); m_armCard->addField("姿态", "RX/RY/RZ"); - m_gasCard = new DataCard("气体检测仪", center); + m_gasCard = new DataCard("气体检测仪", centerLeft); m_gasCard->addField("主机状态", "待机"); m_gasCard->addField("浓度", "0 ppb"); - miniRow->addWidget(m_chassisCard, 1); - miniRow->addWidget(m_armCard, 1); - miniRow->addWidget(m_gasCard, 1); - centerLay->addLayout(miniRow, 1); + centerLeftLay->addWidget(m_chassisCard, 1); + centerLeftLay->addWidget(m_armCard, 1); + centerLeftLay->addWidget(m_gasCard, 1); + centerLay->addWidget(centerLeft, 1); m_washCard = new DataCard("洗消参数", center); m_washCard->addField("洗消模式", "--"); @@ -79,11 +82,11 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent) // 右泵控制区 QWidget* right = new QWidget(this); QVBoxLayout* rightLay = new QVBoxLayout(right); - rightLay->setContentsMargins(0, 0, 0, 0); - rightLay->setSpacing(8); - rightLay->addWidget(makePumpGroup("air", "空压机")); - rightLay->addWidget(makePumpGroup("plunger", "柱塞泵")); - rightLay->addWidget(makePumpGroup("leaf", "叶片泵")); + rightLay->setContentsMargins(10, 10,20,10); + rightLay->setSpacing(40); + rightLay->addWidget(makePumpGroup("air", "空压机(rpm)"), 0, Qt::AlignHCenter); + rightLay->addWidget(makePumpGroup("plunger", "柱塞泵(rpm)"), 0, Qt::AlignHCenter); + rightLay->addWidget(makePumpGroup("leaf", "叶片泵(rpm)"), 0, Qt::AlignHCenter); rightLay->addStretch(1); right->setFixedWidth(420); main->addWidget(right); @@ -92,28 +95,56 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent) // 底部指示灯条 QWidget* bottom = new QWidget(this); + bottom->setObjectName("DebugBottom"); bottom->setFixedHeight(94); - bottom->setStyleSheet(QString("background-color:%1;border-top:1px solid %2;") + // 用 #DebugBottom 限定选择器:不带选择器的样式表会连子控件一起套上边框和底色 + bottom->setStyleSheet(QString("QWidget#DebugBottom{background-color:%1;border-top:1px solid %2;}") .arg(C::PANEL, C::BORDER)); QHBoxLayout* bottomLay = new QHBoxLayout(bottom); - bottomLay->setContentsMargins(36, 0, 24, 0); + // 左起 364 = 36(页面边距) + 300(球阀列) + 28(列间距),与中央“底盘”列左边缘对齐 + bottomLay->setContentsMargins(364, 0, 24, 0); bottomLay->setSpacing(48); - m_ledPlunger = new LedIndicator("柱塞泵", bottom); - m_ledAir = new LedIndicator("空压机", bottom); - m_ledLeaf = new LedIndicator("叶片泵", bottom); - m_ledLevel = new LedIndicator("液位低", bottom); - bottomLay->addWidget(m_ledPlunger); - bottomLay->addWidget(m_ledAir); - bottomLay->addWidget(m_ledLeaf); - bottomLay->addWidget(m_ledLevel); + auto makeLed = [&](const QString& text) { + LedIndicator* led = new LedIndicator(text, bottom); + led->setTextPixelSize(40); // 字号与卡片标题一致 + led->setDotAfterText(true); // 圆点画在文字后面 + bottomLay->addWidget(led); + return led; + }; + m_ledPlunger = makeLed("柱塞泵"); + m_ledAir = makeLed("空压机"); + m_ledLeaf = makeLed("叶片泵"); + m_ledLevel = makeLed("液位低"); bottomLay->addStretch(1); QPushButton* back = button("返回", "", bottom); - back->setFixedSize(144, 76); + back->setFixedSize(160, 76); + back->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字 connect(back, &QPushButton::clicked, this, [this]() { emit navigate(PageJob); }); bottomLay->addWidget(back); root->addWidget(bottom); - connect(ctx->comm(), &UdpCommunication::telemetryUpdated, this, &DebugMaintainPage::refresh); + connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, [this]() { + m_telemetrySeen = true; // 遥测一到,球阀到位灯改由 refresh() 接管 + refresh(); + }); + + // Tab 顺序(循环):返回 → D1控制 → D2控制 → 各泵(转速输入 → 启动 → 停止) → 回到返回。 + // 这些按钮在控件级样式表里写了 :checked,而控件级样式表的优先级高于全局 + // QPushButton:focus,默认焦点链下容易看起来“停不上去”,故显式排一遍保证都能停住 + QWidget* tabChain[] = { + m_valve1Btn, m_valve2Btn, + m_airRpm->editor(), m_airStart, m_airStop, + m_plungerRpm->editor(), m_plungerStart, m_plungerStop, + m_leafRpm->editor(), m_leafStart, m_leafStop, + }; + QWidget* prev = back; + for (QWidget* w : tabChain) { + if (!w) continue; + QWidget::setTabOrder(prev, w); + prev = w; + } + QWidget::setTabOrder(prev, back); // 回到起点,形成循环 + refresh(); } @@ -127,26 +158,36 @@ QWidget* DebugMaintainPage::makeValveGroup(int valve) lay->setContentsMargins(12, 12, 12, 12); lay->setSpacing(8); - QLabel* title = label(QString("D%1 球阀").arg(valve), 20, C::TEXT, true, grp); + QLabel* title = label(QString("D%1 球阀").arg(valve), 30, C::TEXT, true, grp); lay->addWidget(title); - // 控制开/关(toggle) - QPushButton* ctrl = button(QString("D%1 控制:关闭").arg(valve), "", grp); + // 控制开/关(toggle):文字固定,底色表示状态——未选中=关闭(红)、选中=开启(绿) + QPushButton* ctrl = button(QString("D%1 控制").arg(valve), "", grp); ctrl->setCheckable(true); - ctrl->setFixedHeight(64); - connect(ctrl, &QPushButton::toggled, this, [this, valve, ctrl](bool on) { - ctrl->setText(QString("D%1 控制:%2").arg(valve).arg(on ? "开启" : "关闭")); + ctrl->setFixedSize(180, 64); // 窄按钮,与泵组的“上电”一致 + // 控件级样式表优先级高于全局 QSS,故 :focus 也在这里补,否则 Tab 选中看不出变化 + ctrl->setStyleSheet(QString("QPushButton{background-color:%1;color:#FFFFFF;border:none;}" + "QPushButton:checked{background-color:%2;color:#FFFFFF;border:none;}" + "QPushButton:focus{border:4px solid #8ABFFF;}") + .arg(C::DANGER).arg(C::SUCCESS)); + connect(ctrl, &QPushButton::toggled, this, [this, valve](bool on) { ctx()->comm()->sendBallValve(valve, on); }); - lay->addWidget(ctrl); + lay->addWidget(ctrl, 0, Qt::AlignHCenter); - // 开启到位 / 关闭到位 - LedIndicator* open = new LedIndicator("开启到位", grp); - open->setState(LedIndicator::Off); - LedIndicator* close = new LedIndicator("关闭到位", grp); + // 开启到位 / 关闭到位(同一行,居中)。未收到车端遥测时保持缺省显示 + // (开启绿 / 关闭红),首次收到遥测后由 refresh() 按实际到位状态刷新 + LedIndicator* open = new LedIndicator("开启", grp); + open->setState(LedIndicator::Green); + LedIndicator* close = new LedIndicator("关闭", grp); close->setState(LedIndicator::Red); - lay->addWidget(open); - lay->addWidget(close); + QHBoxLayout* ioRow = new QHBoxLayout(); + ioRow->setSpacing(10); + ioRow->addStretch(1); + ioRow->addWidget(open); + ioRow->addWidget(close); + ioRow->addStretch(1); + lay->addLayout(ioRow); // 故障提示 QLabel* fault = label("", 16, C::DANGER, true, grp); @@ -175,29 +216,60 @@ QWidget* DebugMaintainPage::makePumpGroup(const QString& pump, const QString& na grp->setProperty("card", true); grp->setStyleSheet(QString("QWidget[card=\"true\"]{background-color:%1;border:2px solid %2;border-radius:12px;}") .arg(C::PANEL, C::BORDER)); + // 不设固定宽度:卡片按内容自适应(由最宽的“空压机(rpm)”标签决定)。 + // 固定宽度一旦小于布局最小宽度,标签会被压到小于文字宽度而裁字 QVBoxLayout* lay = new QVBoxLayout(grp); - lay->setContentsMargins(12, 8, 12, 8); - lay->setSpacing(4); + lay->setContentsMargins(30, 8, 20, 8); + lay->setSpacing(10); - ParamControl* rpm = new ParamControl(name + "转速", "rpm", 10, 0, 2000, false, false, grp); + // 上限按协议各泵量程:空压机 AirComp_Spd 0-3000、柱塞泵 PlungerPump_Spd 0-1450、 + // 叶片泵 VanePump__Spd 0-2000 rpm。每档 = 显示精度的最小单位,转速是整数显示故为 1 rpm + const int rpmMax = (pump == "air") ? 3000 : (pump == "plunger") ? 1450 : 2000; + ParamControl* rpm = new ParamControl(name, "", 1, 0, rpmMax, false, false, grp); + rpm->setCentered(true); rpm->setValue(200); connect(rpm, &ParamControl::valueChanged, this, [this, pump](double v) { ctx()->comm()->sendPumpRpm(pump, static_cast(v)); }); + lay->addWidget(rpm); - QPushButton* power = button("上电", "", grp); - power->setCheckable(true); - power->setFixedHeight(56); - connect(power, &QPushButton::toggled, this, [this, pump, power](bool on) { - power->setText(on ? "上电中" : "上电"); - ctx()->comm()->sendPumpPower(pump, on); + // 启动 / 停止:互斥,选中态对应当前泵状态(绿=运行、红=停止)。 + // 控件级样式表优先级高于全局 QSS,故 :focus 也在这里补,否则 Tab 选中看不出变化 + QPushButton* start = button("启动", "", grp); + start->setCheckable(true); + start->setFixedSize(120, 60); + start->setStyleSheet(QString("QPushButton:checked{background-color:%1;color:#FFFFFF;border:none;}" + "QPushButton:focus{border:4px solid #8ABFFF;}") + .arg(C::SUCCESS)); + QPushButton* stop = button("停止", "", grp); + stop->setCheckable(true); + stop->setFixedSize(120, 60); + stop->setStyleSheet(QString("QPushButton:checked{background-color:%1;color:#FFFFFF;border:none;}" + "QPushButton:focus{border:4px solid #8ABFFF;}") + .arg(C::DANGER)); + + connect(start, &QPushButton::clicked, this, [this, pump, start, stop]() { + start->setChecked(true); + stop->setChecked(false); + ctx()->comm()->sendPumpPower(pump, true); }); - lay->addWidget(power); + connect(stop, &QPushButton::clicked, this, [this, pump, start, stop]() { + stop->setChecked(true); + start->setChecked(false); + ctx()->comm()->sendPumpPower(pump, false); + }); + QHBoxLayout* pwrRow = new QHBoxLayout(); + pwrRow->setSpacing(12); + pwrRow->addStretch(1); + pwrRow->addWidget(start); + pwrRow->addWidget(stop); + pwrRow->addStretch(1); + lay->addLayout(pwrRow); - if (pump == "air") { m_airRpm = rpm; m_airPower = power; } - else if (pump == "plunger") { m_plungerRpm = rpm; m_plungerPower = power; } - else { m_leafRpm = rpm; m_leafPower = power; } + if (pump == "air") { m_airRpm = rpm; m_airStart = start; m_airStop = stop; } + else if (pump == "plunger") { m_plungerRpm = rpm; m_plungerStart = start; m_plungerStop = stop; } + else { m_leafRpm = rpm; m_leafStart = start; m_leafStop = stop; } return grp; } @@ -220,11 +292,13 @@ void DebugMaintainPage::refresh() m_washCard->setField(5, t.pumpFaults.value(0)); m_washCard->setField(6, t.pumpFaults.value(0)); - // 球阀到位反馈 - m_v1Open->setState(t.d1open ? LedIndicator::Green : LedIndicator::Off); - m_v1Close->setState(t.d1close ? LedIndicator::Red : LedIndicator::Off); - m_v2Open->setState(t.d2open ? LedIndicator::Green : LedIndicator::Off); - m_v2Close->setState(t.d2close ? LedIndicator::Red : LedIndicator::Off); + // 球阀到位反馈:未收到过车端遥测时不动,保留构造时的缺省显示(开启绿 / 关闭红) + if (m_telemetrySeen) { + m_v1Open->setState(t.d1open ? LedIndicator::Green : LedIndicator::Off); + m_v1Close->setState(t.d1close ? LedIndicator::Red : LedIndicator::Off); + m_v2Open->setState(t.d2open ? LedIndicator::Green : LedIndicator::Off); + m_v2Close->setState(t.d2close ? LedIndicator::Red : LedIndicator::Off); + } // 球阀故障提示 bool v1Fault = t.valveFault.contains("D1"); @@ -234,10 +308,10 @@ void DebugMaintainPage::refresh() if (v2Fault) { m_valveFault2->setText(t.valveFault); m_valveFault2->show(); startBlink(m_valveFault2, "blink"); } else { stopBlink(m_valveFault2, "blink"); m_valveFault2->hide(); } - // 泵上电状态同步 - if (m_airPower) m_airPower->setChecked(t.air); - if (m_plungerPower) m_plungerPower->setChecked(t.plunger); - if (m_leafPower) m_leafPower->setChecked(t.leaf); + // 泵启停状态同步(互斥组内先置“启动”再置“停止”,两者互补,结果总是一选中一未选) + if (m_airStart) { m_airStart->setChecked(t.air); m_airStop->setChecked(!t.air); } + if (m_plungerStart){ m_plungerStart->setChecked(t.plunger); m_plungerStop->setChecked(!t.plunger); } + if (m_leafStart) { m_leafStart->setChecked(t.leaf); m_leafStop->setChecked(!t.leaf); } // 底部指示灯 m_ledPlunger->setState(t.plunger ? LedIndicator::Green : LedIndicator::Off); diff --git a/pages/debugmaintain.h b/pages/debugmaintain.h index da2834e..6ae7d81 100644 --- a/pages/debugmaintain.h +++ b/pages/debugmaintain.h @@ -12,7 +12,7 @@ class QPushButton; class QLabel; class QCheckBox; -// ===================== ⑪ 调试维护界面(洗消调试) ===================== +// ===================== ⑪ 调试维护界面 ===================== class DebugMaintainPage : public BasePage { Q_OBJECT @@ -25,7 +25,7 @@ private slots: private: // 单个球阀组:控制开/关 + 开启到位 + 关闭到位 + 故障提示 QWidget* makeValveGroup(int valve); - // 单个泵组:转速调节 + 上电启停 + // 单个泵组:转速调节 + 启动/停止 QWidget* makePumpGroup(const QString& pump, const QString& name); TitleBar* m_titleBar = nullptr; @@ -49,15 +49,21 @@ private: ParamControl* m_airRpm = nullptr; ParamControl* m_plungerRpm = nullptr; ParamControl* m_leafRpm = nullptr; - QPushButton* m_airPower = nullptr; - QPushButton* m_plungerPower = nullptr; - QPushButton* m_leafPower = nullptr; + QPushButton* m_airStart = nullptr; + QPushButton* m_airStop = nullptr; + QPushButton* m_plungerStart = nullptr; + QPushButton* m_plungerStop = nullptr; + QPushButton* m_leafStart = nullptr; + QPushButton* m_leafStop = nullptr; // 底部指示灯 LedIndicator* m_ledPlunger = nullptr; LedIndicator* m_ledAir = nullptr; LedIndicator* m_ledLeaf = nullptr; LedIndicator* m_ledLevel = nullptr; + + // 是否收到过车端遥测:未收到时球阀到位灯保持“开启=绿、关闭=红”的缺省显示 + bool m_telemetrySeen = false; }; #endif // DEBUGMAINTAINPAGE_H diff --git a/pages/faultquery.cpp b/pages/faultquery.cpp index cc43e34..e8d1f4d 100644 --- a/pages/faultquery.cpp +++ b/pages/faultquery.cpp @@ -4,7 +4,8 @@ #include #include #include -#include +#include +#include "confirmdialog.h" #include "global.h" FaultQueryPage::FaultQueryPage(AppContext* ctx, QWidget* parent) @@ -13,58 +14,98 @@ FaultQueryPage::FaultQueryPage(AppContext* ctx, QWidget* parent) m_categories << "机器人底盘" << "机械臂" << "洗消系统" << "气体检测仪"; QVBoxLayout* root = new QVBoxLayout(this); - root->setContentsMargins(36, 20, 36, 20); - root->setSpacing(16); + root->setContentsMargins(20, 20, 20, 20); + root->setSpacing(30); // 顶条:标题 + 汇总数 - m_title = label("历史故障", 28, C::TEXT, true, this); + m_title = label("历史故障", 50, C::TEXT, true, this); root->addWidget(m_title); QHBoxLayout* main = new QHBoxLayout(); - main->setSpacing(28); + main->setSpacing(30); // 表格 m_table = new FaultTable(this); connect(m_table, &FaultTable::rowClicked, this, &FaultQueryPage::onRowClicked); main->addWidget(m_table, 1); - // 分类按钮列(垂直居中 + 平均分布) + // 分类按钮列(左右各留 20px,按钮间距 40px,剩余高度由按钮等分) QWidget* right = new QWidget(this); QVBoxLayout* rightLay = new QVBoxLayout(right); - rightLay->setContentsMargins(0, 0, 0, 0); - rightLay->setSpacing(8); + rightLay->setContentsMargins(80, FaultTable::kHeaderHeight + 10, 30, 80); // 对齐表格内容区后再下移 10px + rightLay->setSpacing(60); // 按钮之间的间隔 for (const QString& cat : m_categories) { QPushButton* b = button(cat + "故障", "", right); b->setCheckable(true); - b->setFixedHeight(110); - b->setStyleSheet(QString("font-size:24px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;") - .arg(C::PANEL_HI, C::TEXT, C::BORDER)); + b->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); // 纵向可拉伸 → 等分剩余空间 + b->setStyleSheet(QString( + "QPushButton{font-size:32px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;}" + "QPushButton:hover,QPushButton:focus,QPushButton:checked" + "{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}") + .arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY)); connect(b, &QPushButton::toggled, this, &FaultQueryPage::onCategoryToggled); m_categoryButtons << b; - rightLay->addStretch(1); - rightLay->addWidget(b); + rightLay->addWidget(b, 1); } - rightLay->addStretch(1); right->setFixedWidth(360); main->addWidget(right); root->addLayout(main, 1); - // 底部:返回 + // 底部:返回(预留更大的行高空间) QHBoxLayout* bottomLay = new QHBoxLayout(); + bottomLay->setContentsMargins(0, 0, 40, 30); bottomLay->addStretch(1); - QPushButton* back = button("返回", "", this); - back->setFixedSize(144, 76); - connect(back, &QPushButton::clicked, this, [this]() { emit navigate(m_returnPage); }); - bottomLay->addWidget(back); + m_back = button("返回", "", this); + m_back->setFixedSize(250, 100); + m_back->setStyleSheet(cancelButtonStyle(60)); // 选中(悬停/按下/焦点)红底白字 + connect(m_back, &QPushButton::clicked, this, [this]() { emit navigate(m_returnPage); }); + bottomLay->addWidget(m_back); root->addLayout(bottomLay); + // Tab 顺序(循环):表格 → 右侧分类按钮 → 返回 → 上一页 → 下一页 → 回到表格 + QWidget* first = m_table->view(); + QWidget* last = first; + for (QPushButton* b : m_categoryButtons) { + QWidget::setTabOrder(last, b); + last = b; + } + QWidget::setTabOrder(last, m_back); + QWidget::setTabOrder(m_back, m_table->prevButton()); + QWidget::setTabOrder(m_table->prevButton(), m_table->nextButton()); + QWidget::setTabOrder(m_table->nextButton(), first); // 回到表格,完成循环 + refresh(); } +void FaultQueryPage::showEvent(QShowEvent* ev) +{ + BasePage::showEvent(ev); + // 进入页面默认聚焦“分页”(上一页优先;首页禁用时退回下一页,再退回表格) + QTimer::singleShot(0, this, [this]() { + QWidget* prev = m_table->prevButton(); + QWidget* next = m_table->nextButton(); + if (prev->isEnabled()) prev->setFocus(); + else if (next->isEnabled()) next->setFocus(); + else m_table->view()->setFocus(); + }); +} + void FaultQueryPage::onCategoryToggled(bool on) { - // 单选筛选:确定当前选中的分类 + // 单选:选中一个分类时,取消其它分类(屏蔽信号,避免它们再触发本函数) + QPushButton* src = qobject_cast(sender()); + if (on && src) { + for (QPushButton* b : m_categoryButtons) { + if (b != src && b->isChecked()) { + b->blockSignals(true); + b->setChecked(false); + b->blockSignals(false); + } + } + } + + // 确定当前选中的分类(都没选 = 显示全部) QString selected; for (int i = 0; i < m_categoryButtons.size(); ++i) { if (m_categoryButtons[i]->isChecked()) { @@ -73,7 +114,6 @@ void FaultQueryPage::onCategoryToggled(bool on) } } m_filter = selected; - Q_UNUSED(on); refresh(); } @@ -105,7 +145,7 @@ void FaultQueryPage::onRowClicked(int globalIndex) if (globalIndex < 0 || globalIndex >= data.size()) return; const FaultRecord& r = data[globalIndex]; - QMessageBox::information(this, "故障详情", + ConfirmDialog::message(this, "故障详情", QString("故障时间:%1\n分系统:%2\n故障代码:%3\n故障描述:%4") .arg(r.time, r.system, r.code, r.desc)); } diff --git a/pages/faultquery.h b/pages/faultquery.h index 03b4d14..306a2b2 100644 --- a/pages/faultquery.h +++ b/pages/faultquery.h @@ -17,6 +17,9 @@ public: void setReturnPage(PageId id) { m_returnPage = id; } +protected: + void showEvent(QShowEvent* ev) override; // 进入页面时默认聚焦“分页” + private slots: void refresh(); void onCategoryToggled(bool on); @@ -25,6 +28,7 @@ private slots: private: FaultTable* m_table = nullptr; QLabel* m_title = nullptr; + QPushButton* m_back = nullptr; QVector m_categoryButtons; QVector m_categories; QString m_filter; // 空 = 全部 diff --git a/pages/gasdetector.cpp b/pages/gasdetector.cpp index 06fa309..1c68307 100644 --- a/pages/gasdetector.cpp +++ b/pages/gasdetector.cpp @@ -2,7 +2,7 @@ #include "titlebar.h" #include "statusstrip.h" #include "datacard.h" -#include "udpcommunication.h" +#include "car.h" #include "confirmdialog.h" #include #include @@ -28,8 +28,8 @@ GasDetectorPage::GasDetectorPage(AppContext* ctx, QWidget* parent) // 中央卡片区(2×2) QWidget* center = new QWidget(this); QGridLayout* grid = new QGridLayout(center); - grid->setSpacing(16); - grid->setContentsMargins(0, 0, 0, 0); + grid->setSpacing(20); + grid->setContentsMargins(20, 0, 20, 0); m_chassisCard = new DataCard("底盘", center); m_chassisCard->addField("电量", "85%"); @@ -60,11 +60,11 @@ GasDetectorPage::GasDetectorPage(AppContext* ctx, QWidget* parent) // 右侧按钮列(垂直居中 + 平均分布) QWidget* right = new QWidget(this); QVBoxLayout* rightLay = new QVBoxLayout(right); - rightLay->setContentsMargins(0, 0, 0, 0); + rightLay->setContentsMargins(40, 40, 40, 40); rightLay->setSpacing(8); m_start = button("启动", "primary", right); - m_start->setFixedHeight(180); + m_start->setFixedHeight(100); connect(m_start, &QPushButton::clicked, this, [this]() { this->ctx()->comm()->sendGasStart(); this->ctx()->gasRunning = true; @@ -72,7 +72,7 @@ GasDetectorPage::GasDetectorPage(AppContext* ctx, QWidget* parent) }); m_stop = button("停机", "", right); - m_stop->setFixedHeight(180); + m_stop->setFixedHeight(100); connect(m_stop, &QPushButton::clicked, this, [this]() { this->ctx()->comm()->sendGasStop(); this->ctx()->gasRunning = false; @@ -90,9 +90,9 @@ GasDetectorPage::GasDetectorPage(AppContext* ctx, QWidget* parent) root->addLayout(main, 1); m_statusStrip = new StatusStrip(ctx, this); + m_statusStrip->setModeVisible(false); // 本页不显示模式徽标 connect(m_statusStrip, &StatusStrip::backClicked, this, [this]() { if (this->ctx()->gasRunning) { - // 【设计建议】检测中返回弹提示 if (ConfirmDialog::confirm(this, "提示", "检测仍在进行,是否退出?")) emit navigate(PageJob); } else { @@ -101,7 +101,7 @@ GasDetectorPage::GasDetectorPage(AppContext* ctx, QWidget* parent) }); root->addWidget(m_statusStrip); - connect(ctx->comm(), &UdpCommunication::telemetryUpdated, this, &GasDetectorPage::refresh); + connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &GasDetectorPage::refresh); updateGating(); refresh(); } diff --git a/pages/home.cpp b/pages/home.cpp new file mode 100644 index 0000000..3404d3b --- /dev/null +++ b/pages/home.cpp @@ -0,0 +1,149 @@ +#include "home.h" +#include "titlebar.h" +#include "statusstrip.h" +#include "datacard.h" +#include "confirmdialog.h" +#include "car.h" +#include +#include +#include +#include +#include "global.h" + +HomePage::HomePage(AppContext* ctx, QWidget* parent) + : BasePage(ctx, parent) +{ + QVBoxLayout* root = new QVBoxLayout(this); + root->setContentsMargins(0, 0, 0, 0); + root->setSpacing(0); + + m_titleBar = new TitleBar(ctx, this); + m_titleBar->setTitle("系统首页面"); + root->addWidget(m_titleBar); + + QHBoxLayout* main = new QHBoxLayout(); + main->setContentsMargins(24, 20, 24, 20); + main->setSpacing(24); + + // ===== 按钮统一样式(改这里即可同时调整对应的按钮)===== + // 左侧 2 个菜单按钮(字号 50px;尺寸见 addMenu 里的 setFixedSize) + const QString menuBtnStyle = + QString("QPushButton{font-size:50px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;}" + "QPushButton:hover,QPushButton:focus{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}") + .arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY); + // 右侧动作按钮(字号 55px;尺寸见下方各自的 setFixedSize) + const QString actionBtnStyle = "QPushButton{font-size:55px;}"; + + // 左侧菜单(按钮在列内水平居中、垂直整体居中,选中发亮) + QWidget* left = new QWidget(this); + QVBoxLayout* leftLay = new QVBoxLayout(left); + leftLay->setContentsMargins(10, 20, 10, 20); + leftLay->setSpacing(100); + auto addMenu = [&](const QString& t, PageId id) { + QPushButton* b = button(t, "", left); + b->setFixedSize(260, 100); // 固定尺寸(改这里同时调整 2 个菜单按钮) + b->setStyleSheet(menuBtnStyle); + connect(b, &QPushButton::clicked, this, [this, id]() { emit navigate(id); }); + leftLay->addWidget(b, 0, Qt::AlignHCenter); // 不占拉伸因子,余量交给上下 stretch 均分 + }; + leftLay->addStretch(1); + addMenu("故障查询", PageFault); + addMenu("作业选择", PageJob); + leftLay->addStretch(1); + left->setFixedWidth(300); + main->addWidget(left); + + // 中央卡片区(2×2,与作业选择页一致) + QWidget* center = new QWidget(this); + QGridLayout* grid = new QGridLayout(center); + grid->setSpacing(24); + grid->setContentsMargins(0, 0, 0, 0); + + m_chassisCard = new DataCard("底盘", center); + m_chassisCard->addField("电量", "85%"); + m_chassisCard->addField("电压", "360V"); + m_chassisCard->addField("故障", "无"); + + m_armCard = new DataCard("机械臂", center); + m_armCard->addField("末端位置", "XYZ"); + m_armCard->addField("姿态", "RX/RY/RZ"); + + m_washCard = new DataCard("洗消", center); + m_washCard->addField("A液位", "100L"); + m_washCard->addField("泡沫液位", "30L"); + + m_gasCard = new DataCard("气体检测仪", center); + m_gasCard->addField("主机状态", "待机"); + m_gasCard->addField("浓度", "0 ppb"); + + // 左列 底盘/机械臂,右列 洗消/气体检测仪 + grid->addWidget(m_chassisCard, 0, 0); + grid->addWidget(m_washCard, 0, 1); + grid->addWidget(m_armCard, 1, 0); + grid->addWidget(m_gasCard, 1, 1); + main->addWidget(center, 1); + + // 右侧按钮列(等分填满整列,选中发亮) + QWidget* right = new QWidget(this); + QVBoxLayout* rightLay = new QVBoxLayout(right); + rightLay->setContentsMargins(0, 0, 0, 0); + rightLay->setSpacing(24); + QPushButton* debug = button("调试维护", "", right); + debug->setFixedSize(240, 100); + debug->setStyleSheet(actionBtnStyle); + connect(debug, &QPushButton::clicked, this, [this]() { emit navigate(PageDebug); }); + QPushButton* help = button("操作说明", "", right); + help->setFixedSize(240, 100); + help->setStyleSheet(actionBtnStyle); + connect(help, &QPushButton::clicked, this, &HomePage::onHelp); + QPushButton* spare = button("备用", "", right); + spare->setFixedSize(240, 100); + spare->setStyleSheet(actionBtnStyle); + spare->setEnabled(false); // 预留功能:置灰禁用,不接任何跳转 + rightLay->addStretch(1); + rightLay->addWidget(debug, 0, Qt::AlignHCenter); + rightLay->addStretch(1); + rightLay->addWidget(help, 0, Qt::AlignHCenter); + rightLay->addStretch(1); + rightLay->addWidget(spare, 0, Qt::AlignHCenter); + rightLay->addStretch(1); + right->setFixedWidth(360); + main->addWidget(right); + + root->addLayout(main, 1); + + m_statusStrip = new StatusStrip(ctx, this); + m_statusStrip->setBackVisible(false); + m_statusStrip->setModeVisible(false); // 本页不显示模式徽标(菜单已移到左右两列) + root->addWidget(m_statusStrip); + + connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &HomePage::refresh); + refresh(); +} + +void HomePage::refresh() +{ + const Telemetry& t = ctx()->telemetry; + m_chassisCard->setField(0, QString("%1%").arg(t.soc)); + m_chassisCard->setField(1, QString("%1V").arg(t.volt, 0, 'f', 0)); + m_chassisCard->setField(2, t.chassisFaults.isEmpty() ? "无" : t.chassisFaults.join(";")); + m_chassisCard->setAlarm(2, !t.chassisFaults.isEmpty()); + + m_armCard->setField(0, t.pos); + m_armCard->setField(1, t.pose); + + m_washCard->setField(0, QString("%1L").arg(t.levelA, 0, 'f', 0)); + m_washCard->setField(1, QString("%1L").arg(t.levelFoam, 0, 'f', 0)); + + m_gasCard->setField(0, t.gasHost == "detecting" ? "检测中" : (t.gasHost == "fault" ? "故障" : "待机")); + m_gasCard->setField(1, QString("%1 ppb").arg(t.gasConc, 0, 'f', 0)); + m_gasCard->setAlarm(1, t.gasConc > 500.0); +} + +void HomePage::onHelp() +{ + ConfirmDialog::message(this, "操作说明", + "系统自检将依次检测:通信系统、底盘通信、机械臂通信、洗消通信、" + "传感器信号、清水液位及电动球阀 D1/D2 的开启与关闭。\n" + "全部正常后方可进入作业选择界面。"); +} diff --git a/pages/home.h b/pages/home.h new file mode 100644 index 0000000..3db9312 --- /dev/null +++ b/pages/home.h @@ -0,0 +1,30 @@ +#ifndef HOMEPAGE_H +#define HOMEPAGE_H + +#include "base.h" + +class TitleBar; +class StatusStrip; +class DataCard; + +// ===================== ⑤ 系统首页面 ===================== +class HomePage : public BasePage +{ + Q_OBJECT +public: + explicit HomePage(AppContext* ctx, QWidget* parent = nullptr); + +private slots: + void refresh(); + void onHelp(); + +private: + TitleBar* m_titleBar = nullptr; + StatusStrip* m_statusStrip = nullptr; + DataCard* m_chassisCard = nullptr; + DataCard* m_armCard = nullptr; + DataCard* m_washCard = nullptr; + DataCard* m_gasCard = nullptr; +}; + +#endif // HOMEPAGE_H diff --git a/pages/jobselect.cpp b/pages/jobselect.cpp index 9c29063..8a5ebfe 100644 --- a/pages/jobselect.cpp +++ b/pages/jobselect.cpp @@ -2,7 +2,7 @@ #include "titlebar.h" #include "statusstrip.h" #include "datacard.h" -#include "udpcommunication.h" +#include "car.h" #include #include #include @@ -21,36 +21,43 @@ JobSelectPage::JobSelectPage(AppContext* ctx, QWidget* parent) root->addWidget(m_titleBar); QHBoxLayout* main = new QHBoxLayout(); - main->setContentsMargins(36, 20, 36, 20); - main->setSpacing(32); + main->setContentsMargins(24, 20, 24, 20); + main->setSpacing(24); - // 左侧菜单(垂直居中 + 平均分布) + // ===== 按钮统一样式(改这里即可同时调整对应的按钮)===== + // 左侧 4 个菜单按钮(字号 50px;尺寸见 addMenu 里的 setFixedSize) + const QString menuBtnStyle = + QString("QPushButton{font-size:50px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;}" + "QPushButton:hover,QPushButton:focus{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}") + .arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY); + // 右侧动作按钮(字号 55px;尺寸见下方各自的 setFixedSize) + const QString actionBtnStyle = "QPushButton{font-size:55px;}"; + + // 左侧菜单(按钮在列内水平居中、垂直整体居中,选中发亮) QWidget* left = new QWidget(this); QVBoxLayout* leftLay = new QVBoxLayout(left); - leftLay->setContentsMargins(0, 0, 0, 0); - leftLay->setSpacing(8); + leftLay->setContentsMargins(10, 20, 10, 20); + leftLay->setSpacing(100); auto addMenu = [&](const QString& t, PageId id) { QPushButton* b = button(t, "", left); - b->setFixedHeight(130); - b->setStyleSheet(QString("font-size:24px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;") - .arg(C::PANEL_HI, C::TEXT, C::BORDER)); + b->setFixedSize(260, 100); // 固定尺寸(改这里同时调整 4 个菜单按钮) + b->setStyleSheet(menuBtnStyle); connect(b, &QPushButton::clicked, this, [this, id]() { emit navigate(id); }); - leftLay->addStretch(1); - leftLay->addWidget(b); + leftLay->addWidget(b, 0, Qt::AlignHCenter); // 不占拉伸因子,余量交给上下 stretch 均分 }; + leftLay->addStretch(1); addMenu("机器人底盘", PageChassis); addMenu("机械臂", PageArm); addMenu("气体检测仪", PageGas); addMenu("调试维护", PageDebug); - addMenu("故障查询", PageFault); leftLay->addStretch(1); - left->setFixedWidth(240); + left->setFixedWidth(300); main->addWidget(left); // 中央卡片区(2×2) QWidget* center = new QWidget(this); QGridLayout* grid = new QGridLayout(center); - grid->setSpacing(16); + grid->setSpacing(24); grid->setContentsMargins(0, 0, 0, 0); m_chassisCard = new DataCard("底盘", center); @@ -70,27 +77,38 @@ JobSelectPage::JobSelectPage(AppContext* ctx, QWidget* parent) m_gasCard->addField("主机状态", "待机"); m_gasCard->addField("浓度", "0 ppb"); + // 左列 底盘/机械臂,右列 洗消/气体检测仪 grid->addWidget(m_chassisCard, 0, 0); - grid->addWidget(m_armCard, 0, 1); - grid->addWidget(m_washCard, 1, 0); + grid->addWidget(m_washCard, 0, 1); + grid->addWidget(m_armCard, 1, 0); grid->addWidget(m_gasCard, 1, 1); main->addWidget(center, 1); - // 右侧按钮列(垂直居中 + 平均分布) + // 右侧按钮列(等分填满整列,选中发亮) QWidget* right = new QWidget(this); QVBoxLayout* rightLay = new QVBoxLayout(right); rightLay->setContentsMargins(0, 0, 0, 0); - rightLay->setSpacing(8); - QPushButton* clean = button("手动消除", "primary", right); - clean->setFixedHeight(170); + rightLay->setSpacing(24); + QPushButton* clean = button("消除作业", "", right); + clean->setFixedSize(240, 100); + clean->setStyleSheet(actionBtnStyle); connect(clean, &QPushButton::clicked, this, [this]() { emit navigate(PageClean); }); - QPushButton* disinfect = button("手动消毒", "primary", right); - disinfect->setFixedHeight(170); + QPushButton* disinfect = button("消毒作业", "", right); + disinfect->setFixedSize(240, 100); + disinfect->setStyleSheet(actionBtnStyle); connect(disinfect, &QPushButton::clicked, this, [this]() { emit navigate(PageDisinfect); }); + // 返回:回系统首页面(⑤系统首页面 → ⑥作业选择) + QPushButton* back = button("返回", "", right); + back->setFixedSize(240, 100); + back->setStyleSheet(cancelButtonStyle(55)); // 选中(悬停/按下/焦点)红底白字 + // connect(back, &QPushButton::clicked, this, [this]() { emit navigate(PageSelfCheck); }); // 原:回自检页 + connect(back, &QPushButton::clicked, this, [this]() { emit navigate(PageHome); }); rightLay->addStretch(1); - rightLay->addWidget(clean); + rightLay->addWidget(clean, 0, Qt::AlignHCenter); rightLay->addStretch(1); - rightLay->addWidget(disinfect); + rightLay->addWidget(disinfect, 0, Qt::AlignHCenter); + rightLay->addStretch(1); + rightLay->addWidget(back, 0, Qt::AlignHCenter); rightLay->addStretch(1); right->setFixedWidth(360); main->addWidget(right); @@ -99,9 +117,10 @@ JobSelectPage::JobSelectPage(AppContext* ctx, QWidget* parent) m_statusStrip = new StatusStrip(ctx, this); m_statusStrip->setBackVisible(false); + m_statusStrip->setModeVisible(false); // 本页不显示模式徽标(返回按钮已移到右侧列) root->addWidget(m_statusStrip); - connect(ctx->comm(), &UdpCommunication::telemetryUpdated, this, &JobSelectPage::refresh); + connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &JobSelectPage::refresh); refresh(); } diff --git a/pages/manualclean.cpp b/pages/manualclean.cpp deleted file mode 100644 index 3e13b8a..0000000 --- a/pages/manualclean.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "manualclean.h" -#include "paramcontrol.h" -#include "udpcommunication.h" -#include "confirmdialog.h" -#include -#include "global.h" - -ManualCleanPage::ManualCleanPage(AppContext* ctx, QWidget* parent) - : WashBasePage(ctx, "手动消除", "洗消(手动消除)", parent) -{ - double flow = ctx->getDouble(ctx->keyPumpFlow(), 20); - m_pumpFlow = new ParamControl("柱塞泵流量", "L/min", 0.5, 0.0, 100.0, true, false, this); - m_pumpFlow->setValue(flow); - connect(m_pumpFlow, &ParamControl::saveClicked, this, [this]() { - this->ctx()->setDouble(this->ctx()->keyPumpFlow(), m_pumpFlow->value()); - this->ctx()->comm()->sendSetParam(this->ctx()->keyPumpFlow(), m_pumpFlow->value()); - ConfirmDialog::toast(this, "已保存"); - }); - paramsLayout()->addWidget(m_pumpFlow); -} - -void ManualCleanPage::resetFactory() -{ - // 出厂值 20 - m_pumpFlow->setValue(20); - ctx()->setDouble(ctx()->keyPumpFlow(), 20); - ConfirmDialog::toast(this, "已恢复出厂值"); -} diff --git a/pages/manualclean.h b/pages/manualclean.h deleted file mode 100644 index 3c8090f..0000000 --- a/pages/manualclean.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef MANUALCLEANPAGE_H -#define MANUALCLEANPAGE_H - -#include "washbase.h" - -class ParamControl; - -// ===================== ⑨ 手动消除界面 ===================== -class ManualCleanPage : public WashBasePage -{ - Q_OBJECT -public: - explicit ManualCleanPage(AppContext* ctx, QWidget* parent = nullptr); - -protected: - void resetFactory() override; - QString washModeTag() const override { return "clean"; } - -private: - ParamControl* m_pumpFlow = nullptr; -}; - -#endif // MANUALCLEANPAGE_H diff --git a/pages/manualdisinfect.cpp b/pages/manualdisinfect.cpp deleted file mode 100644 index cd7b1fd..0000000 --- a/pages/manualdisinfect.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "manualdisinfect.h" -#include "paramcontrol.h" -#include "udpcommunication.h" -#include "confirmdialog.h" -#include -#include "global.h" - -ManualDisinfectPage::ManualDisinfectPage(AppContext* ctx, QWidget* parent) - : WashBasePage(ctx, "手动消毒", "洗消(手动消毒)", parent) -{ - // 发泡倍数(出厂 6) - m_foamRatio = new ParamControl("发泡倍数", "倍", 0.5, 0.0, 20.0, true, false, this); - m_foamRatio->setValue(ctx->getDouble(ctx->keyFoamRatio(), 6)); - connect(m_foamRatio, &ParamControl::saveClicked, this, [this]() { - this->ctx()->setDouble(this->ctx()->keyFoamRatio(), m_foamRatio->value()); - ConfirmDialog::toast(this, "已保存"); - }); - - // 泡沫比例(出厂 3%) - m_foamPercent = new ParamControl("泡沫比例", "%", 0.5, 0.0, 20.0, true, false, this); - m_foamPercent->setValue(ctx->getDouble(ctx->keyFoamPercent(), 3)); - connect(m_foamPercent, &ParamControl::saveClicked, this, [this]() { - this->ctx()->setDouble(this->ctx()->keyFoamPercent(), m_foamPercent->value()); - ConfirmDialog::toast(this, "已保存"); - }); - - // 空压机流量(出厂 100) - m_airFlow = new ParamControl("空压机流量", "L/min", 1, 0.0, 500.0, true, false, this); - m_airFlow->setValue(ctx->getDouble(ctx->keyAirFlow(), 100)); - connect(m_airFlow, &ParamControl::saveClicked, this, [this]() { - this->ctx()->setDouble(this->ctx()->keyAirFlow(), m_airFlow->value()); - ConfirmDialog::toast(this, "已保存"); - }); - - paramsLayout()->addWidget(m_foamRatio); - paramsLayout()->addWidget(m_foamPercent); - paramsLayout()->addWidget(m_airFlow); -} - -void ManualDisinfectPage::resetFactory() -{ - m_foamRatio->setValue(6); - m_foamPercent->setValue(3); - m_airFlow->setValue(100); - ctx()->setDouble(ctx()->keyFoamRatio(), 6); - ctx()->setDouble(ctx()->keyFoamPercent(), 3); - ctx()->setDouble(ctx()->keyAirFlow(), 100); - ConfirmDialog::toast(this, "已恢复出厂值"); -} diff --git a/pages/manualdisinfect.h b/pages/manualdisinfect.h deleted file mode 100644 index ea91543..0000000 --- a/pages/manualdisinfect.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef MANUALDISINFECTPAGE_H -#define MANUALDISINFECTPAGE_H - -#include "washbase.h" - -class ParamControl; - -// ===================== ⑩ 手动消毒界面 ===================== -class ManualDisinfectPage : public WashBasePage -{ - Q_OBJECT -public: - explicit ManualDisinfectPage(AppContext* ctx, QWidget* parent = nullptr); - -protected: - void resetFactory() override; - QString washModeTag() const override { return "disinfect"; } - -private: - ParamControl* m_foamRatio = nullptr; // 发泡倍数 - ParamControl* m_foamPercent = nullptr; // 泡沫比例 - ParamControl* m_airFlow = nullptr; // 空压机流量 -}; - -#endif // MANUALDISINFECTPAGE_H diff --git a/pages/selfcheck.cpp b/pages/selfcheck.cpp index 270e055..a526924 100644 --- a/pages/selfcheck.cpp +++ b/pages/selfcheck.cpp @@ -1,7 +1,7 @@ #include "selfcheck.h" #include "titlebar.h" #include "ledindicator.h" -#include "udpcommunication.h" +#include "car.h" #include #include #include @@ -9,7 +9,7 @@ #include #include #include -#include +#include "confirmdialog.h" #include "global.h" SelfCheckPage::SelfCheckPage(AppContext* ctx, QWidget* parent) @@ -29,7 +29,7 @@ SelfCheckPage::SelfCheckPage(AppContext* ctx, QWidget* parent) main->setSpacing(40); // 两侧按钮统一样式:字号在此统一调整;按钮会拉伸填满整列(sideBtnH 为最小高度) - const int sideBtnH = 200; + const int sideBtnH = 130; const QString sideBtnStyle = "font-size:60px;"; auto makeSideBtn = [&](const QString& text, const QString& variant, QWidget* parent) { QPushButton* b = button(text, variant, parent); @@ -127,8 +127,8 @@ SelfCheckPage::SelfCheckPage(AppContext* ctx, QWidget* parent) root->addLayout(main, 1); // 连接通信信号 - connect(ctx->comm(), &UdpCommunication::selfCheckItem, this, &SelfCheckPage::onSelfCheckItem); - connect(ctx->comm(), &UdpCommunication::selfCheckDone, this, &SelfCheckPage::onSelfCheckDone); + connect(ctx->comm(), &CarCommunication::selfCheckItem, this, &SelfCheckPage::onSelfCheckItem); + connect(ctx->comm(), &CarCommunication::selfCheckDone, this, &SelfCheckPage::onSelfCheckDone); } void SelfCheckPage::showEvent(QShowEvent* ev) @@ -200,12 +200,15 @@ void SelfCheckPage::onSelfCheckDone(bool allOk) m_status->setStyleSheet(QString("color:%1;font-size:60px;font-weight:700;").arg(C::SUCCESS)); m_job->setEnabled(true); m_job->setFocus(); // 自检完成后聚焦到“作业选择” - // 自检完成后停留在本界面,不自动跳转(需进入作业选择请手动点击) - // 【设计建议】自检完成 2s 后自动进入作业选择 + // 自检完成 2s 后自动进入系统首页面(原目标为作业选择页) // QTimer::singleShot(2000, this, [this]() { // if (m_done && isVisible()) // emit navigate(PageJob); // }); + QTimer::singleShot(2000, this, [this]() { + if (m_done && isVisible()) + emit navigate(PageHome); + }); } else { m_status->setText("无法完成自检 请检查设备"); m_status->setStyleSheet(QString("color:%1;font-size:60px;font-weight:700;").arg(C::DANGER)); @@ -218,7 +221,8 @@ void SelfCheckPage::onSelfCheckDone(bool allOk) void SelfCheckPage::onIgnoreFault() { - emit navigate(PageJob); + // emit navigate(PageJob); // 原:直接进作业选择页 + emit navigate(PageHome); // 忽略故障后进入系统首页面 } void SelfCheckPage::onJobSelect() @@ -228,7 +232,7 @@ void SelfCheckPage::onJobSelect() void SelfCheckPage::onHelp() { - QMessageBox::information(this, "操作说明", + ConfirmDialog::message(this, "操作说明", "系统自检将依次检测:通信系统、底盘通信、机械臂通信、洗消通信、" "传感器信号、清水液位及电动球阀 D1/D2 的开启与关闭。\n" "全部正常后方可进入作业选择界面。"); diff --git a/pages/washbase.cpp b/pages/washbase.cpp index 8dd8cac..ed56c11 100644 --- a/pages/washbase.cpp +++ b/pages/washbase.cpp @@ -3,7 +3,7 @@ #include "alarmbanner.h" #include "datacard.h" #include "ledindicator.h" -#include "udpcommunication.h" +#include "car.h" #include "confirmdialog.h" #include #include @@ -68,6 +68,7 @@ WashBasePage::WashBasePage(AppContext* ctx, const QString& title, const QString& m_fFoam = m_washCard->addField("发泡倍数", "0"); m_fAirFault = m_washCard->addField("空压机故障代码", ""); m_fPlungerFault = m_washCard->addField("柱塞泵故障代码", ""); + m_washCard->indentRows(); // 行文字缩进到中间偏左(卡片很宽,贴左缘会显得空) main->addWidget(m_washCard, 1); // 右侧操作列 @@ -109,6 +110,7 @@ WashBasePage::WashBasePage(AppContext* ctx, const QString& title, const QString& m_factory = button("恢复出厂值", "", bottom); m_factory->setFixedSize(220, 64); + m_factory->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字 connect(m_factory, &QPushButton::clicked, this, &WashBasePage::onFactory); bottomLay->addWidget(m_factory); bottomLay->addSpacing(20); @@ -134,6 +136,7 @@ WashBasePage::WashBasePage(AppContext* ctx, const QString& title, const QString& m_back = button("返回", "", bottom); m_back->setFixedSize(144, 76); + m_back->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字 connect(m_back, &QPushButton::clicked, this, &WashBasePage::onBack); bottomLay->addWidget(m_back); @@ -145,7 +148,7 @@ WashBasePage::WashBasePage(AppContext* ctx, const QString& title, const QString& connect(m_faultTimer, &QTimer::timeout, this, &WashBasePage::rotateFaults); m_faultTimer->start(); - connect(ctx->comm(), &UdpCommunication::telemetryUpdated, this, &WashBasePage::refresh); + connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &WashBasePage::refresh); updateGating(); refresh(); } diff --git a/pages/washbase.h b/pages/washbase.h index fd0b3d0..8ffae63 100644 --- a/pages/washbase.h +++ b/pages/washbase.h @@ -12,7 +12,7 @@ class QPushButton; class QVBoxLayout; class QTimer; -// ===================== 洗消页面基类(⑨手动消除 / ⑩手动消毒 共用) ===================== +// ===================== 洗消页面基类(⑨消除作业 / ⑩消毒作业 共用) ===================== // 左侧状态卡 + 中央洗消参数卡 + 右侧启动/停止与参数 + 底部指示灯条 class WashBasePage : public BasePage { diff --git a/pages/washpage.cpp b/pages/washpage.cpp new file mode 100644 index 0000000..7f4d1f0 --- /dev/null +++ b/pages/washpage.cpp @@ -0,0 +1,369 @@ +#include "washpage.h" +#include "titlebar.h" +#include "alarmbanner.h" +#include "datacard.h" +#include "ledindicator.h" +#include "paramcontrol.h" +#include "car.h" +#include "confirmdialog.h" +#include +#include +#include +#include +#include +#include "global.h" + +WashPage::WashPage(AppContext* ctx, Mode mode, QWidget* parent) + : BasePage(ctx, parent), m_mode(mode) +{ + const bool clean = (mode == Clean); + + QVBoxLayout* root = new QVBoxLayout(this); + root->setContentsMargins(0, 0, 0, 0); + root->setSpacing(0); + + m_titleBar = new TitleBar(ctx, this); + m_titleBar->setTitle(clean ? "消除作业" : "消毒作业"); + root->addWidget(m_titleBar); + + m_banner = new AlarmBanner(this); + root->addWidget(m_banner); + + QHBoxLayout* main = new QHBoxLayout(); + main->setContentsMargins(36, 16, 36, 16); + main->setSpacing(28); + + // 左侧状态卡(底盘/机械臂/气体检测仪) + QWidget* left = new QWidget(this); + QVBoxLayout* leftLay = new QVBoxLayout(left); + leftLay->setContentsMargins(0, 0, 0, 0); + leftLay->setSpacing(10); + + m_chassisCard = new DataCard("底盘", left); + m_chassisCard->addField("电量", "85%"); + m_chassisCard->addField("电压", "360V"); + m_chassisCard->addField("故障", "无"); + + m_armCard = new DataCard("机械臂", left); + m_armCard->addField("末端位置", "XYZ"); + m_armCard->addField("姿态", "RX/RY/RZ"); + + m_gasCard = new DataCard("气体检测仪", left); + m_gasCard->addField("主机状态", "待机"); + m_gasCard->addField("浓度", "0 ppb"); + + leftLay->addWidget(m_chassisCard, 1); + leftLay->addWidget(m_armCard, 1); + leftLay->addWidget(m_gasCard, 1); + left->setFixedWidth(600); // 需容纳最长的“末端位置:X:1200 Y:300 Z:800”(42px 字号约 523px) + main->addSpacing(60); // 左列整体右移的量;空白由中间拉伸列让出,卡片宽度不变 + main->addWidget(left); + + // 中央洗消卡 + m_washCard = new DataCard(clean ? "洗消(消除作业)" : "洗消(消毒作业)", this); + m_fMode = m_washCard->addField("洗消模式", "--"); + m_fLevelA = m_washCard->addField("A组分液位", "100L"); + m_fLevelFoam = m_washCard->addField("泡沫箱液位", "30L"); + m_fFlowA = m_washCard->addField("A药剂流量", "0 L/min"); + m_fFlowB = m_washCard->addField("B药剂流量", "0 L/min"); + m_fGasFlow = m_washCard->addField("气体流量", "0 L/min"); + m_fPress = m_washCard->addField("管路压力", "0 MPa"); + m_fAirPress = m_washCard->addField("空压机压力", "0 MPa"); + m_fFoam = m_washCard->addField("发泡倍数", "0"); + m_fAirFault = m_washCard->addField("空压机故障代码", ""); + m_fPlungerFault = m_washCard->addField("柱塞泵故障代码", ""); + main->addSpacing(60); + main->addWidget(m_washCard, 1); + + // 右侧操作列 + QWidget* right = new QWidget(this); + QVBoxLayout* rightLay = new QVBoxLayout(right); + rightLay->setContentsMargins(20, 20, 20, 10); + rightLay->setSpacing(60); + + m_start = button("启动", "primary", right); + m_start->setFixedSize(180, 100); + connect(m_start, &QPushButton::clicked, this, &WashPage::onStart); + + m_stop = button("停止", "danger", right); + m_stop->setFixedSize(180, 100); + connect(m_stop, &QPushButton::clicked, this, &WashPage::onStop); + + rightLay->addWidget(m_start, 0, Qt::AlignHCenter); + rightLay->addWidget(m_stop, 0, Qt::AlignHCenter); + + // 参数区:按模式不同 + m_paramsLayout = new QVBoxLayout(); + m_paramsLayout->setSpacing(8); + rightLay->addLayout(m_paramsLayout); + rightLay->addStretch(1); + + if (clean) { + // 柱塞泵流量(出厂 20;协议 PlungerPump_flow 0-24 L/min) + m_pumpFlow = new ParamControl("柱塞泵流量", "L/min", 0.1, 0.0, 24.0, true, false, this); + m_pumpFlow->setValue(ctx->getDouble(ctx->keyPumpFlow(), 20)); + connect(m_pumpFlow, &ParamControl::saveClicked, this, [this]() { + this->ctx()->setDouble(this->ctx()->keyPumpFlow(), m_pumpFlow->value()); + this->ctx()->comm()->sendSetParam(this->ctx()->keyPumpFlow(), m_pumpFlow->value()); + refreshPumpValues(); // 底部数值跟着更新 + ConfirmDialog::toast(this, "已保存"); + }); + m_paramsLayout->addWidget(m_pumpFlow); + } else { + // 发泡倍数(出厂 6;协议 Expansion_ratioCmd 6-12 倍) + m_foamRatio = new ParamControl("发泡倍数", "倍", 0.1, 6.0, 12.0, true, false, this); + m_foamRatio->setValue(ctx->getDouble(ctx->keyFoamRatio(), 6)); + connect(m_foamRatio, &ParamControl::saveClicked, this, [this]() { + this->ctx()->setDouble(this->ctx()->keyFoamRatio(), m_foamRatio->value()); + this->ctx()->comm()->sendSetParam(this->ctx()->keyFoamRatio(), m_foamRatio->value()); + ConfirmDialog::toast(this, "已保存"); + }); + m_paramsLayout->addWidget(m_foamRatio); + + // 泡沫比例(出厂 3%;协议 Foam_ratio 0-10%) + m_foamPercent = new ParamControl("泡沫比例", "%", 0.1, 0.0, 10.0, true, false, this); + m_foamPercent->setValue(ctx->getDouble(ctx->keyFoamPercent(), 3)); + connect(m_foamPercent, &ParamControl::saveClicked, this, [this]() { + this->ctx()->setDouble(this->ctx()->keyFoamPercent(), m_foamPercent->value()); + this->ctx()->comm()->sendSetParam(this->ctx()->keyFoamPercent(), m_foamPercent->value()); + ConfirmDialog::toast(this, "已保存"); + }); + m_paramsLayout->addWidget(m_foamPercent); + + // 空压机流量(出厂 100;协议 AirComp_flow 0-340 L/min) + m_airFlow = new ParamControl("空压机流量", "L/min", 1, 0.0, 340.0, true, false, this); + m_airFlow->setValue(ctx->getDouble(ctx->keyAirFlow(), 100)); + connect(m_airFlow, &ParamControl::saveClicked, this, [this]() { + this->ctx()->setDouble(this->ctx()->keyAirFlow(), m_airFlow->value()); + this->ctx()->comm()->sendSetParam(this->ctx()->keyAirFlow(), m_airFlow->value()); + refreshPumpValues(); // 底部数值跟着更新 + ConfirmDialog::toast(this, "已保存"); + }); + m_paramsLayout->addWidget(m_airFlow); + } + + right->setFixedWidth(500); // 参数行需容纳 输入框150 + 单位约101 + 保存140 + 两个间距24 ≈ 415px + main->addSpacing(60); // 洗消卡 ↔ 右列的间隙;空白由中间拉伸列让出,卡片文字不受影响 + main->addWidget(right); + + root->addLayout(main, 1); + + // 底部条:左右两个按钮纵向占满两行,中间上下两排(泵数值 / 指示灯)居中 + QWidget* bottom = new QWidget(this); + bottom->setObjectName("WashBottom"); + bottom->setFixedHeight(160); + // 用 #WashBottom 限定选择器:不带选择器的样式表会连子控件一起套上边框和底色 + bottom->setStyleSheet(QString("QWidget#WashBottom{background-color:%1;border-top:1px solid %2;}") + .arg(C::PANEL, C::BORDER)); + QHBoxLayout* bottomRoot = new QHBoxLayout(bottom); + bottomRoot->setContentsMargins(20, 8, 20, 8); + bottomRoot->setSpacing(12); + + // 左:恢复出厂值(占满两行高度) + m_factory = button("恢复出厂值", "", bottom); + m_factory->setFixedSize(220, 100); + m_factory->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字 + connect(m_factory, &QPushButton::clicked, this, &WashPage::onFactory); + bottomRoot->addWidget(m_factory, 0, Qt::AlignVCenter); + + // 中:上排三泵数值、下排指示灯,两排都水平居中 + QVBoxLayout* midLay = new QVBoxLayout(); + midLay->setSpacing(6); + + QHBoxLayout* valueLay = new QHBoxLayout(); + valueLay->setSpacing(40); + valueLay->addStretch(1); + auto addFlow = [&](const QString& name) { + valueLay->addWidget(label(name + ":", 40, C::TEXT_SUB, false, bottom)); + QLabel* v = label("--", 40, C::TEXT, true, bottom); + valueLay->addWidget(v); + return v; + }; + m_valPlunger = addFlow("柱塞泵"); + m_valAir = addFlow("空压机"); + m_valLeaf = addFlow("叶片泵"); + refreshPumpValues(); // 开机先显示参数的出厂默认值 + valueLay->addStretch(1); + + QHBoxLayout* ledLay = new QHBoxLayout(); + ledLay->setSpacing(10); // 8 个灯 40px 字号后总宽约 1300px + ledLay->addStretch(1); + auto makeLed = [&](const QString& text) { + LedIndicator* led = new LedIndicator(text, bottom); + led->setTextPixelSize(40); // 字号与卡片标题一致 + ledLay->addWidget(led); + return led; + }; + m_ledRun = makeLed("启停"); + m_ledPlunger = makeLed("柱塞泵"); + m_ledAir = makeLed("空压机"); + m_ledLeaf = makeLed("叶片泵"); + m_ledLevel = makeLed("液位低"); + m_ledD1 = makeLed("D1"); + m_ledD2 = makeLed("D2"); + m_ledFault = makeLed("系统故障"); + ledLay->addStretch(1); + + midLay->addStretch(1); + midLay->addLayout(valueLay); + midLay->addLayout(ledLay); + midLay->addStretch(1); + bottomRoot->addLayout(midLay, 1); + + // 右:返回(占满两行高度) + m_back = button("返回", "", bottom); + m_back->setFixedSize(170, 100); + m_back->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字 + connect(m_back, &QPushButton::clicked, this, &WashPage::onBack); + bottomRoot->addWidget(m_back, 0, Qt::AlignVCenter); + + root->addWidget(bottom); + + // 故障代码轮流循环显示(2s/条) + m_faultTimer = new QTimer(this); + m_faultTimer->setInterval(2000); + connect(m_faultTimer, &QTimer::timeout, this, &WashPage::rotateFaults); + m_faultTimer->start(); + + connect(ctx->comm(), &CarCommunication::telemetryUpdated, this, &WashPage::refresh); + updateGating(); + refresh(); +} + +QString WashPage::washModeTag() const +{ + return m_mode == Clean ? "clean" : "disinfect"; +} + +// 底部三个泵的数值:柱塞泵/空压机跟右侧参数的设定值(没改过就是出厂默认值), +// 叶片泵本页没有对应参数,先取遥测的实时流量 +void WashPage::refreshPumpValues() +{ + // 这里读的是配置值,一并按协议量程夹取(柱塞泵 0-24、空压机 0-340 L/min), + // 否则旧配置里超范围的历史值会与控件显示、实际上报的数值对不上 + m_valPlunger->setText(QString("%1 L/min").arg(qBound(0.0, ctx()->getDouble(ctx()->keyPumpFlow(), 20), 24.0), 0, 'f', 1)); + m_valAir->setText(QString("%1 L/min").arg(qBound(0.0, ctx()->getDouble(ctx()->keyAirFlow(), 100), 340.0), 0, 'f', 1)); + m_valLeaf->setText(QString("%1 L/min").arg(ctx()->telemetry.flowB, 0, 'f', 1)); +} + +void WashPage::onStart() +{ + ctx()->comm()->sendWashStart(washModeTag()); + ctx()->washRunning = true; + updateGating(); +} + +void WashPage::onStop() +{ + ctx()->comm()->sendWashStop(); + ctx()->washRunning = false; + updateGating(); +} + +void WashPage::onBack() +{ + if (ctx()->washRunning) { + ConfirmDialog::toast(this, "请先停止"); + return; + } + emit navigate(PageJob); +} + +void WashPage::onFactory() +{ + if (ConfirmDialog::confirm(this, "恢复出厂值", "确定将参数恢复为出厂值?")) + resetFactory(); +} + +void WashPage::resetFactory() +{ + if (m_mode == Clean) { + m_pumpFlow->setValue(20); // 出厂 20 + ctx()->setDouble(ctx()->keyPumpFlow(), 20); + ctx()->comm()->sendSetParam(ctx()->keyPumpFlow(), 20); + } else { + m_foamRatio->setValue(6); // 出厂 6 + m_foamPercent->setValue(3); // 出厂 3% + m_airFlow->setValue(100); // 出厂 100 + ctx()->setDouble(ctx()->keyFoamRatio(), 6); + ctx()->setDouble(ctx()->keyFoamPercent(), 3); + ctx()->setDouble(ctx()->keyAirFlow(), 100); + ctx()->comm()->sendSetParam(ctx()->keyFoamRatio(), 6); + ctx()->comm()->sendSetParam(ctx()->keyFoamPercent(), 3); + ctx()->comm()->sendSetParam(ctx()->keyAirFlow(), 100); + } + refreshPumpValues(); // 底部数值跟着回到出厂值 + ConfirmDialog::toast(this, "已恢复出厂值"); +} + +void WashPage::updateGating() +{ + bool running = ctx()->washRunning; + m_start->setEnabled(!running); + m_stop->setEnabled(running); + m_back->setEnabled(!running); +} + +void WashPage::refresh() +{ + const Telemetry& t = ctx()->telemetry; + + if (!t.commOk) m_banner->showAlarm("通信异常"); + else m_banner->clear(); + + // 以泵状态同步运行状态 + if (t.plunger || t.air || t.leaf) ctx()->washRunning = true; + else if (t.washMode == "--") ctx()->washRunning = false; + updateGating(); + + m_chassisCard->setField(0, QString("%1%").arg(t.soc)); + m_chassisCard->setField(1, QString("%1V").arg(t.volt, 0, 'f', 0)); + m_chassisCard->setField(2, t.chassisFaults.isEmpty() ? "无" : t.chassisFaults.join(";")); + m_armCard->setField(0, t.pos); + m_armCard->setField(1, t.pose); + m_gasCard->setField(0, t.gasHost == "detecting" ? "检测中" : (t.gasHost == "fault" ? "故障" : "待机")); + m_gasCard->setField(1, QString("%1 ppb").arg(t.gasConc, 0, 'f', 0)); + + m_washCard->setField(m_fMode, t.washMode); + m_washCard->setField(m_fLevelA, QString("%1L").arg(t.levelA, 0, 'f', 0)); + m_washCard->setField(m_fLevelFoam, QString("%1L").arg(t.levelFoam, 0, 'f', 0)); + m_washCard->setField(m_fFlowA, QString("%1 L/min").arg(t.flowA, 0, 'f', 1)); + m_washCard->setField(m_fFlowB, QString("%1 L/min").arg(t.flowB, 0, 'f', 1)); + m_washCard->setField(m_fGasFlow, QString("%1 L/min").arg(t.gasFlow, 0, 'f', 1)); + m_washCard->setField(m_fPress, QString("%1 MPa").arg(t.press, 0, 'f', 2)); + m_washCard->setField(m_fAirPress, QString("%1 MPa").arg(t.airPress, 0, 'f', 2)); + m_washCard->setField(m_fFoam, QString("%1").arg(t.foam, 0, 'f', 1)); + + // 指示灯 + m_ledRun->setState(ctx()->washRunning ? LedIndicator::Green : LedIndicator::Off); + m_ledPlunger->setState(t.plunger ? LedIndicator::Green : LedIndicator::Off); + m_ledAir->setState(t.air ? LedIndicator::Green : LedIndicator::Off); + m_ledLeaf->setState(t.leaf ? LedIndicator::Green : LedIndicator::Off); + m_ledLevel->setState(t.levelA < 10 ? LedIndicator::RedBlink : LedIndicator::Off); + m_ledD1->setState(t.d1open ? LedIndicator::Green : LedIndicator::Off); + m_ledD2->setState(t.d2open ? LedIndicator::Green : LedIndicator::Off); + m_ledFault->setState((!t.washFaults.isEmpty() || !t.pumpFaults.isEmpty()) + ? LedIndicator::RedBlink : LedIndicator::Off); + + // 底部三个泵的数值 + refreshPumpValues(); + + // 故障代码显示(静态更新,循环由 rotateFaults 驱动) + rotateFaults(); +} + +void WashPage::rotateFaults() +{ + const Telemetry& t = ctx()->telemetry; + QStringList all = t.washFaults + t.pumpFaults; + if (all.isEmpty()) { + m_washCard->setField(m_fAirFault, ""); + m_washCard->setField(m_fPlungerFault, ""); + return; + } + m_faultIndex = m_faultIndex % all.size(); + QString code = all[m_faultIndex]; + m_washCard->setField(m_fAirFault, code); + m_washCard->setField(m_fPlungerFault, code); + m_faultIndex = (m_faultIndex + 1) % all.size(); +} diff --git a/pages/washpage.h b/pages/washpage.h new file mode 100644 index 0000000..31800ff --- /dev/null +++ b/pages/washpage.h @@ -0,0 +1,86 @@ +#ifndef WASHPAGE_H +#define WASHPAGE_H + +#include "base.h" + +class TitleBar; +class AlarmBanner; +class DataCard; +class LedIndicator; +class ParamControl; +class QPushButton; +class QLabel; +class QVBoxLayout; +class QTimer; + +// ===================== 洗消页(⑨消除作业 / ⑩消毒作业 共用) ===================== +// 自包含页面:三列骨架、中央洗消卡、右侧启停与参数区、底部指示灯条都在本文件里。 +// 两个页面只差 mode(标题、洗消标识、参数项及出厂值),调整布局改这一处即可。 +class WashPage : public BasePage +{ + Q_OBJECT +public: + enum Mode { Clean, Disinfect }; + + explicit WashPage(AppContext* ctx, Mode mode, QWidget* parent = nullptr); + +private slots: + void onStart(); + void onStop(); + void onBack(); + void onFactory(); + void refresh(); + void rotateFaults(); + +private: + void updateGating(); + void resetFactory(); + void refreshPumpValues(); // 刷新底部三个泵的数值 + QString washModeTag() const; // "clean" / "disinfect" + + Mode m_mode; + + TitleBar* m_titleBar = nullptr; + AlarmBanner* m_banner = nullptr; + DataCard* m_chassisCard = nullptr; + DataCard* m_armCard = nullptr; + DataCard* m_gasCard = nullptr; + DataCard* m_washCard = nullptr; + QPushButton* m_start = nullptr; + QPushButton* m_stop = nullptr; + QPushButton* m_back = nullptr; + QPushButton* m_factory = nullptr; + QVBoxLayout* m_paramsLayout = nullptr; + + // 参数:消除用柱塞泵流量;消毒用发泡倍数 / 泡沫比例 / 空压机流量 + ParamControl* m_pumpFlow = nullptr; + ParamControl* m_foamRatio = nullptr; + ParamControl* m_foamPercent = nullptr; + ParamControl* m_airFlow = nullptr; + + // 底部指示灯 + LedIndicator* m_ledRun = nullptr; + LedIndicator* m_ledPlunger = nullptr; + LedIndicator* m_ledAir = nullptr; + LedIndicator* m_ledLeaf = nullptr; + LedIndicator* m_ledLevel = nullptr; + LedIndicator* m_ledD1 = nullptr; + LedIndicator* m_ledD2 = nullptr; + LedIndicator* m_ledFault = nullptr; + + // 底部第二行:三个泵的当前流量值 + QLabel* m_valPlunger = nullptr; + QLabel* m_valAir = nullptr; + QLabel* m_valLeaf = nullptr; + + // 中央洗消卡字段索引 + int m_fMode = -1, m_fLevelA = -1, m_fLevelFoam = -1; + int m_fFlowA = -1, m_fFlowB = -1, m_fGasFlow = -1; + int m_fPress = -1, m_fAirPress = -1, m_fFoam = -1; + int m_fAirFault = -1, m_fPlungerFault = -1; + + QTimer* m_faultTimer = nullptr; + int m_faultIndex = 0; +}; + +#endif // WASHPAGE_H diff --git a/tools/__pycache__/can_sender.cpython-311.pyc b/tools/__pycache__/can_sender.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fe0a332ec76afcfb5880c10bab44e27ab2166fb GIT binary patch literal 23152 zcmcJ1Yg80xwrEv%H8jw?5ilwi0t!)tZV-`&hNt2K71Ss(Lz=DzQ3R~+R%DtxZ6-NF zLbT233BD$6CKwVmb|xlaOq@7p&CGS~y63K{oLXnGdR^Dby|F=l9ddKdSy}TZcki#d zi|z(QoH+%x`+MyDJ@&W1{k`^AA4Eq-Qt;gP%k9G#*HhGY=MKF1G#rYY(jildq+ zj^sP2pTvla>o_ir}7u^)g#Wclnu}$$@TvGxU-;~HDG$nC~O^djsretnWQwo>dl-f?2!?=`R(dKY2 z6`+<|3^0Ow4qzm=1Yi`m6rhe<1~8gS0~o_C2N=t-0OPoHfbrZ4fC*d%z(g(+U=o)F za1pl>U^2G~U<#LQPUUjiRl{`CV($6ZsHW$*)c}`pYXC0gase*m)&fl9@&GR9@&U4( z9$-3W0JwrH0GPoQ0?g#r0nFlx0IuZL16;)w1I*?&0L(E*o7q;CImVn0FxI>RV4OJv zV7xgKV1hXdV4`^?z$Ei3fQ!u80F%u*t*W-jrsug5NM$uw3UCcq1~AvWmMcG^!uH~I zD$1OD9b7kj%|}r*#i<&}o3qjOwVI+H-@86><2GAWQO}OQF+Bc{r^iRGjlcP>_u>ef z$37Z;qXJh%rZum0Nm#qh+)n~%Qy&4laJzkN0E zo#WNW$Vb1wI{wzj-#OkGzw^%c=l$bX?gHtfyLTr~4UeDs0Nj&~OOL+3=6&nz3w}<@|Ppy z10Q-nx!@iB;5)~ux=yRPgY~-4LMAs_fz%a1^uNTkkx6pwCv)yF0 zws&=|HFbBh6PI3N_j27;#?D&ac+?DRcRW6QhcFX3J^ty1iTCe5`u&?wkw})!V7<i>Mw;Tpg2`sAf6P{JrzPdYc zdDM?F0PMVQXX4W@C%$~wCk$V`>Kz@$Mki&h+|dZFICr6rnrykPMjI5UNDsw9j{5ut zxkR$)VY9iZaj$9*KTnwIW z-fXe31;~LmqXn|K4U-SMl#bCR$!R$zG-ACR0Uy5^{v zZ!>qAPBtS>%0BaE2I87s!6K2~7#6;(#vLIR?GzE06$De)S z{oqa1@^5`Qe#PtN99qscUnCf-q_h@&I$L>oJV7^8hNWn)zRspk9+8L58dLSk6MtDm7l-v z*j#e(L>GU=(rq-E4;qV_4;ewJ93;?SsOZ+`A3SC~c*1tb*y)3N+nZ&twX3Vcaxh2@ zd$bm_wYjC;YT>gWlS+6kX8|}UuQsZ`VjyfV%u(?$I>BieI52o%iVD+Y5YSJL>EfLg z19j+#cBVUx=duR2gC|Poq@BCZg$e+x^F|G z(uWd;xQmNL-3mX(RJ1?4AD~FkzzBVh27b~1LpK1JHqsz!ir7)qETWbXC~8DRR47W* zV#KLIykzn4i<}ljQ0)^z5V1b>V5VIS!lJ3?aUR4YF`t4U6+jNnFU6lQei;U(Ay|%p zMUakQ1po_nAh|X9OmKR%ZT>dM25&v0>;>SU#jqqywHa7zW%?)f1ioJFq@Enh>#nPOC$ zz@*KrrJ}SPklN{xdiGPTu-JG_8>p{!d)Le&&$OI|cD=h2l2LAH!utua_hS!4n=tpRKU2I?$pVf?}f3oGy76=n# z%O!xia!FTyUoGmY{$zTP{)g5-wu-v#!md4H)E^|A3(6oA66QM zqpesC-(!cRO&bhW@6lFN#{r>sI=F|NqZ_C**vjhq-te-zy2|3pN*;wchw+42th~)+ z^)R5lk9gEusvAgUKU5LU$V|b4`gp>ZfSUj#I7ov4H=^PPOPwc&GhD|gI`|ICh=eE|e%uG-t_z&>DCS+Rrq9y=8*^VgxEHx#Z0`=Iu8 zJqV-vsIdn?dndmHQpr&hRS4r-mq&G|-6xs8=GT}yj7PiqO29M2YheLkWn=pJ3k6R8 zYQfO4i$zz8&TTrsNsP*nqA~;~L#~_BVnBi038ZE3KxlBwa&&O{`-1BVn+}{hu)e|V zd{~hM7$bly!D#`lCRhrt1O5$#hNgn}N^>x-JRvWI>wlNL!a!qFhfxEPXP%OUb6UVw zWPBcM*mwbK>`zaVb|k?@`I{BnPJ`S;)sxOb2H43sz?;O)qsgCO(lp7UD;ojI-DEBT z(z7Bx5iKXJW+TrVPm-Aokt3K}b=f*O4}&XA&_i7&PXsLRn#lrBc9PYUC)#B0=;-J= z(JXso(0i!8?U3yMQ01YGGy0|{s1ej8`Q!0&EN=4 zI}I0XSNFQ~?-z|!UMm%sCMhnCyVL z!mPdP-=UTZ?_D4%ioW1g9MefduX%Dm0oe`ghgL;)V7@^ep6(~%v-C*~r(IB=jCgWC zA<`KsOXp-VFo3T^K&F309B~df$zd3d#{3olIpO?vbY}tZM8KS!YvGMY%tSi*9T=z~ zJ06}$+-Z;nlivg37EIAUG6qPRfybfQh8PHDku;UFVyNQEHX&uL^O*D4$ZnCzlbAe# z$y1uzCy47u8n2a$%kTMs4a-ry$_DRO)lga_&id9 zvvTS%=188WqJgC&(15f(iH5XsSbzrNs{x&_MX(tFOlGIslj-iV^uSQwWjRS;@6479 z2OVMLkLr075ck1rc?G~Mtp@r57=7Foc5Y_htQ?}Rgt?M0#wy*@-;^_B=4c>Pp`?Kl zo@rPN?6^RGJ@`)$po&mVWtih&APesPTm;UZLq#jFTg$7u;3l8UhlJZ5 zKmGL6qw)RqL)(i?rJfmSY$Rx%m#tk zFo)2($3!AM0 z2lGf1hWmPLA&`<@+p9jD#mVX!`)>{-cU@ef%lbGRR+~nvm4|65uD9i7YC5=rC#JqW zT)=li)ESI^2|#eG!48RjVE^ELSaxe_X&Ls@54CZy{;n8OovR(H91b6$N7lJ3M(Iy# zKHMa-r4m~@+A6ZuqP9lT)(GT(j+RjL>mcO3ke0BMS?zyK_a$71oe-RWg>)Q0ugS2d zI=s%KI$ShocPQj}NZ|s!7Hp?r#`-5upd6pJJIujDMFpM*MfkVzFjR$vC?z$>aKPx~ zo9U?Lp@O|9pkjan6;$V3D5}WH?X;2Uwv+1rG=Fw1;j+TBr}nv*5I%XQUPV%xT5UNGs-<(~6yuR_rsU z6*nU-T)dHIetr>M5+A5brsvjVlNGi4B#QYY$^>77pc(<0Y9Bz?W&}9t=FI>mivi@s z2Ta!@@CdH?XcuSpL=vaG*&g2AVS^2L-lI0Q^B$(dY;5&}ar&-qb0_>7$Zv$@q{TNm zuY_bh;bfiIZ1hCRj@B;YQBPPat{{!R-xiM+(rd;rPXuhZ89Oa-C_7UL>R^V*`3|!5 z{OgeV6?iTG3^SEkHKE>VJby%FR!GbWfmtCB{j^g*q`y)oB(HTEoyHkcemQ!Bki42i zr;E%QiCH5sYXYg)3CXK4+GPxxWy;h=LUJ~VJ|;3b5|bk^If2y0Lh|z@@pzG0EitPF zX7y7=Kh0tVW-bR{=qwOZ@}!hJk;#{se1XZA(}x7e+~(kje#9iE7^D<~$P`FSfxr|5 zW9)Yw8_5t;)=DXBXIm1H`O$nBO(XFmRbooMl#(wpdWq2sj6OJ*mqyb4b1acDNQ^;X z408-f%2w6>XPJ}JPuMW-(6M=m=z$xz>Mf+67T{%l&&6AemW>1xvTr4F|&(f6+<-=a>R*5SN;?e?XX@Pt9 zs9H=elak9srd(pm1*Tk)%Q^RkrdOJCNGMJa$g1&wgJZ!{CTvOMI(b6dCS=M4G8Q;x zng<(?paP~!^I$UsTanXH>^Y{fIHbTy-t=l;$N3N1VcdW-JENr;?J_(O=#uvhnG;5S zP8Bbo#q0TVknkwH7ThkJT^vBgcZRI{Knp|YVUbCfm~??jpTBjFi%got;Az+#t*cCk zyP+TpZrz~m>%g{y!G0e7GLz#tYImTd{qzg?QwJ^|H(JHaHGs<8HSp&lkw3s| z!3o2x+=YEu(DkUvcZew^Qc8))luAsgz?25?Ns(JfB_3YV3$j6m@LbLc0shBl54Rt5 zB;xoUdD#`LWkce@it&l@8ZPXK@nHEF9M5TSIUSNqL?~9P2A9>1SPNy8peSE0_HvOS z^sq<|&Lt!s_IaKdzu;Pgr2nMyE6W$9{54!u2)!z_{vq*U^Tg}siH{CF_Gx7l{whlz zrF>>CCM16q7yCs1#m>v$kYlHi^ub2R>Xi0TmaB^VC2`3i`Oi4ERH&6jWdL=` z(@}GH_?=pBLp|xU>|&L&mhpsj_vxDF4e6RCNo*}=m_|fRC zyu$nqMc#q8a@dK>ACJ3Eqb=#(>*ZN%4XsCx<{e_KHomjHvrQ)Z;(9RI*-vxi%_wp% zL)MODgQ^+|!{ead_Vc8VH~*jB*EW&0qOAmiz?4tV;v*L_xP)M-491ig=KbQj_w1*{ zH2`LpXTW7^kjV``@{h*9yhO;qzWwM+H$gph_HqA5#C7@CU@Vq5e(6>3sVn9AJz4pN z{n`0F6%}4N0<4GE$&oqoS{CQ1wl)BMv%u-{WgEc$dGg}>c?D<(ef#YkFx|b4$zn0j zo*V!48oJ&p7vMk5v$rTjmvi*oleN-k*WM1sKpy2fRji5c^t)g zlp``%pWFbhNo+A29{u_)A6ePDHHf7oojhbIaAo|~dpWk%K%p3Yvg3~7pjH6ZlaRdp z@ms$EgW;SUttY0gy18mgMPp-KV{_GxYGTdgiGsk2h8-B75A+y3u@F;TQ?;{xPX&n% z4ydl&>GALotxu2FZTTFa=iPpRnmI@+7KWT;~3m@8dO%#;!{hYyV$bEms^i!0YlE7#vky`O%6 z_XCxb1MH{t%@(7qErzZr^14= z;r!XG5EHYc#H`^SH|;jKd2v;Vw5sIZ7Oc~QO0jIGRJPM!E4_@039rJ0S7E~0a>C2M z31@|H7CB~k8ivv0hON?utszPKN^wkFwLx07;a>6miu?Qny;!J+Xfb<)IRoiq_pCmlAE zuTEpJ8~>F0ApOB^Vb^Z4qCu)?5MvtzZ3Bwv(r?0-3E|7eV$1IJJfI)w9~i|=yQEFK z#MoVecGp-`Y=7ZQ>m-~uk?A&C7X~j&aK$7)fm85li>;tgWeOjBhSaCFt6c$8b$^O5 zb)I$xEG~mWl|>}j50aM91IP?Z@)zDNs z)yG6o5tw?wbaFa%SZcxM(X_$!L8x_~rVpllec^UkBJ={PHE|l>t_j9J0@R>f|1)sc z{P6UidAMC;4-c;CL8y11wlAVD(yq0l#e?thO#wf*gN={R?M7-ibOj9fjl|#A1I)Jj zqDVXm$8lPI`gC@k6^+Gwk3GU3X%E@C_oeT!qMhwDUQ|d+N86+MLw41v#dgLOz;8bV zlqw>a8}_IpL^Ajt*7N|Zs1Pj19)YSlunny%rt#;>%bGQf6Pj~y$+@V@8ct`AzD!>Y zF{lSQ!&ZgUgTC;=@4-#WZW4-Rnzyap|LLR$vok2`%IVT%QanRb2jKEuBXxr6Q6HdA z&^ghv6|b!n@Fp&w^Ez*`S(cJ44$Rx(63HxgOyCj}Sk6~j`HtKwcGBU1dnRB&zo(so za{w|Wn@oVDdKol{ln)Kjf@Kny*B+I==&v-JlLQ8mU8W;ut0%mowz;mpW{)Sbac9-G z=IYH26+1kUV62YuUENj;-`v}D3HXU!} zyN-I)9p+9RClH<}TPK=dHe)c4)gv~TWQ_Hw;Y`@0L0dA5M^j(1qo&cLK4k9k#BVd7 zZ0R!c9NrM%d0V$NC&t4Z>aw6MXBXJ)Gq5>f2CHT$9vmVVF`P5PMiFD=I5=DAYHfvt z&}C}p_#!L@)6&JA1pg6>M{Vt~T5!fqrjZJe?P9~^3pxBhLHz%N*Ydxh)56vUn1PmG zO}>(LD_P1Z5u-|_s8TS5X0)ewzPj^5t(25Cd>n?!(F&2-C@~uaW+TScy;|3Ab?)i! z5Sb+svqWH)JY*t-$juK5h247}tPvykiOh=<^P<4KI2IM{sGd?KX`-i8@ezy17B72u z*`;MJtGIZLw0O z_Y+07Mq+EuMLX5b#-WU{#N?|PZvv?2v?edHv`-t^+&qqC@>JR&F_T6W`$*UIfsvoY=yE8xBc5|DHaW!6xc*tgb zR`W&OZ|g=)e=4|d`9tv^i^an2QsH*N|Np@Bz$C0}5ZOkFZ4}tXvD76){KW{!E0rCK zOLQJPr**2QmQjhxGkNfRIPmSq|H!0t%g`hsdK!#rb^S~~1M>w<^p8LOXu)p#PwMKN z+BMXl*F@HqsQvu+QUC20|qm9C(-ZraD;d zfO44&o3REAZXB$NZ-&p*tjN{@LK${Sh zZ{p3Q;DPT6C0-OnnarguAlK9&+Qr;+3!q)XMciEa)NQydm^TZkU)nQME`Mf9_L(Wu zgDJTc3*cx*5N+lHc$5`HxpD!NtAZ%A7eJX4L>adL%IBY%a`iJ)u9=TA(2|eK<<?SpflIrJGAf#C!?FpoBh%YToCz4%&9vS>lr>lcJwyddli3&Jj05O(Q;u*-t9V)@J#o3p((22pNW0Oboolobo0tPG;8 zS^#Br5amMZSrbHAyMR152T^X}>K33Mw+7K}TR^Vc1A8X75M8z7nJMdoD0gzZ7EsgO zL9`8A;{s^+1k)~nOM8PTe} znHso2KdcyRXl-AFc#!eGNl?F!g<@uvo3Dz2J+k3CEUTM(hwtAlw7dCtlw z*am~O$PHyZ0(V$9*)=da<93F|iYsy7a~SVVlgA#i!3DGMfExL0>OMSMP2IN6IN0de z=ueBHV538g8y%gDf14wyg@0z(JY8G1s%C8oJojp@?+we%#f^d92)$wb+I;xa=g^*T z&PHi)KWIeH6G%$)LRn|3@H! zodp$LD}$QFZg*IQpm~o&P|*-4Ld-SUk*L~_I-{c$Wifs0t`|MWCh;LlOx%i zFW7L`&3oou?+0&s9bZ8<6TkgEocyuCLH7Ut!`t|cn6>%)Fhg=_s_oK6e4<{kVW-BU z!9bFHeQ!)|Zf94na_b5%q(1<}-l*JM92BjfJYnHv#3(v-HNv0eBbC7ox-3 z`acR|TMFrgd~ino6b*i*CjY+`%dKtcX?HV_F==|s$@Vp{T=#i;+gaFhMAq{?1fC?Y z7u(i%jf5OzqKZIX!j*=cuu1G5lSu&NeYGZZZS{LGWwdYgz>Qk7vC|Wwe02sIJlu4s ztKDR_$PECMLCjoY3ELY1k}2E5VoqTBhAE*vEEYz#h$Sv!6N`)kt62HtI7mu3m}}&* zFujnl>_^L3{x28=X`nGIVEm6jZ;MahzTMOtO){0ST3av!#cp;Ihb?S)VvE;SK%Rfu zWJArrW0x_o-StQah4-PhlILGH`9tMS2o3G4s;>edDt`Me!i_>mn8(w9H}`Z9b-$UAgr{K`k;XNG}(-2EPYT4?mG$De-+N#Uo3E{@1T1!<0d_ABTS zkH0<*9~$wO2koMK9jxmGkGi|Ng?yO8$JnV&w*8P5_8+o;%d$$RX7P;p>NWNSHmhw* zFU!KYE;$)qt8dN9LLZzK!?C-*bssB#(rNk(Uj7VHd6vc334gaq))!y=R@M}l^y7OU zgFYL-eHI+(CmX~AIV@O4^KIQm-eUH|RPb%KqvlTQF7)yq9qg9Eg*iEl+!^<1Kx&Lu z@&N%)B%wkCiwBJDEQk0LP&JRPvkN|h;=fz@5(Y*2jjx+qTlil>2-!a6KSS5&2!;{h zyodh)09;4!jO<^eo;YU-pI7z!))TT??bVtP)qd8swYQ3}A^^&7ru|-xwhcia3LP-lonIy&} zFeZOgiK|1{@Crs4CB`T)Mt?-num)+GG3phGc|~Ae@kebKE^yPM(HK)JF|`6y3kRy2 zFj9c^!;9Pu#;lW=bpo@_Pg6Xc+OZ+=AggbMyJai-y%Bq|Uzk zxPJVFhL;Rixk(lLX6|_T%xN5WY49c7VBSlvpY5gLBQe^DvoUYPI8V6Z&;9&Xq5$88 zApdYyySw2pd!?er{uoiaN7C*Q$iGrySKG+Xg{;#1FJkR>NX!m_+2Jqk#*sA`x?W<| z3(R_dXsOFS3y0Rb$}rStuyelqf$8y=xNO+$-g|!$#%z|D%>uI-rAzmQZXjkb2GmhXDqLP(@=eMbA!QAG z4l3OZI_kA;W63G!JB5Ty;*L)^zYH8(zYW*E)&pi4n%IZhnEqpf$MNFxkX6)XO4>|8 zn>hxdVcywKx1!^m`tyYYO^#Y}Y{iX5#|-Xs?moZoYU6O~#h0(VJW_G9+MRwUYc&3= z_-{|hI_6dd6VqA?BS0j-BFoWCHlBwhZp1**eS{`5(ZGCwZ22YKnq3rs*jr zfo7&C1lbfF=ZM9>F)G?Y{sJ6P4)XVHSe!!*gLC3CN6c8lQb&{mV$n4XbOA^NGZN!N zz%=@_iy+5LF;Nk!F(yGs%AQiA^Z9kc`YpuyEfXumub9$6=(m{#!n&=*i4%q-VNv!} zID~^|b%{{AOZI3fO}sO2Dgq<4RHi{F*e-h_DNUk~lsgrLF_Bb0At4?erHK{dvZkUj zMn`1>%@)}c4R1y-?gRMLcn&0C`jK7!yYsDrlkdpzKTVMI~ZX0%drC7Al%Z z?Q*9UVHgF#p_y75LBr?o6p$K5!;!8664dl^FqiWK9Zi#=cp7SHx*iATX$T>i7~lhu zT1-#@$r|)3AWm_Sus9`SQiKEskrfc5xJXz`9L6eOWe9DESB=ODh@rxgoG-zh6IZ=^ zn~?Fsm?qYlA~1`GwvL1gOY&!Ulbjv!5gJ#Q+b(2Pk7wr~3Kd?;&x50bAQ7;zMi@}z`QGKfRwOR-Hn^IHEa}Z3r)&B?5)v+Z2 literal 0 HcmV?d00001 diff --git a/tools/__pycache__/rc_sender.cpython-311.pyc b/tools/__pycache__/rc_sender.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..245463dec6ca04caf088648338e5a000dfe282bd GIT binary patch literal 4913 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 3] |= m + else: + d[b >> 3] &= ~m & 0xFF + + +def get_bits(d, start, ln): + v = 0 + for i in range(ln): + b = start + i + if d[b >> 3] & (1 << (b & 7)): + v |= 1 << i + return v + + +def get_signed(d, start, ln): + raw = get_bits(d, start, ln) + sign = 1 << (ln - 1) + return (raw ^ sign) - sign + + +def checksum(can_id, dlc, d): + return (sum((can_id >> (8 * i)) & 0xFF for i in range(4)) + dlc + sum(d)) & 0xFF + + +def pack(can_id, d): + return struct.pack("setAlignment(Qt::AlignCenter); m_label->setProperty("alert", true); - m_label->setStyleSheet("font-size:30px;font-weight:700;"); + m_label->setStyleSheet("font-size:45px;font-weight:700;"); lay->addWidget(m_label); hide(); // 默认隐藏 diff --git a/widgets/confirmdialog.cpp b/widgets/confirmdialog.cpp index 5613889..3820138 100644 --- a/widgets/confirmdialog.cpp +++ b/widgets/confirmdialog.cpp @@ -15,47 +15,56 @@ ConfirmDialog::ConfirmDialog(QWidget* parent) if (parent) setGeometry(parent->rect()); setStyleSheet("background-color: rgba(0,0,0,102);"); - QWidget* panel = new QWidget(this); - panel->setObjectName("ConfirmPanel"); - panel->setFixedSize(560, 260); - panel->setStyleSheet(QString( + m_panel = new QWidget(this); + m_panel->setObjectName("ConfirmPanel"); + m_panel->setFixedSize(700, 420); + m_panel->setStyleSheet(QString( "QWidget#ConfirmPanel{background-color:%1;border:2px solid %2;border-radius:16px;}") .arg(C::PANEL, C::BORDER)); - QVBoxLayout* lay = new QVBoxLayout(panel); + QVBoxLayout* lay = new QVBoxLayout(m_panel); lay->setContentsMargins(28, 24, 28, 24); - lay->setSpacing(16); + lay->setSpacing(0); // 标题与正文不留间隙(与“操作说明”弹窗一致) - m_title = new QLabel("", panel); - m_title->setStyleSheet(QString("color:%1;font-size:28px;font-weight:700;").arg(C::TEXT)); + m_title = new QLabel("", m_panel); + m_title->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;font-weight:700;").arg(C::TEXT)); m_title->setAlignment(Qt::AlignCenter); lay->addWidget(m_title); + lay->addSpacing(60); // 标题与正文之间空一行 - m_body = new QLabel("", panel); - m_body->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT)); + m_body = new QLabel("", m_panel); + m_body->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;").arg(C::TEXT)); m_body->setAlignment(Qt::AlignCenter); m_body->setWordWrap(true); - lay->addWidget(m_body, 1); + lay->addWidget(m_body); // 不再拉伸:标题与正文紧挨着 + lay->addStretch(1); // 剩余空间放在正文与按钮之间(标题/正文保持自然高度,标题顶到最上面) QHBoxLayout* btnLay = new QHBoxLayout(); btnLay->addStretch(1); - QPushButton* ok = new QPushButton("确认", panel); - ok->setProperty("variant", "primary"); - ok->setFixedSize(160, 72); + QPushButton* ok = new QPushButton("确认", m_panel); + // 常态灰底、选中(悬停/焦点)蓝底白字。 + // 注意:该表设了 background-color,会压住全局的 variant/焦点规则,所以状态必须写在这里。 + ok->setFixedSize(180, 80); + ok->setStyleSheet(QString( + "QPushButton{background-color:%1;color:%2;border:1px solid %3;border-radius:12px;font-size:60px;}" + "QPushButton:hover,QPushButton:focus{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}") + .arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY)); connect(ok, &QPushButton::clicked, this, &ConfirmDialog::accept); - QPushButton* cancel = new QPushButton("取消", panel); - cancel->setFixedSize(160, 72); - cancel->setStyleSheet(cancelButtonStyle(40)); // 选中时红底白字 - connect(cancel, &QPushButton::clicked, this, &ConfirmDialog::reject); + m_cancel = new QPushButton("取消", m_panel); + m_cancel->setFixedSize(180, 80); // 与“确认”同尺寸 + m_cancel->setStyleSheet(cancelButtonStyle(60)); // 与“确认”同字号;选中时红底白字 + connect(m_cancel, &QPushButton::clicked, this, &ConfirmDialog::reject); btnLay->addWidget(ok); - btnLay->addWidget(cancel); + btnLay->addSpacing(80); // “确认”与“取消”之间留 40px + btnLay->addWidget(m_cancel); btnLay->addStretch(1); + lay->addSpacing(30); // 正文与按钮之间留 30px lay->addLayout(btnLay); QVBoxLayout* root = new QVBoxLayout(this); root->setContentsMargins(0, 0, 0, 0); root->addStretch(1); - root->addWidget(panel, 0, Qt::AlignHCenter); + root->addWidget(m_panel, 0, Qt::AlignHCenter); root->addStretch(1); } @@ -70,6 +79,27 @@ bool ConfirmDialog::confirm(QWidget* parent, const QString& title, const QString return dlg.exec() == QDialog::Accepted; } +void ConfirmDialog::message(QWidget* parent, const QString& title, const QString& text) +{ + ConfirmDialog dlg(parent); + dlg.m_panel->setFixedSize(900, 600); // 正文行数少 → 面板收窄 + dlg.m_cancel->hide(); // 只保留“确认” + dlg.m_title->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;font-weight:700;").arg(C::TEXT)); + dlg.m_body->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;").arg(C::TEXT)); + dlg.m_title->setWordWrap(true); + dlg.m_title->setContentsMargins(0, 20, 0, 0); // 标题文字距该行顶部 10px + dlg.m_body->setContentsMargins(0, 0, 0, 0); + dlg.m_body->setAlignment(Qt::AlignCenter); // 正文居中(水平+垂直) + if (QBoxLayout* lay = qobject_cast(dlg.m_panel->layout())) { + lay->setSpacing(0); // 统一间隔置 0,各段间距单独控制 + lay->setContentsMargins(28, 28, 28, 28); // 内容距面板四边 28px + //lay->insertSpacing(lay->count() - 1, 20); // “确认”按钮上方留 20px + } + dlg.setTitle(title); + dlg.setText(text); + dlg.exec(); +} + void ConfirmDialog::toast(QWidget* parent, const QString& text, int ms) { QDialog* d = new QDialog(parent); diff --git a/widgets/confirmdialog.h b/widgets/confirmdialog.h index e98dbb4..df75595 100644 --- a/widgets/confirmdialog.h +++ b/widgets/confirmdialog.h @@ -4,6 +4,7 @@ #include class QLabel; +class QPushButton; // ===================== 确认弹窗 ===================== // 标题 + 正文 + 确认/取消;另提供 2s 自动消失的轻提示(toast) @@ -19,12 +20,17 @@ public: // 模态确认,返回是否“确认” static bool confirm(QWidget* parent, const QString& title, const QString& text); + // 独立信息弹窗(仅“确认”按钮,替代系统 QMessageBox;正文 50px) + static void message(QWidget* parent, const QString& title, const QString& text); + // 轻提示:显示 text,ms 毫秒后自动消失(用于“修改成功”等) static void toast(QWidget* parent, const QString& text, int ms = 2000); private: QLabel* m_title = nullptr; QLabel* m_body = nullptr; + QWidget* m_panel = nullptr; + QPushButton* m_cancel = nullptr; }; #endif // CONFIRMDIALOG_H diff --git a/widgets/datacard.cpp b/widgets/datacard.cpp index 7791f65..80e444c 100644 --- a/widgets/datacard.cpp +++ b/widgets/datacard.cpp @@ -11,14 +11,14 @@ DataCard::DataCard(const QString& title, QWidget* parent) setProperty("card", true); QVBoxLayout* lay = new QVBoxLayout(this); - lay->setContentsMargins(16, 0, 16, 14); - lay->setSpacing(8); + lay->setContentsMargins(24, 0, 24, 20); + lay->setSpacing(16); - // 标题栏(高 44px) + // 标题栏 m_title = new QLabel(title, this); - m_title->setFixedHeight(44); + m_title->setFixedHeight(60); m_title->setProperty("cardTitle", true); - m_title->setStyleSheet(QString("color:%1;font-size:24px;font-weight:700;border-bottom:1px solid %2;") + m_title->setStyleSheet(QString("color:%1;font-size:42px;font-weight:700;border-bottom:1px solid %2;") .arg(C::TEXT, C::BORDER)); lay->addWidget(m_title); @@ -36,15 +36,15 @@ int DataCard::addField(const QString& label, const QString& value, bool big) int index = m_values.size(); QHBoxLayout* row = new QHBoxLayout(); - row->setSpacing(8); + row->setSpacing(16); QLabel* lab = new QLabel(label + ":", this); lab->setProperty("sub", true); - lab->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB)); + lab->setStyleSheet(QString("color:%1;font-size:32px;").arg(C::TEXT_SUB)); QLabel* val = new QLabel(value, this); val->setStyleSheet(QString("color:%1;%2").arg(C::TEXT, - big ? "font-size:64px;font-weight:700;" : "font-size:30px;font-weight:700;")); + big ? "font-size:50px;font-weight:700;" : "font-size:42px;font-weight:700;")); row->addWidget(lab); row->addWidget(val, 1); @@ -89,3 +89,20 @@ void DataCard::setHighlight(bool on) else setStyleSheet(QString()); } + +// 字段行文字从左缘缩进到「中间偏左」:行首弹性 2 : 数值行尾弹性 3 +void DataCard::indentRows() +{ + QVBoxLayout* lay = qobject_cast(layout()); + if (!lay) + return; + for (int i = 0; i < lay->count(); ++i) { + QHBoxLayout* row = qobject_cast(lay->itemAt(i)->layout()); + if (!row || row->count() != 2) // 字段行是 [标签][数值];标题与末尾 stretch 不是子布局 + continue; + if (row->itemAt(0)->spacerItem()) // 已缩进过,避免重复插入 + continue; + row->insertStretch(0, 2); + row->setStretch(2, 3); // 插入后 [0]=行首弹性 [1]=标签 [2]=数值 + } +} diff --git a/widgets/datacard.h b/widgets/datacard.h index 620fff1..dcd5d9a 100644 --- a/widgets/datacard.h +++ b/widgets/datacard.h @@ -19,6 +19,7 @@ public: void setField(int index, const QString& value); void setAlarm(int index, bool on); void setHighlight(bool on); // 主色描边突出 + void indentRows(); // 字段行整体缩进到「中间偏左」(行首 : 行尾 = 2 : 3),只需调用一次 private: QLabel* m_title = nullptr; diff --git a/widgets/faulttable.cpp b/widgets/faulttable.cpp index ac7fd78..58bc95f 100644 --- a/widgets/faulttable.cpp +++ b/widgets/faulttable.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "global.h" FaultTable::FaultTable(QWidget* parent) @@ -14,23 +15,26 @@ FaultTable::FaultTable(QWidget* parent) lay->setContentsMargins(0, 0, 0, 0); lay->setSpacing(10); - // 分页行(上一页 / 页码 / 下一页) + // 分页行(上一页 / 页码 / 下一页)——水平居中,字号与按钮加大 QHBoxLayout* nav = new QHBoxLayout(); + nav->setSpacing(50); nav->addStretch(1); m_prev = new QPushButton("上一页", this); - m_prev->setFixedSize(160, 64); + m_prev->setFixedSize(240, 100); + m_prev->setStyleSheet("font-size:50px;"); connect(m_prev, &QPushButton::clicked, this, [this]() { if (m_page > 0) { setPage(m_page - 1); emit pageChanged(m_page); } }); m_pageLabel = new QLabel("1/1", this); m_pageLabel->setAlignment(Qt::AlignCenter); - m_pageLabel->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT)); - m_pageLabel->setFixedWidth(140); + m_pageLabel->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT)); + m_pageLabel->setFixedWidth(180); m_next = new QPushButton("下一页", this); - m_next->setFixedSize(160, 64); + m_next->setFixedSize(240, 100); + m_next->setStyleSheet("font-size:50px;"); connect(m_next, &QPushButton::clicked, this, [this]() { if (m_page < pageCount() - 1) { setPage(m_page + 1); emit pageChanged(m_page); } }); @@ -38,6 +42,7 @@ FaultTable::FaultTable(QWidget* parent) nav->addWidget(m_prev); nav->addWidget(m_pageLabel); nav->addWidget(m_next); + nav->addStretch(1); // 尾部也留弹性 → 整行居中 lay->addLayout(nav); // 表格 @@ -47,24 +52,127 @@ FaultTable::FaultTable(QWidget* parent) m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_table->setSelectionBehavior(QAbstractItemView::SelectRows); m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setTabKeyNavigation(false); // 关掉视图自带的 Tab 导航,改由下方 eventFilter 按行切换 m_table->verticalHeader()->setVisible(false); + m_table->horizontalHeader()->setFocusPolicy(Qt::NoFocus); // 表头不参与 Tab + m_table->verticalHeader()->setFocusPolicy(Qt::NoFocus); m_table->setAlternatingRowColors(true); m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); m_table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); m_table->setColumnWidth(0, 120); m_table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); - m_table->verticalHeader()->setDefaultSectionSize(100); - m_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + m_table->horizontalHeader()->setFixedHeight(kHeaderHeight); // 固定表头高度(供对齐) + // 每页 6 行等分,平均填满表格可用高度 + m_table->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); connect(m_table, &QTableWidget::cellClicked, this, [this](int row, int) { int global = m_page * m_pageSize + row; if (global >= 0 && global < m_data.size()) emit rowClicked(global); }); + m_table->installEventFilter(this); // 拦截 Tab,实现按行切换 + m_table->viewport()->setMouseTracking(true); // 悬停整行高亮 + m_table->viewport()->installEventFilter(this); // 鼠标移出视口 → 取消高亮 + connect(m_table, &QAbstractItemView::entered, this, + [this](const QModelIndex& idx) { setHoverRow(idx.row()); }); lay->addWidget(m_table, 1); refresh(); } +QWidget* FaultTable::view() const { return m_table; } +QWidget* FaultTable::prevButton() const { return m_prev; } +QWidget* FaultTable::nextButton() const { return m_next; } + +bool FaultTable::eventFilter(QObject* obj, QEvent* ev) +{ + if (obj == m_table->viewport() && ev->type() == QEvent::Leave) { + setHoverRow(-1); // 鼠标移出表格 → 取消悬停高亮 + return QWidget::eventFilter(obj, ev); + } + if (obj != m_table) + return QWidget::eventFilter(obj, ev); + + // 焦点进入表格时选中一行:正向(Tab)进末行→回首行、否则选首行;反向(Shift+Tab)同理选末行 + if (ev->type() == QEvent::FocusIn) { + QFocusEvent* fe = static_cast(ev); + const int last = m_table->rowCount() - 1; + const int col = qMax(0, m_table->currentColumn()); + int row = -1; + if (fe->reason() == Qt::TabFocusReason) { + if (last >= 0 && (m_table->currentRow() < 0 || m_table->currentRow() >= last)) + row = 0; + } else if (fe->reason() == Qt::BacktabFocusReason) { + if (last >= 0 && (m_table->currentRow() < 0 || m_table->currentRow() <= 0)) + row = last; + } + if (row >= 0) { + m_table->setCurrentCell(row, col); + m_table->selectRow(row); // 显式整行选中(只设当前单元格时可能只高亮一格) + } + } + + // 仅键盘(Tab/Shift+Tab)移走焦点时清除选中;鼠标点击/弹窗抢焦点时保留,便于看清当前行 + if (ev->type() == QEvent::FocusOut) { + QFocusEvent* fe = static_cast(ev); + if (fe->reason() == Qt::TabFocusReason || fe->reason() == Qt::BacktabFocusReason) + m_table->clearSelection(); + return QWidget::eventFilter(obj, ev); + } + + // Tab / Shift+Tab:按行切换选中(每一行作为一个单位);回车/空格:等同鼠标点击 + if (ev->type() == QEvent::KeyPress) { + QKeyEvent* ke = static_cast(ev); + // 方向键在本机是"旋钮"键位(只给 ParamControl 调值),表格里不参与导航: + // 换行只走 Tab/Shift+Tab,激活只走回车/空格 + if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down + || ke->key() == Qt::Key_Left || ke->key() == Qt::Key_Right) + return true; + + if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) { + const bool back = (ke->key() == Qt::Key_Backtab) || (ke->modifiers() & Qt::ShiftModifier); + const int next = m_table->currentRow() + (back ? -1 : 1); + if (next >= 0 && next < m_table->rowCount()) { + m_table->setCurrentCell(next, qMax(0, m_table->currentColumn())); + m_table->selectRow(next); // 整行选中 + return true; // 消费事件,焦点留在表格内 + } + // 已在首/末行:不拦截,焦点按 Tab 顺序移到下一个控件 + } else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter + || ke->key() == Qt::Key_Space) { + // 键盘激活:与鼠标 cellClicked 走同一个信号,弹窗逻辑只有一份 + const int row = m_table->currentRow(); + const int global = m_page * m_pageSize + row; + if (row >= 0 && global >= 0 && global < m_data.size()) { + emit rowClicked(global); + return true; + } + } + } + return QWidget::eventFilter(obj, ev); +} + +// 鼠标悬停的整行高亮:颜色与“选中”一致(主色蓝底白字) +void FaultTable::setHoverRow(int row) +{ + if (row == m_hoverRow) + return; + if (m_hoverRow >= 0) { + for (int c = 0; c < m_table->columnCount(); ++c) + if (QTableWidgetItem* it = m_table->item(m_hoverRow, c)) { + it->setData(Qt::BackgroundRole, QVariant()); + it->setData(Qt::ForegroundRole, QVariant()); + } + } + m_hoverRow = row; + if (m_hoverRow >= 0) { + for (int c = 0; c < m_table->columnCount(); ++c) + if (QTableWidgetItem* it = m_table->item(m_hoverRow, c)) { + it->setData(Qt::BackgroundRole, QColor(C::PRIMARY)); + it->setData(Qt::ForegroundRole, QColor("#FFFFFF")); + } + } +} + void FaultTable::setData(const QVector& data) { m_data = data; @@ -87,6 +195,7 @@ int FaultTable::pageCount() const void FaultTable::refresh() { + m_hoverRow = -1; // 单元格会重建,悬停行失效 m_table->setRowCount(0); m_table->setRowCount(m_pageSize); diff --git a/widgets/faulttable.h b/widgets/faulttable.h index 258d5b7..49cd6ce 100644 --- a/widgets/faulttable.h +++ b/widgets/faulttable.h @@ -17,17 +17,29 @@ class FaultTable : public QWidget public: explicit FaultTable(QWidget* parent = nullptr); + // 表头高度:同页其他控件可据此与表格内容区对齐 + static const int kHeaderHeight = 70; + void setData(const QVector& data); void setPage(int p); int page() const { return m_page; } int pageCount() const; + // 供外部设置 Tab 顺序用(定义在 .cpp,避免用到未完整声明的类型) + QWidget* view() const; + QWidget* prevButton() const; + QWidget* nextButton() const; + signals: void pageChanged(int page); void rowClicked(int globalIndex); +protected: + bool eventFilter(QObject* obj, QEvent* ev) override; // 拦截 Tab(按行切换选中)、回车/空格(等同点击) + private: void refresh(); + void setHoverRow(int row); // 鼠标悬停的整行高亮 QTableWidget* m_table = nullptr; QLabel* m_pageLabel = nullptr; @@ -36,6 +48,7 @@ private: QVector m_data; int m_page = 0; int m_pageSize = 6; + int m_hoverRow = -1; }; #endif // FAULTTABLE_H diff --git a/widgets/ledindicator.cpp b/widgets/ledindicator.cpp index 5e1fb4f..85cc0c1 100644 --- a/widgets/ledindicator.cpp +++ b/widgets/ledindicator.cpp @@ -9,9 +9,11 @@ QSize LedIndicator::sizeHint() const QFont f = font(); f.setPixelSize(m_textPx); const QFontMetrics fm(f); + const int textW = fm.horizontalAdvance(m_text); + if (m_dotAfter) + return QSize(textW + 10 + m_radius * 2 + 10, 52); // 文字 + 间距 + 圆点区 + 右侧 10px // 宽度 = 文字起点(圆点区 + 左侧 10px) + 文字宽度 + 右侧 10px - const int w = textLeft() + fm.horizontalAdvance(m_text) + 10; - return QSize(w, 52); // 高度与构造函数 setFixedHeight(52) 一致 + return QSize(textLeft() + textW + 10, 52); // 高度与构造函数 setFixedHeight(52) 一致 } LedIndicator::LedIndicator(const QString& text, QWidget* parent) @@ -55,6 +57,13 @@ void LedIndicator::paintEvent(QPaintEvent*) QPainter p(this); p.setRenderHint(QPainter::Antialiasing); + // 文字度量:圆点位置取决于文字宽度(圆点在文字后时) + QFont f = p.font(); + f.setPixelSize(m_textPx); + p.setFont(f); + const QFontMetrics fm(f); + const int textW = fm.horizontalAdvance(m_text); + // 中心圆点颜色 QColor color = QColor(C::DISABLED); // 灰 switch (m_state) { @@ -70,7 +79,7 @@ void LedIndicator::paintEvent(QPaintEvent*) if ((m_state == RedBlink || m_state == YellowBlink) && !m_on) color.setAlpha(102); - const QPointF c(m_radius + 8, height() / 2.0); + const QPointF c(m_dotAfter ? textW + 10 + m_radius : m_radius + 8, height() / 2.0); // 外发光 if (color.alpha() > 0) { @@ -93,11 +102,9 @@ void LedIndicator::paintEvent(QPaintEvent*) p.setPen(QPen(QColor(C::BORDER), 1)); p.drawEllipse(c, m_radius, m_radius); - // 文字 + // 文字(圆点在文字后时,文字从控件左边缘开始) p.setPen(QColor(C::TEXT)); - QFont f = p.font(); - f.setPixelSize(m_textPx); - p.setFont(f); - const QRectF textRect(textLeft(), 0, width() - textLeft() - 10, height()); + const QRectF textRect(m_dotAfter ? 0 : textLeft(), 0, + m_dotAfter ? textW : width() - textLeft() - 10, height()); p.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, m_text); } diff --git a/widgets/ledindicator.h b/widgets/ledindicator.h index cd961de..8e7568e 100644 --- a/widgets/ledindicator.h +++ b/widgets/ledindicator.h @@ -19,6 +19,7 @@ public: void setText(const QString& t); void setTextPixelSize(int px) { m_textPx = px; updateGeometry(); update(); } // 文字字号 void setRadius(int r) { m_radius = r; updateGeometry(); update(); } // 圆形指示点半径 + void setDotAfterText(bool after) { m_dotAfter = after; updateGeometry(); update(); } // 圆点画在文字后面 State state() const { return m_state; } QSize sizeHint() const override; // 宽度按文字实际宽度计算,避免文字被裁 @@ -35,6 +36,7 @@ private: bool m_on = true; int m_radius = 16; // 圆形指示点 32×32 int m_textPx = 24; // 文字字号 + bool m_dotAfter = false; // false=圆点在文字前,true=圆点在文字后 }; #endif // LEDINDICATOR_H diff --git a/widgets/numpaddialog.cpp b/widgets/numpaddialog.cpp index 0c82b3a..4a72c7b 100644 --- a/widgets/numpaddialog.cpp +++ b/widgets/numpaddialog.cpp @@ -18,101 +18,81 @@ NumPadDialog::NumPadDialog(QWidget* parent) if (parent) setGeometry(parent->rect()); setStyleSheet("background-color: rgba(0,0,0,102);"); - // 面板(居中) - QWidget* panel = new QWidget(this); - panel->setObjectName("NumPadPanel"); - panel->setFixedSize(600, 730); - panel->setStyleSheet(QString( + // 面板(默认居中,可用 setAnchorWidget 定位到某个控件旁边) + m_panel = new QWidget(this); + m_panel->setObjectName("NumPadPanel"); + m_panel->setStyleSheet(QString( "QWidget#NumPadPanel{background-color:%1;border:4px solid %2;border-radius:16px;}" "QPushButton[numpad=\"true\"]{background-color:#2D3947;color:#E6EDF3;border:1px solid #3D4A59;border-radius:12px;font-size:40px;}" "QPushButton[numpad=\"true\"]:pressed{background-color:#2E7BE6;color:#FFFFFF;}" "QPushButton[numpad=\"true\"]:focus{background-color:#3D8BFD;color:#FFFFFF;border:2px solid #8ABFFF;}") .arg(C::PANEL, C::BORDER)); - QVBoxLayout* panelLay = new QVBoxLayout(panel); - panelLay->setContentsMargins(24, 40, 24, 50); - panelLay->setSpacing(50); + m_panelLay = new QVBoxLayout(m_panel); // 输入回显 - m_echo = new QLineEdit(panel); + m_echo = new QLineEdit(m_panel); m_echo->setReadOnly(true); m_echo->setEchoMode(QLineEdit::Password); m_echo->setAlignment(Qt::AlignCenter); - m_echo->setFixedSize(510, 100); - m_echo->setStyleSheet("font-size:70px;"); - m_echo->setFocusPolicy(Qt::NoFocus); // 纯显示回显:不参与 Tab 选中 + m_echo->setFocusPolicy(Qt::NoFocus); // 纯显示回显:不参与 Tab 选中 m_echo->setAttribute(Qt::WA_TransparentForMouseEvents); // 且点击穿透,不响应鼠标 - panelLay->addWidget(m_echo, 0, Qt::AlignHCenter); + m_panelLay->addWidget(m_echo, 0, Qt::AlignHCenter); - // 数字键区(1-9, 0, 退格, 清除) - QGridLayout* grid = new QGridLayout(); - grid->setSpacing(30); + // 数字键区(1-9, 0, 退格, 清除/小数点) + m_grid = new QGridLayout(); const QString digits[3][3] = {{"1","2","3"},{"4","5","6"},{"7","8","9"}}; for (int r = 0; r < 3; ++r) for (int c = 0; c < 3; ++c) { - QPushButton* b = new QPushButton(digits[r][c], panel); + QPushButton* b = new QPushButton(digits[r][c], m_panel); b->setProperty("numpad", true); - b->setStyleSheet("font-size:50px;"); - b->setFixedSize(150, 120); connect(b, &QPushButton::clicked, this, [this, d = digits[r][c]](){ onDigit(d); }); - grid->addWidget(b, r, c); + m_grid->addWidget(b, r, c); + m_digits << b; } - QPushButton* b0 = new QPushButton("0", panel); + QPushButton* b0 = new QPushButton("0", m_panel); b0->setProperty("numpad", true); - b0->setStyleSheet("font-size:50px;"); - b0->setFixedSize(150, 120); connect(b0, &QPushButton::clicked, this, [this](){ onDigit("0"); }); - grid->addWidget(b0, 3, 1); + m_grid->addWidget(b0, 3, 1); + m_digits << b0; - QPushButton* bBack = new QPushButton("退格", panel); - bBack->setProperty("numpad", true); - bBack->setStyleSheet("font-size:40px;"); - bBack->setFixedSize(150, 120); - connect(bBack, &QPushButton::clicked, this, &NumPadDialog::onBackspace); - grid->addWidget(bBack, 3, 0); + m_backKey = new QPushButton("退格", m_panel); + m_backKey->setProperty("numpad", true); + connect(m_backKey, &QPushButton::clicked, this, &NumPadDialog::onBackspace); + m_grid->addWidget(m_backKey, 3, 0); - QPushButton* bClear = new QPushButton("清除", panel); - bClear->setProperty("numpad", true); - bClear->setStyleSheet("font-size:40px;"); - bClear->setFixedSize(150, 108); - connect(bClear, &QPushButton::clicked, this, &NumPadDialog::onClear); - grid->addWidget(bClear, 3, 2); + m_clearKey = new QPushButton("清除", m_panel); + m_clearKey->setProperty("numpad", true); + connect(m_clearKey, &QPushButton::clicked, this, [this]() { + if (m_paramMode) onDot(); + else onClear(); + }); + m_grid->addWidget(m_clearKey, 3, 2); - // 确认 / 取消(与数字键一致的灰底,选中时发亮) - QPushButton* bOk = new QPushButton("确认", panel); - bOk->setProperty("numpad", true); - bOk->setStyleSheet("font-size:60px;"); - bOk->setFixedSize(160, 140); - connect(bOk, &QPushButton::clicked, this, &NumPadDialog::accept); + // 确认 / 取消 + m_okKey = new QPushButton("确认", m_panel); + m_okKey->setProperty("numpad", true); + connect(m_okKey, &QPushButton::clicked, this, &NumPadDialog::accept); - QPushButton* bCancel = new QPushButton("取消", panel); - bCancel->setProperty("numpad", true); - bCancel->setStyleSheet(cancelButtonStyle(60)); // 选中时红底白字 - bCancel->setFixedSize(160, 140); - connect(bCancel, &QPushButton::clicked, this, &NumPadDialog::reject); + m_cancelKey = new QPushButton("取消", m_panel); + m_cancelKey->setProperty("numpad", true); + connect(m_cancelKey, &QPushButton::clicked, this, &NumPadDialog::reject); // 确认 / 取消:单独一行、水平居中,并与数字键盘留出间隔 QHBoxLayout* okRow = new QHBoxLayout(); okRow->setSpacing(80); okRow->addStretch(1); - okRow->addWidget(bOk); - okRow->addWidget(bCancel); + okRow->addWidget(m_okKey); + okRow->addWidget(m_cancelKey); okRow->addStretch(1); - panelLay->addLayout(grid); - panelLay->setAlignment(grid, Qt::AlignHCenter); - //panelLay->addSpacing(20); - panelLay->addLayout(okRow); - panelLay->addStretch(1); - - // 居中放置面板 - QVBoxLayout* root = new QVBoxLayout(this); - root->setContentsMargins(0, 0, 0, 0); - root->addStretch(1); - root->addWidget(panel, 0, Qt::AlignHCenter); - root->addStretch(1); + m_panelLay->addLayout(m_grid); + m_panelLay->setAlignment(m_grid, Qt::AlignHCenter); + m_panelLay->addLayout(okRow); + m_panelLay->addStretch(1); + applySizes(); // 应用尺寸(默认大字版)并摆放面板 refreshEcho(); } @@ -128,6 +108,107 @@ void NumPadDialog::clear() refreshEcho(); } +// 整体不透明度(0.0~1.0) +void NumPadDialog::setOpacity(qreal opacity) +{ + setWindowOpacity(opacity); +} + +// 面板贴在参照控件左侧 gap 像素处弹出(垂直对齐其中心,并钳位到屏幕内) +void NumPadDialog::setAnchorWidget(QWidget* ref, int gap) +{ + m_anchor = ref; + m_gap = gap; + placePanel(); +} + +// 参数输入模式:回显显示实际数字(不掩码),“清除”键换成“小数点” +void NumPadDialog::setParamMode(bool on) +{ + m_paramMode = on; + m_echo->setEchoMode(on ? QLineEdit::Normal : QLineEdit::Password); + m_clearKey->setText(on ? "." : "清除"); + applySizes(); // 重设“小数点/清除”键的字号 +} + +// 紧凑尺寸(按键与面板更小),默认 false = 大字版 +void NumPadDialog::setCompact(bool on) +{ + m_compact = on; + applySizes(); +} + +// 按当前尺寸模式设置面板/按键的大小与字号,并重新摆放面板 +void NumPadDialog::applySizes() +{ + const int digitFont = m_compact ? 40 : 50; + const int fnFont = m_compact ? 30 : 40; + const int dotFont = m_compact ? 46 : 60; + const int okFont = m_compact ? 44 : 60; + const QSize digitSz = m_compact ? QSize(120, 70) : QSize(150, 120); + const QSize clearSz = m_compact ? QSize(120, 70) : QSize(150, 108); + const QSize okSz = m_compact ? QSize(130, 88) : QSize(160, 140); + + m_panel->setFixedSize(m_compact ? QSize(460, 600) : QSize(600, 730)); + m_panelLay->setContentsMargins(m_compact ? 20 : 24, m_compact ? 24 : 40, + m_compact ? 20 : 24, m_compact ? 26 : 50); + m_panelLay->setSpacing(m_compact ? 26 : 50); + m_grid->setSpacing(m_compact ? 18 : 30); + + m_echo->setFixedSize(m_compact ? QSize(400, 70) : QSize(510, 100)); + m_echo->setStyleSheet(QString("font-size:%1px;").arg(m_compact ? 48 : 70)); + + for (QPushButton* b : m_digits) { + b->setFixedSize(digitSz); + b->setStyleSheet(QString("font-size:%1px;").arg(digitFont)); + } + m_backKey->setFixedSize(digitSz); + m_backKey->setStyleSheet(QString("font-size:%1px;").arg(fnFont)); + + m_clearKey->setFixedSize(clearSz); + m_clearKey->setStyleSheet(QString("font-size:%1px;").arg(m_paramMode ? dotFont : fnFont)); + + m_okKey->setFixedSize(okSz); + m_okKey->setStyleSheet(QString("font-size:%1px;").arg(okFont)); + m_cancelKey->setFixedSize(okSz); + m_cancelKey->setStyleSheet(cancelButtonStyle(okFont)); // 选中时红底白字 + + placePanel(); +} + +// 计算面板位置:有参照控件 → 其左侧;否则居中 +void NumPadDialog::placePanel() +{ + const int w = m_panel->width(); + const int h = m_panel->height(); + int x = (width() - w) / 2; + int y = (height() - h) / 2; + + if (m_anchor && m_anchor->window()) { + // 面板放在参照控件左侧;对话框覆盖整个窗口,其局部坐标即窗口坐标 + const QPoint p = m_anchor->mapTo(m_anchor->window(), QPoint(0, 0)); + x = p.x() - m_gap - w; + y = p.y() + m_anchor->height() / 2 - h / 2; + } + + x = qBound(0, x, qMax(0, width() - w)); + y = qBound(0, y, qMax(0, height() - h)); + m_panel->move(x, y); +} + +// 小数点:仅允许一个,空值时补前导 0 +void NumPadDialog::onDot() +{ + if (m_value.contains('.')) + return; + if (m_value.isEmpty()) + m_value = "0"; + if (m_value.size() >= m_maxLen) + return; + m_value += '.'; + refreshEcho(); +} + void NumPadDialog::onDigit(const QString& d) { if (m_value.size() >= m_maxLen) diff --git a/widgets/numpaddialog.h b/widgets/numpaddialog.h index aace347..e29c576 100644 --- a/widgets/numpaddialog.h +++ b/widgets/numpaddialog.h @@ -2,11 +2,16 @@ #define NUMPADDIALOG_H #include +#include class QLineEdit; +class QPushButton; +class QGridLayout; +class QVBoxLayout; // ===================== 数字键盘弹窗(模态) ===================== // 半透明遮罩 + 键盘面板:顶部输入回显,数字键 1-9、0,退格、清除,确认/取消 +// 两种尺寸:默认大字版(密码页用)、紧凑版(参数输入用,setCompact(true)) class NumPadDialog : public QDialog { Q_OBJECT @@ -18,17 +23,41 @@ public: QString value() const { return m_value; } void clear(); + void setOpacity(qreal opacity); // 整体不透明度(默认 1.0) + void setAnchorWidget(QWidget* ref, int gap = 20); // 面板贴参照控件左侧弹出 + + // 参数输入模式:回显显示实际数字(不掩码),并把“清除”键换成“小数点” + void setParamMode(bool on); + + // 紧凑尺寸(按键/面板更小);默认 false = 大字版 + void setCompact(bool on); + private slots: void onDigit(const QString& d); void onBackspace(); void onClear(); + void onDot(); private: void refreshEcho(); + void placePanel(); + void applySizes(); + QWidget* m_panel = nullptr; + QWidget* m_anchor = nullptr; // 定位参照控件(如参数输入框) + QGridLayout* m_grid = nullptr; + QVBoxLayout* m_panelLay = nullptr; QLineEdit* m_echo = nullptr; + QVector m_digits; // 数字键 0-9 + QPushButton* m_backKey = nullptr; + QPushButton* m_clearKey = nullptr; + QPushButton* m_okKey = nullptr; + QPushButton* m_cancelKey = nullptr; QString m_value; int m_maxLen = 4; + int m_gap = 20; + bool m_paramMode = false; + bool m_compact = false; }; #endif // NUMPADDIALOG_H diff --git a/widgets/paramcontrol.cpp b/widgets/paramcontrol.cpp index 7de9627..8847610 100644 --- a/widgets/paramcontrol.cpp +++ b/widgets/paramcontrol.cpp @@ -1,8 +1,11 @@ #include "paramcontrol.h" +#include "numpaddialog.h" #include #include #include +#include #include +#include #include "global.h" ParamControl::ParamControl(const QString& name, const QString& unit, @@ -16,56 +19,50 @@ ParamControl::ParamControl(const QString& name, const QString& unit, lay->setSpacing(6); // 参数名 - QLabel* nameLabel = new QLabel(name, this); - nameLabel->setProperty("sub", true); - nameLabel->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB)); - lay->addWidget(nameLabel); + m_nameLabel = new QLabel(name, this); + m_nameLabel->setProperty("sub", true); + m_nameLabel->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT_SUB)); + lay->addWidget(m_nameLabel); - // 数值 + 加减 - QHBoxLayout* row = new QHBoxLayout(); - row->setSpacing(12); + // 数值输入框 + 单位(同一行;框内只显示数值) + m_valRow = new QHBoxLayout(); + m_valRow->setSpacing(12); - QPushButton* minus = new QPushButton("-", this); - minus->setFixedSize(96, 96); - minus->setStyleSheet(QString("font-size:44px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;") - .arg(C::PANEL_HI, C::TEXT, C::BORDER)); - connect(minus, &QPushButton::clicked, this, &ParamControl::onMinus); + m_valueEdit = new QLineEdit(this); + m_valueEdit->setReadOnly(true); + m_valueEdit->setAlignment(Qt::AlignCenter); + m_valueEdit->setFocusPolicy(Qt::StrongFocus); + m_valueEdit->setFixedWidth(150); // 输入框长度(按需调整) + m_valueEdit->setStyleSheet("font-size:40px;font-weight:700;"); + m_valueEdit->installEventFilter(this); + m_valRow->addStretch(0); // 行首弹性空间:setCentered(true) 时置 1,数值行即居中 + m_valRow->addWidget(m_valueEdit); - m_valueLabel = new QLabel(this); - m_valueLabel->setAlignment(Qt::AlignCenter); - m_valueLabel->setStyleSheet(QString("color:%1;font-size:52px;font-weight:700;").arg(C::TEXT)); - m_valueLabel->setMinimumWidth(180); + m_unitLabel = new QLabel(unit, this); + m_unitLabel->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT_SUB)); + // 同一行右侧还有按钮时,给单位固定占位,保证各参数的按钮 x 一致 + // (110 取全项目最长单位“L/min”在 40px 字号下的宽度约 101px) + if (withSave || withFactory) + m_unitLabel->setFixedWidth(110); + m_valRow->addWidget(m_unitLabel); - QPushButton* plus = new QPushButton("+", this); - plus->setFixedSize(96, 96); - plus->setStyleSheet(QString("font-size:44px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;") - .arg(C::PANEL_HI, C::TEXT, C::BORDER)); - connect(plus, &QPushButton::clicked, this, &ParamControl::onPlus); - - row->addWidget(minus); - row->addWidget(m_valueLabel, 1); - row->addWidget(plus); - lay->addLayout(row); - - // 保存 / 恢复出厂值(可选) - if (withSave || withFactory) { - QHBoxLayout* btnRow = new QHBoxLayout(); - btnRow->setSpacing(12); - if (withSave) { - m_save = new QPushButton("保存", this); - m_save->setProperty("variant", "primary"); - m_save->setMinimumHeight(56); - connect(m_save, &QPushButton::clicked, this, &ParamControl::saveClicked); - btnRow->addWidget(m_save); - } - if (withFactory) { - m_factory = new QPushButton("恢复出厂值", this); - m_factory->setMinimumHeight(56); - connect(m_factory, &QPushButton::clicked, this, &ParamControl::factoryResetClicked); - btnRow->addWidget(m_factory); - } - lay->addLayout(btnRow); + // 保存 / 恢复出厂值(可选):跟在单位后面,与输入框同一行、整体左对齐 + if (withSave) { + m_save = new QPushButton("保存", this); + m_save->setProperty("variant", "primary"); + m_save->setFixedSize(140, 60); // 固定宽高,不再随布局拉伸 + connect(m_save, &QPushButton::clicked, this, &ParamControl::saveClicked); + m_valRow->addWidget(m_save); } + if (withFactory) { + m_factory = new QPushButton("恢复出厂值", this); + m_factory->setMinimumHeight(56); + connect(m_factory, &QPushButton::clicked, this, &ParamControl::factoryResetClicked); + m_valRow->addWidget(m_factory); + } + + m_valRow->addStretch(1); + lay->addLayout(m_valRow); refresh(); } @@ -76,32 +73,90 @@ void ParamControl::setValue(double v) refresh(); } +QWidget* ParamControl::editor() const +{ + return m_valueEdit; +} + void ParamControl::setEnabled(bool enabled) { QWidget::setEnabled(enabled); } -void ParamControl::onMinus() +void ParamControl::setCentered(bool on) { - m_value = qMax(m_min, m_value - m_step); - refresh(); - emit valueChanged(m_value); + m_nameLabel->setAlignment(on ? Qt::AlignCenter : (Qt::AlignLeft | Qt::AlignVCenter)); + m_valRow->setStretch(0, on ? 1 : 0); // 行首弹性空间与行尾均分 → 整行居中(含保存按钮) } -void ParamControl::onPlus() +// 旋钮接口:steps > 0 调大、< 0 调小(每格 = m_step,自动钳位到 [min, max]) +// 目前由数值框上的 ↑/↓ 键驱动,后续接面板机械旋钮也走这里 +void ParamControl::adjustByKnob(int steps) { - m_value = qMin(m_max, m_value + m_step); - refresh(); - emit valueChanged(m_value); + if (steps == 0) + return; + const double old = m_value; + setValue(old + steps * m_step); + if (m_value != old) + emit valueChanged(m_value); +} + +// 点击数值框 / 回车 → 弹出数字键盘(面板靠左、不透明度 80%);↑/↓ → 每档加减一次 +bool ParamControl::eventFilter(QObject* obj, QEvent* ev) +{ + if (obj == m_valueEdit) { + if (ev->type() == QEvent::MouseButtonPress) { + openNumPad(); + return true; + } + if (ev->type() == QEvent::KeyPress) { + QKeyEvent* ke = static_cast(ev); + if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) { + openNumPad(); + return true; + } + // 旋钮档:↑ 加一档、↓ 减一档(每档 = m_step)。Tab/Shift+Tab 走焦点链、 + // 空格激活按钮,各不重叠;按住方向键由按键自动重复继续调 + if (ke->key() == Qt::Key_Up) { + adjustByKnob(1); + return true; + } + if (ke->key() == Qt::Key_Down) { + adjustByKnob(-1); + return true; + } + } + } + return QWidget::eventFilter(obj, ev); +} + +void ParamControl::openNumPad() +{ + NumPadDialog dlg(window()); // 以顶层窗口为父:遮罩覆盖整屏 + dlg.setParamMode(true); // 参数模式:回显显示实际数字,“清除”换成“小数点” + dlg.setCompact(true); // 紧凑尺寸(不影响密码页的大键盘) + dlg.setAnchorWidget(m_valueEdit, 20); // 面板贴在输入框左侧 20px 处弹出 + dlg.setMaxLen(6); + dlg.setValue(m_step < 1.0 ? QString::number(m_value, 'f', 1) + : QString::number(m_value, 'f', 0)); + if (dlg.exec() != QDialog::Accepted) + return; + + bool ok = false; + const double v = dlg.value().toDouble(&ok); + if (!ok) + return; + const double old = m_value; + setValue(v); + if (m_value != old) + emit valueChanged(m_value); } void ParamControl::refresh() { - // 单位格式:整数不显示小数 - QString s; + // 框内只显示数值(单位在框后面的标签里) if (m_step < 1.0) - s = QString("%1").arg(m_value, 0, 'f', 1); + m_valueEdit->setText(QString::number(m_value, 'f', 1)); else - s = QString("%1").arg(m_value, 0, 'f', 0); - m_valueLabel->setText(s + " " + m_unit); + m_valueEdit->setText(QString::number(m_value, 'f', 0)); } diff --git a/widgets/paramcontrol.h b/widgets/paramcontrol.h index 76bf6f7..b19be5e 100644 --- a/widgets/paramcontrol.h +++ b/widgets/paramcontrol.h @@ -4,10 +4,13 @@ #include class QLabel; +class QLineEdit; class QPushButton; +class QHBoxLayout; // ===================== 参数调节控件 ===================== -// 参数名 + 大数值显示 + 减/加按钮 + 保存按钮(可选)+ 恢复出厂值按钮(可选) +// 参数名 + 数值输入框(点击/回车弹出数字键盘,↑/↓ 按档加减)+ 保存按钮(可选)+ 恢复出厂值按钮(可选) +// 旋钮接口 adjustByKnob():调大/调小,数值框的 ↑/↓ 走的就是这条路径 class ParamControl : public QWidget { Q_OBJECT @@ -19,18 +22,24 @@ public: void setValue(double v); double value() const { return m_value; } void setEnabled(bool enabled); + void setCentered(bool on); // 参数名与数值行水平居中(默认左对齐) + QWidget* editor() const; // 数值输入框(供页面编排 Tab 顺序) + +public slots: + // 旋钮接口:steps > 0 右旋调大、steps < 0 左旋调小;每格 = m_step + void adjustByKnob(int steps); signals: void valueChanged(double v); void saveClicked(); void factoryResetClicked(); -private slots: - void onMinus(); - void onPlus(); +protected: + bool eventFilter(QObject* obj, QEvent* ev) override; // 数值框:点击/回车 → 数字键盘,↑/↓ → 按档调值 private: void refresh(); + void openNumPad(); QString m_name; QString m_unit; @@ -38,7 +47,10 @@ private: double m_min; double m_max; double m_value; - QLabel* m_valueLabel = nullptr; + QLineEdit* m_valueEdit = nullptr; + QLabel* m_unitLabel = nullptr; + QLabel* m_nameLabel = nullptr; + QHBoxLayout* m_valRow = nullptr; QPushButton* m_save = nullptr; QPushButton* m_factory = nullptr; }; diff --git a/widgets/statusstrip.cpp b/widgets/statusstrip.cpp index fcc1904..6fd6880 100644 --- a/widgets/statusstrip.cpp +++ b/widgets/statusstrip.cpp @@ -1,6 +1,6 @@ #include "statusstrip.h" #include "appcontext.h" -#include "udpcommunication.h" +#include "car.h" #include #include #include @@ -10,54 +10,51 @@ StatusStrip::StatusStrip(AppContext* ctx, QWidget* parent) : QWidget(parent), m_ctx(ctx) { setObjectName("StatusStrip"); - setFixedHeight(94); + setFixedHeight(120); setStyleSheet(QString("QWidget#StatusStrip{background-color:%1;border-top:1px solid %2;}") .arg(C::PANEL, C::BORDER)); QHBoxLayout* lay = new QHBoxLayout(this); lay->setContentsMargins(36, 0, 24, 0); - lay->setSpacing(48); + lay->setSpacing(40); - auto makeVal = [this](const QString& label) { - QLabel* l = new QLabel(this); - l->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB)); - l->setProperty("label", label); - return l; - }; + // 底部不再显示底盘/液位数据(改由各页面卡片展示),只保留模式徽标 + 返回按钮 + //auto makeVal = [this](const QString& label) { + // QLabel* l = new QLabel(this); + // l->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT_SUB)); + // l->setProperty("label", label); + // return l; + //}; + //m_battery = makeVal("底盘电量"); + //m_voltage = makeVal("底盘电压"); + //m_levelA = makeVal("清水液位"); + //m_levelFoam = makeVal("泡沫液位"); + //lay->addWidget(m_battery); + //lay->addWidget(m_voltage); + //lay->addWidget(m_levelA); + //lay->addWidget(m_levelFoam); - m_battery = makeVal("底盘电量"); - m_voltage = makeVal("底盘电压"); - m_levelA = makeVal("清水液位"); - m_levelFoam = makeVal("泡沫液位"); - - lay->addWidget(m_battery); - lay->addWidget(m_voltage); - lay->addWidget(m_levelA); - lay->addWidget(m_levelFoam); lay->addStretch(1); - // 模式徽标(点击可切换行车/机械臂模式,模拟面板机械旋钮) - m_mode = new QPushButton("行车模式", this); - m_mode->setFixedSize(180, 48); - m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:24px;font-weight:700;border:none;") + // 模式徽标(仅显示:行车/机械臂由遥控器拨钮经串口回传,界面不参与切换) + m_mode = new QLabel("行车模式", this); + m_mode->setFixedSize(260, 80); + m_mode->setAlignment(Qt::AlignCenter); + m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:40px;font-weight:700;") .arg(C::SUCCESS)); - m_mode->setCursor(Qt::PointingHandCursor); - connect(m_mode, &QPushButton::clicked, this, [this]() { - Mode next = (m_ctx->mode == Mode::Drive) ? Mode::Arm : Mode::Drive; - m_ctx->setMode(next); - }); lay->addWidget(m_mode); // 返回按钮 m_back = new QPushButton("返回", this); - m_back->setFixedSize(144, 76); + m_back->setFixedSize(200, 80); + m_back->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字 connect(m_back, &QPushButton::clicked, this, &StatusStrip::backClicked); lay->addWidget(m_back); - connect(m_ctx->comm(), &UdpCommunication::telemetryUpdated, this, &StatusStrip::refresh); + connect(m_ctx->comm(), &CarCommunication::telemetryUpdated, this, &StatusStrip::refresh); connect(m_ctx, &AppContext::modeChanged, this, [this](Mode m) { m_mode->setText(m == Mode::Drive ? "行车模式" : "机械臂模式"); - m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:24px;font-weight:700;") + m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:40px;font-weight:700;") .arg(m == Mode::Drive ? C::SUCCESS : C::WARNING)); }); refresh(); @@ -73,11 +70,17 @@ void StatusStrip::setBackEnabled(bool enabled) m_back->setEnabled(enabled); } +void StatusStrip::setModeVisible(bool visible) +{ + m_mode->setVisible(visible); +} + void StatusStrip::refresh() { - const Telemetry& t = m_ctx->telemetry; - m_battery->setText(QString("底盘电量:%1%").arg(t.soc)); - m_voltage->setText(QString("底盘电压:%1V").arg(t.volt, 0, 'f', 0)); - m_levelA->setText(QString("清水液位:%1L").arg(t.levelA, 0, 'f', 0)); - m_levelFoam->setText(QString("泡沫液位:%1L").arg(t.levelFoam, 0, 'f', 0)); + // 底部已不显示遥测数据,保留空实现(仍连着 telemetryUpdated,方便以后加显示项) + //const Telemetry& t = m_ctx->telemetry; + //m_battery->setText(QString("底盘电量:%1%").arg(t.soc)); + //m_voltage->setText(QString("底盘电压:%1V").arg(t.volt, 0, 'f', 0)); + //m_levelA->setText(QString("清水液位:%1L").arg(t.levelA, 0, 'f', 0)); + //m_levelFoam->setText(QString("泡沫液位:%1L").arg(t.levelFoam, 0, 'f', 0)); } diff --git a/widgets/statusstrip.h b/widgets/statusstrip.h index 7f875ed..0de7e76 100644 --- a/widgets/statusstrip.h +++ b/widgets/statusstrip.h @@ -7,8 +7,8 @@ class QLabel; class QPushButton; class AppContext; -// ===================== 底部状态条(高 94px) ===================== -// 底盘电量、底盘电压、清水液位、泡沫液位 + 模式徽标 + 返回按钮 +// ===================== 底部状态条 ===================== +// 只保留:模式徽标(仅显示)+ 返回按钮。行车/机械臂由遥控器拨钮决定,界面不参与切换。 class StatusStrip : public QWidget { Q_OBJECT @@ -17,6 +17,7 @@ public: void setBackVisible(bool visible); void setBackEnabled(bool enabled); + void setModeVisible(bool visible); // 固定行车模式的页面(如底盘页)可隐藏模式徽标 signals: void backClicked(); @@ -26,11 +27,11 @@ private slots: private: AppContext* m_ctx; - QLabel* m_battery = nullptr; - QLabel* m_voltage = nullptr; - QLabel* m_levelA = nullptr; - QLabel* m_levelFoam = nullptr; - QPushButton* m_mode = nullptr; + //QLabel* m_battery = nullptr; // 底部不再显示数据 + //QLabel* m_voltage = nullptr; + //QLabel* m_levelA = nullptr; + //QLabel* m_levelFoam = nullptr; + QLabel* m_mode = nullptr; // 模式徽标(只显示,不参与切换) QPushButton* m_back = nullptr; }; diff --git a/widgets/titlebar.cpp b/widgets/titlebar.cpp index 400f1d0..946c6a3 100644 --- a/widgets/titlebar.cpp +++ b/widgets/titlebar.cpp @@ -1,14 +1,10 @@ #include "titlebar.h" #include "ledindicator.h" #include "appcontext.h" -#include "udpcommunication.h" -#include "confirmdialog.h" +#include "car.h" #include #include -#include -#include #include "global.h" -#include TitleBar::TitleBar(AppContext* ctx, QWidget* parent) : QWidget(parent), m_ctx(ctx) @@ -18,34 +14,38 @@ TitleBar::TitleBar(AppContext* ctx, QWidget* parent) setStyleSheet(QString("QWidget#TitleBar{background-color:%1;border-bottom:1px solid %2;}") .arg(C::PANEL, C::BORDER)); - // 左侧区(通信状态) + // 左侧区(车载端通信状态) QWidget* leftBox = new QWidget(this); QHBoxLayout* leftLay = new QHBoxLayout(leftBox); - leftLay->setContentsMargins(0, 0, 0, 0); + leftLay->setContentsMargins(260, 0, 0, 0); leftLay->setSpacing(20); m_comm = new LedIndicator("通信正常", leftBox); + m_comm->setFixedHeight(80); // 容纳放大后的文字,避免上下裁切 + m_comm->setTextPixelSize(45); m_comm->setState(LedIndicator::Green); leftLay->addWidget(m_comm); leftLay->addStretch(1); - // 右侧区(时间 + 软急停) + // 右侧区(遥控器状态:链路 + 电量) QWidget* rightBox = new QWidget(this); QHBoxLayout* rightLay = new QHBoxLayout(rightBox); - rightLay->setContentsMargins(0, 0, 0, 0); - rightLay->setSpacing(20); - m_time = new QLabel("", rightBox); - m_time->setStyleSheet(QString("color:%1;font-size:60px;").arg(C::TEXT_SUB)); - m_time->setAlignment(Qt::AlignCenter); - m_estop = new QPushButton("急停", rightBox); - m_estop->setProperty("variant", "estop"); - m_estop->setStyleSheet("font-size:60px;"); - m_estop->setFixedSize(180, 80); + rightLay->setContentsMargins(0, 0, 20, 0); + rightLay->setSpacing(30); rightLay->addStretch(1); - rightLay->addWidget(m_time); - rightLay->addWidget(m_estop); + m_rc = new LedIndicator("遥控器断开", rightBox); + m_rc->setFixedHeight(80); + m_rc->setTextPixelSize(45); + m_rc->setState(LedIndicator::RedBlink); + rightLay->addWidget(m_rc); + m_rcBat = new QLabel("--%", rightBox); + m_rcBat->setStyleSheet(QString("color:%1;font-size:60px;").arg(C::TEXT_SUB)); + m_rcBat->setAlignment(Qt::AlignCenter); + rightLay->addWidget(m_rcBat); // 左右两侧等宽 → 中间标题恰好居中于屏幕 - const int sideW = 380; // 需 >= 较宽一侧所需宽度(右侧:时间 + 急停) + // 左区需 260(左边距)+ 240(圆点区与留白 60 + 文字 4×45)= 500 + // 右区需 285(圆点区与留白 60 + 文字 5×45)+ 30 + 130(电量)+ 20 = 465 + const int sideW = 520; leftBox->setFixedWidth(sideW); rightBox->setFixedWidth(sideW); @@ -61,30 +61,16 @@ TitleBar::TitleBar(AppContext* ctx, QWidget* parent) lay->addWidget(m_title, 1); lay->addWidget(rightBox); - connect(m_estop, &QPushButton::clicked, this, [this]() { - if (m_ctx->estopTriggered) { - if (ConfirmDialog::confirm(window(), "急停复位", "确定复位急停并恢复系统?")) - m_ctx->resetEstop(); - } else { - m_ctx->triggerEstop(); - } - }); - - // 通信状态刷新 - connect(m_ctx->comm(), &UdpCommunication::commOkChanged, this, [this](bool ok) { + // 车载端通信状态刷新 + connect(m_ctx->comm(), &CarCommunication::commOkChanged, this, [this](bool ok) { if (ok) { m_comm->setText("通信正常"); m_comm->setState(LedIndicator::Green); } else { m_comm->setText("通信异常"); m_comm->setState(LedIndicator::RedBlink); } }); - // 急停状态刷新 - connect(m_ctx, &AppContext::estopChanged, this, &TitleBar::updateEstop); - updateEstop(); - - // 时钟 - m_clock = new QTimer(this); - connect(m_clock, &QTimer::timeout, this, &TitleBar::updateTime); - m_clock->start(1000); - updateTime(); + // 遥控器链路与电量刷新 + connect(m_ctx->rc(), &RcCommunication::linkChanged, this, &TitleBar::onRcLink); + connect(m_ctx->rc(), &RcCommunication::frameReceived, this, &TitleBar::onRcFrame); + onRcLink(m_ctx->rc()->linkOk()); } void TitleBar::setTitle(const QString& title) @@ -92,25 +78,19 @@ void TitleBar::setTitle(const QString& title) m_title->setText(title); } -void TitleBar::setEstopVisible(bool visible) +void TitleBar::onRcLink(bool ok) { - m_estop->setVisible(visible); -} - -void TitleBar::updateEstop() -{ - if (m_ctx->estopTriggered) { - m_estop->setText("复位"); - m_estop->setProperty("variant", "danger"); + if (ok) { + m_rc->setText("遥控器正常"); + m_rc->setState(LedIndicator::Green); } else { - m_estop->setText("急停"); - m_estop->setProperty("variant", "estop"); + m_rc->setText("遥控器断开"); + m_rc->setState(LedIndicator::RedBlink); + m_rcBat->setText("--%"); } - m_estop->style()->unpolish(m_estop); - m_estop->style()->polish(m_estop); } -void TitleBar::updateTime() +void TitleBar::onRcFrame(const RcFrame& f) { - m_time->setText(QTime::currentTime().toString("HH:mm")); + m_rcBat->setText(QString("%1%").arg(f.battery)); } diff --git a/widgets/titlebar.h b/widgets/titlebar.h index a0a835e..bef1aff 100644 --- a/widgets/titlebar.h +++ b/widgets/titlebar.h @@ -2,15 +2,14 @@ #define TITLEBAR_H #include -#include +#include "uart.h" class QLabel; -class QPushButton; class AppContext; class LedIndicator; // ===================== 顶条组件(高 98px) ===================== -// 左侧“通信状态:●正常/异常”,中央页面标题,右侧时间 + 软急停按钮 +// 左侧“车载端通信:●正常/异常”,中央页面标题,右侧“遥控器:●正常/断开 + 电量” class TitleBar : public QWidget { Q_OBJECT @@ -18,19 +17,17 @@ public: explicit TitleBar(AppContext* ctx, QWidget* parent = nullptr); void setTitle(const QString& title); - void setEstopVisible(bool visible); private slots: - void updateEstop(); - void updateTime(); + void onRcLink(bool ok); + void onRcFrame(const RcFrame& f); private: AppContext* m_ctx; - LedIndicator* m_comm; + LedIndicator* m_comm = nullptr; + LedIndicator* m_rc = nullptr; + QLabel* m_rcBat = nullptr; QLabel* m_title = nullptr; - QLabel* m_time = nullptr; - QPushButton* m_estop = nullptr; - QTimer* m_clock = nullptr; }; #endif // TITLEBAR_H diff --git a/wuhan.pro b/wuhan.pro index c077016..0de0cb8 100644 --- a/wuhan.pro +++ b/wuhan.pro @@ -1,8 +1,8 @@ -QT += core gui widgets network +QT += core gui widgets serialport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets -TARGET = wuhan +TARGET = Ui_wuhan TEMPLATE = app CONFIG += c++14 @@ -16,8 +16,8 @@ SOURCES += \ mainwindow.cpp \ app/global.cpp \ app/appcontext.cpp \ - comm/udpcommunication.cpp \ - comm/mockvehicle.cpp \ + comm/car.cpp \ + comm/uart.cpp \ widgets/titlebar.cpp \ widgets/statusstrip.cpp \ widgets/ledindicator.cpp \ @@ -34,13 +34,12 @@ SOURCES += \ pages/passwordmgmt.cpp \ pages/passwordreset.cpp \ pages/selfcheck.cpp \ + pages/home.cpp \ pages/jobselect.cpp \ pages/chassis.cpp \ pages/arm.cpp \ pages/gasdetector.cpp \ - pages/washbase.cpp \ - pages/manualclean.cpp \ - pages/manualdisinfect.cpp \ + pages/washpage.cpp \ pages/debugmaintain.cpp \ pages/faultquery.cpp @@ -48,8 +47,8 @@ HEADERS += \ mainwindow.h \ app/global.h \ app/appcontext.h \ - comm/udpcommunication.h \ - comm/mockvehicle.h \ + comm/car.h \ + comm/uart.h \ widgets/titlebar.h \ widgets/statusstrip.h \ widgets/ledindicator.h \ @@ -66,12 +65,11 @@ HEADERS += \ pages/passwordmgmt.h \ pages/passwordreset.h \ pages/selfcheck.h \ + pages/home.h \ pages/jobselect.h \ pages/chassis.h \ pages/arm.h \ pages/gasdetector.h \ - pages/washbase.h \ - pages/manualclean.h \ - pages/manualdisinfect.h \ + pages/washpage.h \ pages/debugmaintain.h \ pages/faultquery.h diff --git a/洗消机器人遥控器通信协议20260810.csv b/洗消机器人遥控器通信协议20260810.csv new file mode 100644 index 0000000..eef9f88 --- /dev/null +++ b/洗消机器人遥控器通信协议20260810.csv @@ -0,0 +1,324 @@ +"波特率 +500K",报文名称,报文类型,报文ID,报文发送类型,报文周期时间,报文长度,信号名称,信号描述,排列格式,起始位,信号发送类型,信号长度,数据类型,精度,偏移量,物理最小值(phys),物理最大值(phys),总线最小值(Hex),总线最大值(Hex),初始值(Hex),无效值(Hex),单位,信号值描述,所属节点,, +,Chassis_ControlCmd,Extended,0x18208081,Cycle,50,8,Chassis_Cmd,底盘指令,,,,,,,,,,,,,-,,,遥控器,, +,,,,,,,Chassis_modeCmd,行车模式,Intel,0,Cycle,1,Unsigned,1,,,,,,,,,"0=无效 +1有效",,, +,,,,,,,MCU_modeCmd,底盘预留1,Intel,1,Cycle,2,Unsigned,1,,,,,,,,,,,, +,,,,,,,MCU_enable,底盘使能,Intel,3,Cycle,1,Unsigned,1,,,,,,,,,"0=无效 +1=有效",,, +,,,,,,,Reserve,底盘预留2,Intel,4,Cycle,4,Unsigned,1,,,,,,,,,置0,,, +,,,,,,,Vehi_gear,整车档位指令,Intel,8,Cycle,16,Unsigned,1,,,,,,,,,0-4096,,左侧摇杆 Y轴(后:0、中位2048、前:4096),底盘模式时左侧摇杆 +,,,,,,,Vehi_TargetSpd,底盘目标速度指令,Intel,24,Cycle,16,Unsigned,0.01,,0,500,,,,,,0-5Km/h 速度范围,,"=DISPIMG(""ID_F67903083048486180A4DD49DBD500E6"",1)","设置完可以保存 +重启后读取上一次设置数值" +,,,,,,,Vehi_turning,底盘转向指令,Intel,40,Cycle,16,Signed,1,,,,,,,,,0-4096,,左侧摇杆 X轴(左:0、中位2048、右:4096),底盘模式时左侧摇杆 +,Arm_ControlCmd1,Extended,0x18218081,Cycle,50,8,Arm_Cmd1,机械臂指令1,,,,,,,,,,,,,,,,遥控器,, +,,,,,,,Arm_modeCmd,机械臂模式,Intel,0,Cycle,1,Unsigned,1,,,,,,,,,"0=无效 +1=有效",,机械臂模式时置“1”, +,,,,,,,Arm_enable,机械臂使能,Intel,1,Cycle,1,Unsigned,,,,,,,,,,"0=未使能 +1=使能",,, +,,,,,,,Reserve,机械臂预留,Intel,2,Cycle,6,Unsigned,1,,,,,,,,,,,, +,,,,,,,UpDown_motion,末端位置上下动作,Intel,8,Cycle,16,Unsigned,1,,,,,,,,,0-4096,,左侧摇杆 Z轴(左旋:0、中位2048、右旋:4096),"机械臂模式时 +左侧摇杆" +,,,,,,,LeftRight_motion,末端位置左右动作,Intel,24,Cycle,16,Unsigned,1,,,,,,,,,0-4096,,左侧摇杆 X轴(左:0、中位2048、右:4096), +,,,,,,,FrontRear_motion,末端位置前后动作,Intel,40,Cycle,16,Unsigned,1,,,,,,,,,0-4096,,左侧摇杆 Y轴(后:0、中位2048、前:4096), +,Arm_ControlCmd2,Extended,0x18228081,Cycle,50,8,Arm_Cmd2,机械臂指令2,,,,,,,,,,,,,,,,遥控器,, +,,,,,,,Roll_motion,末端姿态横滚动作,Intel,0,Cycle,16,Unsigned,1,,,,,,,,,0-4096,,右侧摇杆 X轴 (左:0、中位2048、右:4096),右侧摇杆 +,,,,,,,Pitch_motion,末端姿态俯仰动作,Intel,16,Cycle,16,Unsigned,1,,,,,,,,,0-4096,,右侧摇杆 Y轴 (后:0、中位2048、前:4096), +,,,,,,,Yaw_motion,末端姿态偏航动作,Intel,32,Cycle,16,Unsigned,1,,,,,,,,,0-4096,,右侧摇杆 Z轴 (左旋:0、中位2048、右旋:4096), +,,,,,,,System_homing_pause,系统回位/暂停,Intel,48,Cycle,8,Unsigned,1,,,,,,,,,"0xAA=系统回位 +0x55=暂停回位",,, +,Decontam_ControlCmd,Extended,0x18238081,Cycle,100,8,Decontam_Control,洗消系统指令,,,,,,,,,,,,,,,,遥控器,, +,,,,,,,Decontam_modeCmd,洗消模式选择,Intel,0,Cycle,2,Unsigned,1,,,,,,,,,"2=手动消除 +3=手动消毒",,"=DISPIMG(""ID_66C333C31FE34D3A966FE7FB8979E20E"",1)","设置完可以保存 +重启后读取上一次设置数值" +,,,,,,,Decontam_start,洗消启停,Intel,2,Cycle,1,Unsigned,1,,,,,,,,,"0=停止 +1=启动",,, +,,,,,,,Reserve,预留,Intel,3,Cycle,5,Unsigned,1,,,,,,,,,置0,,, +,,,,,,,Expansion_ratioCmd,发泡倍数,Intel,8,Cycle,8,Unsigned,1,,,,,,,,,6-12 倍可调范围,,, +,,,,,,,Foam_ratio,泡沫比例,Intel,16,Cycle,8,Unsigned,1,,,,,,,,%,0-10%,,, +,,,,,,,AirComp_flow,空压机流量,Intel,24,Cycle,8,Unsigned,1,,,,,,,,L/min,0-340L/min,,, +,,,,,,,PlungerPump_flow,柱塞泵流量,Intel,32,Cycle,8,Unsigned,1,,,,,,,,L/min,0-24L/min,,, +,Decontam_DebugCmd,Extended,0x18248081,Cycle,100,8,Decontam_Debug,洗消调试指令,,,,,,,,,,,,,,,,遥控器,, +,,,,,,,Debug_mode,调试模式,Intel,0,Cycle,1,Unsigned,1,,,,,,,,,"0=正常模式 +1=调试模式",,, +,,,,,,,PlungerPump_PwrOn,柱塞泵上电,Intel,1,Cycle,1,Unsigned,1,,,,,,,,,"0=下电 +1=上电",,, +,,,,,,,AirComp_PwrOn,空压机上电,Intel,2,Cycle,1,Unsigned,1,,,,,,,,,"0=下电 +1=上电",,, +,,,,,,,VanePump_PwrOn,叶片泵上电,Intel,3,Cycle,1,Unsigned,1,,,,,,,,,"0=下电 +1=上电",,, +,,,,,,,MBV_D1__PwrOn,电动球阀D1,Intel,4,Cycle,1,Unsigned,1,,,,,,,,,"0=关闭 +1=开启",,, +,,,,,,,MBV_D2__PwrOn,电动球阀D2,Intel,5,Cycle,1,Unsigned,1,,,,,,,,,"0=关闭 +1=开启",,, +,,,,,,,Debug_Reserve1,调试预留1,Intel,6,Cycle,2,Unsigned,1,,,,,,,,,,,, +,,,,,,,AirComp_Spd,空压机转速,Intel,8,Cycle,16,Unsigned,1,,,,,,,,rpm,0-3000rpm,,, +,,,,,,,PlungerPump_Spd,柱塞泵转速,Intel,24,Cycle,16,Unsigned,1,,,,,,,,rpm,0-1450rpm,,, +,,,,,,,VanePump__Spd,叶片泵转速,Intel,40,Cycle,16,Unsigned,1,,,,,,,,rpm,0-2000rpm,,, +,,,,,,,AirComp_start,空压机启停,Intel,56,Cycle,1,Unsigned,1,,,,,,,,,"0=停止 +1=启动",,, +,,,,,,,PlungerPump_start,柱塞泵启停,Intel,57,Cycle,1,Unsigned,1,,,,,,,,,"0=停止 +1=启动",,, +,,,,,,,VanePump_start,叶片泵启停,Intel,58,Cycle,1,Unsigned,1,,,,,,,,,"0=停止 +1=启动",,, +,Detector_ControlCmd,Extended,0x18258081,Cycle,1000,8,Detector_Cmd,气体检测设备指令,,,,,,,,,,,,,-,,,遥控器,, +,,,,,,,Detector_start,启停指令,Intel,0,Cycle,8,Unsigned,1,,,,,,,,,"0xAA=启动检测 +0x55=停止检测",,"=DISPIMG(""ID_F9FC5542F4E84DA1B522F1EEC3593D3C"",1)", +,Emergency_Cmd,Extended,0x18268081,Cycle,50,8,Emergency_Stop,紧急停止指令,,,,,,,,,,,,,,,,遥控器,, +,,,,,,,Emerg_stop,紧急停止,Intel,0,Cycle,8,Unsigned,1,,,,,,,,,"0x55=急停 +0=正常",,, +,Vehicle_StatusFeedBack1,Extended,0x18308180,Cycle,100,8,Chassis_WorkStatus1,底盘状态1(BMS系统),,,,,,,,,,,,,,,,VCU,, +,,,,,,,Total_voltage,总电压,Intel,0,Cycle,16,Unsigned,0.1,,,,,,,,,,,BMS系统故障, +,,,,,,,SOC,电量SOC,Intel,16,Cycle,16,Unsigned,0.1,,,,,,,,,,,, +,,,,,,,MCU_FaultCode,底盘电机故障码,Intel,32,Cycle,8,Unsigned,1,,,,,,,,,"6:电机缺相预警 +17:控制器过温警告 +18:电机过温警告 +19:控制器温度传感器警告 +20:电机温度传感器警告 +21:控制器过载警告 +22:电机超速警告 +23:直流母线电压过压警告 +24:直流母线电压欠压警告 +35:电流霍尔传感U相器故障 +36:电流霍尔传感W相器故障 +37:VCU报文丢失故障 +38:VCU循环计数错误 +39:VCU校验和错误 +40:CAN BussOff故障 +49:母线硬件过压故障 +50:相电流硬件过流故障 +51:IGBT短路故障 +52:驱动欠压故障 +53:旋变故障 +54:电机超速故障 +55:低压电源过压故障 +56:低压电源欠压故障 +57:直流母线电压过压故障 +58:直流母线电压欠压故障 +59:控制器过温故障 +60:电机过温故障 +61:电机缺相故障 +62:驻坡失效故障",,"两个数值同时发送,配合使用 + + +例如: +底盘电机故障码:18 +底盘电机故障等级:1 + +显示: +底盘电机:18-电机过温警告,1级 + + + + +不会出现故障等级是0,但是有故障码的情况",底盘电机故障 +,,,,,,,MCU_FaultLevel,底盘电机故障等级,Intel,40,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,底盘预留,预留,Intel,42,Cycle,3,Unsigned,1,,,,,,,,,,,?, +,,,,,,,UnderOver_FaultLevel,单体过低故障等级,Intel,45,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,BMS系统故障, +,,,,,,,CellOver_FaultLevel,单体过高故障等级,Intel,47,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,UnderVol_FaultLevel,总电压过低故障等级,Intel,49,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障;,,, +,,,,,,,OverVol_FaultLevel,总电压过高故障等级,Intel,51,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障;,,, +,,,,,,,DischgLowTmp_FaultLevel,放电温度过低故障等级,Intel,53,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,DischgOverTmp_FaultLevel,放电温度过高故障等级,Intel,55,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,ChgLowTmp_FaultLevel,充电温度过低故障等级,Intel,57,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,ChgOverTmp_FaultLevel,充电温度过高故障等级,Intel,59,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,TI_FaultLevel,温度不均衡故障等级,Intel,61,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,Vehicle_StatusFeedBack2,Extended,0x18318180,Cycle,100,8,Chassis_WorkStatus2,底盘状态2(BMS系统),,,,,,,,,,,,,,,,VCU,, +,,,,,,,VI_FaultLevel,压差不均衡故障等级,Intel,0,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,DischgOverCurr_FaultLevel,放电过流故障等级,Intel,2,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,ChgOverCurr_FaultLevel,充电过流故障等级,Intel,4,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,InComm_FaultLevel,内部通讯故障,Intel,6,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:故障;,,, +,,,,,,,ExtComm_FaultLevel,外部通讯故障,Intel,8,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:故障;,,, +,,,,,,,SOC_Low_FaultLevel,SOC过低故障等级,Intel,10,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,Insulation_FaultLevel,绝缘故障等级,Intel,12,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:一级故障;2:二级故障;3:三级故障,,, +,,,,,,,HeatRelay welding,加热继电器粘连,Intel,14,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:故障;,,, +,,,,,,,ChgRelay welding,充电继电器粘连,Intel,16,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:故障;,,, +,,,,,,,MainNegRelay welding,总负继电器粘连,Intel,18,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:故障;,,, +,,,,,,,MainPosRelay welding,总正继电器粘连,Intel,20,Cycle,2,Unsigned,1,,,,,,,,,0:正常;1:故障;,,, +,,,,,,,Handbrake,手刹信号,Intel,22,Cycle,2,Unsigned,1,,,,,,,,,0:无效;1:有效;,,20260804删除, +,,,,,,,Vehi_spd,车速,Intel,24,Cycle,8,Unsigned,0.01,,,,,,,,km/h,,,, +,,,,,,,Vehi_RdyStatus,底盘ready状态,Intel,32,Cycle,2,Unsigned,1,,,,,,,,,"0=初始状态 +1=高压上电完成 +2=准备就绪",,, +,,,,,,,Reserve,底盘预留,Intel,34,Cycle,6,Unsigned,1,,,,,,,,,,,, +,,,,,,,All_Comm_FaultCode,整系统通信故障码,Intel,40,Cycle,8,Unsigned,1,,,,,,,,,"0=通信正常 +1=底盘电机通信异常 +2=底盘BMS通信异常 +3=机械臂通信异常 +4=空压机通信异常 +5=柱塞泵通信异常 +6=叶片泵通信异常 +7=气体检测仪通信异常 +8=遥控器通信异常",,, +,Arm_StatusFeedBack1,Extended,0x18328180,Cycle,100,8,Arm_WorkStatus1,机械臂状态1,,,,,,,,,,,,,,,,VCU,, +,,,,,,,End_positionX,末端位置:X,Intel,0,Cycle,16,Signed,1,,,,,,,,mm,,,, +,,,,,,,End_positionY,末端位置:Y,Intel,16,Cycle,16,Signed,1,,,,,,,,mm,,,, +,,,,,,,End_positionZ,末端位置:Z,Intel,32,Cycle,16,Signed,1,,,,,,,,mm,,,, +,Arm_StatusFeedBack2,Extended,0x18338180,Cycle,100,8,Arm_WorkStatus2,机械臂状态2,,,,,,,,,,,,,,,,VCU,, +,,,,,,,End_postureRX,末端姿态:RX,Intel,0,Cycle,16,Signed,1,,,,,,,,,,,, +,,,,,,,End_postureRY,末端姿态:RY,Intel,16,Cycle,16,Signed,1,,,,,,,,,,,, +,,,,,,,End_postureRZ,末端姿态:RZ,Intel,32,Cycle,16,Signed,1,,,,,,,,,,,, +,,,,,,,End_spd,末端速度,Intel,48,Cycle,8,Signed,1,,,,,,,,,,,, +,Arm_StatusFeedBack3,Extended,0x18348180,Cycle,100,8,Arm_WorkStatus3,机械臂状态3,,,,,,,,,,,,,,,,VCU,, +,,,,,,,Joint1_angle,Joint1关节角,Intel,0,Cycle,16,Signed,1,,,,,,,,°,,,, +,,,,,,,Joint2_angle,Joint2关节角,Intel,16,Cycle,16,Signed,1,,,,,,,,°,,,, +,,,,,,,Joint3_angle,Joint3关节角,Intel,32,Cycle,16,Signed,1,,,,,,,,°,,,, +,,,,,,,Joint4_angle,Joint4关节角,Intel,48,Cycle,16,Signed,1,,,,,,,,°,,,, +,Arm_StatusFeedBack4,Extended,0x18358180,Cycle,100,8,Arm_WorkStatus4,机械臂状态4,,,,,,,,,,,,,,,,VCU,, +,,,,,,,Joint5_angle,Joint5关节角,Intel,0,Cycle,16,Signed,1,,,,,,,,°,,,, +,,,,,,,Joint6_angle,Joint6关节角,Intel,16,Cycle,16,Signed,1,,,,,,,,°,,,, +,Decontam_StatusFeedBack1,Extended,0x18368180,Cycle,200,8,Decontam_WorkStatus1,洗消系统工作状态1,,,,,,,,,,,,,,,,VCU,, +,,,,,,,Decontam_mode,洗消模式,Intel,0,Cycle,2,Unsigned,1,,,,,,,,,"2=消除作业 +3=消毒作业",,, +,,,,,,,Start,启停,Intel,2,Cycle,1,Unsigned,1,,,,,,,,,"0=停止 +1=启动",,, +,,,,,,,PlungerPump_PwrOnSta,柱塞泵上电sta,Intel,3,Cycle,1,Unsigned,1,,,,,,,,,"0=未上电 +1=上电",,, +,,,,,,,AirComp_PwrOnSta,空压机上电sta,Intel,4,Cycle,1,Unsigned,1,,,,,,,,,"0=未上电 +1=上电",,, +,,,,,,,VanePump_PwrOnSta,叶片泵上电sta,Intel,5,Cycle,1,Unsigned,1,,,,,,,,,"0=未上电 +1=上电",,, +,,,,,,,A_LiquidLevel_Low,A组分/清水液位低,Intel,6,Cycle,1,Unsigned,1,,,,,,,,,"0=正常 +1=液位低",,, +,,,,,,,B_LiquidLevel_Low,B组分液位低,Intel,7,Cycle,1,Unsigned,1,,,,,,,,,"0=正常 +1=液位低",,, +,,,,,,,MBV_D1_Sta,电动球阀D1sta,Intel,8,Cycle,1,Unsigned,1,,,,,,,,,"0=关闭到位 +1=开启到位",,, +,,,,,,,MBV_D2_Sta,电动球阀D2sta,Intel,9,Cycle,1,Unsigned,1,,,,,,,,,"0=关闭到位 +1=开启到位",,, +,,,,,,,Sys_fault,系统故障,Intel,10,Cycle,1,Unsigned,1,,,,,,,,,"0=无故障 +1=故障",,, +,,,,,,,Sta_reserve,状态预留,Intel,11,Cycle,5,Unsigned,1,,,,,,,,,置0,,, +,,,,,,,PartA_Water level,A组分/清水液位,Intel,16,Cycle,8,Unsigned,1,,,,,,,,,,,, +,,,,,,,Foam_level,B组分液位,Intel,24,Cycle,8,Unsigned,1,,,,,,,,,,,, +,,,,,,,ChemicalA_flow,A药剂流量,Intel,32,Cycle,8,Unsigned,1,,,,,,,,,,,, +,,,,,,,ChemicalB_flow,B药剂流量,Intel,40,Cycle,8,Unsigned,1,,,,,,,,,,,, +,,,,,,,Air_flow,气体流量,Intel,48,Cycle,8,Unsigned,1,,,,,,,,,,,, +,,,,,,,Expansion_ratio,发泡倍数,Intel,56,Cycle,8,Unsigned,1,,,,,,,,,,,, +,Decontam_StatusFeedBack2,Extended,0x18378180,Cycle,200,8,Decontam_WorkStatus2,洗消系统工作状态2,,,,,,,,,,,,,,,,VCU,, +,,,,,,,Pipe_pressure,管路压力,Intel,0,Cycle,8,Unsigned,0.1,,,,,,,,Mpa,,,, +,,,,,,,AirComp_pressure,空压机压力,Intel,8,Cycle,8,Unsigned,0.1,,,,,,,,Mpa,,,, +,,,,,,,PlungerPump_flowFB,柱塞泵流量反馈,Intel,16,Cycle,8,Unsigned,1,,,,,,,,,,,, +,,,,,,,AirComp_flowFB,空压机流量反馈,Intel,32,Cycle,8,Unsigned,1,,,,,,,,,,,, +,,,,,,,VanePump__flowFB,叶片泵流量反馈,Intel,40,Cycle,8,Unsigned,1,,,,,,,,,,,, +,,,,,,,AirComp_FaultCode,空压机故障代码,Intel,48,Cycle,8,Unsigned,1,,,,,,,,,2=加速过流;3=减速过流;4=恒速过流;5=加速过压;6=减速过压;7=恒速过压;8=缓冲过载;9=欠压;10=控制器过载;11=电机过载;12=输入缺相;13=输出缺相;14=模块过热;15=外部故障;16=通信异常;17=接触器异常;18=电流检测故障;19=自学习故障;20=编码器故障;21=参数读写异常;22=变频器硬件故障;23=电机对地短路故障;31=PID反馈丢失;40=快速限流超时故障;42=速度偏差过大;43=电机超速;45=电机过温;90=编码器线数设定错误;91=未接编码器;92=初始位置错误;94=速度反馈错误;,,, +,,,,,,,PlungerPump_FaultCode,柱塞泵故障代码,Intel,56,Cycle,8,Unsigned,1,,,,,,,,,1=短路故障;2=欠压;3=过压;4=控制器过温;5=控制器温度传感器故障;6=电机过温;7=电机温度传感器故障,,, +,Detector_StatusFeedBack1,Extended,0x18388180,Cycle,1000,8,Detector_WorkStatus1,气体检测设备结果状态1,,,,,,,,,,,,,,,,VCU,, +,,,,,,,Detector_Sta,检测仪当前状态,Intel,0,Cycle,8,Unsigned,1,,,,,,,,,"2=计算目标基线状态 +3=等待稳定状态 +4=检测状态 +5=等待点火状态 +6=确定初始基线状态",,, +,,,,,,,Toxic_types,有毒物种类,Intel,8,Cycle,8,Unsigned,1,,,,,,,,,"1=P类 +2=S类 +4=HNO类 +8=CH类 +16=AS类",,, +,,,,,,,Toxicity_level,有毒物等级,Intel,16,Cycle,8,Unsigned,1,,,,,,,,,等级1-5,,, +,,,,,,,Warnning,告警信息,Intel,24,Cycle,8,Unsigned,1,,,,,,,,,"1=进气嘴未接 +2=氢气流量不足 +4=仪器故障 +8=浓度过高",,, +,Detector_StatusFeedBack2,Extended,0x18398180,Cycle,1000,8,Detector_WorkStatus2,气体检测设备结果状态2,,,,,,,,,,,,,,,,VCU,, +,,,,,,,Toxic_ concentration,有毒物浓度,Intel,0,Cycle,40,Unsigned,1,,,,,,,,ppb,有毒物浓度:单位PPB,1000PPB=1PPM.,,, +,Vehicle_StatusFeedBack2,Extended,0x183A8180,Cycle,100,8,Chassis_WorkStatus3,底盘状态3,,,,,,,,,,,,,,,,VCU,, +,,,,,,,EPS_RotAngNotAligned,EPS转子角未对中,Intel,0,Cycle,1,Unsigned,1,,,,,,,,,,," + + + + + + + + + + + + + + + + +转向制动系统故障 + + + + + + + + + + + + + + + + + + + + + + + + +故障码待确认 +", +,,,,,,,EPS_RotAngDisconn,EPS转子角度断开,Intel,1,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_RotAng_SPI_Fault,EPS转子角度SPI故障,Intel,2,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPSEEPROM_Fault,EPSEEPROM_故障,Intel,3,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_MidpointNotAligned,EPS方向盘中点未对中,Intel,4,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_Angle_exceeds,EPS方向盘角度超限,Intel,5,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_Smalltooth_Disconn,EPS方向盘小齿信号断开,Intel,6,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_Gearsignal_Disconn,EPS方向盘中齿信号断开,Intel,7,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_overcurrent_fault,EPS过电流故障,Intel,8,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_A_phase_CurrAbnormal,EPS电机A相电流异常,Intel,9,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_C_phase_CurrAbnormal,EPS电机C相电流异常,Intel,10,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_B_phase_CurrAbnormal,EPS电机B相电流异常,Intel,11,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPSVCU_signal_loss,EPSVCU信号丢失,Intel,12,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_without_assistance,EPS电机无助力,Intel,13,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_pre_config_failed,EPS预驱配置失败,Intel,14,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_semi_stall_protect,EPS半堵转保护,Intel,15,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_Temp_thermal_protect,EPS温度热保护,Intel,16,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_vol_ManageProtect,EPS电压管理保护,Intel,17,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_response_NotInPlace,EPS转角响应不到位,Intel,18,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_angle_PWM_fault,EPS转子角度PWM故障,Intel,19,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_vol_verify_anomaly,EPS电压校验异常,Intel,20,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_thermistor_open_or_short,EPS热敏电阻开路或短路,Intel,21,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_thermistor_TempDiff_too_large ,EPS热敏电阻温差过大,Intel,22,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_overload_fault,制动器过载故障,Intel,23,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_overTemppres_fault,制动器过温或过压故障,Intel,24,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_overcurrent_fault,制动器过流故障,Intel,25,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_undervoltage_fault,制动器电源欠压故障,Intel,26,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_overvoltage_fault,制动器电源过压故障,Intel,27,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Insufficient_brake_pressure,制动器压力不足,Intel,28,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_Phase_loss_fault,制动器缺相故障,Intel,29,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_Comm_fault,制动器通讯故障,Intel,30,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_Curr_sample_fault,制动器电流采样故障,Intel,31,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_drive_failure,制动器驱动故障,Intel,32,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_magnetic_encoder_fault,制动器磁编故障,Intel,33,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_pressure_sensor_fault,制动器压力传感器故障,Intel,34,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_pedal_position_fault,制动器踏板位置传感器故障,Intel,35,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_Mechanical_failure,制动器机械故障,Intel,36,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_locked_rotor_fault,制动器堵转故障,Intel,37,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_Emerg_stop_closed,制动器急停按键闭合警告,Intel,38,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_applied_too_long,制动器驻车时间过长,Intel,39,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_CAN1_interrupt_warning,制动器CAN1中断警告,Intel,40,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_CAN2_interrupt_warning,制动器CAN2中断警告,Intel,41,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_ignition_disconnected,制动器点火线断开警告,Intel,42,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_Abnormal_current,制动器电流异常警告,Intel,43,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_Temp_too_high,制动器温度过高警告,Intel,44,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_input_vol_too_high,制动器输入电压过高警告,Intel,45,Cycle,1,Unsigned,1,,,,,,,,,,,, +,,,,,,,Brake_Reserve,制动预留,Intel,46,Cycle,2,Unsigned,1,,,,,,,,,,,, +,,,,,,,EPS_error_code,EPS故障码,Intel,48,Cycle,8,Unsigned,1,,,,,,,,,,,, +,Vehicle_StatusFeedBack2,Extended,0x183B8180,Cycle,100,8,Chassis_WorkStatus4,底盘状态4,,,,,,,,,,,,,,,,VCU,, +,,,,,,,Gear_Position,挡位状态,Intel,0,Cycle,2,Unsigned,1,,,,,,,,,0:N挡;1:D挡;2:R挡,,, +,,,,,,,Parking_Status,驻车状态,Intel,2,Cycle,2,Unsigned,1,,,,,,,,,0x00:未驻车;0x01:左电机驻车;0x02:右电机驻车;0x03:完全驻车;,,, +,,,,,,,AWD_Mode_Status,四驱模式反馈,Intel,4,Cycle,2,Unsigned,1,,,,,,,,,0:二驱模式;1:四驱模式;2:四驱锁定模式;,,, +,,,,,,,底盘预留,状态4预留,Intel,6,Cycle,2,Unsigned,1,,,,,,,,,,,, +,,,,,,,Steering_Angle,方向盘角度,Intel,8,Cycle,16,Unsigned,1,,,,,,,,,,,, +,,,,,,,Angle_alignment_status,角度对中状态,Intel,24,Cycle,8,Unsigned,1,,,,,,,,,0:未进行过角度对中;0x11:角度对中失败;0xee:角度对中成功;0x55:已对中成功;,,, +,,,,,,,Brake_Right_Mot_fault,左制动电机故障状态,Intel,32,Cycle,8,Unsigned,1,,,,,,,,,0x00:无故障;0x01:电机开路;0x02:电机短路;0x03:制动执行超时;0x04:解除制动执行超时;0x05:驱动电路故障;0x06:供电电压过低;0x07:供电电压过高;,,转向制动系统故障, +,,,,,,,Brake_Left_Mot_fault,右制动电机故障状态,Intel,40,Cycle,8,Unsigned,1,,,,,,,,,0x00:无故障;0x01:电机开路;0x02:电机短路;0x03:制动执行超时;0x04:解除制动执行超时;0x05:驱动电路故障;0x06:供电电压过低;0x07:供电电压过高;,,,