Home Assistant 自动化 Trace 定位助手
纯前端:粘贴 automation YAML(可选 + trace JSON),工具按「trigger → condition → action → mode」四步静态定位卡在哪一步,并给每步该查什么。数据不出浏览器,适合排查「跑一半就停」「动作时灵时不灵」。
自动化最常见的两类抱怨——"不触发"和"动作时灵时不灵"——真正卡住的地方其实就四处:trigger 没触发、condition 没过、action 报错、mode 把新触发卡住了。HA 的 Trace 面板能看到每一步真实结果,但字段多、不熟的人容易看花。这个工具把"该按什么顺序查"固化成一张四步路径,先把你 YAML 里缺 trigger、action 实体写错这类结构问题标出来,再指你最可疑的那一步。
四步定位:卡在哪一步
| 步骤 | 卡在这的特征 | 该查什么 |
|---|---|---|
| ① trigger | 日志里根本没这条自动化的"触发"记录 | 缺 trigger / 实体写错 / for 没到时 / 类型不合法 |
| ② condition | 触发了但 action 没跑,Trace 里某条 condition 标"失败" | 哪条 condition 没过(时间/状态/模板);常是值写反或时区不对 |
| ③ action | Trace 里 action 步骤标"错误" | action: 的实体名拼写、服务名(domain.service)写错、模板变量未定义 |
| ④ mode | 高频率触发时只跑第一条 | mode: single 上一条没跑完就忽略新触发;看要不要改 restart/queued |
Trace 定位工具
粘 automation YAML(可整段或单条),需要的话再粘 HA 导出的 trace JSON,点「定位」:
先粘 automation YAML,再点「定位」。
'; return; } var lines = raw.split(/\r?\n/); var report = []; var foundMode = findMode(raw); var hasTrigger = hasAny(raw, ['trigger','triggers']); var hasCond = hasAny(raw, ['condition','conditions']); var hasAction = hasAny(raw, ['action','actions']); // ① trigger 层 if (!hasTrigger) { report.push({ step: 1, level: 'fail', text: '缺 trigger / triggers:这条自动化永远不会触发。先补 trigger,再谈后面三步。' }); } else { report.push({ step: 1, level: 'ok', text: '有 trigger。日志里若完全没有"触发"记录,查实体名对不对、for 有没有到、trigger 类型是否合法。' }); } // ② condition 层 if (hasCond) { report.push({ step: 2, level: 'warn', text: '含 condition。"动作时灵时不灵"最常见的卡点:看 Trace 里哪条 condition 标"失败"——时间条件(after/before)和模板变量最常写反。' }); } else { report.push({ step: 2, level: 'ok', text: '没有 condition:触发即执行,跳过这步;若怀疑某步没跑,重点看第①③④步。' }); } // ③ action 层 if (!hasAction) { report.push({ step: 3, level: 'fail', text: '缺 action / actions:即使触发也没有动作。补 action。' }); } else { report.push({ step: 3, level: 'ok', text: '有 action。Trace 里 action 步骤若标"错误",查:实体名拼写、服务名是否 domain.service、模板变量是否未定义。' }); } // ④ mode 层 if (foundMode === 'single') { report.push({ step: 4, level: 'warn', text: 'mode: single——上一条没跑完时,新触发会被忽略(日志会打 Already running)。高频触发场景改 restart(重头来)或 queued(排队)。' }); } else if (foundMode === 'queued') { report.push({ step: 4, level: 'ok', text: 'mode: queued——新触发排队等前一条跑完。若发现"跑不过来/堆积",看是不是 delay 太长。' }); } else if (foundMode === 'parallel') { report.push({ step: 4, level: 'ok', text: 'mode: parallel——每次触发都并发起一个新实例。多实例互不干扰,但注意延迟动作会不会打架。' }); } else { report.push({ step: 4, level: 'info', text: '未显式写 mode,默认按 single 处理。高频场景建议显式指定。' }); } // trace JSON(可选) var traceNote = null; if (tj) { try { var data = JSON.parse(tj); // HA trace 常见结构:steps 是对象,键是 step 名;或 run 里有 var steps = null; if (data && data.steps) { steps = data.steps; } if (steps) { var keys = Object.keys(steps); if (keys.length) { var last = keys[keys.length - 1]; var lv = ''; try { lv = steps[last] && steps[last].result ? String(steps[last].result) : ''; } catch (e) {} traceNote = 'trace 里共 ' + keys.length + ' 步,最后停在「' + last + '」。这步就是真正卡住的地方,重点看它。' + (lv ? '(result:' + esc(lv.slice(0, 40)) + ')' : ''); } else { traceNote = 'trace 的 steps 为空——说明这条自动化根本没跑起来,回到第①步查 trigger。'; } } else { traceNote = 'trace JSON 解析成功,但没找到 steps 字段——确认粘的是 HA 导出的完整 trace,不是某一步的片段。'; } } catch (e) { traceNote = 'trace JSON 解析失败(不是合法 JSON)。HA 的 trace 导出是 JSON,先确认复制完整再粘;没有也留空,按 YAML 排查顺序走即可。'; } } // 渲染 var html = ''; if (traceNote) { html += '' + esc(traceNote) + '
'; } html += '- ';
for (var i = 0; i < report.length; i++) {
var r = report[i];
var mark = r.level === 'fail' ? '✗' : (r.level === 'warn' ? '⚠' : (r.level === 'info' ? 'ⓘ' : '✓'));
var color = r.level === 'fail' ? 'var(--md-primary-fg-color)' : (r.level === 'warn' ? 'var(--md-code-fg-color)' : 'var(--md-default-fg-color--light)');
html += '
- 第' + r.step + '步 ' + mark + ' ' + esc(r.text) + ' '; } html += '
这是静态启发式定位,不模拟 HA 运行时。哪一步真断了,以 HA Trace 面板与日志为准。
'; out.innerHTML = html; } document.addEventListener('DOMContentLoaded', function () { var btn = document.getElementById('trace-go'); if (btn) { btn.addEventListener('click', locate); } }); })();