尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
样章-第08讲-用状态机管理设备生命周期
第 8 讲 | 用状态机管理设备生命周期【本讲要点】当设备有 6 个阶段、10 个事件时if-else会变成一团解不开的乱麻。状态机不是架构洁癖它是小内存系统的救命稻草。一、标志位地狱先看一段真实项目里被迭代出来的代码已经简化过/* 反面教材靠一堆标志位描述设备状态 */staticbool s_wifi_okfalse;staticbool s_mqtt_okfalse;staticbool s_wifi_connectingfalse;staticbool s_need_reconnectfalse;staticbool s_mqtt_waitingfalse;staticbool s_in_provisionfalse;staticuint8_ts_retry_cnt0;voidapp_poll(void){if(!s_wifi_ok!s_wifi_connectings_retry_cnt3){if(!s_in_provision){wifi_connect();s_wifi_connectingtrue;}}elseif(s_wifi_ok!s_mqtt_ok!s_mqtt_waiting){if(!s_need_reconnect){mqtt_connect();s_mqtt_waitingtrue;}}elseif(s_wifi_oks_mqtt_ok){/* 正常运行 */if(s_need_reconnect){...}}/* ……还有 40 行 */}这段代码的问题不是丑而是这三个状态爆炸7 个bool理论上可以组合出 128 种情况其中大部分是非法的比如wifi_ok true同时wifi_connecting true。没人知道当前到底处于哪种组合。无法调试设备卡住了你打印这些标志位看到s_retry_cnt 3, s_need_reconnect 1, s_wifi_ok 0——这到底是什么状态没人能一句话说清。改一处崩一处想加一个休眠阶段就要在 6 个if里都补判断漏一个就是 bug。根源用多个正交的布尔量去描述一个互斥的单一状态。这是典型的建模错误。二、状态机的三要素一个状态机只有三样东西但能表达极其复杂的行为要素含义例子状态State设备当前在做什么配网中、连接中、运行中事件Event外界发生的、需要响应的事收到 WIFI GOT IP、超时、按键按下动作Action进入某状态或响应某事件时做什么发 AT 指令、点灯、启动定时器核心约束任何时刻设备只能处于一个状态。这一条约束就把上面那 128 种组合压缩成了 6 种合法状态。三、设备生命周期的状态定义以一个联网 IoT 设备为例我通常定义这 7 个状态┌──────────────┐ 上电 ──────► │ ST_INIT │ 初始化外设、读参数 └──────┬───────┘ │ 初始化完成 ▼ ┌──────────────┐ │ ST_PROVISION │ 配网首次使用/参数丢失 └──────┬───────┘ │ 拿到 SSID/密码 ▼ ┌──────────────┐ 连接失败/超时 │ ST_WIFI_CONN │──────────────┐ └──────┬───────┘ │ │ WIFI GOT IP │ ▼ │ ┌──────────────┐ │ │ ST_MQTT_CONN │──────────────┤ └──────┬───────┘ │ │ MQTT CONNACK │ ▼ │ ┌──────────────┐ 断开 │ │ ST_RUNNING │──────────────┤ └──────┬───────┘ │ │ 收到休眠指令 │ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ ST_SLEEP │ │ ST_ERROR │ └──────────────┘ └──────────────┘关键设计原则任何异常都是一个明确的状态而不是标志位为 1。ST_ERROR不是出错了而是一个有明确进入条件、明确退出条件、明确动作的状态。这才叫工程化。四、表驱动状态机推荐实现状态机的实现方式有三种方式优点缺点switch-case嵌套直观嵌套深、代码长表驱动结构清晰、易扩展需要写转移表函数指针状态对象面向对象、最灵活小内存系统开销略大在 16KB RAM 的系统里我推荐表驱动——它几乎不占内存且把逻辑集中在一张表里一眼能看懂整机行为。4.1 定义状态与事件/* fsm.h */#ifndefFSM_H#defineFSM_Htypedefenum{ST_INIT0,ST_PROVISION,ST_WIFI_CONN,ST_MQTT_CONN,ST_RUNNING,ST_SLEEP,ST_ERROR,ST_MAX}dev_state_t;typedefenum{EV_NONE0,EV_INIT_DONE,EV_CRED_FOUND,EV_WIFI_GOT_IP,EV_MQTT_CONNACK,EV_TIMEOUT,EV_DISCONNECT,EV_SLEEP_CMD,EV_ERROR,EV_MAX}dev_event_t;voidfsm_init(void);voidfsm_post(dev_event_tev);/* 投递事件可在中断/其他任务调用 */voidfsm_run(void);/* 状态机主循环常驻任务调用 */dev_state_tfsm_state(void);/* 供外部查询用于日志/诊断 */#endif4.2 状态转移表/* fsm.c */#includefsm.h#includelog.h/* 状态处理函数原型 */staticvoidst_init_enter(void);staticvoidst_init_handle(dev_event_tev);staticvoidst_wifi_conn_enter(void);staticvoidst_wifi_conn_handle(dev_event_tev);/* …… 其余状态省略声明 */typedefstruct{dev_state_tstate;constchar*name;void(*on_enter)(void);void(*on_event)(dev_event_tev);}state_entry_t;staticconststate_entry_ts_state_table[ST_MAX]{[ST_INIT]{ST_INIT,INIT,st_init_enter,st_init_handle},[ST_PROVISION]{ST_PROVISION,PROVISION,st_prov_enter,st_prov_handle},[ST_WIFI_CONN]{ST_WIFI_CONN,WIFI_CONN,st_wifi_conn_enter,st_wifi_conn_handle},[ST_MQTT_CONN]{ST_MQTT_CONN,MQTT_CONN,st_mqtt_conn_enter,st_mqtt_conn_handle},[ST_RUNNING]{ST_RUNNING,RUNNING,st_running_enter,st_running_handle},[ST_SLEEP]{ST_SLEEP,SLEEP,st_sleep_enter,st_sleep_handle},[ST_ERROR]{ST_ERROR,ERROR,st_error_enter,st_error_handle},};4.3 状态机主体staticdev_state_ts_stateST_INIT;staticvoidfsm_transition(dev_state_tnext){if(nextST_MAX||nexts_state){return;}log_info(FSM: %s - %s,s_state_table[s_state].name,s_state_table[next].name);s_statenext;if(s_state_table[next].on_enter){s_state_table[next].on_enter();/* 进入动作 */}}voidfsm_init(void){s_stateST_INIT;s_state_table[ST_INIT].on_enter();}voidfsm_run(void){for(;;){dev_event_tevfsm_wait_event(10);/* 从事件队列取超时 10ms */if(ev!EV_NONEs_state_table[s_state].on_event){s_state_table[s_state].on_event(ev);/* 当前状态处理事件 */}/* 定期让状态有机会体检比如超时判断 */fsm_tick();}}4.4 一个状态的实现示例/* ---------- 连接 WiFi 状态 ---------- */staticuint32_ts_deadline;staticvoidst_wifi_conn_enter(void){at_send_raw(ATCWJAP\ssid\,\pwd\\r\n);s_deadlinexTaskGetTickCount()pdMS_TO_TICKS(15000);}staticvoidst_wifi_conn_handle(dev_event_tev){switch(ev){caseEV_WIFI_GOT_IP:fsm_post(EV_NONE);fsm_transition(ST_MQTT_CONN);break;caseEV_TIMEOUT:caseEV_DISCONNECT:/* 重试次数超限则进错误态 */if(s_retry_cnt3){s_retry_cnt0;fsm_transition(ST_ERROR);}else{st_wifi_conn_enter();/* 原地重试 */}break;default:break;}}注意状态处理函数只做决策不写长长的动作逻辑。五、四个实践要点1. 事件投递要统一所有事件必须走fsm_post()进同一个队列绝不能直接调用状态处理函数。原因保证状态迁移是串行的没有并发问题中断里也能安全投递用FromISR版本便于日志记录事件时序。/* 在串口中断里 */voidon_at_line_received(constchar*line){if(strstr(line,WIFI GOT IP)){fsm_post_from_isr(EV_WIFI_GOT_IP);}}2. 超时是状态的一部分不是全局定时器每个状态自己管自己的超时。因为不同状态的合理等待时间完全不同状态合理超时连接 WiFi15s连接 MQTT10s等待上报 ACK1s运行态心跳30s把超时写进状态里逻辑自然清晰。3. 状态必须可观测/* 周期性上报状态量产设备靠这个远程定位问题 */log_info(FSM state%s, retry%u, heap%u,s_state_table[s_state].name,s_retry_cnt,xPortGetFreeHeapSize());当前在哪个状态这一条信息能解决现场 80% 的设备没反应工单。4. 进入动作和事件处理分开on_enter进入状态时执行一次发指令、启动定时器on_event状态存活期间响应事件。混在一起就会导致每次事件都把指令重发一遍。六、收益对比标志位版本状态机版本合法状态数理论 128 种组合7 种加一个休眠阶段改 6 处if加 1 个状态 改 1 张表现场定位问题打印 7 个 bool 猜打印状态名一目了然代码行数越多越乱每状态独立可扩展七、小结四句话1. 用多个 bool描述设备状态是建模错误——任何时刻只能有一个状态。2. 状态机的三要素状态、事件、动作异常也是一个正常状态。3. 表驱动实现把整机行为集中在一张转移表里易改易读。4. 事件统一投递、超时归属状态、状态可观测——这三点决定状态机好不好用。下一讲第 9 讲我们回到第 7 讲留下的核心命题采集节拍和上报节拍如何做到 1:1 匹配。这一讲会把生产-消费模型讲透并给出背压back-pressure的三种工程策略。欢迎点赞收藏加关注解锁更多专栏内容
RELATED

相关推荐

Folia PWA 手机化完整指南:3 分钟把全屏歌词动画播放器装进主屏幕

Folia PWA 手机化完整指南:3 分钟把全屏歌词动画播放器装进主屏幕

Folia PWA 手机化完整指南:3 分钟把全屏歌词动画播放器装进主屏幕 【免费下载链接】folia-major 专注于绚丽的歌词动画效果的本地音乐/navidrome/第三方多平台在线音乐播放器 项目地址: https://gitcode.com/GitHub_Trending/fo/folia-major Folia 是一款主打…

📅 2026/10/31 6:52:45
mermaid-live-editor:免费在线 Mermaid 图表编辑器,几行文本即可预览、导出并分享

mermaid-live-editor:免费在线 Mermaid 图表编辑器,几行文本即可预览、导出并分享

mermaid-live-editor:免费在线 Mermaid 图表编辑器,几行文本即可预览、导出并分享 【免费下载链接】mermaid-live-editor Edit, preview and share mermaid charts/diagrams. New implementation of the live editor. 项目地址: https://gitcode.com/G…

📅 2026/10/31 6:52:45
Sandboxie 启动失败修复指南:5 分钟恢复驱动、服务与沙箱的完整教程

Sandboxie 启动失败修复指南:5 分钟恢复驱动、服务与沙箱的完整教程

Sandboxie 启动失败修复指南:5 分钟恢复驱动、服务与沙箱的完整教程 【免费下载链接】Sandboxie Sandboxie Plus & Classic 项目地址: https://gitcode.com/gh_mirrors/sa/Sandboxie 看到 SBIE9153、SBIE2203 这类红色报错,SandMan 无法连接&…

📅 2026/10/31 6:47:44
MORE NEWS

更多资讯

📰

10款免费网络安全抓包工具详解与选型指南

1. 网络安全抓包工具概述在网络安全领域,抓包工具就像网络工程师的"听诊器",能够实时监听和分析网络通信数据。这类工具通过捕获网络接口传输的数据包,帮助安全人员诊断网络问题、分析协议交互、检测安全漏洞。对于刚入门网络安全的…

📰

MySQL高可用架构实战:主主复制+HAProxy+Keepalived

1. MySQL高可用架构核心设计解析在企业级数据库应用中,单点故障是绝对不能容忍的风险。我经历过多次深夜被叫醒处理数据库宕机的惨痛教训后,彻底认识到高可用架构的重要性。本次搭建的MySQL高可用方案采用主主复制HAProxyKeepalived的组合,这…

📰

灰度放量与准入控制:GATER五段式门控框架落地实践

做灰度放量和准入控制这件事&#xff0c;我前前后后折腾了好几年。最早是在业务代码里塞if user_id % 100 < 5&#xff0c;后来是配置文件里写一堆开关&#xff0c;再后来是一堆人围着发布窗口熬夜盯监控。真正让我下决心把这件事抽象出来&#xff0c;是因为一次不太体面的线…

📰

Hive在电商大数据处理中的核心应用与优化策略

1. Hive在电商行业的核心价值解析电商行业每天产生的数据量级早已突破传统数据库的处理极限。以某头部电商平台为例&#xff0c;其单日用户行为日志就超过20TB&#xff0c;传统MySQL这类关系型数据库在这种场景下完全无能为力。这正是Hive这类大数据工具的价值所在——它不仅能…

📰

Node-RED 集成 InfluxDB v2.x 实战:写入查询闭环与生产避坑指南

简介&#xff1a;本资源是 Node-RED 生态中专用于 InfluxDB 时间序列数据库交互的官方扩展节点包&#xff08;node-red-contrib-influxdb&#xff09;&#xff0c;面向物联网开发、工业数据采集及边缘计算场景下的中级以上开发者&#xff0c;解决在低代码流式编程环境中高效写入…

📰

如何准确获取真实屏幕分辨率:跨平台实战指南

1. 项目概述&#xff1a;为什么“屏幕分辨率”突然成了2020年的生活刚需&#xff1f;2020年那会儿&#xff0c;我帮三个不同行业的客户做远程协作系统适配&#xff0c;结果全卡在同一个地方——不是网络延迟&#xff0c;不是软件崩溃&#xff0c;而是屏幕分辨率不匹配导致的界面…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬