增加通讯协议
This commit is contained in:
+157
-9
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user