Files
2026-10-16 09:06:03 +08:00

201 lines
11 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#ifndef CAR_H
#define CAR_H
#include <QByteArray>
#include <QObject>
#include <QSet>
#include <QString>
#include <QTimer>
#include <QVector>
#include "global.h"
class QUdpSocket;
// 摇杆量协议范围 0~4096,中位 2048(分类一各轴、Vehi_gear/Vehi_turning 通用)
static const int kRcAxisMid = 2048;
// 自检项数目(④自检界面与本地自检序列共用,顺序即界面显示顺序):
// 0 通信系统(All_Comm_FaultCode=0) → 1~8 各分系统通信(码1~8) → 9 清水液位
// (A_LiquidLevel_Low) → 10~13 球阀 D1/D2 开启、关闭(MBV_D1_Sta / MBV_D2_Sta)
static const int kSelfCheckItemCount = 14;
// 球阀 4 项的自检起始下标:10=D1开启 11=D1关闭 12=D2开启 13=D2关闭。
// 这 4 项不能只靠本端计时通过——要先下发 Decontam_DebugCmd,再等
// MBV_D1_Sta / MBV_D2_Sta 反馈与指令一致;不一致即超时判故障。
static const int kSelfCheckValveFirst = 10;
// 球阀等待反馈的节拍数(自检定时器 450ms/拍):18 拍 ≈ 8s 未到位即判故障。
static const int kValveWaitTicks = 18;
// 演示故障所在的自检项(config.ini 的 net/selfcheck_fault),只作用于 0~9 段,
// 免得覆盖球阀 4 项的真实反馈结果。9 = 清水液位。
static const int kSelfCheckDemoFaultIndex = 9;
// CAN 反馈只给故障码与故障等级,这里带上一个故障所需的全部要素。
// text() 渲染的是完整一行:<组名>:[<码>-]<描述>[,<N>级],
// “底盘电机故障:18-电机过温警告,1级”;各卡片那一行太窄,只用 desc(+等级后缀)。
struct CanFault {
QString system; // 故障历史分组(机器人底盘/洗消系统/气体检测仪)
QString group; // 组名(底盘电机故障/BMS系统故障/…),完整一行才用得到
QString code; // 追溯码,如 MCU18 / SOCL1 / COMM3,供故障历史去重与故障查询
QString codeNum; // 完整一行里的码,如 "18";空表示该故障没有码(等级型故障)
QString desc; // 描述,不含等级,如“电机过温警告”
int level = 0; // 0=不显示等级;1~3 → 尾部追加“,N级”
QString text() const; // 完整一行,如“底盘电机故障:18-电机过温警告,1级”
};
// ===================== 车端通信层(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(小端)位序。
// 传输走 UDP:bind 本机 9000 端口收下行反馈,上行命令发往车端 192.168.1.224:3001。
// UDP 载荷统一为 16 字节:帧头 0xAA + canId + DLC + 8 字节数据 + 校验和 + 帧尾 0xBB。
// 通信超时判定:连续 2s 未收到任何合法帧则判为通信异常(最慢的下行周期 1000ms)。
class CarCommunication : public QObject
{
Q_OBJECT
public:
explicit CarCommunication(Telemetry* tel, QObject* parent = nullptr);
~CarCommunication() override;
bool start();
void stop();
// 行车/机械臂模式(面板旋钮或遥控器报文)→ Arm_ControlCmd1 的 Arm_modeCmd / Arm_enable
void setArmMode(bool arm) { m_armMode = arm; }
// 遥控器是否正在操作(由 AppContext 维护)。面板那两路入口据此让位:
// 两端都能操作,但遥控器一动手就以遥控器为准;遥控器松手后由页面重发按住的方向/轴。
void setRcActive(bool on) { m_rcActive = on; }
// ---------- 上行命令(分类一)----------
// 命令只更新本端控制状态,实际报文由周期定时器编码发送(CAN 控制帧本就是周期报文)。
// 底盘/机械臂各有**两个入口**:面板一个、遥控器一个(名字带 Rc)。两路写的是同一份
// 轴值缓存,差别只在“遥控器在操作时面板那一路直接忽略”。
// turn 非空("left"/"right")时与 dir 同时下发,支持边前进/后退边转向
void sendChassisMove(const QString& dir, double speed, const QString& turn = QString());
void sendChassisMoveRc(const QString& dir, double speed, const QString& turn = QString());
void sendArmMove(const QString& axis, int dir); // dir: +1 / -1 / 0(停止)
void sendArmMoveRc(const QString& axis, int dir);
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(); // 一批数据报解析完(页面据此刷新,同批多帧只发一次)
// 顶部左区那行通信状态变化(Telemetry::commText)。周期报文内容不变时不发。
void commTextChanged();
void selfCheckItem(int index, bool ok); // 自检单项结果(0~kSelfCheckItemCount-1)
void selfCheckItemBusy(int index); // 自检单项开始执行(已下发指令、正等反馈)
void selfCheckDone(bool allOk); // 自检完成
void faultAppended(const QString& system, const QString& code, const QString& desc);
private slots:
void onDatagram();
void onSendFast(); // 50ms 组:底盘 / 机械臂 / 急停
void onSendMid(); // 100ms 组:洗消指令 / 洗消调试
void onSendSlow(); // 1000ms 组:气体检测仪
void onHeartbeatTimeout();
void onSelfCheckTick();
private:
// 收包:按 canId 分流;发包:16 字节封帧后写 UDP
void handleFrame(quint32 canId, const quint8* d);
void sendFrame(quint32 canId, const quint8* data);
void cacheFaults();
void updateCommText(); // 重建顶部那行通信状态(Telemetry::commText)
void updateWashSwing(); // 洗消作业期间的末端俯仰摆动(每 50ms 一拍)
void setCommOk(bool ok);
void finishSelfCheckItem(bool ok); // 记录单项结果并推进到下一项
Telemetry* m_tel = nullptr;
QUdpSocket* m_sock = nullptr;
QTimer* m_hbTimer = nullptr;
QTimer* m_fastTimer = nullptr;
QTimer* m_midTimer = nullptr;
QTimer* m_slowTimer = nullptr;
QTimer* m_selfCheckTimer = nullptr;
bool m_lastCommOk = true;
int m_commFaultCode = 0; // 最近的 All_Comm_FaultCode,0 = 通信正常
bool m_armMode = false;
bool m_rcActive = false; // 遥控器正在操作:面板那两路入口让位(见 setRcActive)
// 本地自检序列(CAN 矩阵无自检报文,见 .cpp TODO)
int m_selfCheckIndex = 0;
bool m_selfCheckFault = true;
int m_selfCheckWait = 0; // 当前项已等待的节拍数:0 = 尚未下发指令
bool m_selfCheckAllOk = true; // 已跑完各项的累计结果,决定 selfCheckDone 的入参
// 自检要临时置调试模式才能操作球阀,结束后恢复原值,免得把整车留在调试模式
bool m_debugModeBeforeCheck = false;
// ---- 分类一 上行控制状态 ----
// 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;
// 洗消作业期间的末端俯仰摆动:回中 → 正弦摆动 → 还原(见 updateWashSwing,50ms/拍)
enum SwingState { SwingOff, SwingCentering, SwingSine, SwingRestoring };
SwingState m_swingState = SwingOff;
int m_swingPhase = 0; // 正弦相位,0..kWashSwingTicks
int m_swingTick = 0; // 过渡段已走的拍数
int m_swingFrom = kRcAxisMid; // 还原过渡的起始轴值
quint16 m_pitchBeforeWash = 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<CanFault> m_vsf1Faults, m_vsf2Faults, m_vsf3Faults, m_vsf4Faults;
QVector<CanFault> m_washFaults, m_pumpFaults, m_detectorFaults;
// 整系统通信故障码单独一份:它是全系统级的,不属于任何一张卡片,
// 只进顶部那行通信状态与故障历史,不并进 chassisFaults。
QVector<CanFault> m_commFaults;
QSet<QString> m_reportedFaults; // 已记入故障历史的“系统|码”,周期性报文只上报新增故障
};
#endif // CAR_H