增加通讯协议

This commit is contained in:
xu
2026-10-14 09:59:42 +08:00
parent 32878a9a80
commit 950e9e6c9e
102 changed files with 5538 additions and 2679 deletions
+157 -9
View File
@@ -1,9 +1,26 @@
#include "appcontext.h"
#include "udpcommunication.h"
#include "mockvehicle.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;
@@ -25,18 +42,31 @@ AppContext::AppContext(QObject* parent)
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");
if (!m_settings->contains("param/admin_pwd"))m_settings->setValue("param/admin_pwd", "6789");
// 通信层(UDP 收包发包)
m_comm = new UdpCommunication(&telemetry, this);
// 通信层(19 条 CAN 报文经 /dev/ttyS7 收发,见 comm/car.h)
m_comm = new CarCommunication(&telemetry, this);
// 内置模拟车载端(离线演示:回环 UDP 收发)
m_vehicle = new MockVehicle(this);
// 遥控器(串口 /dev/ttyS5,24 字节二进制帧,见 comm/uart.h)
m_rc = new RcCommunication(this);
// 通信层故障信号 → 写入故障历史
connect(m_comm, &UdpCommunication::faultAppended,
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();
}
@@ -117,9 +147,10 @@ void AppContext::setPassword(const QString& pwd)
m_settings->sync();
}
// 管理员密码为固定值,不持久化、不可修改
QString AppContext::adminPassword() const
{
return m_settings->value("param/admin_pwd", "6789").toString();
return "9527";
}
void AppContext::resetEstop()
@@ -144,4 +175,121 @@ 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();
}
+27 -6
View File
@@ -5,9 +5,9 @@
#include <QSettings>
#include <QVector>
#include "global.h"
#include "uart.h"
class UdpCommunication;
class MockVehicle;
class CarCommunication;
// 故障历史记录
struct FaultRecord {
@@ -25,8 +25,8 @@ class AppContext : public QObject
public:
static AppContext* instance();
UdpCommunication* comm() const { return m_comm; }
MockVehicle* vehicle() const { return m_vehicle; }
CarCommunication* comm() const { return m_comm; }
RcCommunication* rc() const { return m_rc; }
// 遥测数据快照(由通信层填充)
Telemetry telemetry;
@@ -68,17 +68,38 @@ public:
// 模式切换(行车/机械臂,由面板机械旋钮或演示按钮触发)
void setMode(Mode m);
// 遥控器:链路状态与“正在操作”判据(遥控器优先时界面按钮让位)
bool rcDriving() const { return m_rcDriving; }
signals:
void modeChanged(Mode m);
void estopChanged(bool triggered);
void commChanged(bool ok);
void rcDrivingChanged(bool driving);
private slots:
void onRcFrame(const RcFrame& f);
void onRcLinkChanged(bool ok);
private:
explicit AppContext(QObject* parent = nullptr);
void seedFaultHistory();
void applyRcChassis(int gear, int steer, bool enabled);
void applyRcArm(const RcFrame& f);
void sendChassisCommand(); // 按当前档位/转向方向下发(两者可同时)
void updateRcDriving();
QSettings* m_settings = nullptr;
UdpCommunication* m_comm = nullptr;
MockVehicle* m_vehicle = nullptr;
CarCommunication* m_comm = nullptr;
RcCommunication* m_rc = nullptr;
// 遥控器动作边沿状态:只在方向变化时下发,避免 100ms 刷屏
int m_rcGearDir = 0; // -1=后 0=中 +1=前
int m_rcSteerDir = 0; // -1=左 0=中 +1=右
int m_rcAxisDir[6] = {0, 0, 0, 0, 0, 0}; // 顺序见 kArmAxes
bool m_rcEstopPrev = false;
bool m_rcDriving = false;
};
// 便捷访问宏
+32 -4
View File
@@ -1,8 +1,10 @@
#include "global.h"
#include <QStyle>
#include <QApplication>
#include <QAbstractButton>
#include <QPushButton>
#include <QEvent>
#include <QKeyEvent>
#include <QGraphicsDropShadowEffect>
// 启动 500ms 闪烁:每 500ms 切换一次动态属性,QSS 据此在 40%↔100% 透明度间切换
@@ -61,7 +63,7 @@ bool ButtonFocusGlow::eventFilter(QObject* obj, QEvent* ev)
if (ev->type() == QEvent::FocusIn) {
if (!w->graphicsEffect()) { // 挂一圈发光(偏移 0 → 四周光晕)
QGraphicsDropShadowEffect* g = new QGraphicsDropShadowEffect(w);
g->setBlurRadius(55); // 光晕大小(越大越强)
g->setBlurRadius(40); // 光晕大小(越大越强)
g->setOffset(0, 0);
g->setColor(m_glow);
w->setGraphicsEffect(g);
@@ -80,6 +82,33 @@ void installButtonFocusGlow(QApplication* app)
app->installEventFilter(new ButtonFocusGlow(QColor("#7FBBFF"), app)); // 亮蓝,深色底更醒目
}
// ===================== 方向键(旋钮)隔离 =====================
ArrowKeyGuard::ArrowKeyGuard(QObject* parent)
: QObject(parent)
{
}
bool ArrowKeyGuard::eventFilter(QObject* obj, QEvent* ev)
{
if (ev->type() == QEvent::KeyPress) {
// 只在焦点落在按钮上时拦截:否则 QAbstractButton 会拿方向键去换焦
QWidget* fw = QApplication::focusWidget();
if (fw && qobject_cast<QAbstractButton*>(fw)) {
const int k = static_cast<QKeyEvent*>(ev)->key();
if (k == Qt::Key_Up || k == Qt::Key_Down
|| k == Qt::Key_Left || k == Qt::Key_Right)
return true;
}
}
return QObject::eventFilter(obj, ev);
}
void installArrowKeyGuard(QApplication* app)
{
if (app)
app->installEventFilter(new ArrowKeyGuard(app));
}
QString appStyleSheet()
{
const QString s = QString(R"QSS(
@@ -108,7 +137,6 @@ QPushButton {
border: 1px solid BD;
border-radius: 12px;
font-size: 40px;
min-height: 56px;
}
QPushButton:hover { background-color: PRI; color: TXT; border-color: PRI; }
QPushButton:pressed { background-color: PRI; color: #FFFFFF; border-color: PRI; }
@@ -165,7 +193,7 @@ QHeaderView::section {
font-size: 40px;
}
QTableWidget::item { padding: 6px; }
QTableWidget::item:selected { background-color: rgba(61,139,253,0.15); color: TXT; }
QTableWidget::item:selected { background-color: PRI; color: #FFFFFF; }
/* ===== 数字键盘 ===== */
QPushButton[numpad="true"] {
@@ -177,7 +205,7 @@ QPushButton[numpad="true"]:pressed { background-color: PRI; color: #FFFFFF; }
QPushButton:focus {
background-color: PRI;
color: #FFFFFF;
border: 2px solid #8ABFFF;
border: 4px solid #8ABFFF;
}
QPushButton:focus:disabled {
background-color: DIS; color: DIST; border: 1px solid DIS;
+58 -12
View File
@@ -12,9 +12,10 @@
class QApplication;
// ===================== 画布与全局常量 =====================
// 屏幕分辨率(1920×1080 横屏);若部署在更低分辨率屏,可按同一比例整体缩放
// 屏幕分辨率(1920×1200);窗口按此固定尺寸,页面布局靠 layout 自适应,
// 富余的垂直空间由各页的 addStretch 分配(控件像素尺寸与字号不随屏幕缩放)
static const int SCREEN_W = 1920;
static const int SCREEN_H = 1080;
static const int SCREEN_H = 1200;
// ===================== 工业风格配色体系(QSS 十六进制) =====================
namespace C {
@@ -44,25 +45,32 @@ enum PageId {
PageMgmt, // 密码管理
PageReset, // 密码重置
PageSelfCheck, // ④ 自检
PageJob, // ⑤ 作业选择
PageChassis, // ⑥ 机器人底盘
PageArm, // ⑦ 机械臂
PageGas, // ⑧ 气体检测仪
PageClean, // ⑨ 手动消除
PageDisinfect, // ⑩ 手动消毒
PageDebug, // ⑪ 调试维护
PageFault, // ⑫ 故障查询
PageHome, // ⑤ 系统首页面(自检完成后进入)
PageJob, // ⑥ 作业选择
PageChassis, // ⑦ 机器人底盘
PageArm, // ⑧ 机械臂
PageGas, // ⑨ 气体检测仪
PageClean, // ⑩ 消除作业
PageDisinfect, // ⑪ 消毒作业
PageDebug, // ⑫ 调试维护
PageFault, // ⑬ 故障查询
PageCount
};
// ===================== 遥测数据(车载端 → UI) =====================
// 由通信层解析 UDP 报文后填充,页面 500ms 定时读取刷新
// 由通信层解析车端反馈报文后填充,页面刷新时读取
struct Telemetry {
// 底盘
int soc = 85; // 电量 %
double volt = 360.0; // 电压 V
double speed = 3.45; // 车速 km/h
bool handbrake = true; // 手刹
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; // 方向盘角度
QStringList chassisFaults; // 底盘故障(多条 2s 轮换)
// 机械臂
@@ -74,13 +82,17 @@ struct Telemetry {
bool armHomed = false; // 是否已回位(用于底盘“机械臂未回位”告警)
// 洗消
QString washMode = "--"; // 手动消除 / 手动消毒
QString washMode = "--"; // 消除作业 / 消毒作业
bool washStart = false; // 洗消启停反馈
double flowA = 0.0; // A 药剂流量 L/min
double flowB = 0.0; // B 药剂流量 L/min
double gasFlow = 0.0; // 气体流量 L/min
double press = 0.0; // 管路压力 MPa
double airPress = 0.0; // 空压机压力 MPa
double foam = 0.0; // 发泡倍数
double flowPlungerFb = 0.0; // 柱塞泵流量反馈 L/min
double flowAirFb = 0.0; // 空压机流量反馈 L/min
double flowVaneFb = 0.0; // 叶片泵流量反馈 L/min
QStringList washFaults; // 洗消故障代码
// 液位
@@ -106,6 +118,23 @@ struct Telemetry {
bool leaf = false; // 叶片泵上电
QStringList pumpFaults; // 泵故障代码
// 遥控器(24 字节二进制帧,见 comm/uart.h)
bool rcLinkOk = false; // 遥控器链路是否在线
int rcBattery = 0; // 遥控器电量 %
bool rcDriveMode = false; // bit0 行车模式
bool rcArmMode = false; // bit1 机械臂模式
bool rcArmEnabled = false; // bit2 机械臂使能
bool rcChassisEnabled = false;// bit3 底盘使能
bool rcEstop = false; // bit4 急停
int rcGear = 2048; // 整车档位(底盘 Y)0~4096,中位 2048
int rcSteer = 2048; // 整车转向(底盘 X)
int rcPosZ = 2048; // 末端位置上下
int rcPosX = 2048; // 末端位置左右
int rcPosY = 2048; // 末端位置前后
int rcRoll = 2048; // 末端姿态横滚
int rcPitch = 2048; // 末端姿态俯仰
int rcYaw = 2048; // 末端姿态偏航
// 全局
bool commOk = true; // 通信状态
bool estop = false; // 急停触发
@@ -139,6 +168,23 @@ private:
// 为整个应用安装按钮焦点光圈
void installButtonFocusGlow(QApplication* app);
// ===================== 方向键(旋钮)隔离 =====================
// 方向键在本机是"旋钮"键位,只由 ParamControl 用来调值。但 QAbstractButton 会把
// 方向键当成沿焦点链换焦(等同 Tab/Shift+Tab),停在按钮上时按方向键焦点会乱跳。
// 这个全局过滤器把"焦点在按钮上时"的方向键吞掉,方向键只做调值、换焦只走 Tab。
class ArrowKeyGuard : public QObject
{
Q_OBJECT
public:
explicit ArrowKeyGuard(QObject* parent = nullptr);
protected:
bool eventFilter(QObject* obj, QEvent* ev) override;
};
// 为整个应用安装方向键隔离
void installArrowKeyGuard(QApplication* app);
// 全局 QSS 样式表
QString appStyleSheet();