296 lines
10 KiB
C++
296 lines
10 KiB
C++
#include "appcontext.h"
|
||
#include "car.h"
|
||
#include <QApplication>
|
||
#include <QDateTime>
|
||
|
||
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();
|
||
}
|