欢迎光临
我们一直在努力

智能家居中控跳转缺失——状态条件漏判,列全转移表 + default 处理

智能家居中控跳转缺失——状态条件漏判,列全转移表 + default 处理

文章标签:#嵌入式开发 #单片机 #状态机

摘要

本文围绕智能家居中控"离家模式"中"灯关了却关不掉空调"的典型故障,剖析状态机因条件耦合 + 提前 return 导致后续动作被整体跳过的根因。文章从"先画转移表再写码"的入门认知讲起,说明完整"状态×事件→下一状态/动作"转移表如何把覆盖度显性化;进而用流程图还原漏步骤的发生路径,并给出错误写法与"转移表 + 独立动作 + 表驱动 + default 兜底"的规范实现,附改前改后实测对比。适合正在用状态机管理多设备联动、遇到"偶发漏执行"问题的嵌入式开发者参考。

一、开篇:那个"灯关了却关不掉空调"的中控

做智能家居中控,状态机管理"离家模式":触发离家 → 关灯 → 关空调 → 关窗帘。一次用户先手动关了灯,再触发"离家",中控检测到"灯已关"就跳过了"关灯"步骤,却因为条件写成了 if (灯开) 关灯; if(空调开) 关空调,而灯已关导致流程提前 return,空调没关。表现:人走了空调还在吹。复现率取决于操作顺序,用户手快必现。

如果你状态机"条件漏判导致跳过步骤",这一讲能帮你用"完整转移表"设计。我会从状态转移表讲起,给规范写法,并附实测对比。

全文导航:入门认知(转移表是什么)→ 进阶原理(为什么条件耦合会漏步骤)→ 高级应用(错误写法复现 + 转移表代码 + 实测对比)。

二、入门认知:状态机要先"画表"再"写码"

健壮状态机的第一步不是写 switch,而是画出完整的"状态×事件→下一状态/动作"转移表。每个单元格都明确:“在当前态收到某事件,做什么、去哪”。漏一个单元格 = 漏一种情况 = 潜在 Bug。代码只是表格的执行。

类比:做菜先看完整菜谱(转移表),再下锅。凭记忆边做边想"下一步",容易漏步——盐忘了放,菜就废。

如图 1 展示"凭感觉写"vs"按转移表写"的覆盖度:

#mermaid-svg-AlJlHjVJaMruJmEH{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AlJlHjVJaMruJmEH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AlJlHjVJaMruJmEH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AlJlHjVJaMruJmEH .error-icon{fill:#552222;}#mermaid-svg-AlJlHjVJaMruJmEH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AlJlHjVJaMruJmEH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AlJlHjVJaMruJmEH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AlJlHjVJaMruJmEH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AlJlHjVJaMruJmEH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AlJlHjVJaMruJmEH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AlJlHjVJaMruJmEH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AlJlHjVJaMruJmEH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AlJlHjVJaMruJmEH .marker.cross{stroke:#333333;}#mermaid-svg-AlJlHjVJaMruJmEH svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AlJlHjVJaMruJmEH p{margin:0;}#mermaid-svg-AlJlHjVJaMruJmEH .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-AlJlHjVJaMruJmEH .cluster-label text{fill:#333;}#mermaid-svg-AlJlHjVJaMruJmEH .cluster-label span{color:#333;}#mermaid-svg-AlJlHjVJaMruJmEH .cluster-label span p{background-color:transparent;}#mermaid-svg-AlJlHjVJaMruJmEH .label text,#mermaid-svg-AlJlHjVJaMruJmEH span{fill:#333;color:#333;}#mermaid-svg-AlJlHjVJaMruJmEH .node rect,#mermaid-svg-AlJlHjVJaMruJmEH .node circle,#mermaid-svg-AlJlHjVJaMruJmEH .node ellipse,#mermaid-svg-AlJlHjVJaMruJmEH .node polygon,#mermaid-svg-AlJlHjVJaMruJmEH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AlJlHjVJaMruJmEH .rough-node .label text,#mermaid-svg-AlJlHjVJaMruJmEH .node .label text,#mermaid-svg-AlJlHjVJaMruJmEH .image-shape .label,#mermaid-svg-AlJlHjVJaMruJmEH .icon-shape .label{text-anchor:middle;}#mermaid-svg-AlJlHjVJaMruJmEH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AlJlHjVJaMruJmEH .rough-node .label,#mermaid-svg-AlJlHjVJaMruJmEH .node .label,#mermaid-svg-AlJlHjVJaMruJmEH .image-shape .label,#mermaid-svg-AlJlHjVJaMruJmEH .icon-shape .label{text-align:center;}#mermaid-svg-AlJlHjVJaMruJmEH .node.clickable{cursor:pointer;}#mermaid-svg-AlJlHjVJaMruJmEH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AlJlHjVJaMruJmEH .arrowheadPath{fill:#333333;}#mermaid-svg-AlJlHjVJaMruJmEH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AlJlHjVJaMruJmEH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AlJlHjVJaMruJmEH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AlJlHjVJaMruJmEH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AlJlHjVJaMruJmEH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AlJlHjVJaMruJmEH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AlJlHjVJaMruJmEH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AlJlHjVJaMruJmEH .cluster text{fill:#333;}#mermaid-svg-AlJlHjVJaMruJmEH .cluster span{color:#333;}#mermaid-svg-AlJlHjVJaMruJmEH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AlJlHjVJaMruJmEH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AlJlHjVJaMruJmEH rect.text{fill:none;stroke-width:0;}#mermaid-svg-AlJlHjVJaMruJmEH .icon-shape,#mermaid-svg-AlJlHjVJaMruJmEH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AlJlHjVJaMruJmEH .icon-shape p,#mermaid-svg-AlJlHjVJaMruJmEH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AlJlHjVJaMruJmEH .icon-shape .label rect,#mermaid-svg-AlJlHjVJaMruJmEH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AlJlHjVJaMruJmEH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AlJlHjVJaMruJmEH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AlJlHjVJaMruJmEH :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

凭感觉写 switch

条件耦合,漏步骤(漏关空调)

先列转移表:每个(态,事件)有去向

代码严格照表,无遗漏

图 1:无表凭感觉漏步骤 vs 按转移表全覆盖

适用边界:所有多状态多事件的业务逻辑;事件/状态少时表格简单,多时更要靠表防漏。

三、进阶原理:为什么"条件耦合"会漏步骤

错误写法常把多个动作挤在一个 if 里,且用 return/提前退出:一旦某个前置条件不满足(如灯已关),后续动作被一起跳过。根因是"步骤顺序"和"前置判断"耦合——正确设计应是"每个动作独立判断,互不因对方跳过"。

易混淆点对比:

设计漏步骤风险可维护性
凭感觉 if 链 高 差
完整转移表 低 好
表驱动代码 最低 最好

[坑] 高频翻车:用 if (灯开) {关灯;} 然后 if (空调开){关空调;} 看似独立,但外层包了个 if (刚触发离家) { … return; }——若"灯已关"走了另一条分支,整段被跳过。务必保证"离家"动作列表对每个子设备独立执行,不受彼此状态影响。另一坑:忘了 default,未知态无处理(见第86讲)。

下图 2 展示"条件耦合 + 提前 return"是如何把后续动作一起吞掉的:

#mermaid-svg-uWYQsZhI0LshZHAf{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uWYQsZhI0LshZHAf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uWYQsZhI0LshZHAf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uWYQsZhI0LshZHAf .error-icon{fill:#552222;}#mermaid-svg-uWYQsZhI0LshZHAf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uWYQsZhI0LshZHAf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uWYQsZhI0LshZHAf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uWYQsZhI0LshZHAf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uWYQsZhI0LshZHAf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uWYQsZhI0LshZHAf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uWYQsZhI0LshZHAf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uWYQsZhI0LshZHAf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uWYQsZhI0LshZHAf .marker.cross{stroke:#333333;}#mermaid-svg-uWYQsZhI0LshZHAf svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uWYQsZhI0LshZHAf p{margin:0;}#mermaid-svg-uWYQsZhI0LshZHAf .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-uWYQsZhI0LshZHAf .cluster-label text{fill:#333;}#mermaid-svg-uWYQsZhI0LshZHAf .cluster-label span{color:#333;}#mermaid-svg-uWYQsZhI0LshZHAf .cluster-label span p{background-color:transparent;}#mermaid-svg-uWYQsZhI0LshZHAf .label text,#mermaid-svg-uWYQsZhI0LshZHAf span{fill:#333;color:#333;}#mermaid-svg-uWYQsZhI0LshZHAf .node rect,#mermaid-svg-uWYQsZhI0LshZHAf .node circle,#mermaid-svg-uWYQsZhI0LshZHAf .node ellipse,#mermaid-svg-uWYQsZhI0LshZHAf .node polygon,#mermaid-svg-uWYQsZhI0LshZHAf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uWYQsZhI0LshZHAf .rough-node .label text,#mermaid-svg-uWYQsZhI0LshZHAf .node .label text,#mermaid-svg-uWYQsZhI0LshZHAf .image-shape .label,#mermaid-svg-uWYQsZhI0LshZHAf .icon-shape .label{text-anchor:middle;}#mermaid-svg-uWYQsZhI0LshZHAf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uWYQsZhI0LshZHAf .rough-node .label,#mermaid-svg-uWYQsZhI0LshZHAf .node .label,#mermaid-svg-uWYQsZhI0LshZHAf .image-shape .label,#mermaid-svg-uWYQsZhI0LshZHAf .icon-shape .label{text-align:center;}#mermaid-svg-uWYQsZhI0LshZHAf .node.clickable{cursor:pointer;}#mermaid-svg-uWYQsZhI0LshZHAf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uWYQsZhI0LshZHAf .arrowheadPath{fill:#333333;}#mermaid-svg-uWYQsZhI0LshZHAf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uWYQsZhI0LshZHAf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uWYQsZhI0LshZHAf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uWYQsZhI0LshZHAf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uWYQsZhI0LshZHAf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uWYQsZhI0LshZHAf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uWYQsZhI0LshZHAf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uWYQsZhI0LshZHAf .cluster text{fill:#333;}#mermaid-svg-uWYQsZhI0LshZHAf .cluster span{color:#333;}#mermaid-svg-uWYQsZhI0LshZHAf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uWYQsZhI0LshZHAf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uWYQsZhI0LshZHAf rect.text{fill:none;stroke-width:0;}#mermaid-svg-uWYQsZhI0LshZHAf .icon-shape,#mermaid-svg-uWYQsZhI0LshZHAf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uWYQsZhI0LshZHAf .icon-shape p,#mermaid-svg-uWYQsZhI0LshZHAf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uWYQsZhI0LshZHAf .icon-shape .label rect,#mermaid-svg-uWYQsZhI0LshZHAf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uWYQsZhI0LshZHAf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uWYQsZhI0LshZHAf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uWYQsZhI0LshZHAf :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

灯开

灯已关

触发离家模式

灯是否开着?

关灯

跳过关灯分支

提前 return 退出流程

空调/窗帘动作未执行

人走了空调还在吹

正确做法:每个动作独立判定

关灯(按需)

关空调(按需)

关窗帘(按需)

全部动作执行完毕

图 2:条件耦合导致漏步骤 vs 动作独立判定

四、高级应用:复现与根治

错误写法演示

void leave_home_bad(uint8_t triggered)
{
if (!triggered) return;
if (light_is_on()) turn_off_light(); // 灯已关 → 此句跳过
if (ac_is_on()) turn_off_ac(); // 若上面被包在会 return 的分支里,也跳过
if (curtain_is_open()) close_curtain();
}
/* 问题:若触发前用户已关灯,流程可能因提前 return 漏掉空调 */

正确完整代码(转移表 + 独立动作)

#include <stdint.h>

/* 1. 先列转移表(文字/注释即设计文档) */
/* 事件 LEAVE_HOME: 不论当前各设备状态,依次确保 灯关/空调关/窗帘关 */
/* 每个动作独立,互不影响 */

typedef enum { IDLE, LEAVING } st_t;
st_t st = IDLE;

void leave_home_good(void)
{
st = LEAVING;
/* 每个子设备独立判定,不互相跳过 */
if (light_is_on()) turn_off_light();
if (ac_is_on()) turn_off_ac();
if (curtain_is_open()) close_curtain();
st = IDLE;
}

/* 表驱动进阶:把转移写成数据,一眼看出覆盖 */
typedef void (*action_t)(void);
typedef struct { uint8_t event; action_t fn; } trans_t;
const trans_t table[] = {
{ EV_LEAVE_HOME, leave_home_good },
{ EV_COME_HOME, come_home_good },
/* … 所有事件列全 */
};
void dispatch(uint8_t ev)
{
for (int i=0; i<sizeof(table)/sizeof(table[0]); i++)
if (table[i].event == ev) { table[i].fn(); return; }
default_handler(ev); // 未知事件兜底
}

改前 vs 改后实测对比

指标漏判条件转移表+独立动作
离家漏关空调 偶发 无
覆盖度 靠记忆,易漏 表驱动,全列
可维护性 差 好

[提示] 用 Mermaid stateDiagram 把状态机画进设计文档,评审时逐单元格核对;代码与表一一对应,改需求先改表再改码。配合第86讲 default 兜底。

下图 3 用 stateDiagram 把"离家模式"的状态机画进设计文档,评审时逐条转移核对:

#mermaid-svg-UqPSsjfg8XDyAC31{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UqPSsjfg8XDyAC31 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UqPSsjfg8XDyAC31 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UqPSsjfg8XDyAC31 .error-icon{fill:#552222;}#mermaid-svg-UqPSsjfg8XDyAC31 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UqPSsjfg8XDyAC31 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UqPSsjfg8XDyAC31 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UqPSsjfg8XDyAC31 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UqPSsjfg8XDyAC31 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UqPSsjfg8XDyAC31 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UqPSsjfg8XDyAC31 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UqPSsjfg8XDyAC31 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UqPSsjfg8XDyAC31 .marker.cross{stroke:#333333;}#mermaid-svg-UqPSsjfg8XDyAC31 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UqPSsjfg8XDyAC31 p{margin:0;}#mermaid-svg-UqPSsjfg8XDyAC31 defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-UqPSsjfg8XDyAC31 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-UqPSsjfg8XDyAC31 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-UqPSsjfg8XDyAC31 g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-UqPSsjfg8XDyAC31 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-UqPSsjfg8XDyAC31 g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-UqPSsjfg8XDyAC31 .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-UqPSsjfg8XDyAC31 .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-UqPSsjfg8XDyAC31 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-UqPSsjfg8XDyAC31 .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-UqPSsjfg8XDyAC31 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-UqPSsjfg8XDyAC31 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-UqPSsjfg8XDyAC31 .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-UqPSsjfg8XDyAC31 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UqPSsjfg8XDyAC31 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UqPSsjfg8XDyAC31 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UqPSsjfg8XDyAC31 .edgeLabel .label text{fill:#333;}#mermaid-svg-UqPSsjfg8XDyAC31 .label div .edgeLabel{color:#333;}#mermaid-svg-UqPSsjfg8XDyAC31 .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-UqPSsjfg8XDyAC31 .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-UqPSsjfg8XDyAC31 .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-UqPSsjfg8XDyAC31 .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-UqPSsjfg8XDyAC31 .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-UqPSsjfg8XDyAC31 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UqPSsjfg8XDyAC31 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UqPSsjfg8XDyAC31 #statediagram-barbEnd{fill:#333333;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UqPSsjfg8XDyAC31 .cluster-label,#mermaid-svg-UqPSsjfg8XDyAC31 .nodeLabel{color:#131300;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-UqPSsjfg8XDyAC31 .note-edge{stroke-dasharray:5;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-note text{fill:black;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram-note .nodeLabel{color:black;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagram .edgeLabel{color:red;}#mermaid-svg-UqPSsjfg8XDyAC31 #dependencyStart,#mermaid-svg-UqPSsjfg8XDyAC31 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-UqPSsjfg8XDyAC31 .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UqPSsjfg8XDyAC31 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

"EV_LEAVE_HOME"

"灯/空调/窗帘 全部处理完"

"EV_COME_HOME"

"回家动作完成"

"未知事件"

"default_handler 兜底"

IDLE

LEAVING

HOME

ERROR

每个子设备独立判定不因某个已关而跳过其他

图 3:离家模式状态机(含 default 兜底)

五、收尾:要点复盘与最佳实践

核心要点:① 状态机先画转移表再写码;② 各动作独立判定,不互相跳过;③ 表驱动让覆盖度一眼可见;④ 配合 default 兜底。

最佳实践清单(可收藏):

  • 写状态机前先列"状态×事件"完整转移表。
  • 多子动作独立 if 判定,避免一个跳过连累其他。
  • 用表驱动数组实现转移,覆盖度可见可查。
  • 每个事件/状态都有明确去向,未知走 default。
  • 设计文档画状态图(Mermaid),评审逐格核对。

进阶延伸:用代码生成器从转移表自动生成状态机骨架;形式化方法(如状态机验证工具)可证明"无死锁/全覆盖"。

下图 4 把整篇文章的落地路径串成一条线,照着走即可避免漏步骤:

#mermaid-svg-eyYjchXCN73qIbTY{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eyYjchXCN73qIbTY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eyYjchXCN73qIbTY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eyYjchXCN73qIbTY .error-icon{fill:#552222;}#mermaid-svg-eyYjchXCN73qIbTY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eyYjchXCN73qIbTY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eyYjchXCN73qIbTY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eyYjchXCN73qIbTY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eyYjchXCN73qIbTY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eyYjchXCN73qIbTY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eyYjchXCN73qIbTY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eyYjchXCN73qIbTY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eyYjchXCN73qIbTY .marker.cross{stroke:#333333;}#mermaid-svg-eyYjchXCN73qIbTY svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eyYjchXCN73qIbTY p{margin:0;}#mermaid-svg-eyYjchXCN73qIbTY .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-eyYjchXCN73qIbTY .cluster-label text{fill:#333;}#mermaid-svg-eyYjchXCN73qIbTY .cluster-label span{color:#333;}#mermaid-svg-eyYjchXCN73qIbTY .cluster-label span p{background-color:transparent;}#mermaid-svg-eyYjchXCN73qIbTY .label text,#mermaid-svg-eyYjchXCN73qIbTY span{fill:#333;color:#333;}#mermaid-svg-eyYjchXCN73qIbTY .node rect,#mermaid-svg-eyYjchXCN73qIbTY .node circle,#mermaid-svg-eyYjchXCN73qIbTY .node ellipse,#mermaid-svg-eyYjchXCN73qIbTY .node polygon,#mermaid-svg-eyYjchXCN73qIbTY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eyYjchXCN73qIbTY .rough-node .label text,#mermaid-svg-eyYjchXCN73qIbTY .node .label text,#mermaid-svg-eyYjchXCN73qIbTY .image-shape .label,#mermaid-svg-eyYjchXCN73qIbTY .icon-shape .label{text-anchor:middle;}#mermaid-svg-eyYjchXCN73qIbTY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eyYjchXCN73qIbTY .rough-node .label,#mermaid-svg-eyYjchXCN73qIbTY .node .label,#mermaid-svg-eyYjchXCN73qIbTY .image-shape .label,#mermaid-svg-eyYjchXCN73qIbTY .icon-shape .label{text-align:center;}#mermaid-svg-eyYjchXCN73qIbTY .node.clickable{cursor:pointer;}#mermaid-svg-eyYjchXCN73qIbTY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eyYjchXCN73qIbTY .arrowheadPath{fill:#333333;}#mermaid-svg-eyYjchXCN73qIbTY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eyYjchXCN73qIbTY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eyYjchXCN73qIbTY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eyYjchXCN73qIbTY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eyYjchXCN73qIbTY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eyYjchXCN73qIbTY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eyYjchXCN73qIbTY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eyYjchXCN73qIbTY .cluster text{fill:#333;}#mermaid-svg-eyYjchXCN73qIbTY .cluster span{color:#333;}#mermaid-svg-eyYjchXCN73qIbTY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eyYjchXCN73qIbTY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eyYjchXCN73qIbTY rect.text{fill:none;stroke-width:0;}#mermaid-svg-eyYjchXCN73qIbTY .icon-shape,#mermaid-svg-eyYjchXCN73qIbTY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eyYjchXCN73qIbTY .icon-shape p,#mermaid-svg-eyYjchXCN73qIbTY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eyYjchXCN73qIbTY .icon-shape .label rect,#mermaid-svg-eyYjchXCN73qIbTY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eyYjchXCN73qIbTY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eyYjchXCN73qIbTY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eyYjchXCN73qIbTY :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

列全状态×事件转移表

每个动作独立判定

表驱动数组实现

未知事件走 default

Mermaid 状态图进设计文档

评审逐格核对

改需求先改表再改码

图 4:状态机设计落地路径

互动:你漏过状态机步骤吗?最后怎么发现"空调没关"的?评论区见。

赞(0)
未经允许不得转载:171主机测评 » 智能家居中控跳转缺失——状态条件漏判,列全转移表 + default 处理
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址