Agent Skillsallocnode/oh-my-sage › mijia-automation

mijia-automation

GitHub

指导米家自动化场景创建、设备联动及变量全生命周期管理。提供模式匹配策略,明确专用MCP工具与网关API差异,规范变量作用域、ID格式及创建流程,解决工具缺失或写入失败时的排查路径,确保规则设计的准确性与可维护性。

.agents/skills/mijia-automation/SKILL.md allocnode/oh-my-sage

触发场景

创建智能场景或设备联动 设置定时任务或条件触发 创建、读取、修改或删除自动化变量

安装

npx skills add allocnode/oh-my-sage --skill mijia-automation -g -y
更多选项

非标准路径

npx skills add https://github.com/allocnode/oh-my-sage/tree/main/.agents/skills/mijia-automation -g -y

不安装直接使用

npx skills use allocnode/oh-my-sage@mijia-automation

指定 Agent (Claude Code)

npx skills add allocnode/oh-my-sage --skill mijia-automation -a claude-code -g -y

安装 repo 全部 skill

npx skills add allocnode/oh-my-sage --all -g -y

预览 repo 内 skill

npx skills add allocnode/oh-my-sage --list

SKILL.md

Frontmatter
{
    "name": "mijia-automation",
    "metadata": {
        "author": "oh-my-sage",
        "version": "3.8"
    },
    "description": "米家自动化极客版规则与变量管理指南。当用户想要创建智能场景、设备联动、定时任务、条件触发,或创建、读取、修改、删除自动化变量时使用此 Skill。"
}

米家自动化规则创建

设计新自动化时的知识检索

不要一开始就堆节点。先把需求拆成:触发、状态、变换、条件、动作、退出、恢复,再读取 知识索引 中最相关的一至三个模式文件。

快速选择模式:

问题 模式
需要跨事件记住值、模式或时间点 PAT-STATE-01
需要计算、映射、取整、量化或夹紧 PAT-NUM-01
需要多源汇总、任一/全部或至少 k 个 PAT-AGG-01
需要日期、时长、时间窗口或节律 PAT-TIME-01
需要重复执行、停止、清零或恢复 PAT-LOOP-01
需要设备跟随、双向同步或按实际状态切档 PAT-SYNC-01
需要跨设备型号、跨规则、模板或虚拟事件 PAT-ADAPT-01

常见组合:节律照明 = TIME + NUM + LOOP;外部可改档设备 = STATE + SYNC;重复提醒 = STATE + LOOP + ADAPT

模式用于选择结构和发现风险,不是固定模板。组合模式后仍须根据目标设备 MIOT Spec 重新确定 DID、URN、字段、量程、步进、枚举和动作参数。不得照抄案例中的设备、阈值、变量名或私有标识。

案例知识按以下状态升级:

video → ui-sample → graph-diff → local-tested → gateway-roundtrip → runtime-verified → reusable-pattern

视频案例只是设计线索。未达到 runtime-verified 的行为不得写成强制校验规则;达到 reusable-pattern 还必须满足脱敏、跨场景适用和边界明确,才可作为通用模式推荐。证据可从真实规则、源码或网关样本开始,不要求机械经过每一级。

完整节点字段按需读取 米家自动化规则完全参考,不要把全部节点模板和全部案例同时加载。

变量生命周期能力

变量管理分为三层,不能把其中一层的限制误判成网关不支持:

层级 能力与限制
网关 API 支持 createVardeleteVargetVarValuegetVarConfigsetVarValue
专用 MCP 工具 使用 mijia_create_variablemijia_delete_variablemijia_get_variable_valuemijia_get_variable_configmijia_set_variable
通用原始 API 工具 mijia_call_api 故意只允许只读方法;写方法被拒绝不代表网关没有写能力

关键规则:

  • 新建变量必须调用 mijia_create_variablemijia_set_variable 只修改已存在变量,不会自动创建。
  • varSetNumbervarSetStringdeviceInputSetVardeviceGetSetVar 只写已存在变量,不能用作变量创建器。
  • 变量 ID 必须匹配 ^[a-zA-Z0-9]+$,不能含下划线、连字符或中文;显示名称 name 可以包含中文。
  • type 只能是 numberstring,初始值和后续值必须与类型一致。
  • createVar 的显示名称必须放在 userData: { name },不能传顶层 name。缺少 userData.name 时变量虽可按 ID 读取,但极客版 UI 的变量选择器不会显示它。
  • 删除变量前检查所有规则引用;删除是不可恢复操作。

本规则变量的作用域名与创建时机

  • 作用域名 = R + 规则 ID 的数字部分,去掉 graph_ 前缀。例:规则 graph_1700000000000 → 作用域 R1700000000000;规则 1700000000000R1700000000000。可用 mijia_call_api getVarScopeList 核对。
  • 节点里写 "scope": "rule" 的自动替换只发生在 mijia_create_graphmijia_graph_draft_begin(经由 variables 参数)。
  • mijia_update_graph 没有 variables 参数。给已有规则新增变量时,必须先单独调用 mijia_create_variable 并显式传真实作用域字符串,节点里也要写真实作用域,不能写 "rule"

工具缺失或写入失败时

按以下顺序判断,不要直接宣布“网关无法读写变量”:

  1. 查看当前 MCP 工具列表是否包含上述专用变量工具。
  2. 工具缺失时检查运行中的 MCP 是否为旧构建;源码新增工具后必须重新构建并重启 MCP,当前进程不会动态注册新工具。
  3. 检查 src/core/tools/variable.tssrc/mcp/tools/variable.ts 和实际运行的 dist,确认功能是未实现、未构建还是未加载。
  4. mijia_set_variable 返回变量不存在时,改用 mijia_create_variable,不要尝试用规则节点自动创建。
  5. mijia_call_api 拒绝 createVar 等写方法时,改用专用工具,不要放宽通用工具的只读白名单。
  6. 若怀疑功能曾存在但被回归删除,检查 Git 历史或 Session 中的真实工具调用记录,再下结论。

实机验证过的生命周期:创建临时变量 -> 读取配置和值 -> 修改 -> 回读 -> 删除 -> 再次读取确认不存在。创建或恢复变量工具后应完整执行一次该流程,并清理临时变量。

发现网关尚未封装的新能力时,读取 网关能力发现方法。不要靠猜测 API 名称,也不要因为当前 MCP 没有工具就判定网关不支持。

规则结构

{
  "id": "13位纯数字规则ID",
  "nodes": [节点1, 节点2, ...],
  "cfg": {
    "id": "13位纯数字规则ID",
    "enable": true,
    "uiType": "graph",
    "userData": {
      "name": "规则名称",
      "lastUpdateTime": 1710000000000,
      "transform": {"x": 0, "y": 0, "scale": 1, "rotate": 0}
    }
  }
}

节点位置自动布局:create_graph 会根据节点连接关系自动计算位置,无需手动设置 cfg.pos。布局规则:

  • 从左到右表示流程方向
  • 分支节点上下排列
  • 节点尺寸:528×164

关键校验规则

  1. 节点 id:只允许 [0-9a-zA-Z],不能用下划线、连字符
  2. outputs 连接格式"portName": ["nodeId.inputPort"](必须是点分隔,如 "cond1.trigger"
    • ❌ 错误:"output": ["range1"](缺少 .inputPort
    • ✅ 正确:"output": ["range1.trigger"]
  3. outputs 值必须是数组"output": [] ✓,"output": null
  4. 所有节点必须声明 outputs 端口:即使没有输出连接,也要声明端口(如 "outputs": {"output": []}
  5. deviceGet:必须有 outputs.outputoutputs.output2
  6. inputs 命名
    • deviceGet, varGet, statusLast, delayinput
    • deviceOutput, condition 等用 trigger
    • timeRange, alarmClock源节点没有输入端口inputs: {}
  7. dtype 映射boolbooleanuint8/int32intfloatfloat
  8. props 必须存在"props": {} 不能省略
  9. cfg.name:值为节点类型名(如 "deviceInput"
  10. cfg.unit/value:delay 节点的 cfg.unitcfg.value 是可选的(UI 显示用)

inputs/outputs 工作机制

inputs 中的 null 是什么?

"inputs": {"trigger": null} 中的 null 不是"未连接",而是声明端口存在。信号实际由上游节点的 outputs 数组传来。

上游 outputs 数组 ──→ 决定 → 下游 inputs 端口
"output": ["cond1.trigger"]      "inputs": {"trigger": null}  ← 端口声明,null 是正确的

outputs 数组决定连接关系

整个规则的连接关系完全由每个节点的 outputs 数组决定,inputs 只是端口声明。生成规则后必须逐个检查每个节点的 outputs 数组,确保:

  1. 引用的目标节点 ID 存在
  2. 引用的端口名是目标节点 inputs 中声明的端口名
  3. 点分隔格式正确:"目标节点ID.目标端口名"

事件源、条件状态和流程节点

类型 特征 代表节点
事件源 无输入,主动产生流程事件 deviceInput, alarmClock, onLoad, varChange
条件状态源/变换 向 condition 或 logic 提供布尔状态 timeRange, 设备属性状态, logicOr/logicAnd/logicNot
流程节点 接收事件后查询、判断、延时或执行 deviceGet, condition, delay, deviceOutput

⚠️ 无输入节点不能接收上游连接。条件状态通常连接 condition.condition,也可以先进入 logicOr / logicAnd / logicNot,再连接 condition.condition

⚠️ 网关不检查连接完整性(已验证)

网关 setGraph 只校验节点级别结构(字段类型、必填字段),不校验连接级别的逻辑完整性。以下错误都能通过网关校验但在运行时不工作:

  • condition 的 condition 端口未连接 → 网关允许保存,但实测不会执行 met 分支
  • deviceGet.output2 连到 state 节点 → 通过校验,但语义错误

因此,生成规则后必须自行验证连接完整性,不能依赖网关报错。

工作流程(必须遵循)

当用户要求创建/修改自动化规则时,按以下步骤执行:

  1. 理解需求:分析用户的自动化逻辑,确定需要哪些节点
  2. 先写设计表:列出业务触发、持续状态、true/false/unknown 分支、变量及初值、状态转换、循环启停、恢复动作、失败行为和所选模式
  3. 生成节点列表:按照节点模板和连接规则构建 nodes 数组;超过 10 个节点时先列出事件边和状态边两张连接清单
  4. 调用能力校验:MCP 使用 mijia_validate_graph_capabilities,Web Agent 使用 validate_graph_capabilities,确认设备字段、权限、类型、范围、动作参数和变量引用
  5. 调用结构校验:MCP 使用 mijia_validate_graph,Web Agent 使用 validate_graph,检查连接完整性
  6. 修复错误:任一校验器报告 error 时修复并重新校验,直到全部通过
  7. 调用 create_graph 或 update_graph:两项校验通过后调用创建/更新工具;工具内部仍会再次校验
  8. 确认结果:回读规则并确认启用状态、变量作用域和关键节点

设计表使用固定格式:

项目 设计
业务触发 哪个瞬时事件启动流程
持续状态 判断时读取哪些状态,值是否可能陈旧
分支 true / false 分别做什么;unknown 如何识别和降级
变量 ID、类型、初值、生产者、清理时机
状态转换 当前状态、允许的下一状态、异常兜底
循环 start、每轮检查、最大次数、stop、zero
恢复 结束、禁用、重启和人工操作后如何收敛
失败行为 查询失败、写入拒绝、校验失败或重试耗尽后停止、保持还是通知
模式与证据 选用的 PAT 及关键命题证据等级

deviceGet.output2 只表示比较结果不满足,不是查询失败或 unknown 专口。无法直接区分 unknown 时,用初始化/就绪变量、最后更新时间、在线状态或保守停止表达;不要虚构第三个输出端口。

MCP 模式下创建超过 10 个节点的复杂规则时,不要调用两个独立校验工具后再重复发送完整图。先确认当前 MCP 工具列表包含 mijia_graph_draft_*,然后使用分块草稿流程。草稿工具不用于更新现有规则;复杂更新仍使用 mijia_update_graph

  1. mijia_graph_draft_begin 创建草稿并取得 draftId
  2. mijia_graph_draft_append 每批追加 5 至 10 个节点,每个节点只上传一次。
  3. 校验失败后用 mijia_graph_draft_edit 按 ID 修正或删除节点;用 mijia_graph_draft_status 查看轻量状态。
  4. mijia_graph_draft_commit 只传 draftId;服务端一次完成结构校验、设备能力校验、布局和创建,不再调用两个独立校验工具。
  5. 放弃方案时调用 mijia_graph_draft_discard;草稿会临时持久化并在 30 分钟后自动过期。

小规则和 Web Agent 继续使用原双校验流程。草稿工具缺失时不要虚构调用,改用原流程。复杂规则草稿提交失败时会保留;成功响应丢失或重复提交时返回同一个规则 ID。

⚠️ create_graph / update_graph 内置了校验逻辑。如果节点连接有 error 级别的问题,工具会返回错误并拒绝调用 setGraph。修复后重新调用即可。

节点模板(直接复制使用)

deviceInput - 设备触发(属性变化)

{"id":"$ID","type":"deviceInput","cfg":{"urn":"$URN","name":"deviceInput","version":1},"props":{"did":"$DID","siid":$SIID,"piid":$PIID,"preload":false,"dtype":"$DTYPE","operator":"=","v1":$V1},"inputs":{},"outputs":{"output":["$NEXT.trigger"]}}

deviceInput - 设备触发(事件)

{"id":"$ID","type":"deviceInput","cfg":{"urn":"$URN","name":"deviceInput","version":1},"props":{"did":"$DID","siid":$SIID,"eiid":$EIID,"preload":false},"inputs":{},"outputs":{"output":["$NEXT.trigger"]}}

deviceOutput - 控制设备(设置属性)

{"id":"$ID","type":"deviceOutput","cfg":{"urn":"$URN","name":"deviceOutput","version":1},"props":{"did":"$DID","siid":$SIID,"piid":$PIID,"value":$VALUE},"inputs":{"trigger":null},"outputs":{"output":[]}}

deviceOutput - 使用变量动态设置属性

{"id":"$ID","type":"deviceOutput","cfg":{"urn":"$URN","name":"deviceOutput","version":1},"props":{"did":"$DID","siid":$SIID,"piid":$PIID,"id":"$VAR_ID","scope":"$SCOPE","dtype":"number","min":$MIN,"max":$MAX,"step":$STEP},"inputs":{"trigger":null},"outputs":{"output":[]}}

⚠️ 极客版 UI 实测支持将规则变量直接写入设备属性。dtype 数值属性用 numbermin/max/step 必须与目标 MIOT 属性范围一致。

deviceOutput - 控制设备(执行动作)

{"id":"$ID","type":"deviceOutput","cfg":{"urn":"$URN","name":"deviceOutput","version":1},"props":{"did":"$DID","siid":$SIID,"aiid":$AIID,"ins":[{"piid":$PIID,"value":$VALUE}]},"inputs":{"trigger":null},"outputs":{"output":[]}}

deviceGet - 查询状态

{"id":"$ID","type":"deviceGet","cfg":{"urn":"$URN","name":"deviceGet","version":1},"props":{"did":"$DID","siid":$SIID,"piid":$PIID,"dtype":"$DTYPE","operator":"=","v1":$V1},"inputs":{"input":null},"outputs":{"output":["$NEXT1.trigger"],"output2":["$NEXT2.trigger"]}}

⚠️ inputs 必须用 input,不是 triggeroutputoutput2 都应连到 event 节点(如 condition.trigger)。禁止将 output2 连到 state 节点。

deviceGet 取得网关可用于判定的属性值,不得默认等同于物理设备的实时状态。缓存、最近上报、离线和未知值语义需按设备与固件实测;安全动作必须设计未知/旧值保护。

alarmClock - 定时触发(事件源)

{"id":"$ID","type":"alarmClock","cfg":{"name":"alarmClock","version":1,"happenType":"now","tempOffset":0},"props":{"type":"periodicAlarm","hour":$H,"minute":$M,"second":0,"filter":{"day":[0,1,2,3,4,5,6]}},"inputs":{},"outputs":{"output":["$NEXT.input"]}}

⚠️ 事件源,inputs: {}。连 delay 用 delay1.input,连 condition 用 cond1.trigger。禁止任何节点的 output 连到 alarmClock。

timeRange - 时间段(state 节点)

{"id":"$ID","type":"timeRange","cfg":{"name":"timeRange","version":1},"props":{"start":{"hour":$SH,"minute":$SM,"second":0},"end":{"hour":$EH,"minute":$EM,"second":0},"filter":{"day":[0,1,2,3,4,5,6]}},"inputs":{},"outputs":{"output":["$NEXT.condition"]}}

⚠️ state 节点,inputs: {}output 可直接连到 condition.condition,也可连到 logicOr.inputN / logicAnd.inputN / logicNot.input 后再进入 condition.condition。多分支时 output 数组可包含多个目标。禁止任何节点的 output 连到 timeRange。禁止使用 output2。

delay - 延时

{"id":"$ID","type":"delay","cfg":{"name":"delay","version":1,"unit":"s","value":$SEC},"props":{"timeout":$MS},"inputs":{"input":null},"outputs":{"output":["$NEXT.input"]}}

⚠️ inputs 必须是 {"input": null}(用 input,不是 trigger)。props.timeout 是实际执行的毫秒数(整数)。cfg.unitcfg.value 是 UI 显示用的,可选。

condition - 当-如果-就(条件判断)

{"id":"$ID","type":"condition","cfg":{"name":"condition","version":1},"props":{},"inputs":{"trigger":null,"condition":null},"outputs":{"met":["$NEXT1.trigger"],"unmet":["$NEXT2.trigger"]}}

⚠️ triggercondition 必须都有信号来源,缺一不可。trigger = "当"(event 节点触发),condition = "如果"(state/logic 节点提供条件值)。condition 未连接时网关虽允许保存,但实测不会执行 met 分支;trigger 未连接时节点不会被触发。

signalOr - 任一事件

{"id":"$ID","type":"signalOr","cfg":{"name":"signalOr","version":1},"props":{},"inputs":{"input0":null,"input1":null},"outputs":{"output":["$NEXT.trigger"]}}

⚠️ 输入名格式必须是 input + 连续数字(input0, input1, ...)。

logicOr - 满足任一条件

{"id":"$ID","type":"logicOr","cfg":{"name":"logicOr","version":1},"props":{},"inputs":{"input0":null,"input1":null},"outputs":{"output":["$NEXT.condition"]}}

⚠️ inputs 值可以是 boolean | null(状态条件)。

logicAnd - 满足全部条件

{"id":"$ID","type":"logicAnd","cfg":{"name":"logicAnd","version":1},"props":{},"inputs":{"input0":null,"input1":null},"outputs":{"output":["$NEXT.condition"]}}

logicNot - 状态取反

{"id":"$ID","type":"logicNot","cfg":{"name":"logicNot","version":1},"props":{},"inputs":{"input":null},"outputs":{"output":["$NEXT.condition"]}}

loop - 循环

{"id":"$ID","type":"loop","cfg":{"name":"loop","version":1},"props":{"interval":$MS},"inputs":{"start":null,"stop":null},"outputs":{"output":["$NEXT.trigger"]}}

⚠️ inputs{start, stop},不是 input。已验证。

onlyNTimes - 最多触发N次

{"id":"$ID","type":"onlyNTimes","cfg":{"name":"onlyNTimes","version":1},"props":{"n":$N},"inputs":{"input":null,"zero":null},"outputs":{"output":["$NEXT.trigger"]}}

counter - 达到N次时触发

{"id":"$ID","type":"counter","cfg":{"name":"counter","version":1},"props":{"n":$N},"inputs":{"input":null,"zero":null},"outputs":{"output":["$NEXT.trigger"]}}

modeSwitch - 模式切换

{"id":"$ID","type":"modeSwitch","cfg":{"name":"modeSwitch","version":1},"props":{},"inputs":{"input":null},"outputs":{"output0":[],"output1":[],"output2":[]}}

⚠️ outputs 根据模式数量声明 output0, output1, ... 空 outputs 仍占用并推进一个模式轮次,当前网关已实测;规则重启后的游标位置仍需验证。外部 App、语音或实体控制会改变设备状态时,优先使用状态查询链,不依赖内部游标。

register - 自定义布尔状态

{"id":"$ID","type":"register","cfg":{"name":"register","version":1},"props":{},"inputs":{"setTrue":null,"setFalse":null},"outputs":{"output":["$NEXT.trigger"]}}

⚠️ outputs.output上升沿触发(已实测):只在 false → true 那一刻发一次信号;setFalse 不发,重复 setTrue 也不发。接 condition.condition 时当状态读,接 xxx.trigger 时当上升沿事件用。需要「变 false 时也执行动作」必须另引事件链。

onLoad - 启用时触发(事件源)

{"id":"$ID","type":"onLoad","cfg":{"name":"onLoad","version":1},"props":{},"inputs":{},"outputs":{"output":["$NEXT.trigger"]}}

statusLast - 状态持续一段时间

{"id":"$ID","type":"statusLast","cfg":{"name":"statusLast","version":1},"props":{"timeout":$MS},"inputs":{"input":null},"outputs":{"output":["$NEXT.trigger"]}}

eventSequence - 事件先后发生

{"id":"$ID","type":"eventSequence","cfg":{"name":"eventSequence","version":1},"props":{"timeout":$MS},"inputs":{"input1":null,"input2":null},"outputs":{"output":["$NEXT.trigger"]}}

varSetNumber - 数值运算

{"id":"$ID","type":"varSetNumber","cfg":{"name":"varSetNumber","version":1},"props":{"scope":"global","id":"$VAR_ID","elements":[{"type":"const","value":"$ + 1"}]},"inputs":{"input":null},"outputs":{"output":["$NEXT.trigger"]}}

⚠️ inputs 是 input,不是 trigger

varSetString - 文本拼接

{"id":"$ID","type":"varSetString","cfg":{"name":"varSetString","version":1},"props":{"scope":"global","id":"$VAR_ID","elements":[{"type":"const","value":"文本"}]},"inputs":{"input":null},"outputs":{"output":["$NEXT.trigger"]}}

deviceInputSetVar - 设备触发赋值(事件源)

{"id":"$ID","type":"deviceInputSetVar","cfg":{"urn":"$URN","name":"deviceInputSetVar","version":1},"props":{"did":"$DID","siid":$SIID,"piid":$PIID,"dtype":"number","scope":"global","id":"$VAR_ID","preload":false},"inputs":{},"outputs":{"output":["$NEXT.trigger"]}}

⚠️ 无输入事件源,inputs: {}。dtype 用 "number" 而非 "int"/"float"

deviceGetSetVar - 查询设备赋值

{"id":"$ID","type":"deviceGetSetVar","cfg":{"urn":"$URN","name":"deviceGetSetVar","version":1},"props":{"did":"$DID","siid":$SIID,"piid":$PIID,"dtype":"number","scope":"global","id":"$VAR_ID"},"inputs":{"input":null},"outputs":{"output":["$NEXT.trigger"]}}

⚠️ inputsinput。极客版 UI 实测只生成 outputs.output,没有 output2;不要套用 deviceGet 的双输出结构。

varChange - 变量值更新时触发(事件源)

{"id":"$ID","type":"varChange","cfg":{"name":"varChange","version":1},"props":{"scope":"global","id":"$VAR_ID","varType":"number","preload":true,"operator":">=","v1":$V1},"inputs":{},"outputs":{"output":["$NEXT.trigger"]}}

varGet - 查询变量值

{"id":"$ID","type":"varGet","cfg":{"name":"varGet","version":1},"props":{"scope":"global","id":"$VAR_ID","varType":"number","operator":">=","v1":$V1},"inputs":{"input":null},"outputs":{"output":["$NEXT1.trigger"],"output2":["$NEXT2.trigger"]}}

⚠️ 同 deviceGet,inputsinput,outputs 必须有 outputoutput2

操作符与 dtype

dtype 允许的 operator v1 值类型
boolean = true/false
int >=, <=, =, !=, >, <, between, include 整数
float >, <, between 数字
string = 字符串

设备控制常见模式

  • 开关灯:siid=2, piid=1, value=true/false
  • 亮度:siid=2, piid=2, value=1-100
  • 窗帘开合:siid=2, piid=1, value=0-100

详细参考

版本历史

  • 947d28a 当前 2026-08-02 21:10

    新增自动化设计知识检索章节,引入基于需求拆分的7种核心模式(如状态、时间、循环等)及组合建议;补充案例证据升级标准;细化变量作用域名计算逻辑,明确mijia_update_graph无variables参数时需单独创建变量;调整规则ID示例格式。

  • b71f661 2026-07-24 11:44

元信息

文件数
0
版本
947d28a
Hash
63d6f38d
收录时间
2026-07-24 11:44

首页 - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-08 22:42
浙ICP备14020137号-1 $访客地图$