#include "appcontext.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; if (!s) s = new AppContext(); return s; } AppContext::AppContext(QObject* parent) : QObject(parent) { // 参数与密码持久化:QSettings 写入程序目录 config.ini m_settings = new QSettings(QApplication::applicationDirPath() + "/config.ini", QSettings::IniFormat, this); // 首次运行写入默认值 if (!m_settings->contains(keySpeed())) m_settings->setValue(keySpeed(), 4.5); if (!m_settings->contains(keyPumpFlow())) m_settings->setValue(keyPumpFlow(), 20); if (!m_settings->contains(keyFoamRatio())) m_settings->setValue(keyFoamRatio(), 6); 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"); // 通信层(19 条 CAN 报文经 /dev/ttyS7 收发,见 comm/car.h) m_comm = new CarCommunication(&telemetry, this); // 遥控器(串口 /dev/ttyS5,24 字节二进制帧,见 comm/uart.h) m_rc = new RcCommunication(this); // 通信层故障信号 → 写入故障历史 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(); } void AppContext::seedFaultHistory() { // 演示用历史故障汇总(协议示例数据 + 补充) struct Seed { const char* t; const char* sys; const char* code; const char* d; }; const Seed seeds[] = { {"2026-05-16 10:08", "机器人底盘", "C01", "底盘电量低"}, {"2026-05-16 09:58", "机器人底盘", "C02", "底盘电机温度高"}, {"2026-05-16 09:41", "机器人底盘", "C03", "总电压低"}, {"2026-05-15 09:08", "机械臂", "A01", "机械臂通信故障"}, {"2026-05-15 08:55", "机械臂", "A02", "机械臂自检故障"}, {"2026-05-15 08:30", "机械臂", "A03", "关节J2过载"}, {"2026-05-14 09:58", "洗消系统", "W01", "柱塞泵故障"}, {"2026-05-14 09:20", "洗消系统", "W02", "空压机压力异常"}, {"2026-05-14 08:45", "洗消系统", "W03", "电动球阀D1开启故障"}, {"2026-05-13 09:08", "洗消系统", "W04", "清水液位低"}, {"2026-05-13 08:18", "洗消系统", "W05", "药剂液位低"}, {"2026-05-13 08:02", "洗消系统", "W06", "叶片泵故障"}, {"2026-05-12 11:20", "气体检测仪", "G01", "检测仪通信故障"}, {"2026-05-12 10:50", "气体检测仪", "G02", "有毒物浓度超阈值"}, {"2026-05-11 09:30", "气体检测仪", "G03", "主机标定异常"}, {"2026-05-11 09:10", "机器人底盘", "C04", "手刹失效"}, {"2026-05-10 08:40", "机械臂", "A04", "末端速度超限"}, {"2026-05-10 08:15", "气体检测仪", "G04", "传感器信号异常"}, }; for (const Seed& s : seeds) { FaultRecord r; r.time = QString::fromUtf8(s.t); r.system = QString::fromUtf8(s.sys); r.code = QString::fromUtf8(s.code); r.desc = QString::fromUtf8(s.d); faultHistory.append(r); // 种子数据按“最新在前”顺序直接追加 } } void AppContext::appendFault(const QString& system, const QString& code, const QString& desc) { FaultRecord r; r.time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm"); r.system = system; r.code = code; r.desc = desc; faultHistory.prepend(r); // 最新在前 } double AppContext::getDouble(const QString& key, double def) const { return m_settings->value(key, def).toDouble(); } void AppContext::setDouble(const QString& key, double val) { m_settings->setValue(key, val); m_settings->sync(); } QString AppContext::getString(const QString& key, const QString& def) const { return m_settings->value(key, def).toString(); } void AppContext::setString(const QString& key, const QString& val) { m_settings->setValue(key, val); m_settings->sync(); } QString AppContext::password() const { return m_settings->value("param/pwd", "6789").toString(); } void AppContext::setPassword(const QString& pwd) { m_settings->setValue("param/pwd", pwd); m_settings->sync(); } // 管理员密码为固定值,不持久化、不可修改 QString AppContext::adminPassword() const { return "9527"; } void AppContext::resetEstop() { estopTriggered = false; telemetry.estop = false; emit estopChanged(false); // 通知车载端复位 if (m_comm) m_comm->sendEstopReset(); } void AppContext::triggerEstop() { estopTriggered = true; telemetry.estop = true; emit estopChanged(true); if (m_comm) m_comm->sendEstop(); } 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(); }