新版布局
This commit is contained in:
+385
-118
@@ -6,38 +6,57 @@
|
||||
#include <QSettings>
|
||||
#include <QUdpSocket>
|
||||
#include <QtGlobal>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
|
||||
// ===================== 线格式 =====================
|
||||
// 一个 UDP 数据报承载 = 32 位帧头 + 32 位 CAN ID + DLC + 8 字节数据 + 8 位校验和,共 18 字节。
|
||||
// 多字节字段按主机小端排列,与车载端网关一致;帧头与校验和用于确认数据报内容合法。
|
||||
// 一个 UDP 数据报承载 = 1 字节帧头 + 32 位 CAN ID + DLC + 8 字节数据 + 8 位校验和 + 1 字节帧尾,
|
||||
// 共 16 字节。多字节字段按主机小端排列,与车载端网关一致;帧头/帧尾与校验和用于确认数据报内容合法。
|
||||
#pragma pack(push, 1)
|
||||
struct CanFrame {
|
||||
quint32 header; // 固定帧头 0x5AA5FF00(线上字节序 00 FF A5 5A)
|
||||
quint8 header; // 固定帧头 0xAA
|
||||
quint32 canId; // 29 位扩展帧 ID,直接用于分流
|
||||
quint8 dlc; // 数据长度码,固定 8
|
||||
quint8 data[8]; // 8 字节 CAN 原生数据段
|
||||
quint8 checksum; // canId 4 字节 + dlc + data[8],共 13 字节求和取低 8 位
|
||||
quint8 checksum; // 帧头 + canId 4 字节 + dlc + data[8] + 帧尾,共 15 字节求和取低 8 位
|
||||
quint8 tail; // 固定帧尾 0xBB
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
const quint32 kFrameHeader = 0x5AA5FF00u;
|
||||
const quint8 kFrameHeader = 0xAA;
|
||||
const quint8 kFrameTail = 0xBB;
|
||||
const int kFrameLen = int(sizeof(CanFrame));
|
||||
const int kDataLen = 8;
|
||||
|
||||
// 车端 CAN 的 UDP 端口:本机 9000 收(车端反馈),上行命令发往 9001
|
||||
// 洗消作业期间机械臂末端的自动俯仰摆动:中位 ±1024(轴量程 0~4096)、4 秒一个往返。
|
||||
// 摆动在 50ms 那一组里推进(Arm_ControlCmd2 就在这组),所以一拍 = 50ms、
|
||||
// 一个往返 = 4s ÷ 50ms = 80 拍。kWashSwingAmp 别超过 kRcAxisMid,不然会顶到量程两端。
|
||||
const int kWashSwingAmp = 1024;
|
||||
const int kWashSwingTicks = 80;
|
||||
const int kSwingRampTicks = 10; // 回中 / 还原的过渡时长:10 拍 = 0.5s
|
||||
const double kPi = 3.14159265358979323846;
|
||||
|
||||
// 轴值线性插值并夹到量程 0~4096。回中与还原都走斜坡,别一步跳变把机械臂顿一下。
|
||||
quint16 lerpAxis(int from, int to, double t)
|
||||
{
|
||||
return quint16(qBound(0, int(from + (to - from) * qBound(0.0, t, 1.0)), 4096));
|
||||
}
|
||||
|
||||
// 车端 CAN 的 UDP 端口:本机 9000 收(车端反馈),上行命令发往车端 192.168.1.224:3001。
|
||||
// 收端绑 AnyIPv4(各网卡都收),发端必须写明车端 IP —— "0.0.0.0" 不是合法目的地址。
|
||||
const quint16 kCanRxPort = 9000;
|
||||
const quint16 kCanTxPort = 9001;
|
||||
const char* kCanTxHost = "0.0.0.0";
|
||||
const quint16 kCanTxPort = 3001;
|
||||
const char* kCanTxHost = "192.168.1.224";
|
||||
|
||||
quint8 canChecksum(quint32 canId, quint8 dlc, const quint8* data)
|
||||
{
|
||||
quint8 sum = 0;
|
||||
quint8 sum = kFrameHeader; // 帧头/帧尾也算进去(帧尾虽在校验和之后,双方按定长解析无妨)
|
||||
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];
|
||||
sum += kFrameTail;
|
||||
return sum;
|
||||
}
|
||||
|
||||
@@ -182,42 +201,65 @@ const char* const kBrakeMotFaults[8] = {
|
||||
"驱动电路故障", "供电电压过低", "供电电压过高",
|
||||
};
|
||||
|
||||
void addFault(QVector<CanFault>& out, const QString& system, const char* codePrefix,
|
||||
quint8 code, const char* text)
|
||||
void addFault(QVector<CanFault>& out, const QString& system, const QString& group,
|
||||
const char* codePrefix, quint8 code, const char* text, int level = 0)
|
||||
{
|
||||
if (!text) return;
|
||||
CanFault f;
|
||||
f.system = system;
|
||||
f.code = QString::fromLatin1(codePrefix) + QString::number(code);
|
||||
f.desc = QString::fromUtf8(text);
|
||||
f.system = system;
|
||||
f.group = group;
|
||||
f.code = QString::fromLatin1(codePrefix) + QString::number(code);
|
||||
f.codeNum = QString::number(code);
|
||||
f.desc = QString::fromUtf8(text);
|
||||
f.level = level;
|
||||
out.append(f);
|
||||
}
|
||||
|
||||
// 未在码表中登记的码也上报,避免“有故障但界面无提示”
|
||||
void addFaultByCode(QVector<CanFault>& out, const QString& system, const char* codePrefix,
|
||||
const CodeText* tbl, int n, quint8 code)
|
||||
void addFaultByCode(QVector<CanFault>& out, const QString& system, const QString& group,
|
||||
const char* codePrefix, const CodeText* tbl, int n, quint8 code, int level = 0)
|
||||
{
|
||||
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);
|
||||
if (t) {
|
||||
addFault(out, system, group, codePrefix, code, t, level);
|
||||
return;
|
||||
}
|
||||
CanFault f;
|
||||
f.system = system;
|
||||
f.group = group;
|
||||
f.code = QString::fromLatin1(codePrefix) + QString::number(code);
|
||||
f.codeNum = QString::number(code);
|
||||
f.desc = QString::fromLatin1(codePrefix) + QString::number(code);
|
||||
f.level = level;
|
||||
out.append(f);
|
||||
}
|
||||
|
||||
// 故障等级:>0 才上报,描述形如“单体电压过低二级故障”
|
||||
void addLevelFault(QVector<CanFault>& out, const QString& system, const char* codePrefix,
|
||||
const char* name, quint8 level)
|
||||
// 故障等级:>0 才上报。等级型信号没有“码”,顶部显示成“<组名>:<描述>,N级”
|
||||
void addLevelFault(QVector<CanFault>& out, const QString& system, const QString& group,
|
||||
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]);
|
||||
f.group = group;
|
||||
f.code = QString::fromLatin1(codePrefix) + QString::number(level); // 追溯用,顶部不显示
|
||||
f.desc = QString::fromUtf8(name);
|
||||
f.level = level;
|
||||
out.append(f);
|
||||
}
|
||||
|
||||
// 没有“故障码”的故障(单个故障位、方向盘对中状态这类无编码信号):
|
||||
// 顶部只显示描述,tracePrefix+traceSuffix 仅作故障历史追溯用。
|
||||
void addNoCodeFault(QVector<CanFault>& out, const QString& system, const QString& group,
|
||||
const char* tracePrefix, int traceSuffix, const char* text)
|
||||
{
|
||||
if (!text) return;
|
||||
CanFault f;
|
||||
f.system = system;
|
||||
f.group = group;
|
||||
f.code = QString::fromLatin1(tracePrefix) + QString::number(traceSuffix);
|
||||
f.desc = QString::fromUtf8(text);
|
||||
out.append(f);
|
||||
}
|
||||
|
||||
@@ -225,8 +267,38 @@ const char* const kSysChassis = "机器人底盘";
|
||||
const char* const kSysWash = "洗消系统";
|
||||
const char* const kSysDetector = "气体检测仪";
|
||||
|
||||
// 顶部轮播的显示组名。前三个取自协议 CSV 的故障分组列(底盘状态 1~4 各自归属),
|
||||
// 洗消/检测仪协议里没给分组列,按 system 命名。某组没有故障时顶部就不出现该组名。
|
||||
const char* const kGroupChassisMotor = "底盘电机故障";
|
||||
const char* const kGroupBms = "BMS系统故障";
|
||||
const char* const kGroupSteerBrake = "转向制动系统故障";
|
||||
const char* const kGroupWash = "洗消系统故障";
|
||||
const char* const kGroupDetector = "气体检测仪故障";
|
||||
// All_Comm_FaultCode 本身也是“一组”故障,顶部按“通信异常:3-机械臂通信异常”显示
|
||||
const char* const kGroupComm = "通信异常";
|
||||
|
||||
// All_Comm_FaultCode → 顶部那行的文案。表里写的就是“机械臂通信异常”这种,
|
||||
// 不再加“xx系统”前缀。码不认识时退回笼统的“通信异常”。
|
||||
QString commFaultText(quint8 code)
|
||||
{
|
||||
for (const CodeText& c : kCommFaults)
|
||||
if (c.code == code) return QString::fromUtf8(c.text);
|
||||
return QString::fromUtf8(kGroupComm);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// 完整一行:<组名>:[<码>-]<描述>[,<N>级];desc 为空则只显示组名(链路超时用)
|
||||
QString CanFault::text() const
|
||||
{
|
||||
if (desc.isEmpty()) return group;
|
||||
QString s = group + QString::fromUtf8(":");
|
||||
if (!codeNum.isEmpty()) s += codeNum + "-";
|
||||
s += desc;
|
||||
if (level > 0) s += QString::fromUtf8(",%1级").arg(level);
|
||||
return s;
|
||||
}
|
||||
|
||||
// ===================== 构造与生命周期 =====================
|
||||
CarCommunication::CarCommunication(Telemetry* tel, QObject* parent)
|
||||
: QObject(parent), m_tel(tel)
|
||||
@@ -287,6 +359,7 @@ bool CarCommunication::start()
|
||||
// 端口被占用时走这里:不启动定时器,界面显示通信异常
|
||||
qWarning() << "车端 UDP 端口绑定失败:" << kCanRxPort << m_sock->errorString();
|
||||
setCommOk(false);
|
||||
updateCommText(); // 收不到帧也不会有 cacheFaults,顶部得靠这里才显示“通信异常”
|
||||
return false;
|
||||
}
|
||||
m_hbTimer->start();
|
||||
@@ -311,7 +384,6 @@ void CarCommunication::setCommOk(bool ok)
|
||||
if (ok == m_lastCommOk) return;
|
||||
m_lastCommOk = ok;
|
||||
if (m_tel) m_tel->commOk = ok;
|
||||
emit commOkChanged(ok);
|
||||
}
|
||||
|
||||
// ===================== 收包与分流 =====================
|
||||
@@ -319,81 +391,97 @@ void CarCommunication::setCommOk(bool ok)
|
||||
// 不再需要串口时代那套跨读字节流的残帧缓冲与滑窗重同步。
|
||||
void CarCommunication::onDatagram()
|
||||
{
|
||||
bool got = false;
|
||||
while (m_sock->hasPendingDatagrams()) {
|
||||
const QByteArray d = m_sock->receiveDatagram().data();
|
||||
int off = 0;
|
||||
while (off + kFrameLen <= d.size()) {
|
||||
CanFrame f;
|
||||
std::memcpy(&f, d.constData() + off, kFrameLen);
|
||||
if (f.header == kFrameHeader && f.dlc == kDataLen
|
||||
if (f.header == kFrameHeader && f.tail == kFrameTail && f.dlc == kDataLen
|
||||
&& f.checksum == canChecksum(f.canId, f.dlc, f.data)) {
|
||||
handleFrame(f.canId, f.data);
|
||||
got = true;
|
||||
off += kFrameLen;
|
||||
} else {
|
||||
++off; // 数据报内可能有杂字节,向后找下一个帧头
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 一批数据报**全部解完**再统一刷一次界面。
|
||||
// 积压时 Telemetry 里留下的本来就是各字段的最新值,中间每帧都推一遍纯属浪费——
|
||||
// 一批 100 帧会变成 100×(7 个页面 + 8 个底条) 次刷新,而界面只需要最后那一次。
|
||||
// 只在"同一批"里合并,不额外等定时器,所以不引入延迟。
|
||||
if (!got)
|
||||
return;
|
||||
m_hbTimer->start(); // 收到任意合法帧即刷新链路(CAN 无独立心跳报文)
|
||||
setCommOk(true);
|
||||
cacheFaults();
|
||||
emit telemetryUpdated();
|
||||
}
|
||||
|
||||
void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
{
|
||||
if (!m_tel) return;
|
||||
|
||||
// 收到任意合法帧即刷新链路(CAN 无独立心跳报文)
|
||||
m_hbTimer->start();
|
||||
setCommOk(true);
|
||||
|
||||
// 这里只把数据写进 Telemetry,不动链路状态、也不刷界面——那两件事统一在
|
||||
// onDatagram() 排空一批之后做一次,这样一大批积压也只会重绘一次。
|
||||
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_tel->hasBattery = true;
|
||||
|
||||
m_vsf1Faults.clear();
|
||||
addFaultByCode(m_vsf1Faults, QString::fromUtf8(kSysChassis), "MCU",
|
||||
// 底盘电机:故障码(8位)与故障等级(2位)是配套下发的两个信号,协议注明“不会出现
|
||||
// 等级为 0 却有故障码”的情况,故合并成一条“底盘电机故障:18-电机过温警告,1级”。
|
||||
addFaultByCode(m_vsf1Faults, QString::fromUtf8(kSysChassis),
|
||||
QString::fromUtf8(kGroupChassisMotor), "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)));
|
||||
quint8(intelBits(d, 32, 8)), int(intelBits(d, 40, 2)));
|
||||
// 以下 *FaultLevel 都归 BMS 系统
|
||||
const QString bms = QString::fromUtf8(kGroupBms);
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"CELLL", "单体电压过低", quint8(intelBits(d, 45, 2)));
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"CELLHL", "单体电压过高", quint8(intelBits(d, 47, 2)));
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"VOLTL", "总电压过低", quint8(intelBits(d, 49, 2)));
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"VOLTHL", "总电压过高", quint8(intelBits(d, 51, 2)));
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"DLTPL", "放电低温", quint8(intelBits(d, 53, 2)));
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"DHTPL", "放电高温", quint8(intelBits(d, 55, 2)));
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"CLTPL", "充电低温", quint8(intelBits(d, 57, 2)));
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"CHTPL", "充电高温", quint8(intelBits(d, 59, 2)));
|
||||
addLevelFault(m_vsf1Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"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)));
|
||||
const QString bms = QString::fromUtf8(kGroupBms);
|
||||
addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"VIL", "压差不均衡", quint8(intelBits(d, 0, 2)));
|
||||
addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"DOCL", "放电过流", quint8(intelBits(d, 2, 2)));
|
||||
addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"COCL", "充电过流", quint8(intelBits(d, 4, 2)));
|
||||
addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"INCL", "内部通讯", quint8(intelBits(d, 6, 2)));
|
||||
addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"EXCL", "外部通讯", quint8(intelBits(d, 8, 2)));
|
||||
addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"SOCL", "SOC过低", quint8(intelBits(d, 10, 2)));
|
||||
addLevelFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), bms,
|
||||
"INSL", "绝缘", quint8(intelBits(d, 12, 2)));
|
||||
|
||||
// 继电器粘连:0=正常 非 0=故障
|
||||
struct { int bit; const char* name; const char* code; } relays[] = {
|
||||
@@ -404,7 +492,7 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
};
|
||||
for (const auto& r : relays) {
|
||||
if (intelBits(d, r.bit, 2) != 0)
|
||||
addFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), r.code, 1, r.name);
|
||||
addFault(m_vsf2Faults, QString::fromUtf8(kSysChassis), bms, r.code, 1, r.name);
|
||||
}
|
||||
|
||||
m_tel->handbrake = intelBits(d, 22, 2) != 0;
|
||||
@@ -412,9 +500,13 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
// 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));
|
||||
m_tel->hasSpeed = true;
|
||||
|
||||
const quint8 comm = quint8(intelBits(d, 40, 8)); // All_Comm_FaultCode
|
||||
addFaultByCode(m_vsf2Faults, QString::fromUtf8(kSysChassis), "COMM",
|
||||
m_commFaultCode = comm;
|
||||
m_commFaults.clear();
|
||||
addFaultByCode(m_commFaults, QString::fromUtf8(kSysChassis),
|
||||
QString::fromUtf8(kGroupComm), "COMM",
|
||||
kCommFaults, int(sizeof(kCommFaults) / sizeof(CodeText)), comm);
|
||||
break;
|
||||
}
|
||||
@@ -422,17 +514,20 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
// ---------- 0x183A8180 底盘状态3:EPS / 制动故障位 ----------
|
||||
case kIdVehicleFb3: {
|
||||
m_vsf3Faults.clear();
|
||||
const QString sys = QString::fromUtf8(kSysChassis);
|
||||
const QString brake = QString::fromUtf8(kGroupSteerBrake);
|
||||
for (int i = 0; i < 46; ++i) {
|
||||
if (intelBits(d, i, 1) != 0)
|
||||
addFault(m_vsf3Faults, QString::fromUtf8(kSysChassis), "EPS", quint8(i),
|
||||
kVsb3Bits[i]);
|
||||
addNoCodeFault(m_vsf3Faults, sys, brake, "EPS", 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);
|
||||
f.system = sys;
|
||||
f.group = brake;
|
||||
f.code = QString("EPSC") + QString::number(eps);
|
||||
f.codeNum = QString::number(eps);
|
||||
f.desc = QString::fromUtf8("EPS故障码");
|
||||
m_vsf3Faults.append(f);
|
||||
}
|
||||
break;
|
||||
@@ -445,19 +540,25 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
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));
|
||||
m_tel->hasChassis4 = true;
|
||||
|
||||
// ALIGN 是状态字节(0x11=对中失败、0xEE/0x55=正常),不是故障码,顶部只显示描述
|
||||
const QString sys = QString::fromUtf8(kSysChassis);
|
||||
const QString brake = QString::fromUtf8(kGroupSteerBrake);
|
||||
const quint8 align = quint8(intelBits(d, 24, 8));
|
||||
if (align == 0x11) addFault(m_vsf4Faults, QString::fromUtf8(kSysChassis), "ALIGN", 1, "方向盘对中失败");
|
||||
m_tel->alignStatus = align; // 底盘页「角度对中状态」行
|
||||
if (align == 0x11)
|
||||
addNoCodeFault(m_vsf4Faults, sys, brake, "ALIGN", 1, "方向盘对中失败");
|
||||
else if (align != 0xEE && align != 0x55)
|
||||
addFault(m_vsf4Faults, QString::fromUtf8(kSysChassis), "ALIGN", 0, "方向盘未对中");
|
||||
addNoCodeFault(m_vsf4Faults, sys, brake, "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,
|
||||
if (br != 0) addFault(m_vsf4Faults, sys, brake, "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,
|
||||
if (bl != 0) addFault(m_vsf4Faults, sys, brake, "BRKL", bl,
|
||||
bl < 8 ? kBrakeMotFaults[bl] : "制动电机故障");
|
||||
break;
|
||||
}
|
||||
@@ -477,6 +578,7 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
.arg(intelSigned(d, 16, 16))
|
||||
.arg(intelSigned(d, 32, 16));
|
||||
m_tel->vel = double(intelSigned(d, 48, 8));
|
||||
m_tel->hasArmPose = true;
|
||||
break;
|
||||
}
|
||||
// ---------- 0x18348180 机械臂状态3:关节角 J1~J4 ----------
|
||||
@@ -484,6 +586,7 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
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));
|
||||
m_tel->hasJoints = true;
|
||||
break;
|
||||
}
|
||||
// ---------- 0x18358180 机械臂状态4:关节角 J5~J6 ----------
|
||||
@@ -491,6 +594,7 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
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));
|
||||
m_tel->hasJoints = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -511,12 +615,18 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
m_tel->d2close = !m_tel->d2open;
|
||||
|
||||
m_washFaults.clear();
|
||||
const QString sys = QString::fromUtf8(kSysWash);
|
||||
const QString grp = QString::fromUtf8(kGroupWash);
|
||||
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组分液位低");
|
||||
addNoCodeFault(m_washFaults, sys, grp, "SYS", 1, "洗消系统故障");
|
||||
const bool lowA = intelBits(d, 6, 1) != 0;
|
||||
const bool lowB = intelBits(d, 7, 1) != 0;
|
||||
if (lowA)
|
||||
addNoCodeFault(m_washFaults, sys, grp, "LVL", 1, "A组分/清水液位低");
|
||||
if (lowB)
|
||||
addNoCodeFault(m_washFaults, sys, grp, "LVL", 2, "B组分液位低");
|
||||
m_tel->levelALow = lowA; // 首页/作业选择中间区域的液位数值据此红闪
|
||||
m_tel->levelBLow = lowB;
|
||||
|
||||
m_tel->levelA = double(intelBits(d, 16, 8));
|
||||
m_tel->levelFoam = double(intelBits(d, 24, 8));
|
||||
@@ -524,6 +634,7 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
m_tel->flowB = double(intelBits(d, 40, 8));
|
||||
m_tel->gasFlow = double(intelBits(d, 48, 8));
|
||||
m_tel->foam = double(intelBits(d, 56, 8));
|
||||
m_tel->hasWash = true;
|
||||
break;
|
||||
}
|
||||
// ---------- 0x18378180 洗消工作状态2:压力 / 流量反馈 / 泵故障码 ----------
|
||||
@@ -533,12 +644,14 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
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_tel->hasWashPress = true;
|
||||
|
||||
m_pumpFaults.clear();
|
||||
addFaultByCode(m_pumpFaults, QString::fromUtf8(kSysWash), "AIR",
|
||||
const QString washGrp = QString::fromUtf8(kGroupWash);
|
||||
addFaultByCode(m_pumpFaults, QString::fromUtf8(kSysWash), washGrp, "AIR",
|
||||
kAirCompFaults, int(sizeof(kAirCompFaults) / sizeof(CodeText)),
|
||||
quint8(intelBits(d, 48, 8)));
|
||||
addFaultByCode(m_pumpFaults, QString::fromUtf8(kSysWash), "PLG",
|
||||
addFaultByCode(m_pumpFaults, QString::fromUtf8(kSysWash), washGrp, "PLG",
|
||||
kPlungerFaults, int(sizeof(kPlungerFaults) / sizeof(CodeText)),
|
||||
quint8(intelBits(d, 56, 8)));
|
||||
break;
|
||||
@@ -552,6 +665,7 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
m_tel->gasHost = (warn & 0x04) ? QString("fault")
|
||||
: (sta == 4) ? QString("detecting")
|
||||
: QString("standby");
|
||||
m_tel->hasGasHost = true;
|
||||
|
||||
// Toxic_types 是位组合:1=P类 2=S类 4=HNO类 8=CH类 16=AS类
|
||||
const quint8 types = quint8(intelBits(d, 8, 8));
|
||||
@@ -567,25 +681,24 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
m_tel->gasLevel = (lvl >= 1 && lvl <= 5) ? QString("%1级").arg(lvl) : QString("--");
|
||||
|
||||
m_detectorFaults.clear();
|
||||
// 告警是位组合(1/2/4/8),没有故障码,顶部只显示描述
|
||||
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)});
|
||||
addNoCodeFault(m_detectorFaults, QString::fromUtf8(kSysDetector),
|
||||
QString::fromUtf8(kGroupDetector), "W", w.code,
|
||||
w.text);
|
||||
break;
|
||||
}
|
||||
// ---------- 0x18398180 检测仪结果状态2:40 位浓度(ppb) ----------
|
||||
case kIdDetectorFb2: {
|
||||
m_tel->gasConc = double(intelBits(d, 0, 40));
|
||||
m_tel->hasGasConc = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return; // 未知 canId 不触发刷新(分类四本次未实现)
|
||||
break; // 未知 canId(分类四本次未实现):这一帧不写任何东西
|
||||
}
|
||||
|
||||
cacheFaults();
|
||||
emit telemetryUpdated();
|
||||
}
|
||||
|
||||
// 把各帧的故障合并进 Telemetry,并把“新出现”的故障写入故障历史
|
||||
@@ -596,35 +709,70 @@ void CarCommunication::cacheFaults()
|
||||
const QVector<QVector<CanFault>*> groups = {
|
||||
&m_vsf1Faults, &m_vsf2Faults, &m_vsf3Faults, &m_vsf4Faults,
|
||||
};
|
||||
|
||||
// 页面按 desc 显示:等级型故障补回“一级故障/二级故障/三级故障”后缀,
|
||||
// 与顶部轮播的“,N级”写法不同,保持页面既有文案不变。
|
||||
auto pageText = [](const CanFault& f) {
|
||||
return f.level > 0 ? f.desc + QString::fromUtf8(kLevels[f.level]) : f.desc;
|
||||
};
|
||||
|
||||
QStringList chassis;
|
||||
for (const QVector<CanFault>* g : groups)
|
||||
for (const CanFault& f : *g) chassis << f.desc;
|
||||
for (const CanFault& f : *g) chassis << pageText(f);
|
||||
m_tel->chassisFaults = chassis;
|
||||
|
||||
QStringList wash;
|
||||
for (const CanFault& f : m_washFaults) wash << f.desc;
|
||||
for (const CanFault& f : m_detectorFaults) wash << f.desc;
|
||||
for (const CanFault& f : m_washFaults) wash << pageText(f);
|
||||
m_tel->washFaults = wash;
|
||||
|
||||
QStringList pump;
|
||||
for (const CanFault& f : m_pumpFaults) pump << f.desc;
|
||||
for (const CanFault& f : m_pumpFaults) pump << pageText(f);
|
||||
m_tel->pumpFaults = pump;
|
||||
|
||||
// 气体检测仪单列一份:洗消页底栏的“系统故障”灯只认洗消与泵,
|
||||
// 检测仪的告警归到它自己的卡片,不能顺带点亮洗消的故障灯。
|
||||
QStringList det;
|
||||
for (const CanFault& f : m_detectorFaults) det << pageText(f);
|
||||
m_tel->detectorFaults = det;
|
||||
|
||||
updateCommText();
|
||||
|
||||
// 周期报文会反复报到同一故障,只在首次出现时写故障历史
|
||||
const QVector<QVector<CanFault>*> all = {
|
||||
&m_vsf1Faults, &m_vsf2Faults, &m_vsf3Faults, &m_vsf4Faults,
|
||||
&m_washFaults, &m_pumpFaults, &m_detectorFaults,
|
||||
&m_washFaults, &m_pumpFaults, &m_detectorFaults, &m_commFaults,
|
||||
};
|
||||
for (const QVector<CanFault>* 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);
|
||||
emit faultAppended(f.system, f.code, pageText(f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 顶部左区固定显示的那一行:只反映通信状态,不再轮播各项故障明细
|
||||
// (底盘/洗消/气体检测仪的明细各自进对应卡片的“故障”行)。
|
||||
void CarCommunication::updateCommText()
|
||||
{
|
||||
if (!m_tel) return;
|
||||
|
||||
QString text;
|
||||
if (!m_lastCommOk)
|
||||
text = QString::fromUtf8(kGroupComm); // 链路断时没有码,只能笼统报“通信异常”
|
||||
else if (m_commFaultCode != 0)
|
||||
text = commFaultText(quint8(m_commFaultCode));
|
||||
else
|
||||
text = QString::fromUtf8("系统通信正常");
|
||||
|
||||
// 周期报文会反复刷同样的内容,只有真变了才通知顶部
|
||||
if (text == m_tel->commText && m_commFaultCode == m_tel->commCode) return;
|
||||
m_tel->commText = text;
|
||||
m_tel->commCode = m_commFaultCode;
|
||||
emit commTextChanged();
|
||||
}
|
||||
|
||||
// ===================== 发包 =====================
|
||||
void CarCommunication::sendFrame(quint32 canId, const quint8* data)
|
||||
{
|
||||
@@ -636,10 +784,51 @@ void CarCommunication::sendFrame(quint32 canId, const quint8* data)
|
||||
f.dlc = kDataLen;
|
||||
std::memcpy(f.data, data, kDataLen);
|
||||
f.checksum = canChecksum(canId, f.dlc, f.data);
|
||||
f.tail = kFrameTail;
|
||||
m_sock->writeDatagram(reinterpret_cast<const char*>(&f), kFrameLen,
|
||||
QHostAddress(kCanTxHost), kCanTxPort);
|
||||
}
|
||||
|
||||
// 洗消作业期间的末端俯仰动作,每 50ms 一拍,共三段:
|
||||
// 启动 → SwingCentering:先把俯仰轴斜坡放回中位,到位后转 SwingSine
|
||||
// 作业 → SwingSine: 从中位起按正弦来回,一个往返 kWashSwingTicks 拍
|
||||
// 停止 → SwingRestoring:从当前的摆动值斜坡还原成作业前的轴值,到位后收手
|
||||
// 这三轴在协议里叫「末端姿态俯仰动作」,是像摇杆偏摆那样的量(中位 = 不动),不是绝对位置:
|
||||
// 所以"回中心"就是把轴值放回 kRcAxisMid,"回到洗消前的位置"就是放回 m_pitchBeforeWash。
|
||||
// 停的时候**必须**把轴值收回到一个不动值——Arm_ControlCmd2 是周期报文,
|
||||
// 停在哪个值,车端就认为还在给哪个俯仰指令、末端会一直走下去。
|
||||
void CarCommunication::updateWashSwing()
|
||||
{
|
||||
switch (m_swingState) {
|
||||
case SwingOff:
|
||||
return;
|
||||
|
||||
case SwingCentering:
|
||||
m_pitch = lerpAxis(m_pitchBeforeWash, kRcAxisMid, double(++m_swingTick) / kSwingRampTicks);
|
||||
if (m_swingTick >= kSwingRampTicks) {
|
||||
m_swingState = SwingSine;
|
||||
m_swingPhase = 0; // 起摆点固定是中位,作业过程可复现
|
||||
}
|
||||
return;
|
||||
|
||||
case SwingSine: {
|
||||
const double ph = 2.0 * kPi * m_swingPhase / kWashSwingTicks;
|
||||
// 夹一下:万一以后把 kWashSwingAmp 调过 kRcAxisMid,负数转 quint16 会翻成极值
|
||||
m_pitch = quint16(qBound(0, int(kRcAxisMid + kWashSwingAmp * std::sin(ph)), 4096));
|
||||
m_swingPhase = (m_swingPhase + 1) % kWashSwingTicks;
|
||||
return;
|
||||
}
|
||||
|
||||
case SwingRestoring:
|
||||
m_pitch = lerpAxis(m_swingFrom, m_pitchBeforeWash, double(++m_swingTick) / kSwingRampTicks);
|
||||
if (m_swingTick >= kSwingRampTicks) {
|
||||
m_pitch = m_pitchBeforeWash; // 收尾对齐,别停在插值的截断值上
|
||||
m_swingState = SwingOff;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 50ms 组:Chassis_ControlCmd / Arm_ControlCmd1 / Arm_ControlCmd2 / Emergency_Cmd
|
||||
void CarCommunication::onSendFast()
|
||||
{
|
||||
@@ -664,6 +853,8 @@ void CarCommunication::onSendFast()
|
||||
setIntelBits(d, 40, 16, m_frontrear);
|
||||
sendFrame(kIdArmCmd1, d);
|
||||
}
|
||||
// 洗消作业期间的末端俯仰摆动:这一拍该发什么轴值由它决定
|
||||
updateWashSwing();
|
||||
{
|
||||
quint8 d[8] = {0};
|
||||
setIntelBits(d, 0, 16, m_roll);
|
||||
@@ -727,23 +918,34 @@ void CarCommunication::onSendSlow()
|
||||
}
|
||||
|
||||
// ===================== 上行命令 → 控制状态 =====================
|
||||
// 方向名 → 轴量:档位 后 0 / 中 2048 / 前 4096;转向 左 0 / 中 2048 / 右 4096
|
||||
static void chassisAxis(const QString& dir, const QString& turn, quint16& gear, quint16& turning)
|
||||
{
|
||||
gear = (dir == "fwd") ? 4096 : (dir == "back") ? 0 : kRcAxisMid;
|
||||
turning = (turn == "left") ? 0 : (turn == "right") ? 4096 : kRcAxisMid;
|
||||
}
|
||||
|
||||
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;
|
||||
if (m_rcActive) return; // 遥控器正在操作:面板这一路让位(遥控器松手后页面会重发)
|
||||
sendChassisMoveRc(dir, speed, turn);
|
||||
}
|
||||
|
||||
// 转向推杆量:左 0 / 中 2048 / 右 4096
|
||||
if (turn == "left") m_turning = 0;
|
||||
else if (turn == "right") m_turning = 4096;
|
||||
else m_turning = kRcAxisMid;
|
||||
void CarCommunication::sendChassisMoveRc(const QString& dir, double speed, const QString& turn)
|
||||
{
|
||||
chassisAxis(dir, turn, m_gear, m_turning);
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (m_rcActive) return; // 遥控器正在操作:面板这一路让位(遥控器松手后页面会重发)
|
||||
sendArmMoveRc(axis, dir);
|
||||
}
|
||||
|
||||
void CarCommunication::sendArmMoveRc(const QString& axis, int dir)
|
||||
{
|
||||
// 轴位推杆量:dir +1 / -1 / 0 → 4096 / 0 / 中位
|
||||
const quint16 v = (dir > 0) ? 4096 : (dir < 0) ? 0 : kRcAxisMid;
|
||||
@@ -780,11 +982,32 @@ void CarCommunication::sendWashStart(const QString& mode)
|
||||
{
|
||||
m_decontamMode = (mode == "disinfect") ? 3 : 2; // 3=手动消毒 2=手动消除
|
||||
m_decontamStart = true;
|
||||
|
||||
// 末端俯仰摆动:记下作业前的轴值,先回中心再起摆(见 updateWashSwing)
|
||||
m_pitchBeforeWash = m_pitch;
|
||||
m_swingTick = 0;
|
||||
if (m_pitch == kRcAxisMid) {
|
||||
m_swingState = SwingSine; // 本来就在中位,不用空走一段回中
|
||||
m_swingPhase = 0;
|
||||
} else {
|
||||
m_swingState = SwingCentering;
|
||||
}
|
||||
}
|
||||
|
||||
void CarCommunication::sendWashStop()
|
||||
{
|
||||
m_decontamStart = false;
|
||||
|
||||
// 摆动收尾:从当前值斜坡还原成作业前的轴值。
|
||||
// 不直接把状态清掉,是因为清掉等于把最后一拍的轴值一直重发下去——末端会接着走。
|
||||
if (m_swingState != SwingOff) {
|
||||
m_swingFrom = int(m_pitch);
|
||||
m_swingTick = 0;
|
||||
// 已经就在作业前的位置(常见:作业前本来就是中位),不必多走一段
|
||||
m_swingState = (m_swingFrom == int(m_pitchBeforeWash)) ? SwingOff : SwingRestoring;
|
||||
if (m_swingState == SwingOff)
|
||||
m_pitch = m_pitchBeforeWash;
|
||||
}
|
||||
}
|
||||
|
||||
void CarCommunication::sendBallValve(int valve, bool open)
|
||||
@@ -829,6 +1052,9 @@ void CarCommunication::sendSetParam(const QString& key, double value)
|
||||
void CarCommunication::sendSelfCheckStart()
|
||||
{
|
||||
m_selfCheckIndex = 0;
|
||||
m_selfCheckWait = 0;
|
||||
m_selfCheckAllOk = true;
|
||||
m_debugModeBeforeCheck = m_debugMode;
|
||||
m_selfCheckTimer->start();
|
||||
}
|
||||
|
||||
@@ -842,23 +1068,64 @@ void CarCommunication::sendEstopReset()
|
||||
m_emergStop = 0;
|
||||
}
|
||||
|
||||
// TODO:CAN 矩阵(sheet RC)没有自检报文,故暂由本端跑完 10 项序列,仅为维持
|
||||
// TODO:CAN 矩阵(sheet RC)没有自检报文,故 0~9 项暂由本端计时直接判过,仅为维持
|
||||
// ④自检 → ⑤作业选择 的流程不中断(onSelfCheckDone 是唯一点亮“作业选择”的地方)。
|
||||
// 拿到真实自检报文后,改为解析该报文并删除此本地驱动。
|
||||
// 拿到真实自检报文后,改为解析该报文并删除这段本地驱动。
|
||||
// 14 项里只有球阀那 4 项(kSelfCheckValveFirst 起)是真检查:要下发指令并等反馈。
|
||||
void CarCommunication::onSelfCheckTick()
|
||||
{
|
||||
bool ok = true;
|
||||
if (m_selfCheckFault && m_selfCheckIndex == 6) ok = false; // 演示:球阀D1关闭故障
|
||||
// ---- 球阀 D1/D2 开启、关闭:下发 Decontam_DebugCmd,等 MBV_D1_Sta/MBV_D2_Sta 反馈到位 ----
|
||||
if (m_selfCheckIndex >= kSelfCheckValveFirst) {
|
||||
const int k = m_selfCheckIndex - kSelfCheckValveFirst; // 0..3
|
||||
const bool isD1 = (k < 2);
|
||||
const bool wantOpen = (k % 2 == 0);
|
||||
|
||||
if (m_selfCheckWait == 0) {
|
||||
// 项首拍:置调试模式 + 写目标阀位。命令帧由 100ms 周期定时器持续重发,
|
||||
// 所以只需在这里改状态,不必每拍重发。
|
||||
m_debugMode = true;
|
||||
if (isD1) m_d1Open = wantOpen;
|
||||
else m_d2Open = wantOpen;
|
||||
emit selfCheckItemBusy(m_selfCheckIndex);
|
||||
}
|
||||
|
||||
// 车端完全没有反馈时不能算“回复正常”:否则 d1open/d2open 的缺省值 false
|
||||
// 会让“关闭”两项在遥测全无的情况下被误判通过。
|
||||
const bool fbOpen = m_tel && (isD1 ? m_tel->d1open : m_tel->d2open);
|
||||
const bool pass = m_lastCommOk && (fbOpen == wantOpen);
|
||||
|
||||
if (!pass && m_selfCheckWait < kValveWaitTicks) {
|
||||
++m_selfCheckWait; // 反馈还没到位:继续等,本轮不推进序列
|
||||
return;
|
||||
}
|
||||
m_selfCheckWait = 0;
|
||||
finishSelfCheckItem(pass);
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- 0~9:无对应报文,本地计时判过。演示开关只在此段生效 ----
|
||||
finishSelfCheckItem(!(m_selfCheckFault && m_selfCheckIndex == kSelfCheckDemoFaultIndex));
|
||||
}
|
||||
|
||||
// 记录单项结果并推进序列;跑完最后一项时收尾(恢复调试模式、发 selfCheckDone)。
|
||||
// allOk 由本端累计,不能再用演示开关推——球阀那 4 项真的可能失败。
|
||||
void CarCommunication::finishSelfCheckItem(bool ok)
|
||||
{
|
||||
m_selfCheckAllOk = m_selfCheckAllOk && ok;
|
||||
emit selfCheckItem(m_selfCheckIndex, ok);
|
||||
|
||||
++m_selfCheckIndex;
|
||||
if (m_selfCheckIndex >= 10) {
|
||||
m_selfCheckTimer->stop();
|
||||
emit selfCheckDone(!m_selfCheckFault);
|
||||
}
|
||||
if (++m_selfCheckIndex < kSelfCheckItemCount)
|
||||
return;
|
||||
|
||||
m_selfCheckTimer->stop();
|
||||
m_debugMode = m_debugModeBeforeCheck; // 退出调试模式,别把整车留在自检状态
|
||||
emit selfCheckDone(m_selfCheckAllOk);
|
||||
}
|
||||
|
||||
void CarCommunication::onHeartbeatTimeout()
|
||||
{
|
||||
setCommOk(false);
|
||||
// 超时后不再有收帧,cacheFaults 不会跑:这里补刷一次顶部,
|
||||
// 否则顶部会一直停在“系统通信正常”上、看不出链路已经断了。
|
||||
updateCommText();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user