新版布局
This commit is contained in:
+27
-5
@@ -172,12 +172,30 @@ void AppContext::triggerEstop()
|
||||
|
||||
void AppContext::setMode(Mode m)
|
||||
{
|
||||
// 洗消作业期间强制机械臂模式:作业时末端要自动俯仰摆动,而 Arm_ControlCmd1 的
|
||||
// 使能位就挂在这个模式上。遥控器**每帧**都会调进来,所以这个覆盖只能放在这里——
|
||||
// 放到 sendWashStart 之类的地方,会被下一帧遥控器报文冲掉。
|
||||
if (washRunning) m = Mode::Arm;
|
||||
|
||||
if (mode == m) return;
|
||||
mode = m;
|
||||
emit modeChanged(m);
|
||||
if (m_comm) m_comm->setArmMode(m == Mode::Arm); // 同步到 Arm_ControlCmd1 的 Arm_modeCmd/Arm_enable
|
||||
}
|
||||
|
||||
void AppContext::setWashRunning(bool on)
|
||||
{
|
||||
if (on == washRunning) return;
|
||||
washRunning = on;
|
||||
|
||||
if (on) {
|
||||
m_modeBeforeWash = mode;
|
||||
setMode(Mode::Arm); // 覆盖生效,进入洗消状态
|
||||
} else {
|
||||
setMode(m_modeBeforeWash); // 作业结束,还原作业前的模式
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 遥控器(24 字节二进制帧) =====================
|
||||
void AppContext::onRcFrame(const RcFrame& f)
|
||||
{
|
||||
@@ -236,7 +254,7 @@ void AppContext::applyRcArm(const RcFrame& f)
|
||||
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);
|
||||
m_comm->sendArmMoveRc(QString::fromLatin1(kArmAxes[i]), d);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,11 +271,11 @@ void AppContext::sendChassisCommand()
|
||||
else if (m_rcSteerDir < 0) turn = "left";
|
||||
|
||||
if (dir == "stop" && turn.isEmpty()) {
|
||||
m_comm->sendChassisMove("stop", 0);
|
||||
m_comm->sendChassisMoveRc("stop", 0);
|
||||
return;
|
||||
}
|
||||
const double spd = (dir == "stop") ? 0.0 : getDouble(keySpeed(), 4.5);
|
||||
m_comm->sendChassisMove(dir, spd, turn);
|
||||
m_comm->sendChassisMoveRc(dir, spd, turn);
|
||||
}
|
||||
|
||||
void AppContext::updateRcDriving()
|
||||
@@ -270,6 +288,9 @@ void AppContext::updateRcDriving()
|
||||
|
||||
if (driving == m_rcDriving) return;
|
||||
m_rcDriving = driving;
|
||||
// 先告诉通信层再发信号:页面收到 rcDrivingChanged(false) 会重发按住的方向,
|
||||
// 那一下必须畅通(让位标志得已经落下)
|
||||
if (m_comm) m_comm->setRcActive(driving);
|
||||
emit rcDrivingChanged(driving);
|
||||
}
|
||||
|
||||
@@ -280,15 +301,16 @@ void AppContext::onRcLinkChanged(bool ok)
|
||||
if (!ok) {
|
||||
// 链路中断:撤销所有遥控器动作,否则最后一条指令会被一直挂住。
|
||||
// 不动急停状态——丢包导致的 bit4 读为 0 不应被当成遥控器下发的复位。
|
||||
// 这里必须走 Rc 入口:此刻 rcActive 还是真,走面板入口会被让位挡掉。
|
||||
if (m_rcGearDir != 0 || m_rcSteerDir != 0) {
|
||||
m_rcGearDir = 0;
|
||||
m_rcSteerDir = 0;
|
||||
m_comm->sendChassisMove("stop", 0);
|
||||
m_comm->sendChassisMoveRc("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);
|
||||
m_comm->sendArmMoveRc(QString::fromLatin1(kArmAxes[i]), 0);
|
||||
}
|
||||
}
|
||||
updateRcDriving();
|
||||
|
||||
+9
-1
@@ -34,9 +34,14 @@ public:
|
||||
// 全局状态
|
||||
Mode mode = Mode::Drive; // 行车/机械臂模式(机械旋钮)
|
||||
bool estopTriggered = false; // 急停是否触发(需人工复位)
|
||||
bool washRunning = false; // 洗消运行中(消除/消毒共用)
|
||||
bool gasRunning = false; // 气体检测仪检测中
|
||||
|
||||
// 洗消作业开关(消除/消毒共用)。作业期间**强制机械臂模式**——末端要自动俯仰摆动,
|
||||
// 而 Arm_ControlCmd1 的使能位就挂在这个模式上;作业结束后回到作业前的模式。
|
||||
// 页面不要直接改成员,走这个入口。
|
||||
void setWashRunning(bool on);
|
||||
bool isWashRunning() const { return washRunning; }
|
||||
|
||||
// ===================== 存储区参数(QSettings 持久化) =====================
|
||||
double getDouble(const QString& key, double def) const;
|
||||
void setDouble(const QString& key, double val);
|
||||
@@ -100,6 +105,9 @@ private:
|
||||
int m_rcAxisDir[6] = {0, 0, 0, 0, 0, 0}; // 顺序见 kArmAxes
|
||||
bool m_rcEstopPrev = false;
|
||||
bool m_rcDriving = false;
|
||||
|
||||
bool washRunning = false;
|
||||
Mode m_modeBeforeWash = Mode::Drive; // 洗消作业前的工作模式,作业结束后还原
|
||||
};
|
||||
|
||||
// 便捷访问宏
|
||||
|
||||
+60
-18
@@ -8,33 +8,62 @@
|
||||
#include <QMouseEvent>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
|
||||
// 启动 500ms 闪烁:每 500ms 切换一次动态属性,QSS 据此在 40%↔100% 透明度间切换
|
||||
QTimer* startBlink(QWidget* w, const char* prop)
|
||||
// ===================== 全局闪烁节拍 =====================
|
||||
BlinkBeat::BlinkBeat(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
// 复用已存在的定时器
|
||||
if (auto* t = w->findChild<QTimer*>(QString("blink_") + prop))
|
||||
return t;
|
||||
QTimer* t = new QTimer(w);
|
||||
t->setObjectName(QString("blink_") + prop);
|
||||
t->setInterval(500);
|
||||
QObject::connect(t, &QTimer::timeout, w, [w, prop]() {
|
||||
bool cur = w->property(prop).toBool();
|
||||
w->setProperty(prop, !cur);
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setInterval(500); // 闪烁半周期:亮 500ms、灭 500ms
|
||||
connect(m_timer, &QTimer::timeout, this, [this]() {
|
||||
m_on = !m_on;
|
||||
emit toggled(m_on);
|
||||
});
|
||||
m_timer->start();
|
||||
}
|
||||
|
||||
BlinkBeat* BlinkBeat::instance()
|
||||
{
|
||||
// 首次用到才建(此时 QApplication 已就绪),进程结束自然销毁。
|
||||
// 不挂在某个控件下:它要跨页面、跨控件地给所有闪烁源提供同一个相位。
|
||||
static BlinkBeat beat;
|
||||
return &beat;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// 订阅句柄挂在被闪的控件下、名字固定,stopBlink 靠它找回并删掉。
|
||||
// QObject 析构时会自动断开它身上所有的连接,所以“删除句柄”就等于“退订”。
|
||||
QString blinkKey(const char* prop) { return QString("blinkconn_") + prop; }
|
||||
}
|
||||
|
||||
// 挂到全局节拍上:每拍翻转一次动态属性,QSS 据此在 40%↔100% 透明度间切换。
|
||||
// 用公共节拍而不是各起一个定时器——各自起的话相位各异,同屏几处会各闪各的。
|
||||
void startBlink(QWidget* w, const char* prop)
|
||||
{
|
||||
if (w->findChild<QObject*>(blinkKey(prop)))
|
||||
return; // 已订阅
|
||||
|
||||
QObject* holder = new QObject(w);
|
||||
holder->setObjectName(blinkKey(prop));
|
||||
// 以 holder 作接收者:它随控件一起销毁,连接也就一并断开
|
||||
QObject::connect(BlinkBeat::instance(), &BlinkBeat::toggled, holder, [w, prop](bool on) {
|
||||
w->setProperty(prop, on);
|
||||
w->style()->unpolish(w);
|
||||
w->style()->polish(w);
|
||||
w->update();
|
||||
});
|
||||
w->setProperty(prop, true);
|
||||
t->start();
|
||||
return t;
|
||||
|
||||
// 立刻按当前相位显示,不然要等到下一拍
|
||||
w->setProperty(prop, BlinkBeat::instance()->on());
|
||||
w->style()->unpolish(w);
|
||||
w->style()->polish(w);
|
||||
w->update();
|
||||
}
|
||||
|
||||
void stopBlink(QWidget* w, const char* prop)
|
||||
{
|
||||
if (auto* t = w->findChild<QTimer*>(QString("blink_") + prop)) {
|
||||
t->stop();
|
||||
delete t;
|
||||
}
|
||||
if (QObject* holder = w->findChild<QObject*>(blinkKey(prop)))
|
||||
delete holder; // 删除句柄 = 退订
|
||||
|
||||
w->setProperty(prop, false);
|
||||
w->style()->unpolish(w);
|
||||
w->style()->polish(w);
|
||||
@@ -101,6 +130,13 @@ bool ButtonFocusGlow::eventFilter(QObject* obj, QEvent* ev)
|
||||
parent->update(parent->rect());
|
||||
}
|
||||
break;
|
||||
case QEvent::EnabledChange:
|
||||
// 光标一直停在按钮上时,禁用期间 Qt 仍给它留着 WA_UnderMouse;重新启用的一瞬间
|
||||
// :hover 规则就生效了(:disabled 排在 :hover 之后才压得住它),看着像"被选中"。
|
||||
// 这里把这层残影清掉,和 FocusIn 那条是同一件事:保证同时只有一个按钮呈选中样式。
|
||||
if (w->isEnabled() && w->underMouse())
|
||||
clearStaleHover(QApplication::focusWidget());
|
||||
break;
|
||||
case QEvent::MouseButtonPress:
|
||||
// 触摸/点击与 Tab 归一到同一个焦点:谁最后操作,谁就是唯一的选中
|
||||
if (static_cast<QMouseEvent*>(ev)->button() == Qt::LeftButton
|
||||
@@ -264,6 +300,12 @@ QPushButton[numpad="true"]:focus {
|
||||
border: 2px solid #8ABFFF;
|
||||
}
|
||||
|
||||
/* ===== 数据卡片数值 ===== */
|
||||
/* 数值标签的颜色必须由这里给:卡片的行内样式只写字号/字重,不写 color。
|
||||
行内样式优先级高于应用级样式表,写上 color 就会把下面的告警红 / 暗红闪一起盖掉。
|
||||
本规则与 [alert] 同为单属性选择器,靠位置分胜负,故必须排在告警规则之前。 */
|
||||
QLabel[cardValue="true"] { color: TXT; }
|
||||
|
||||
/* ===== 告警文字(红色闪烁由 blink 属性驱动) ===== */
|
||||
QLabel[alert="true"] { color: DAN; font-weight: 700; }
|
||||
QLabel[alert="true"][blink="false"] { color: rgba(229,72,77,102); }
|
||||
|
||||
+130
-13
@@ -58,23 +58,26 @@ enum PageId {
|
||||
};
|
||||
|
||||
// ===================== 遥测数据(车载端 → UI) =====================
|
||||
// 由通信层解析车端反馈报文后填充,页面刷新时读取
|
||||
// 由通信层解析车端反馈报文后填充,页面刷新时读取。
|
||||
// 这里不留任何演示初值:数值字段只由报文写入,配套的 has* 标志在收到对应报文时置位,
|
||||
// 页面按 dashIf() 判断“还没有值”,未收到时显示 "--"。
|
||||
struct Telemetry {
|
||||
// 底盘
|
||||
int soc = 85; // 电量 %
|
||||
double volt = 360.0; // 电压 V
|
||||
double speed = 3.45; // 车速 km/h
|
||||
bool handbrake = true; // 手刹
|
||||
int soc = 0; // 电量 %
|
||||
double volt = 0.0; // 电压 V
|
||||
double speed = 0.0; // 车速 km/h
|
||||
bool handbrake = false; // 手刹
|
||||
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; // 方向盘角度
|
||||
int alignStatus = 0; // 角度对中状态:0x11=对中失败、0xEE/0x55=成功、其它=未对中
|
||||
QStringList chassisFaults; // 底盘故障(多条 2s 轮换)
|
||||
|
||||
// 机械臂
|
||||
QVector<double> js{30, 20, 0, 90, 45, 0}; // 关节角 J1~J6(°)
|
||||
QVector<double> js{0, 0, 0, 0, 0, 0}; // 关节角 J1~J6(°)
|
||||
QString pos = "--"; // 末端位置 XYZ
|
||||
QString pose = "--"; // 姿态 RX/RY/RZ
|
||||
double vel = 0.0; // 末端速度
|
||||
@@ -96,11 +99,13 @@ struct Telemetry {
|
||||
QStringList washFaults; // 洗消故障代码
|
||||
|
||||
// 液位
|
||||
double levelA = 100.0; // 清水/药剂液位 L
|
||||
double levelFoam = 30.0; // 泡沫液位 L
|
||||
double levelA = 0.0; // 清水/药剂液位 L
|
||||
double levelFoam = 0.0; // 泡沫液位 L
|
||||
bool levelALow = false; // A组分/清水液位低(A_LiquidLevel_Low)
|
||||
bool levelBLow = false; // B组分/泡沫液位低(B_LiquidLevel_Low)
|
||||
|
||||
// 气体检测仪
|
||||
QString gasHost = "standby"; // 主机状态 standby/detecting/fault
|
||||
QString gasHost; // 主机状态 standby/detecting/fault,空=未收到
|
||||
QString gasType = "--"; // 有毒物种类
|
||||
QString gasLevel = "--"; // 有毒物等级
|
||||
double gasConc = 0.0; // 有毒物浓度 ppb
|
||||
@@ -110,7 +115,6 @@ struct Telemetry {
|
||||
bool d1close = true; // D1 关闭到位
|
||||
bool d2open = false; // D2 开启到位
|
||||
bool d2close = true; // D2 关闭到位
|
||||
QString valveFault; // 球阀故障码
|
||||
|
||||
// 泵
|
||||
bool plunger = false; // 柱塞泵上电
|
||||
@@ -118,6 +122,10 @@ struct Telemetry {
|
||||
bool leaf = false; // 叶片泵上电
|
||||
QStringList pumpFaults; // 泵故障代码
|
||||
|
||||
// 气体检测仪故障。单列一份、不并进 washFaults:洗消页底栏的“系统故障”灯
|
||||
// 只看洗消系统与泵,气体检测仪的告警要归到它自己的卡片上。
|
||||
QStringList detectorFaults;
|
||||
|
||||
// 遥控器(24 字节二进制帧,见 comm/uart.h)
|
||||
bool rcLinkOk = false; // 遥控器链路是否在线
|
||||
int rcBattery = 0; // 遥控器电量 %
|
||||
@@ -138,13 +146,122 @@ struct Telemetry {
|
||||
// 全局
|
||||
bool commOk = true; // 通信状态
|
||||
bool estop = false; // 急停触发
|
||||
|
||||
// 顶部左区固定显示的一行通信状态,由 CarCommunication 渲染:
|
||||
// 链路正常且 All_Comm_FaultCode=0 → “系统通信正常”;有码 → 该码的文案
|
||||
// (“机械臂通信异常”…);链路断开、没有码 → “通信异常”。
|
||||
// 各项子系统故障明细不在这里,而是各自进对应卡片的“故障”行。
|
||||
QString commText = QString::fromUtf8("系统通信正常");
|
||||
int commCode = 0; // All_Comm_FaultCode 原值,0 = 通信正常
|
||||
|
||||
// ---- 各报文是否已收到 ----
|
||||
// CarCommunication 每解析到一条报文就置位对应的 has*,页面按它决定“还没有值”时显示 "--"。
|
||||
// 按“一条报文覆盖哪些显示项”分组,而不是每个字段一个标志。
|
||||
bool hasBattery = false; // 0x18308180 底盘状态1:电量 / 电压
|
||||
bool hasSpeed = false; // 0x18318180 底盘状态2:车速 / 手刹 / Ready状态
|
||||
bool hasChassis4 = false; // 0x183B8180 底盘状态4:挡位 / 驻车 / 四驱 / 方向盘角度
|
||||
bool hasArmPose = false; // 0x18338180 机械臂状态2:末端速度
|
||||
bool hasJoints = false; // 0x18348180 / 0x18358180:关节角 J1~J6
|
||||
bool hasWash = false; // 0x18368180 洗消状态1:液位 / 流量 / 发泡倍数
|
||||
bool hasWashPress= false; // 0x18378180 洗消状态2:管路压力 / 空压机压力
|
||||
bool hasGasHost = false; // 0x18388180 检测仪状态1:主机状态
|
||||
bool hasGasConc = false; // 0x18398180 检测仪状态2:有毒物浓度
|
||||
};
|
||||
|
||||
// 遥测未收到(has* 为 false)时统一显示 "--"
|
||||
inline QString dashIf(bool has, const QString& text)
|
||||
{
|
||||
return has ? text : QStringLiteral("--");
|
||||
}
|
||||
|
||||
// 气体检测仪主机状态 → 显示文案
|
||||
inline QString gasHostLabel(const Telemetry& t)
|
||||
{
|
||||
if (!t.hasGasHost) return QStringLiteral("--");
|
||||
return t.gasHost == "detecting" ? QString::fromUtf8("检测中")
|
||||
: t.gasHost == "fault" ? QString::fromUtf8("故障")
|
||||
: QString::fromUtf8("待机");
|
||||
}
|
||||
|
||||
// 洗消卡片“故障”行的内容:洗消系统与泵都算它,气体检测仪另走一份 detectorFaults。
|
||||
inline QStringList washCardFaults(const Telemetry& t)
|
||||
{
|
||||
return t.washFaults + t.pumpFaults;
|
||||
}
|
||||
|
||||
// ---- 底盘的几个枚举信号 → 显示文案(取值见协议「信号值描述」列) ----
|
||||
inline QString gearLabel(int v)
|
||||
{
|
||||
return v == 1 ? QString::fromUtf8("D挡")
|
||||
: v == 2 ? QString::fromUtf8("R挡")
|
||||
: QString::fromUtf8("N挡");
|
||||
}
|
||||
|
||||
inline QString parkingLabel(int v)
|
||||
{
|
||||
switch (v) {
|
||||
case 1: return QString::fromUtf8("左电机驻车");
|
||||
case 2: return QString::fromUtf8("右电机驻车");
|
||||
case 3: return QString::fromUtf8("完全驻车");
|
||||
default: return QString::fromUtf8("未驻车");
|
||||
}
|
||||
}
|
||||
|
||||
inline QString awdLabel(int v)
|
||||
{
|
||||
return v == 1 ? QString::fromUtf8("四驱")
|
||||
: v == 2 ? QString::fromUtf8("四驱锁定")
|
||||
: QString::fromUtf8("二驱");
|
||||
}
|
||||
|
||||
inline QString rdyLabel(int v)
|
||||
{
|
||||
return v == 1 ? QString::fromUtf8("高压上电完成")
|
||||
: v == 2 ? QString::fromUtf8("准备就绪")
|
||||
: QString::fromUtf8("初始状态");
|
||||
}
|
||||
|
||||
// Angle_alignment_status:0 是"还没做过",与失败要分清楚。
|
||||
// 文案比协议描述短一点("未进行过角度对中"→"未进行过对中"):标签已经写了"角度对中状态",
|
||||
// 值里再重复一遍就溢出行宽了(底盘卡正文区 548px)。
|
||||
inline QString alignLabel(int v)
|
||||
{
|
||||
switch (v) {
|
||||
case 0x11: return QString::fromUtf8("角度对中失败");
|
||||
case 0xEE: return QString::fromUtf8("角度对中成功");
|
||||
case 0x55: return QString::fromUtf8("已对中成功");
|
||||
default: return QString::fromUtf8("未进行过对中");
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== 全局闪烁节拍 =====================
|
||||
// 界面上有好几处在“异常闪烁”:顶部通信状态、卡片故障行、告警横幅、底栏指示灯、
|
||||
// 自检结果……各自起定时器的话相位互不相同,同一屏上会各闪各的。
|
||||
// 这里做成**一个**公共节拍,所有闪烁都挂在它上面:整屏同时亮、同时灭。
|
||||
// 要改闪烁快慢只改这一处的 interval(500ms)。
|
||||
class BlinkBeat : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static BlinkBeat* instance();
|
||||
|
||||
bool on() const { return m_on; } // 当前是“亮”还是“灭”,供刚订阅上来的对齐相位
|
||||
|
||||
signals:
|
||||
void toggled(bool on);
|
||||
|
||||
private:
|
||||
explicit BlinkBeat(QObject* parent = nullptr);
|
||||
|
||||
QTimer* m_timer = nullptr;
|
||||
bool m_on = true;
|
||||
};
|
||||
|
||||
// ===================== 工具函数 =====================
|
||||
// 启动 500ms 闪烁:每 500ms 切换一次 QSS 动态属性(40% ↔ 100% 透明度由 QSS 规则体现)
|
||||
QTimer* startBlink(QWidget* w, const char* prop = "blink");
|
||||
// 启动闪烁:挂到全局节拍上,每拍翻转一次动态属性(40% ↔ 100% 透明度由 QSS 规则体现)
|
||||
void startBlink(QWidget* w, const char* prop = "blink");
|
||||
|
||||
// 关闭闪烁定时器并复位属性
|
||||
// 退订全局节拍并复位属性
|
||||
void stopBlink(QWidget* w, const char* prop = "blink");
|
||||
|
||||
// 退出/取消按钮预设样式:被选中(悬停/按下/焦点)时红底白字
|
||||
|
||||
Reference in New Issue
Block a user