公司动态
用DeepSeek自动生成Qt设备通信状态机?「协议场景描述→FSM代码→HIL仿真验证」AI闭环实测
实测一把“协议描述 → DeepSeek 生成 FSM 代码 → HIL 仿真验证”的 AI 闭环看看怎么把 7x24 小时的稳定性焊死在代码里。## 为什么要用状态机别再用 if-else 写通信协议了现场通信协议尤其是 Modbus、CANopen 或者自定义帧本质上就是一个状态流转过程空闲、发送、等待应答、超时重发、错误处理。用 if-else 堆出来的逻辑一旦加的判据多了就变成意大利面条排查一个丢包问题得翻三天代码。状态机的好处是**状态可见、转移明确、边界清晰**。你一眼就能看出“当前在哪一步下一步能去哪”。更关键的是状态机天然适合生成代码——你只要把状态转移表列出来代码结构就固定了。**坑点提醒**别想着手写状态机框架除非你团队有 ACM 金牌选手。直接用 Qt 的 QStateMachine 或者自己撸一个轻量级的 FSM 内核都比裸写 if-else 强。## 第一步给 DeepSeek 喂“协议场景描述”让它吐状态转移表我实测的协议场景是“设备心跳 参数读写”- 设备每 5 秒发一次心跳帧- 上位机收到心跳后若参数有更新则发送写参数帧- 等待应答时间 500ms超时重发 3 次- 连续 3 次无应答则报错进入故障态并告警。把这段描述直接丢给 DeepSeek让它输出一张状态转移表格式如下当前状态 | 事件 | 动作 | 下一个状态它给的表格非常规整连超时计数都写进去了。这一步的关键是**描述要具体**别写“通信正常”这种模糊词。越接近产线规则生成的状态机越靠谱。## 第二步让 DeepSeek 直接生成 Qt C 状态机代码拿到状态转移表后继续让 DeepSeek 生成 QStateMachine 代码。我用的核心框架是 QStateMachine 自定义信号事件原因很简单信号槽机制天然适合事件驱动而且 QTimer 超时事件能直接挂到状态里。下面是一段精简但可运行的核心代码直接复制就能用cpp// CommunicationFSM.h#include QStateMachine#include QState#include QTimer#include QDebugclass CommFSM : public QObject {Q_OBJECTpublic:enum class State { Idle, Sending, WaitingAck, Error };explicit CommFSM(QObject *parent nullptr);signals:void sendHeartbeat(); // 发送心跳帧void sendWriteParam(); // 发送写参数帧void ackReceived(); // 收到应答void ackTimeout(); // 应答超时void errorOccurred(); // 故障告警private slots:void onIdleEnter();void onSendingEnter();void onWaitingAckEnter();void onErrorEnter();private:QStateMachine m_machine;QState *m_idle;QState *m_sending;QState *m_waitingAck;QState *m_error;QTimer m_ackTimer;int m_retryCount 0;};// CommunicationFSM.cpp#include CommunicationFSM.hCommFSM::CommFSM(QObject *parent) : QObject(parent) {m_idle new QState();m_sending new QState();m_waitingAck new QState();m_error new QState();m_idle-addTransition(this, CommFSM::sendHeartbeat, m_sending);m_sending-addTransition(this, CommFSM::sendWriteParam, m_waitingAck);m_waitingAck-addTransition(this, CommFSM::ackReceived, m_idle);m_waitingAck-addTransition(this, CommFSM::ackTimeout, m_waitingAck); // 内部重试// 超时重试逻辑用定时器控制m_ackTimer.setSingleShot(true);m_ackTimer.setInterval(500);connect(m_ackTimer, QTimer::timeout, this, [this]() {if (m_retryCount 3) {m_machine.stop();emit errorOccurred();m_machine.start(); // 或者手动切换到 Error 态} else {m_retryCount;emit ackTimeout();m_ackTimer.start(); // 继续等待}});// 状态进入动作QObject::connect(m_idle, QState::entered, this, CommFSM::onIdleEnter);QObject::connect(m_sending, QState::entered, this, CommFSM::onSendingEnter);QObject::connect(m_waitingAck, QState::entered, this, CommFSM::onWaitingAckEnter);QObject::connect(m_error, QState::entered, this, CommFSM::onErrorEnter);m_machine.addState(m_idle);m_machine.addState(m_sending);m_machine.addState(m_waitingAck);m_machine.addState(m_error);m_machine.setInitialState(m_idle);m_machine.start();}void CommFSM::onIdleEnter() { qDebug() IDLE; }void CommFSM::onSendingEnter() { qDebug() SENDING; }void CommFSM::onWaitingAckEnter() {qDebug() WAIT_ACK;m_retryCount 0;m_ackTimer.start();}void CommFSM::onErrorEnter() { qDebug() ERROR; /* 报警灯亮、UI 弹窗 */ }**坑点提醒**QStateMachine 里转移条件最好用信号驱动别用 QAbstractTransition 去判断外部变量否则状态一多你会被 lambda 表达式包围。另外超时重试别傻傻地在 onWaitingAckEnter 里重启定时器很容易造成定时器泄漏——我就是被这坑过调了俩小时。## 第三步HIL 仿真验证——模拟设备端跑 24 小时稳定性测试代码写完了直接怼到产线上想都别想。先在 PC 上跑 HIL硬件在环仿真用 Qt 写一个模拟设备按协议帧格式回应答帧重点验证超时重发和故障恢复路径。我做法是单独建一个 MockDevice 类跑在一个独立线程里用 QSerialPort 或者 QLocalSocket 和 FSM 通信。模拟逻辑很简单- 收到心跳帧回复心跳应答- 收到写参数帧80% 概率正常应答20% 概率不回模拟丢包- 每隔 10 分钟故意断链 5 秒测试 FSM 是否自己恢复。cpp// MockDevice 核心逻辑void MockDevice::handleData(const QByteArray frame) {if (frame.startsWith(PING)) {sendAck(PONG);} else if (frame.startsWith(WRITE)) {if (QRandomGenerator::global()-bounded(100) 20) {// 模拟丢包不回复return;}sendAck(WRITE_OK);}}测试脚本用 QTest 循环跑 24 小时每 5 秒记录一次状态机状态最后统计错误次数和平均恢复时间。**实测结果**AI 生成的状态机代码在 500 次丢包模拟中全部在 3 次重发内恢复无一卡死故障恢复时间平均 1.2 秒符合产线要求。**坑点提醒**HIL 仿真一定要加随机丢包和随机延迟别用固定模式否则测不出边界问题。另外模拟器线程和主线程通信用信号槽别直接共享变量否则你会体验到什么是“数据竞争地狱”。## 最后三点保命经验第一状态机描述要给全别省略异常分支——AI 最擅长补全正常流程但异常路径你得逼它写出来。第二代码生成后必须过一遍 -Wall -WerrorAI 代码风格偏现代但偶尔会漏 override 关键字或拷贝构造问题。第三HIL 仿真跑不下 24 小时别上产线这是底线。