iotsaas/docs/10寸中控屏/04-中控屏直连MumIot对接协议-评审稿.html
2995840376 0ce191c448 chore: 初始化 iotsaas 版本库基线
纳入范围:
- backend/  Spring Boot 多模块后端(iot-common/device/rule/data/alarm/auth)
- frontend/admin/  Vue3 + Element Plus 管理后台
- Android/   涂鸦原生 APP (com.xhai.iot)
- backend/sql/  Flyway 风格迁移脚本(V2~V21)
- docs/       对接文档与设计文档

排除范围:
- 构建产物: node_modules/ target/ build/ dist/ .gradle/
- 签名密钥: *.jks *.keystore (严禁入库)
- 本机配置: Android/local.properties
- 运行日志: logs/ *.log
- AI 协作本地数据 .codebuddy/ 与第三方 demo tuya-demo/

.gitattributes 统一以 LF 存储, Windows 批处理保留 CRLF
2026-10-27 12:00:41 +08:00

414 lines
239 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><meta name="description" content="MumIot 10 寸中控屏直连接入协议:会话、设备、控制、回执、状态同步与 OTA。v0.1 评审稿。"><title>MumIot API · 中控屏直连接入协议</title><style>
:root{color-scheme:light;--ink:#263248;--muted:#718096;--line:#e4e9f1;--accent:#4565d9;--soft:#edf2ff;--side:254px;--mono:Consolas,"SFMono-Regular","Liberation Mono",monospace}
*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:118px}body{margin:0;background:#fff;color:var(--ink);font:14px/1.8 -apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",sans-serif}
a{color:var(--accent);text-decoration:none}a:hover{color:#2948b5}button,input{font:inherit}button{cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible,summary:focus-visible,[tabindex]:focus-visible{outline:3px solid #9cb3ff;outline-offset:3px}button:disabled{cursor:default;opacity:.6}
.sidebar{position:fixed;inset:0 auto 0 0;width:var(--side);background:#f7f9fc;border-right:1px solid var(--line);display:flex;flex-direction:column;z-index:40}
.brand{padding:27px 25px 20px;display:block;color:var(--ink);border-bottom:1px solid var(--line)}.brand-line{display:flex;align-items:center;gap:10px}.brand-icon{display:grid;place-items:center;width:31px;height:31px;background:var(--accent);color:white;border-radius:9px;font-size:14px;font-weight:700;letter-spacing:-1px}.brand-title{font-size:20px;font-weight:700;letter-spacing:-.3px}.brand-subtitle{display:block;color:var(--muted);font-size:12px;margin-top:8px}
.sidebar-nav{overflow-y:auto;flex:1;padding:16px 12px 22px;scrollbar-width:thin}.nav-group{margin-bottom:17px}.nav-label{padding:0 14px;margin:10px 0 6px;color:#95a0b2;font-size:11px;font-weight:650;letter-spacing:1px}.nav-item{display:flex;align-items:center;justify-content:space-between;padding:8px 13px;border-radius:7px;color:#566176;line-height:1.65;margin:2px 0;font-size:13px;transition:background .15s,color .15s}.nav-item:hover{background:#edf0f6;color:#263248}.nav-item[aria-current="page"]{background:#e8edff;color:#3b58bf;font-weight:650}.nav-arrow{opacity:0;font-size:18px;line-height:1}.nav-item[aria-current="page"] .nav-arrow{opacity:1}
.sidebar-foot{border-top:1px solid var(--line);padding:15px 25px;font-size:11px;color:#8894a7;line-height:1.7}.version-dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:#617bd4;margin-right:5px}
.workspace{margin-left:var(--side)}.topbar{position:sticky;top:0;z-index:25;background:rgba(255,255,255,.97);border-bottom:1px solid var(--line);backdrop-filter:blur(10px)}.toolbar-main{height:62px;display:flex;align-items:center;gap:18px;padding:0 36px}.breadcrumb{display:flex;gap:10px;align-items:center;color:#8d97aa;font-size:12px;white-space:nowrap}.breadcrumb strong{font-weight:500;color:#526078}.search-box{margin-left:auto;position:relative;max-width:345px;flex:1;min-width:170px}.search-icon{position:absolute;top:50%;left:12px;transform:translateY(-50%);color:#8f9ab0;font-size:18px;pointer-events:none}.search-box input{width:100%;height:36px;padding:0 45px 0 36px;border:1px solid #dfe5ee;border-radius:7px;background:#f9fafc;color:var(--ink);font-size:12px}.search-box input:focus{background:#fff;border-color:#8ba3ee;outline:2px solid #edf1ff}.search-box kbd{position:absolute;right:10px;top:9px;border:1px solid #e1e5ed;border-radius:3px;padding:0 4px;color:#95a0b1;line-height:17px;font-size:10px}.toolbar-button{border:1px solid var(--line);background:#fff;border-radius:6px;padding:5px 10px;color:#66738a;font-size:12px;white-space:nowrap}.toolbar-button:hover{background:#f6f8fc}.toolbar-secondary{height:42px;border-top:1px solid #f3f5f8;padding:0 36px;display:flex;align-items:center;gap:12px}.base-label{color:#9aa4b5;font-size:10px;text-transform:uppercase;letter-spacing:.7px}.base-value{font:11px var(--mono);color:#63728a;background:#f4f6fa;border:1px solid #e9edf3;border-radius:4px;padding:3px 8px}.review-label{margin-left:auto;border:1px solid #e5e2d2;border-radius:5px;background:#fffdf5;color:#9b8351;font-size:10px;line-height:1.7;padding:1px 7px}.menu-button{display:none;border:0;background:transparent;color:#526078;font-size:22px;padding:0}
.main-content{max-width:1130px;margin:0 auto;padding:42px 50px 22px}.eyebrow{font-size:10px;letter-spacing:1.7px;font-weight:650;color:#8c99b0;margin-bottom:8px}.page-heading h1{font-size:29px;line-height:1.4;letter-spacing:-.5px;margin:0 0 9px;font-weight:700;color:#25334b}.page-description{color:#8590a3;margin:0 0 24px;font-size:13px}.overview-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:0;border:1px solid var(--line);border-radius:10px;padding:17px 0;margin:27px 0 25px;background:#fcfdff}.overview-stats>div{display:flex;flex-direction:column;padding:0 23px;border-right:1px solid var(--line)}.overview-stats>div:last-child{border:0}.overview-stats strong{font-size:22px;font-weight:650;line-height:1.4;color:#435fbd}.overview-stats span{font-size:11px;color:#8b95a7;margin-top:4px}.intro-card{font-size:13px;background:#f8faff;border:1px solid #e1e8f8;border-left:3px solid #8aa0e8;border-radius:7px;padding:17px 22px;margin-bottom:25px}.intro-card p{margin:0 0 11px}.intro-card p:last-child{margin:0}.intro-card ul{margin:8px 0;padding-left:20px}
.on-this-page{display:flex;flex-wrap:wrap;gap:6px 20px;border-bottom:1px solid var(--line);padding:0 0 17px;margin-bottom:29px}.on-this-page a{font-size:12px;color:#6a7ca5}.on-this-page a:hover{text-decoration:underline}.prose p{margin:12px 0;color:#4d596d;overflow-wrap:break-word}.prose strong{font-weight:650;color:#344158}.prose h3{margin:37px 0 16px;font-size:19px;line-height:1.6;color:#2f3e57;font-weight:650;border-top:1px solid #edf0f6;padding-top:27px;scroll-margin-top:118px}.prose h3:first-child{border-top:0;margin-top:10px;padding-top:0}.prose h4{font-size:16px;margin:26px 0 12px}.anchor{font-size:14px;font-weight:400;color:#a3b1ce;opacity:0;margin-left:8px}.prose h3:hover .anchor,.anchor:focus{opacity:1}.prose ul,.prose ol{padding-left:23px;margin:13px 0;color:#4d596d}.prose li{padding-left:2px;margin:5px 0}.prose code,.intro-card code{font:12px/1.7 var(--mono);background:#f1f4fa;color:#4a6299;border:1px solid #e9edf5;border-radius:4px;padding:1px 5px;overflow-wrap:anywhere}.external{font-size:10px;margin-left:3px;color:#9aa8c1}
.endpoint{scroll-margin-top:119px;margin:18px 0 14px;padding:13px 15px;display:flex;align-items:center;gap:12px;border:1px solid #e0e6f0;border-left:3px solid #8da3dc;border-radius:6px;background:#fafbfe;line-height:1.6}.endpoint>code{font:13px/1.7 var(--mono);color:#34445f;background:transparent;border:0;padding:0;word-break:break-word;flex:1}.method{font:700 11px/1.6 var(--mono);min-width:46px;padding:3px 7px;text-align:center;border-radius:4px;flex-shrink:0}.get{color:#17836b;background:#e7f6ee}.post{color:#4262c7;background:#eaf0ff}.put,.patch{color:#a57925;background:#fff4dc}.delete{color:#b9535c;background:#fcecee}.badge{font-size:10px;white-space:nowrap;padding:2px 7px;border-radius:4px;flex-shrink:0}.badge.new{color:#5c6fb3;background:#edf1ff;border:1px solid #dce4ff}.badge.extend{color:#96732e;background:#fff8e9;border:1px solid #f0e4c6}.badge.reuse{color:#5c8476;background:#ecf6f1;border:1px solid #d8ebe2}.endpoint-anchor{color:#a1abc0;font-size:12px}
.table-scroll{overflow:auto;border:1px solid var(--line);border-radius:7px;margin:18px 0 22px;scrollbar-width:thin}table{border-collapse:collapse;width:100%;font-size:12px;text-align:left;line-height:1.75}th{background:#f5f7fb;font-size:11px;color:#748096;font-weight:650;padding:11px 13px;white-space:nowrap;border-bottom:1px solid var(--line)}td{padding:11px 13px;border-bottom:1px solid #edf0f5;color:#556176;vertical-align:top}tr:last-child td{border-bottom:0}tbody tr:hover{background:#fbfcff}td:first-child{color:#384761;font-weight:500}td code{font-size:11px!important}table th:nth-child(2),table td:nth-child(2){min-width:94px}.table-scroll table{min-width:540px}
.example{margin:17px 0 21px;border:1px solid #e0e6f0;border-radius:7px;overflow:hidden;background:#fff}.example summary{display:flex;align-items:center;gap:9px;padding:11px 14px;cursor:pointer;color:#5c6b86;list-style:none;background:#f8faff;font-size:12px}.example summary::-webkit-details-marker{display:none}.example summary:before{content:"›";font:17px/1 var(--mono);color:#8391ad;transition:transform .15s}.example[open] summary:before{transform:rotate(90deg)}.example-icon{font-size:13px;color:#8a9bc1}.example-meta{margin-left:auto;font:10px var(--mono);color:#9aa6bc}.code-tools{display:flex;align-items:center;justify-content:space-between;padding:8px 15px 2px;background:#172235;color:#8f9db5;font:10px var(--mono)}.copy-code{background:#243149;border:1px solid #35435b;border-radius:4px;color:#b4c2db;padding:2px 9px;font:10px/1.7 inherit}.copy-code:hover{background:#31415e;color:#fff}.example pre{background:#172235;color:#d1dbef;margin:0;padding:12px 18px 19px;overflow:auto;font:12px/1.9 var(--mono);tab-size:2;scrollbar-width:thin}.example pre code{font:inherit;background:none;border:0;border-radius:0;padding:0;color:inherit;overflow-wrap:normal;white-space:pre}.json-key{color:#a4c6fa}.json-string{color:#b8d8b2}.json-number{color:#e9c58e}.json-literal{color:#c1ade8}
.diagram{margin:23px 0;padding:18px;border:1px solid var(--line);border-radius:9px;background:#fbfcfe;overflow:auto}.diagram svg{display:block;width:100%;min-width:530px;height:auto}.diagram figcaption{font-size:11px;color:#8b97aa;text-align:center;margin-top:9px}.chapter-pagination{display:flex;justify-content:space-between;gap:24px;margin:45px 0 10px;padding-top:23px;border-top:1px solid var(--line)}.chapter-pagination a{display:flex;flex-direction:column;font-size:13px;max-width:46%}.chapter-pagination small{font-size:10px;color:#96a0b2;margin-bottom:5px}.chapter-pagination .next{text-align:right}.footer{margin-top:24px;padding:22px 0 10px;border-top:1px solid #f0f2f7;color:#98a2b4;font-size:11px;text-align:center}
.search-panel{position:fixed;top:67px;right:34px;width:min(640px,calc(100vw - var(--side) - 65px));max-height:calc(100vh - 105px);overflow:auto;z-index:60;border:1px solid #dce3f0;border-radius:10px;background:#fff;box-shadow:0 15px 60px #233b6520}.search-summary{display:flex;justify-content:space-between;align-items:center;padding:13px 18px;font-size:11px;color:#8994a6;border-bottom:1px solid #eef1f7;position:sticky;top:0;background:#fff}.search-close{border:0;color:#7d8da8;background:none;padding:2px 5px;font-size:16px}.search-result{display:block;padding:15px 20px;color:#4c5c78;border-bottom:1px solid #f1f3f8}.search-result:hover{background:#f6f8ff}.search-result .result-kind{font-size:10px;color:#98a5bd;margin-bottom:4px}.search-result strong{display:block;font-size:13px;color:#3e5586;font-weight:600;word-break:break-word}.search-result p{font-size:11px;line-height:1.7;margin:5px 0 0;color:#8794a9;word-break:break-word}.search-empty{padding:35px 20px;text-align:center;color:#8c99af;font-size:13px}mark{background:#fff0be;color:#735820;padding:0 1px;border-radius:2px}.toast{position:fixed;bottom:26px;left:calc(50% + var(--side)/2);transform:translateX(-50%);padding:9px 17px;border-radius:7px;background:#26364f;color:#fff;font-size:12px;box-shadow:0 8px 28px #1a2d5226;z-index:100}.backdrop{display:none}.noscript{margin:12px 20px;color:#96732e}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}[hidden]{display:none!important}
@media(min-width:1500px){.main-content{padding-top:50px}.toolbar-main,.toolbar-secondary{padding-left:max(40px,calc((100vw - var(--side) - 1030px)/2));padding-right:max(40px,calc((100vw - var(--side) - 1030px)/2))}}
@media(max-width:1100px){:root{--side:228px}.main-content{padding:30px 28px 20px}.toolbar-main,.toolbar-secondary{padding-left:26px;padding-right:26px}.breadcrumb span:first-child,.breadcrumb .slash{display:none}.search-box{max-width:300px}.endpoint{flex-wrap:wrap}.endpoint>code{min-width:180px}.endpoint .badge{margin-left:auto}}
@media(max-width:760px){:root{--side:254px}.workspace{margin-left:0}.sidebar{transform:translateX(-100%);transition:transform .2s}.nav-open .sidebar{transform:translateX(0)}.nav-open .backdrop{display:block;position:fixed;inset:0;background:#17243b55;z-index:35}.menu-button{display:block}.toolbar-main{height:57px;gap:12px;padding:0 16px}.breadcrumb{display:none}.toolbar-secondary{padding:0 16px;height:39px;gap:8px}.base-value{font-size:10px}.review-label{font-size:9px}.search-box{max-width:none;min-width:100px}.search-box input{font-size:11px;padding-right:25px}.search-box kbd{display:none}.toolbar-button{font-size:11px;padding:4px 8px}.main-content{padding:27px 19px 15px}.page-heading h1{font-size:25px}.overview-stats>div{padding:0 12px}.overview-stats strong{font-size:19px}.overview-stats span{font-size:10px}.intro-card{padding:15px 16px}.endpoint{padding:11px;gap:8px}.endpoint>code{font-size:11px;min-width:150px}.method{font-size:10px;min-width:42px}.badge{font-size:9px}.search-panel{top:61px;right:12px;width:calc(100vw - 24px)}.toast{left:50%}.table-scroll table{min-width:540px}.prose h3{font-size:18px}.example pre{font-size:11px}.diagram{padding:8px}.footer{font-size:10px}}
@media print{html{scroll-behavior:auto}body{font-size:10pt;color:#222}.sidebar,.topbar,.search-panel,.toast,.on-this-page,.chapter-pagination,.copy-code,.anchor,.endpoint-anchor,.footer,.backdrop{display:none!important}.workspace{margin:0}.main-content{max-width:none;padding:0}.doc-page[hidden],.doc-page{display:block!important}.doc-page{break-before:page}.doc-page:first-child{break-before:auto}.page-heading h1{font-size:22pt}.eyebrow{display:none}.prose h3{font-size:14pt;break-after:avoid}.table-scroll{overflow:visible;border-radius:0}table{font-size:9pt;min-width:0!important}tr{break-inside:avoid}.example pre{white-space:pre-wrap;overflow:visible;font-size:8pt;background:#f6f7fa;color:#223047}.example pre code{white-space:pre-wrap;word-break:break-word}.example pre span{color:#263248!important}.example{overflow:visible;break-inside:auto}.example:not([open])>*:not(summary){display:block!important}.code-tools{background:#eee;color:#555}.overview-stats{padding:10px 0}.endpoint{break-inside:avoid}.diagram svg{min-width:0}.page-description{margin-bottom:15px}a{color:#334b78;text-decoration:none}}
</style><noscript><style>.doc-page[hidden]{display:block!important}.doc-page{margin-bottom:50px}.search-box,#expand-examples{display:none}</style></noscript></head>
<body><a class="sr-only" href="#main-content">跳到文档正文</a><aside class="sidebar" aria-label="文档目录"><a href="#chapter-1" class="brand"><div class="brand-line"><span class="brand-icon" aria-hidden="true">M</span><span class="brand-title">MumIot · API</span></div><span class="brand-subtitle">10 寸中控屏 · 直连接入协议</span></a><nav class="sidebar-nav"><div class="nav-group"><div class="nav-label">入门</div><a class="nav-item" href="#chapter-1" data-chapter="chapter-1"><span>概览</span><span class="nav-arrow" aria-hidden="true">›</span></a><a class="nav-item" href="#chapter-2" data-chapter="chapter-2"><span>通用约定</span><span class="nav-arrow" aria-hidden="true">›</span></a></div><div class="nav-group"><div class="nav-label">中控与设备</div><a class="nav-item" href="#chapter-3" data-chapter="chapter-3"><span>中控身份与会话</span><span class="nav-arrow" aria-hidden="true">›</span></a><a class="nav-item" href="#chapter-4" data-chapter="chapter-4"><span>家庭快照与事件同步</span><span class="nav-arrow" aria-hidden="true">›</span></a><a class="nav-item" href="#chapter-5" data-chapter="chapter-5"><span>设备登记与标准控制模型</span><span class="nav-arrow" aria-hidden="true">›</span></a></div><div class="nav-group"><div class="nav-label">执行与同步</div><a class="nav-item" href="#chapter-6" data-chapter="chapter-6"><span>控制命令与执行回执</span><span class="nav-arrow" aria-hidden="true">›</span></a><a class="nav-item" href="#chapter-7" data-chapter="chapter-7"><span>状态与活动上报</span><span class="nav-arrow" aria-hidden="true">›</span></a></div><div class="nav-group"><div class="nav-label">平台服务</div><a class="nav-item" href="#chapter-8" data-chapter="chapter-8"><span>多屏、场景与 AI</span><span class="nav-arrow" aria-hidden="true">›</span></a><a class="nav-item" href="#chapter-9" data-chapter="chapter-9"><span>配置、内容、日志和 OTA</span><span class="nav-arrow" aria-hidden="true">›</span></a></div><div class="nav-group"><div class="nav-label">参考</div><a class="nav-item" href="#chapter-10" data-chapter="chapter-10"><span>建议错误码</span><span class="nav-arrow" aria-hidden="true">›</span></a><a class="nav-item" href="#chapter-11" data-chapter="chapter-11"><span>字段模型汇总</span><span class="nav-arrow" aria-hidden="true">›</span></a><a class="nav-item" href="#chapter-12" data-chapter="chapter-12"><span>对接顺序与评审确认</span><span class="nav-arrow" aria-hidden="true">›</span></a><a class="nav-item" href="#chapter-13" data-chapter="chapter-13"><span>文档依据</span><span class="nav-arrow" aria-hidden="true">›</span></a></div></nav><div class="sidebar-foot"><span class="version-dot"></span>v0.1 评审稿 · 2026-10-16<br>沿用 / 扩展建议 / 新增建议</div></aside><button class="backdrop" id="nav-backdrop" aria-label="关闭文档目录"></button><div class="workspace"><header class="topbar"><div class="toolbar-main"><button class="menu-button" id="menu-toggle" aria-label="打开文档目录" aria-expanded="false">☰</button><div class="breadcrumb"><span>接口文档</span><span class="slash">/</span><strong id="current-page">概览</strong></div><div class="search-box"><span class="search-icon" aria-hidden="true">⌕</span><label class="sr-only" for="search-input">搜索接口、字段和协议内容</label><input id="search-input" type="search" placeholder="搜索接口 / 字段 / 协议内容" autocomplete="off" aria-controls="search-panel" aria-expanded="false"><kbd>/</kbd></div><button class="toolbar-button" id="expand-examples" type="button">展开示例</button><button class="toolbar-button" id="print-document" type="button">打印</button></div><div class="toolbar-secondary"><span class="base-label">Base URL</span><code class="base-value">https://mum.tonzou.cn</code><button id="copy-base" class="toolbar-button" aria-label="复制 Base URL" type="button">复制</button><span class="review-label">协议评审稿</span></div></header><main id="main-content" class="main-content"><noscript><p class="noscript">当前浏览器未启用 JavaScript,已显示完整正文;搜索和复制功能需启用 JavaScript。</p></noscript><section class="doc-page" id="chapter-1" data-title="概览" ><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>概览</h1><p class="page-description">总体结构、通信方式与接口目录</p></div><div class="overview-stats"><div><strong>v0.1</strong><span>评审稿</span></div><div><strong>13</strong><span>文档章节</span></div><div><strong>29</strong><span>JSON 示例</span></div><div><strong>直连</strong><span>MumIot 平台</span></div></div><div class="intro-card"><p>版本:v0.1 评审稿 日期:2026 年 10 月 16 日 适用方式:Android 中控屏直接连接 MumIot 平台。</p><p>本文沿用现有文档的接口、鉴权、参数、字段表和 JSON 示例格式,给出中控屏直连平台的协议建议。平台已有业务接口继续复用;中控身份、命令下发、回执及同步部分提出补充约定。</p><p><strong>标记说明:</strong></p><ul><li><strong>沿用</strong>:路径和用途已见现有文档;实际响应按现有实现核对。</li><li><strong>扩展建议</strong>:路径已存在,但本文建议补充鉴权、字段或结果语义。</li><li><strong>新增建议</strong>:拟新增的中控接口,路径、模型和默认参数供双方评审,尚不表示平台已实现。</li></ul><p>下文补充接口、示例字段和默认值均属于本稿建议。示例 ID、设备及 Token 全为占位或虚构数据。正式业务后端仅为 MumIot,不增加中转服务器。</p></div><nav class="on-this-page" aria-label="本页目录"><a href="#chapter-1-heading-1">1. Base URL</a><a href="#chapter-1-heading-2">2. 业务关系</a><a href="#chapter-1-heading-3">3. 主通信方式</a><a href="#chapter-1-heading-4">4. 接口目录</a></nav><article class="prose"><h3 id="chapter-1-heading-1">1. Base URL<a class="anchor" href="#chapter-1-heading-1" aria-label="链接到1. Base URL">#</a></h3><details class="example plain" open id="block-code-chapter-1-6"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>地址与流程</span><span class="example-meta">TEXT · 1 行</span></summary><div class="code-tools"><span>文本</span><button class="copy-code" data-copy="code-chapter-1-6" type="button" aria-label="复制文本">复制</button></div><pre tabindex="0"><code id="code-chapter-1-6">https://mum.tonzou.cn</code></pre></details><p>本文接口路径统一包含 <code>/api/v1</code>,与 Base URL 拼接时不重复添加版本前缀。</p><h3 id="chapter-1-heading-2">2. 业务关系<a class="anchor" href="#chapter-1-heading-2" aria-label="链接到2. 业务关系">#</a></h3><figure class="diagram"><svg viewBox="0 0 820 300" role="img" aria-labelledby="diagram-title diagram-desc"><title id="diagram-title">中控屏直连 MumIot 的业务关系</title><desc id="diagram-desc">手机和中控屏直接连接 MumIot 平台。平台连接云设备;中控屏连接本地设备,并向平台登记和报告状态。</desc><defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0 0L10 5L0 10" fill="#8794ab"/></marker></defs><g fill="none" stroke="#8794ab" stroke-width="1.7" marker-end="url(#arrow)"><path d="M200 72H310"/><path d="M510 72H620"/><path d="M410 215V112"/><path d="M510 245H620"/></g><g font-family="system-ui,Microsoft YaHei,sans-serif"><rect x="20" y="40" width="180" height="64" rx="12" fill="#fff" stroke="#dbe1eb"/><text x="110" y="78" text-anchor="middle" fill="#334155" font-size="15">手机 / 网页</text><rect x="310" y="32" width="200" height="80" rx="14" fill="#eef2ff" stroke="#bbc9f8"/><text x="410" y="66" text-anchor="middle" fill="#334b9f" font-size="17" font-weight="600">MumIot 平台</text><text x="410" y="89" text-anchor="middle" fill="#697aa6" font-size="12">唯一业务后端</text><rect x="620" y="40" width="180" height="64" rx="12" fill="#fff" stroke="#dbe1eb"/><text x="710" y="78" text-anchor="middle" fill="#334155" font-size="15">平台已有云设备</text><rect x="310" y="215" width="200" height="62" rx="12" fill="#fff" stroke="#c9d4e4"/><text x="410" y="252" text-anchor="middle" fill="#26344b" font-size="16" font-weight="600">10 寸中控屏</text><rect x="620" y="215" width="180" height="62" rx="12" fill="#fff" stroke="#dbe1eb"/><text x="710" y="240" text-anchor="middle" fill="#334155" font-size="14">本地设备</text><text x="710" y="260" text-anchor="middle" fill="#64748b" font-size="11">Matter / Zigbee / BLE</text><text x="428" y="147" fill="#64748b" font-size="12">业务查询与云端控制</text><text x="428" y="168" fill="#64748b" font-size="12">领取命令与同步事件</text><text x="428" y="189" fill="#64748b" font-size="12">设备登记与状态回报</text><text x="565" y="232" text-anchor="middle" fill="#64748b" font-size="12">本地协议</text></g></svg><figcaption>屏直接连接平台,本地设备由屏执行。</figcaption></figure><ul><li>账号、家庭、房间、平台设备和平台场景由 MumIot 管理。</li><li>中控屏负责自己的界面、本地缓存、本地协议连接和实际执行。</li><li>每台本地设备在平台上关联唯一执行屏;其他屏通过平台发起控制。</li><li>本地断网控制保留,过期远程命令不在恢复联网后补执行。</li><li>状态同步和执行命令是两种消息,收到同步事件不会自动再次执行设备动作。</li></ul><h3 id="chapter-1-heading-3">3. 主通信方式<a class="anchor" href="#chapter-1-heading-3" aria-label="链接到3. 主通信方式">#</a></h3><p>本稿以 HTTPS REST 为主,命令领取和事件同步使用有时限的长轮询。屏主动建立连接,不需要平台从公网直接连接屏的地址。</p><p>建议默认心跳间隔 30 秒、在线租期 90 秒、单次长轮询最多 20 秒。具体值由会话响应下发;客户端 HTTP 读取超时应大于等待时间并留有传输余量。命令处理、事件同步、心跳分别调度,长轮询不阻塞心跳或回执。</p><p>若平台已有网关通信协议,可将本文的命令、状态和回执模型映射到已有通道;本稿不要求平台同时维护多套通信机制。</p><h3 id="chapter-1-heading-4">4. 接口目录<a class="anchor" href="#chapter-1-heading-4" aria-label="链接到4. 接口目录">#</a></h3><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">分类</th><th scope="col">方法与路径</th><th scope="col">标记</th></tr></thead><tbody><tr><td>屏会话</td><td>POST /api/v1/panels/session</td><td>新增建议</td></tr><tr><td>刷新会话</td><td>POST /api/v1/panels/session/refresh</td><td>新增建议</td></tr><tr><td>屏信息</td><td>GET /api/v1/panels/{panelId}</td><td>新增建议</td></tr><tr><td>心跳</td><td>POST /api/v1/panels/{panelId}/heartbeat</td><td>新增建议</td></tr><tr><td>家庭/房间</td><td>GET /api/v1/homes;GET /api/v1/homes/{homeId}/rooms</td><td>扩展建议:接受受限屏身份</td></tr><tr><td>初始化快照</td><td>GET /api/v1/panels/{panelId}/snapshot</td><td>新增建议</td></tr><tr><td>增量事件</td><td>GET /api/v1/panels/{panelId}/events</td><td>新增建议</td></tr><tr><td>本地设备登记</td><td>POST /api/v1/panels/{panelId}/devices/register</td><td>新增建议</td></tr><tr><td>本地设备能力更新</td><td>PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema</td><td>新增建议</td></tr><tr><td>设备与物模型</td><td>GET /api/v1/devices/{deviceId};GET /api/v1/devices/{deviceId}/control-schema;GET /api/v1/products/{id}/spec</td><td>扩展建议:屏权限与模型约定</td></tr><tr><td>控制提交</td><td>POST /api/v1/devices/{deviceId}/commands</td><td>扩展建议:幂等与命令结果</td></tr><tr><td>控制查询</td><td>GET /api/v1/device-commands/{commandId}</td><td>新增建议</td></tr><tr><td>屏领取命令</td><td>POST /api/v1/panels/{panelId}/commands/pull</td><td>新增建议</td></tr><tr><td>屏申请执行</td><td>POST /api/v1/panels/{panelId}/commands/{commandId}/start</td><td>新增建议</td></tr><tr><td>屏报告结果</td><td>POST /api/v1/panels/{panelId}/commands/{commandId}/report</td><td>新增建议</td></tr><tr><td>屏上报状态</td><td>POST /api/v1/panels/{panelId}/reports</td><td>新增建议</td></tr><tr><td>场景/规则/AI</td><td>现有 scenes、rules、ai 接口</td><td>沿用业务入口,扩展执行归属和回报</td></tr><tr><td>场景运行查询</td><td>GET /api/v1/scene-runs/{sceneRunId}</td><td>新增建议</td></tr><tr><td>本地场景结果上报</td><td>POST /api/v1/panels/{panelId}/scene-runs/report</td><td>新增建议</td></tr><tr><td>屏配置/内容</td><td>GET /api/v1/panels/{panelId}/configuration</td><td>新增建议</td></tr><tr><td>屏诊断事件</td><td>POST /api/v1/panels/{panelId}/diagnostics</td><td>新增建议</td></tr><tr><td>OTA 任务领取</td><td>GET /api/v1/panels/{panelId}/ota-tasks/next</td><td>新增建议</td></tr><tr><td>OTA 进度回报</td><td>POST /api/v1/ota-tasks/{id}/report</td><td>扩展建议</td></tr><tr><td>天气/历史/告警</td><td>现有 weather、data、alarms 接口</td><td>沿用,并限制为当前屏授权范围</td></tr></tbody></table></div></article><div class="chapter-pagination"><span></span><a class="next" href="#chapter-2"><small>下一章</small><span>通用约定 →</span></a></div></section>
<section class="doc-page" id="chapter-2" data-title="通用约定" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>通用约定</h1><p class="page-description">请求头、幂等、响应和标识</p></div><nav class="on-this-page" aria-label="本页目录"><a href="#chapter-2-heading-1">1. 请求头</a><a href="#chapter-2-heading-2">2. 成功响应</a><a href="#chapter-2-heading-3">3. 错误响应</a><a href="#chapter-2-heading-4">4. 标识与时间</a></nav><article class="prose"><h3 id="chapter-2-heading-1">1. 请求头<a class="anchor" href="#chapter-2-heading-1" aria-label="链接到1. 请求头">#</a></h3><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">名称</th><th scope="col">类型</th><th scope="col">必填</th><th scope="col">说明</th></tr></thead><tbody><tr><td>Authorization</td><td>String</td><td>是,除另有说明</td><td>Bearer 加中控 Access Token</td></tr><tr><td>Content-Type</td><td>String</td><td>有 JSON 请求体时</td><td>application/json</td></tr><tr><td>X-MUM-Protocol</td><td>String</td><td>本文新增接口及采用扩展契约的请求</td><td>固定为 panel-v1,用于识别本文的请求和响应约定</td></tr><tr><td>X-Request-Id</td><td>String</td><td>建议</td><td>单次请求的跟踪编号,不承担业务去重</td></tr><tr><td>Idempotency-Key</td><td>String</td><td>本文所有 POST,以及扩展后的业务写请求</td><td>一次逻辑操作的唯一编号;重试时保持不变</td></tr></tbody></table></div><p>幂等范围为当前租户、调用主体、方法、规范化资源路径及 Idempotency-Key。相同键与相同业务内容返回同一操作结果;相同键但不同内容返回 <code>IDEMPOTENCY_CONFLICT</code>。服务端在产生副作用前原子登记该键,并发重试也只产生一项业务操作。</p><p>建议业务幂等记录至少保留 7 天;超过保留期的旧控制请求不得作为自动重试重新提交。命令执行记录与幂等记录的保留期均由平台明确。身份类响应含凭证,按第三节规定处理,不作为普通日志保存。</p><p>认证和当前资源权限检查先于幂等结果返回;撤权后不能借旧幂等键取回受限结果或执行许可。长轮询返回命令或事件前也要重查身份,不在等待期间把已经失效的访问继续放行。</p><p>对于已有路径,本文新增的必填字段、HTTP 202 和响应模型仅用于明确声明 panel-v1 的调用。原手机/网页请求格式由平台保留兼容,并在内部归入同一设备路由和命令记录;不能直接把新必填字段施加到所有旧客户端。旧调用若没有稳定请求键,不能声称具备本文定义的客户端重试去重保证。</p><h3 id="chapter-2-heading-2">2. 成功响应<a class="anchor" href="#chapter-2-heading-2" aria-label="链接到2. 成功响应">#</a></h3><details class="example json" id="example-1"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 01</span><span class="example-meta">JSON · 5 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-2-28" type="button" aria-label="复制 JSON 示例 1">复制</button></div><pre tabindex="0"><code id="code-chapter-2-28">{
<span class="json-key">&quot;code&quot;</span>: <span class="json-number">0</span>,
<span class="json-key">&quot;message&quot;</span>: <span class="json-string">&quot;success&quot;</span>,
<span class="json-key">&quot;data&quot;</span>: {}
}</code></pre></details><p>单条结果在 data;分页业务沿用现有 PageResult。中控快照、事件、命令领取及批量上报采用各自定义的数据结构,不强套 PageResult。</p><p>业务 code=0 只表示该接口请求处理成功。命令是否执行,以 data.state、data.terminal 及执行证据为准。</p><h3 id="chapter-2-heading-3">3. 错误响应<a class="anchor" href="#chapter-2-heading-3" aria-label="链接到3. 错误响应">#</a></h3><details class="example json" id="example-2"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 02</span><span class="example-meta">JSON · 9 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-2-46" type="button" aria-label="复制 JSON 示例 2">复制</button></div><pre tabindex="0"><code id="code-chapter-2-46">{
<span class="json-key">&quot;code&quot;</span>: <span class="json-number">40901</span>,
<span class="json-key">&quot;message&quot;</span>: <span class="json-string">&quot;请求编号已用于另一项操作&quot;</span>,
<span class="json-key">&quot;data&quot;</span>: {
<span class="json-key">&quot;errorCode&quot;</span>: <span class="json-string">&quot;IDEMPOTENCY_CONFLICT&quot;</span>,
<span class="json-key">&quot;requestId&quot;</span>: <span class="json-string">&quot;req-example-01&quot;</span>,
<span class="json-key">&quot;retryable&quot;</span>: <span class="json-literal">false</span>
}
}</code></pre></details><p>错误使用对应非 2xx HTTP 状态。若现有业务接口采用其他错误结构,由平台提供准确映射;屏不能仅凭 HTTP 200 判定设备控制成功。</p><h3 id="chapter-2-heading-4">4. 标识与时间<a class="anchor" href="#chapter-2-heading-4" aria-label="链接到4. 标识与时间">#</a></h3><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">类型</th><th scope="col">约定</th></tr></thead><tbody><tr><td>homeId、memberId、productId、数据库 id</td><td>本文新增响应统一为十进制字符串;现有 Long 字段由客户端按原契约无损解析</td></tr><tr><td>deviceId</td><td>平台业务设备 ID;与数据库主键 id 分开</td></tr><tr><td>panelId</td><td>平台中控 ID</td></tr><tr><td>localDeviceId</td><td>屏内持久化设备 ID;重启不变,与本地协议绑定关联</td></tr><tr><td>installationId</td><td>当前应用安装实例 ID;重装后的恢复需验证既有终端身份</td></tr><tr><td>bootId</td><td>每次进程启动生成的新 UUID</td></tr><tr><td>requestId、commandId、reportId、eventId</td><td>字符串,不依赖可解析的内部格式</td></tr><tr><td>revision、sequence</td><td>非负整数;事件游标为不透明字符串,不能由客户端计算</td></tr><tr><td>时间戳</td><td>Unix 毫秒,Long;expiresIn、waitSeconds、leaseSeconds 为秒</td></tr></tbody></table></div><p>设备离线、平台连接中断和状态陈旧分别表示,不互相替代。平台响应中的 serverTime 用于估计时差,执行截止时间在屏内换算为单调时钟,防止系统时间跳变使过期命令继续执行。</p></article><div class="chapter-pagination"><a href="#chapter-1"><small>上一章</small><span>← 概览</span></a><a class="next" href="#chapter-3"><small>下一章</small><span>中控身份与会话 →</span></a></div></section>
<section class="doc-page" id="chapter-3" data-title="中控身份与会话" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>中控身份与会话</h1><p class="page-description">终端身份、会话、续期与心跳</p></div><nav class="on-this-page" aria-label="本页目录"><a href="#chapter-3-heading-1">1. 身份模型</a><a href="#chapter-3-heading-2">2. 建立会话</a><a href="#chapter-3-heading-3">3. 刷新会话</a><a href="#chapter-3-heading-4">4. 屏信息与心跳</a></nav><article class="prose"><h3 id="chapter-3-heading-1">1. 身份模型<a class="anchor" href="#chapter-3-heading-1" aria-label="链接到1. 身份模型">#</a></h3><p><strong>新增建议。</strong> 平台为屏配置独立的终端身份及家庭关联,签发 <code>ROLE_PANEL</code> 身份。AppSecret 继续留在平台服务端;屏端不持有全应用共享的长期 Secret。</p><p>开发阶段为已配置测试板预置终端身份与测试家庭,联网后自动建立会话,不增加用户输入激活码或人工审批。预置终端凭证与对应平台记录由平台方通过受控配置交付;序列号仅用于定位和核对,不能单凭序列号获得授权。终端凭证不能直接固化成所有 APK 共用值。</p><p>家庭、会员关联及可执行设备范围由平台决定,不能由屏在请求里任意指定。设备、家庭或成员关系失效后,平台在每次敏感调用及消息投递时重新检查。</p><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">建议权限</th><th scope="col">可执行内容</th></tr></thead><tbody><tr><td>panel.read</td><td>读取自身信息与当前家庭快照</td></tr><tr><td>panel.report</td><td>上报自身心跳及负责设备的状态</td></tr><tr><td>device.read</td><td>读取已授权家庭设备与物模型</td></tr><tr><td>device.control</td><td>控制授权范围内设备</td></tr><tr><td>device.manage</td><td>管理被授予的设备与房间操作</td></tr><tr><td>gateway.execute</td><td>领取并执行明确分配给本屏的本地设备命令</td></tr><tr><td>scene.use</td><td>查看、执行授权场景;编辑权限另由平台控制</td></tr><tr><td>ota.read / ota.report</td><td>查询自己的升级任务并回报</td></tr></tbody></table></div><p>本稿建议允许受限屏身份调用既有家庭、设备、场景和必要涂鸦/AI 业务入口。平台负责将屏身份关联到当前家庭的业务上下文,不能把屏 Token 当成管理员 Token,也不要求把会员密码下发到屏。</p><h3 id="chapter-3-heading-2">2. 建立会话<a class="anchor" href="#chapter-3-heading-2" aria-label="链接到2. 建立会话">#</a></h3><div class="endpoint" id="endpoint-1"><span class="method post">POST</span><code>/api/v1/panels/session</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-1" aria-label="定位 POST /api/v1/panels/session">#</a></div><p>鉴权:<code>Authorization: PanelBootstrap &lt;终端初始化凭证&gt;</code></p><p>参数:body: PanelSessionRequest</p><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">字段</th><th scope="col">类型</th><th scope="col">必填</th><th scope="col">说明</th></tr></thead><tbody><tr><td>serialNumber</td><td>String</td><td>是</td><td>与预置终端记录核对</td></tr><tr><td>installationId</td><td>String</td><td>是</td><td>安装实例</td></tr><tr><td>bootId</td><td>String</td><td>是</td><td>当前启动实例</td></tr><tr><td>model</td><td>String</td><td>是</td><td>屏型号</td></tr><tr><td>appVersion</td><td>String</td><td>是</td><td>应用版本</td></tr><tr><td>protocolVersion</td><td>String</td><td>是</td><td>本文建议为 1</td></tr><tr><td>features</td><td>List&lt;String&gt;</td><td>是</td><td>本屏实际支持的协议/功能,不等同于外设在线</td></tr></tbody></table></div><details class="example json" id="example-3"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 03</span><span class="example-meta">JSON · 9 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-3-51" type="button" aria-label="复制 JSON 示例 3">复制</button></div><pre tabindex="0"><code id="code-chapter-3-51">{
<span class="json-key">&quot;serialNumber&quot;</span>: <span class="json-string">&quot;PANEL-DEMO-001&quot;</span>,
<span class="json-key">&quot;installationId&quot;</span>: <span class="json-string">&quot;install-example-01&quot;</span>,
<span class="json-key">&quot;bootId&quot;</span>: <span class="json-string">&quot;boot-example-01&quot;</span>,
<span class="json-key">&quot;model&quot;</span>: <span class="json-string">&quot;RK3568-10INCH&quot;</span>,
<span class="json-key">&quot;appVersion&quot;</span>: <span class="json-string">&quot;1.0.0&quot;</span>,
<span class="json-key">&quot;protocolVersion&quot;</span>: <span class="json-string">&quot;1&quot;</span>,
<span class="json-key">&quot;features&quot;</span>: [<span class="json-string">&quot;MATTER&quot;</span>, <span class="json-string">&quot;ZIGBEE&quot;</span>, <span class="json-string">&quot;BLE_GATT&quot;</span>]
}</code></pre></details><p>响应 data: PanelSession</p><details class="example json" id="example-4"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 04</span><span class="example-meta">JSON · 21 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-3-77" type="button" aria-label="复制 JSON 示例 4">复制</button></div><pre tabindex="0"><code id="code-chapter-3-77">{
<span class="json-key">&quot;code&quot;</span>: <span class="json-number">0</span>,
<span class="json-key">&quot;message&quot;</span>: <span class="json-string">&quot;success&quot;</span>,
<span class="json-key">&quot;data&quot;</span>: {
<span class="json-key">&quot;panelId&quot;</span>: <span class="json-string">&quot;panel-001&quot;</span>,
<span class="json-key">&quot;gatewayDeviceId&quot;</span>: <span class="json-string">&quot;gateway-001&quot;</span>,
<span class="json-key">&quot;homeId&quot;</span>: <span class="json-string">&quot;10001&quot;</span>,
<span class="json-key">&quot;memberId&quot;</span>: <span class="json-string">&quot;20001&quot;</span>,
<span class="json-key">&quot;role&quot;</span>: <span class="json-string">&quot;GATEWAY&quot;</span>,
<span class="json-key">&quot;accessToken&quot;</span>: <span class="json-string">&quot;&lt;panel-access-token&gt;&quot;</span>,
<span class="json-key">&quot;refreshToken&quot;</span>: <span class="json-string">&quot;&lt;panel-refresh-token&gt;&quot;</span>,
<span class="json-key">&quot;tokenType&quot;</span>: <span class="json-string">&quot;Bearer&quot;</span>,
<span class="json-key">&quot;expiresIn&quot;</span>: <span class="json-number">3600</span>,
<span class="json-key">&quot;refreshExpiresIn&quot;</span>: <span class="json-number">2592000</span>,
<span class="json-key">&quot;stateEpoch&quot;</span>: <span class="json-number">12</span>,
<span class="json-key">&quot;heartbeatIntervalSeconds&quot;</span>: <span class="json-number">30</span>,
<span class="json-key">&quot;leaseSeconds&quot;</span>: <span class="json-number">90</span>,
<span class="json-key">&quot;maxWaitSeconds&quot;</span>: <span class="json-number">20</span>,
<span class="json-key">&quot;serverTime&quot;</span>: <span class="json-number">1792123200000</span>
}
}</code></pre></details><p>上述有效期仅为建议示例,客户端按实际响应处理。role 为 GATEWAY 或 DISPLAY;同一家庭可有多个网关,但每台本地设备只有一个 executorPanelId。</p><p>stateEpoch 是平台为当前 bootId 分配的状态世代。同一 bootId 重试建立会话得到同一世代;新的 bootId 建立有效会话后,旧会话不能继续领取新命令或覆盖当前状态。设备执行中的旧记录按第六节处理,不自动交给新实例重做。</p><h3 id="chapter-3-heading-3">3. 刷新会话<a class="anchor" href="#chapter-3-heading-3" aria-label="链接到3. 刷新会话">#</a></h3><div class="endpoint" id="endpoint-2"><span class="method post">POST</span><code>/api/v1/panels/session/refresh</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-2" aria-label="定位 POST /api/v1/panels/session/refresh">#</a></div><p>鉴权:请求体中的 refreshToken;不要求尚未过期的 Access Token。</p><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">字段</th><th scope="col">类型</th><th scope="col">必填</th><th scope="col">说明</th></tr></thead><tbody><tr><td>refreshToken</td><td>String</td><td>是</td><td>当前刷新凭证</td></tr><tr><td>bootId</td><td>String</td><td>是</td><td>必须与当前有效启动实例一致</td></tr></tbody></table></div><details class="example json" id="example-5"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 05</span><span class="example-meta">JSON · 4 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-3-99" type="button" aria-label="复制 JSON 示例 5">复制</button></div><pre tabindex="0"><code id="code-chapter-3-99">{
<span class="json-key">&quot;refreshToken&quot;</span>: <span class="json-string">&quot;&lt;panel-refresh-token&gt;&quot;</span>,
<span class="json-key">&quot;bootId&quot;</span>: <span class="json-string">&quot;boot-example-01&quot;</span>
}</code></pre></details><p>成功 data 返回新的 accessToken、refreshToken、expiresIn、refreshExpiresIn 和 serverTime,保留原 panelId/homeId/stateEpoch。</p><p>建议刷新凭证轮换,并为同一旧凭证与同一 Idempotency-Key 保留 120 秒的加密响应重放窗口。刷新响应丢失时可用原请求取回同一结果;不同内容不得复用该键。窗口外或凭证撤销后返回明确错误,不放宽家庭范围。</p><h3 id="chapter-3-heading-4">4. 屏信息与心跳<a class="anchor" href="#chapter-3-heading-4" aria-label="链接到4. 屏信息与心跳">#</a></h3><div class="endpoint" id="endpoint-3"><span class="method get">GET</span><code>/api/v1/panels/{panelId}</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-3" aria-label="定位 GET /api/v1/panels/{panelId}">#</a></div><p>鉴权:Bearer;panelId 必须为自身。响应含 panelId、gatewayDeviceId、homeId、role、enabled、online、lastSeenAt、features。</p><div class="endpoint" id="endpoint-4"><span class="method post">POST</span><code>/api/v1/panels/{panelId}/heartbeat</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-4" aria-label="定位 POST /api/v1/panels/{panelId}/heartbeat">#</a></div><p>参数:body: PanelHeartbeat</p><details class="example json" id="example-6"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 06</span><span class="example-meta">JSON · 12 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-3-128" type="button" aria-label="复制 JSON 示例 6">复制</button></div><pre tabindex="0"><code id="code-chapter-3-128">{
<span class="json-key">&quot;bootId&quot;</span>: <span class="json-string">&quot;boot-example-01&quot;</span>,
<span class="json-key">&quot;appVersion&quot;</span>: <span class="json-string">&quot;1.0.0&quot;</span>,
<span class="json-key">&quot;networkType&quot;</span>: <span class="json-string">&quot;ETHERNET&quot;</span>,
<span class="json-key">&quot;uptimeSeconds&quot;</span>: <span class="json-number">120</span>,
<span class="json-key">&quot;deviceCounts&quot;</span>: {<span class="json-key">&quot;total&quot;</span>: <span class="json-number">6</span>, <span class="json-key">&quot;online&quot;</span>: <span class="json-number">5</span>},
<span class="json-key">&quot;protocols&quot;</span>: {
<span class="json-key">&quot;matter&quot;</span>: <span class="json-string">&quot;READY&quot;</span>,
<span class="json-key">&quot;zigbee&quot;</span>: <span class="json-string">&quot;NOT_CONNECTED&quot;</span>,
<span class="json-key">&quot;ble&quot;</span>: <span class="json-string">&quot;READY&quot;</span>
}
}</code></pre></details><p>bootId 必填;其余运行字段建议上报。协议状态为 READY、NOT_CONNECTED、ERROR 或 UNSUPPORTED。缺少外设只影响相应协议,不使整个屏启动失败。</p><p>响应 data:</p><details class="example json" id="example-7"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 07</span><span class="example-meta">JSON · 6 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-3-141" type="button" aria-label="复制 JSON 示例 7">复制</button></div><pre tabindex="0"><code id="code-chapter-3-141">{
<span class="json-key">&quot;serverTime&quot;</span>: <span class="json-number">1792123230000</span>,
<span class="json-key">&quot;leaseSeconds&quot;</span>: <span class="json-number">90</span>,
<span class="json-key">&quot;configurationRevision&quot;</span>: <span class="json-number">4</span>,
<span class="json-key">&quot;homeRevision&quot;</span>: <span class="json-number">28</span>
}</code></pre></details><p>租期从平台收到有效心跳时计算。领取命令或其他 API 调用不隐式替代心跳。屏离线后,平台拒绝新的远程本地设备控制;本地离线执行不以云端租期为前提。</p></article><div class="chapter-pagination"><a href="#chapter-2"><small>上一章</small><span>← 通用约定</span></a><a class="next" href="#chapter-4"><small>下一章</small><span>家庭快照与事件同步 →</span></a></div></section>
<section class="doc-page" id="chapter-4" data-title="家庭快照与事件同步" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>家庭快照与事件同步</h1><p class="page-description">家庭快照、分页与增量事件</p></div><nav class="on-this-page" aria-label="本页目录"><a href="#chapter-4-heading-1">1. 既有家庭、房间接口</a><a href="#chapter-4-heading-2">2. 初始化快照</a><a href="#chapter-4-heading-3">3. 增量事件</a></nav><article class="prose"><h3 id="chapter-4-heading-1">1. 既有家庭、房间接口<a class="anchor" href="#chapter-4-heading-1" aria-label="链接到1. 既有家庭、房间接口">#</a></h3><div class="endpoint" id="endpoint-5"><span class="method get">GET</span><code>/api/v1/homes</code><a class="endpoint-anchor" href="#endpoint-5" aria-label="定位 GET /api/v1/homes">#</a></div><div class="endpoint" id="endpoint-6"><span class="method get">GET</span><code>/api/v1/homes/{homeId}/rooms</code><a class="endpoint-anchor" href="#endpoint-6" aria-label="定位 GET /api/v1/homes/{homeId}/rooms">#</a></div><p>标记:扩展建议,仅补充屏身份访问规则。</p><p>屏只能读取会话绑定的家庭。既有 memberId 等参数即使保留,也必须与屏身份关联校验,不能作为越权选择器。改名、房间编辑等沿用现有接口并受相应管理权限约束。</p><h3 id="chapter-4-heading-2">2. 初始化快照<a class="anchor" href="#chapter-4-heading-2" aria-label="链接到2. 初始化快照">#</a></h3><div class="endpoint" id="endpoint-7"><span class="method get">GET</span><code>/api/v1/panels/{panelId}/snapshot</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-7" aria-label="定位 GET /api/v1/panels/{panelId}/snapshot">#</a></div><p>参数:query: pageToken(首请求省略)。</p><p>响应 data: PanelSnapshotPage</p><details class="example json" id="example-8"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 08</span><span class="example-meta">JSON · 37 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-4-57" type="button" aria-label="复制 JSON 示例 8">复制</button></div><pre tabindex="0"><code id="code-chapter-4-57">{
<span class="json-key">&quot;snapshotId&quot;</span>: <span class="json-string">&quot;snapshot-001&quot;</span>,
<span class="json-key">&quot;homeId&quot;</span>: <span class="json-string">&quot;10001&quot;</span>,
<span class="json-key">&quot;homeName&quot;</span>: <span class="json-string">&quot;示例家庭&quot;</span>,
<span class="json-key">&quot;homeRevision&quot;</span>: <span class="json-number">28</span>,
<span class="json-key">&quot;cursor&quot;</span>: <span class="json-string">&quot;cursor-snapshot-001&quot;</span>,
<span class="json-key">&quot;serverTime&quot;</span>: <span class="json-number">1792123200000</span>,
<span class="json-key">&quot;expiresIn&quot;</span>: <span class="json-number">120</span>,
<span class="json-key">&quot;rooms&quot;</span>: [
{<span class="json-key">&quot;id&quot;</span>: <span class="json-string">&quot;30001&quot;</span>, <span class="json-key">&quot;name&quot;</span>: <span class="json-string">&quot;客厅&quot;</span>, <span class="json-key">&quot;sortOrder&quot;</span>: <span class="json-number">0</span>}
],
<span class="json-key">&quot;devices&quot;</span>: [
{
<span class="json-key">&quot;id&quot;</span>: <span class="json-string">&quot;40001&quot;</span>,
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;device-001&quot;</span>,
<span class="json-key">&quot;deviceName&quot;</span>: <span class="json-string">&quot;客厅灯&quot;</span>,
<span class="json-key">&quot;homeId&quot;</span>: <span class="json-string">&quot;10001&quot;</span>,
<span class="json-key">&quot;roomId&quot;</span>: <span class="json-string">&quot;30001&quot;</span>,
<span class="json-key">&quot;productId&quot;</span>: <span class="json-string">&quot;50001&quot;</span>,
<span class="json-key">&quot;platform&quot;</span>: <span class="json-string">&quot;local&quot;</span>,
<span class="json-key">&quot;deviceType&quot;</span>: <span class="json-string">&quot;light&quot;</span>,
<span class="json-key">&quot;executionSource&quot;</span>: <span class="json-string">&quot;LOCAL_PANEL&quot;</span>,
<span class="json-key">&quot;executorPanelId&quot;</span>: <span class="json-string">&quot;panel-001&quot;</span>,
<span class="json-key">&quot;routeRevision&quot;</span>: <span class="json-number">3</span>,
<span class="json-key">&quot;schemaRevision&quot;</span>: <span class="json-number">2</span>,
<span class="json-key">&quot;online&quot;</span>: <span class="json-literal">true</span>,
<span class="json-key">&quot;stateFresh&quot;</span>: <span class="json-literal">true</span>,
<span class="json-key">&quot;properties&quot;</span>: {<span class="json-key">&quot;switch_1&quot;</span>: <span class="json-literal">true</span>},
<span class="json-key">&quot;stateVersion&quot;</span>: <span class="json-number">80</span>,
<span class="json-key">&quot;reportedAt&quot;</span>: <span class="json-number">1792123195000</span>,
<span class="json-key">&quot;receivedAt&quot;</span>: <span class="json-number">1792123195100</span>,
<span class="json-key">&quot;quality&quot;</span>: <span class="json-string">&quot;GOOD&quot;</span>
}
],
<span class="json-key">&quot;scenes&quot;</span>: [],
<span class="json-key">&quot;nextPageToken&quot;</span>: <span class="json-literal">null</span>
}</code></pre></details><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">字段</th><th scope="col">类型</th><th scope="col">说明</th></tr></thead><tbody><tr><td>snapshotId</td><td>String</td><td>本轮一致性快照标识</td></tr><tr><td>homeRevision</td><td>Long</td><td>家庭目录与配置版本,不作为设备状态序号</td></tr><tr><td>cursor</td><td>String</td><td>快照对应事件位置;后续只应用该位置之后的事件</td></tr><tr><td>rooms/devices/scenes</td><td>List</td><td>本页包含的对象,可能为空</td></tr><tr><td>nextPageToken</td><td>String/null</td><td>后续页;null 表示已结束</td></tr></tbody></table></div><p>平台需在同一逻辑版本上构造所有分页,并使 cursor 与该快照一致,避免分页期间丢失变化。建议单快照缓存 120 秒、每页最多 200 个对象。屏全部拉取成功后原子替换对应家庭的云缓存;失败时保留上一份可用数据。分页过期返回 SNAPSHOT_EXPIRED,重新开始。</p><p>删除平台缓存不自动清除屏的本地配网密钥。完整快照里没出现的平台设备可以退出云目录;本地物理解绑仍需明确动作。</p><h3 id="chapter-4-heading-3">3. 增量事件<a class="anchor" href="#chapter-4-heading-3" aria-label="链接到3. 增量事件">#</a></h3><div class="endpoint" id="endpoint-8"><span class="method get">GET</span><code>/api/v1/panels/{panelId}/events</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-8" aria-label="定位 GET /api/v1/panels/{panelId}/events">#</a></div><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">查询参数</th><th scope="col">类型</th><th scope="col">必填</th><th scope="col">说明</th></tr></thead><tbody><tr><td>after</td><td>String</td><td>是</td><td>上次已持久化的游标,首次用快照 cursor</td></tr><tr><td>waitSeconds</td><td>Integer</td><td>否</td><td>默认 20,范围 0–20</td></tr><tr><td>limit</td><td>Integer</td><td>否</td><td>默认 100,上限 200</td></tr></tbody></table></div><details class="example json" id="example-9"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 09</span><span class="example-meta">JSON · 28 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-4-110" type="button" aria-label="复制 JSON 示例 9">复制</button></div><pre tabindex="0"><code id="code-chapter-4-110">{
<span class="json-key">&quot;code&quot;</span>: <span class="json-number">0</span>,
<span class="json-key">&quot;message&quot;</span>: <span class="json-string">&quot;success&quot;</span>,
<span class="json-key">&quot;data&quot;</span>: {
<span class="json-key">&quot;events&quot;</span>: [
{
<span class="json-key">&quot;eventId&quot;</span>: <span class="json-string">&quot;event-001&quot;</span>,
<span class="json-key">&quot;type&quot;</span>: <span class="json-string">&quot;DEVICE_STATE&quot;</span>,
<span class="json-key">&quot;homeId&quot;</span>: <span class="json-string">&quot;10001&quot;</span>,
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;device-001&quot;</span>,
<span class="json-key">&quot;createdAt&quot;</span>: <span class="json-number">1792123230000</span>,
<span class="json-key">&quot;originPanelId&quot;</span>: <span class="json-string">&quot;panel-001&quot;</span>,
<span class="json-key">&quot;reportId&quot;</span>: <span class="json-string">&quot;report-001&quot;</span>,
<span class="json-key">&quot;stateVersion&quot;</span>: <span class="json-number">81</span>,
<span class="json-key">&quot;payload&quot;</span>: {
<span class="json-key">&quot;online&quot;</span>: <span class="json-literal">true</span>,
<span class="json-key">&quot;properties&quot;</span>: {<span class="json-key">&quot;switch_1&quot;</span>: <span class="json-literal">false</span>},
<span class="json-key">&quot;reportedAt&quot;</span>: <span class="json-number">1792123229500</span>,
<span class="json-key">&quot;receivedAt&quot;</span>: <span class="json-number">1792123230000</span>,
<span class="json-key">&quot;quality&quot;</span>: <span class="json-string">&quot;GOOD&quot;</span>
}
}
],
<span class="json-key">&quot;nextCursor&quot;</span>: <span class="json-string">&quot;cursor-event-001&quot;</span>,
<span class="json-key">&quot;hasMore&quot;</span>: <span class="json-literal">false</span>,
<span class="json-key">&quot;serverTime&quot;</span>: <span class="json-number">1792123230000</span>
}
}</code></pre></details><p>事件类型建议包括 ROOM_UPSERT、ROOM_DELETED、DEVICE_UPSERT、DEVICE_DELETED、DEVICE_STATE、SCENE_UPSERT、SCENE_DELETED、COMMAND_UPDATED、CONFIGURATION_CHANGED、OTA_TASK_AVAILABLE、HOME_ACCESS_REVOKED。</p><p>UPSERT 携带更新后的完整公开对象;DELETED 携带对应 ID 和 revision;STATE 按第七节合并;COMMAND_UPDATED 携带 CommandResult;配置与 OTA 事件只提示重新读取相应接口。权限撤销事件可尽力通知,后续请求仍须独立拒绝,不能依赖客户端收到事件才生效。</p><p>平台按家庭顺序产生事件,屏落库和推进游标在同一事务中完成;重复 eventId 不重复应用。建议游标保留至少 24 小时,过期返回 HTTP 410 / CURSOR_EXPIRED,屏重新拉完整快照。没有事件时 events=[],nextCursor 保持已确认位置。hasMore=true 时立即继续获取,不再等待。</p></article><div class="chapter-pagination"><a href="#chapter-3"><small>上一章</small><span>← 中控身份与会话</span></a><a class="next" href="#chapter-5"><small>下一章</small><span>设备登记与标准控制模型 →</span></a></div></section>
<section class="doc-page" id="chapter-5" data-title="设备登记与标准控制模型" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>设备登记与标准控制模型</h1><p class="page-description">设备登记、能力更新与控制模型</p></div><nav class="on-this-page" aria-label="本页目录"><a href="#chapter-5-heading-1">1. 本地设备登记</a><a href="#chapter-5-heading-2">2. 控制模型</a></nav><article class="prose"><h3 id="chapter-5-heading-1">1. 本地设备登记<a class="anchor" href="#chapter-5-heading-1" aria-label="链接到1. 本地设备登记">#</a></h3><div class="endpoint" id="endpoint-9"><span class="method post">POST</span><code>/api/v1/panels/{panelId}/devices/register</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-9" aria-label="定位 POST /api/v1/panels/{panelId}/devices/register">#</a></div><p>鉴权:本屏 gateway.execute 及被授予的登记权限。只登记实际归本屏执行的设备。</p><p>参数:body: LocalDeviceRegistration</p><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">字段</th><th scope="col">类型</th><th scope="col">必填</th><th scope="col">说明</th></tr></thead><tbody><tr><td>localDeviceId</td><td>String</td><td>是</td><td>屏内稳定设备 ID</td></tr><tr><td>deviceName</td><td>String</td><td>是</td><td>名称</td></tr><tr><td>protocol</td><td>String</td><td>是</td><td>MATTER / ZIGBEE / BLE_GATT / BLUETOOTH_MESH</td></tr><tr><td>deviceType</td><td>String</td><td>是</td><td>平台约定的设备分类</td></tr><tr><td>roomId</td><td>String</td><td>否</td><td>当前家庭内房间</td></tr><tr><td>manufacturer/model</td><td>String</td><td>否</td><td>型号资料</td></tr><tr><td>productId</td><td>String</td><td>否</td><td>已有平台产品;必须属于允许的产品范围</td></tr><tr><td>schema</td><td>Object</td><td>是</td><td>包含 properties/actions;登记时不指定平台 schemaRevision,不上报推测能力</td></tr></tbody></table></div><details class="example json" id="example-10"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 10</span><span class="example-meta">JSON · 23 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-5-45" type="button" aria-label="复制 JSON 示例 10">复制</button></div><pre tabindex="0"><code id="code-chapter-5-45">{
<span class="json-key">&quot;localDeviceId&quot;</span>: <span class="json-string">&quot;local-device-001&quot;</span>,
<span class="json-key">&quot;deviceName&quot;</span>: <span class="json-string">&quot;客厅灯&quot;</span>,
<span class="json-key">&quot;protocol&quot;</span>: <span class="json-string">&quot;MATTER&quot;</span>,
<span class="json-key">&quot;deviceType&quot;</span>: <span class="json-string">&quot;light&quot;</span>,
<span class="json-key">&quot;roomId&quot;</span>: <span class="json-string">&quot;30001&quot;</span>,
<span class="json-key">&quot;manufacturer&quot;</span>: <span class="json-string">&quot;Example&quot;</span>,
<span class="json-key">&quot;model&quot;</span>: <span class="json-string">&quot;LIGHT-A&quot;</span>,
<span class="json-key">&quot;schema&quot;</span>: {
<span class="json-key">&quot;properties&quot;</span>: [
{
<span class="json-key">&quot;code&quot;</span>: <span class="json-string">&quot;switch_1&quot;</span>,
<span class="json-key">&quot;name&quot;</span>: <span class="json-string">&quot;开关&quot;</span>,
<span class="json-key">&quot;type&quot;</span>: <span class="json-string">&quot;bool&quot;</span>,
<span class="json-key">&quot;access&quot;</span>: [<span class="json-string">&quot;read&quot;</span>, <span class="json-string">&quot;write&quot;</span>, <span class="json-string">&quot;notify&quot;</span>],
<span class="json-key">&quot;semantic&quot;</span>: <span class="json-string">&quot;ON_OFF&quot;</span>,
<span class="json-key">&quot;channel&quot;</span>: <span class="json-string">&quot;1&quot;</span>,
<span class="json-key">&quot;completionMode&quot;</span>: <span class="json-string">&quot;STATE&quot;</span>
}
],
<span class="json-key">&quot;actions&quot;</span>: []
}
}</code></pre></details><p>响应 data:</p><details class="example json" id="example-11"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 11</span><span class="example-meta">JSON · 11 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-5-61" type="button" aria-label="复制 JSON 示例 11">复制</button></div><pre tabindex="0"><code id="code-chapter-5-61">{
<span class="json-key">&quot;id&quot;</span>: <span class="json-string">&quot;40001&quot;</span>,
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;device-001&quot;</span>,
<span class="json-key">&quot;productId&quot;</span>: <span class="json-string">&quot;50001&quot;</span>,
<span class="json-key">&quot;homeId&quot;</span>: <span class="json-string">&quot;10001&quot;</span>,
<span class="json-key">&quot;parentDeviceId&quot;</span>: <span class="json-string">&quot;gateway-001&quot;</span>,
<span class="json-key">&quot;executorPanelId&quot;</span>: <span class="json-string">&quot;panel-001&quot;</span>,
<span class="json-key">&quot;routeRevision&quot;</span>: <span class="json-number">3</span>,
<span class="json-key">&quot;schemaRevision&quot;</span>: <span class="json-number">2</span>,
<span class="json-key">&quot;duplicate&quot;</span>: <span class="json-literal">false</span>
}</code></pre></details><p>平台以 panelId + localDeviceId 识别同一登记,不重复创建设备。重复登记相同资料返回同一映射;已登记后名称/房间等资料通过现有设备更新接口处理,schema 通过下方能力更新接口处理,不能借重新登记覆盖正在使用的物模型。没有 productId 时平台按已配置产品模板或确认后的 schema 建立对应产品;无法匹配则返回明确错误,不把空能力设备标成可控。</p><p>平台维护设备 id、deviceId、父网关和执行归属,屏不传本地网络密钥、Matter Fabric 密钥或用户口令。端点、属性码和本地协议地址的映射持久化在屏内。</p><div class="endpoint" id="endpoint-10"><span class="method put">PUT</span><code>/api/v1/panels/{panelId}/devices/{deviceId}/schema</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-10" aria-label="定位 PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema">#</a></div><p>请求体含 expectedSchemaRevision、routeRevision、properties、actions;properties/actions 使用下方定义。平台核对执行归属及期望版本,原子保存并返回新的 schemaRevision。同一幂等请求返回同一版本,版本不符返回 SCHEMA_CHANGED。</p><p>更新影响尚未开始的旧模型命令时,旧命令在执行前失败并给出 SCHEMA_CHANGED;已经开始的命令按原模型快照保留执行证据。旧模型状态不得覆盖语义已变化的新属性。</p><h3 id="chapter-5-heading-2">2. 控制模型<a class="anchor" href="#chapter-5-heading-2" aria-label="链接到2. 控制模型">#</a></h3><div class="endpoint" id="endpoint-11"><span class="method get">GET</span><code>/api/v1/devices/{deviceId}/control-schema</code><span class="badge extend">扩展建议</span><a class="endpoint-anchor" href="#endpoint-11" aria-label="定位 GET /api/v1/devices/{deviceId}/control-schema">#</a></div><p>路径沿用;以下 data 结构为补充建议,需要与平台实际模型核对。</p><details class="example json" id="example-12"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 12</span><span class="example-meta">JSON · 31 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-5-111" type="button" aria-label="复制 JSON 示例 12">复制</button></div><pre tabindex="0"><code id="code-chapter-5-111">{
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;device-001&quot;</span>,
<span class="json-key">&quot;productId&quot;</span>: <span class="json-string">&quot;50001&quot;</span>,
<span class="json-key">&quot;schemaRevision&quot;</span>: <span class="json-number">2</span>,
<span class="json-key">&quot;properties&quot;</span>: [
{
<span class="json-key">&quot;code&quot;</span>: <span class="json-string">&quot;switch_1&quot;</span>,
<span class="json-key">&quot;dpId&quot;</span>: <span class="json-string">&quot;1&quot;</span>,
<span class="json-key">&quot;name&quot;</span>: <span class="json-string">&quot;开关&quot;</span>,
<span class="json-key">&quot;type&quot;</span>: <span class="json-string">&quot;bool&quot;</span>,
<span class="json-key">&quot;access&quot;</span>: [<span class="json-string">&quot;read&quot;</span>, <span class="json-string">&quot;write&quot;</span>, <span class="json-string">&quot;notify&quot;</span>],
<span class="json-key">&quot;semantic&quot;</span>: <span class="json-string">&quot;ON_OFF&quot;</span>,
<span class="json-key">&quot;channel&quot;</span>: <span class="json-string">&quot;1&quot;</span>,
<span class="json-key">&quot;completionMode&quot;</span>: <span class="json-string">&quot;STATE&quot;</span>
},
{
<span class="json-key">&quot;code&quot;</span>: <span class="json-string">&quot;brightness&quot;</span>,
<span class="json-key">&quot;name&quot;</span>: <span class="json-string">&quot;亮度&quot;</span>,
<span class="json-key">&quot;type&quot;</span>: <span class="json-string">&quot;int&quot;</span>,
<span class="json-key">&quot;access&quot;</span>: [<span class="json-string">&quot;read&quot;</span>, <span class="json-string">&quot;write&quot;</span>, <span class="json-string">&quot;notify&quot;</span>],
<span class="json-key">&quot;semantic&quot;</span>: <span class="json-string">&quot;BRIGHTNESS&quot;</span>,
<span class="json-key">&quot;channel&quot;</span>: <span class="json-string">&quot;1&quot;</span>,
<span class="json-key">&quot;min&quot;</span>: <span class="json-number">0</span>,
<span class="json-key">&quot;max&quot;</span>: <span class="json-number">100</span>,
<span class="json-key">&quot;step&quot;</span>: <span class="json-number">1</span>,
<span class="json-key">&quot;unit&quot;</span>: <span class="json-string">&quot;%&quot;</span>,
<span class="json-key">&quot;completionMode&quot;</span>: <span class="json-string">&quot;STATE&quot;</span>
}
],
<span class="json-key">&quot;actions&quot;</span>: []
}</code></pre></details><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">字段</th><th scope="col">类型</th><th scope="col">说明</th></tr></thead><tbody><tr><td>code</td><td>String</td><td>设备内唯一业务属性码,命令使用该值</td></tr><tr><td>dpId</td><td>String</td><td>可选,平台原始功能点标识</td></tr><tr><td>type</td><td>String</td><td>bool / int / float / enum / string / json</td></tr><tr><td>access</td><td>List&lt;String&gt;</td><td>read、write、notify;产品旧模型若是字符串,由平台明确转换</td></tr><tr><td>semantic</td><td>String</td><td>可选的标准能力语义,如 ON_OFF、BRIGHTNESS</td></tr><tr><td>channel</td><td>String</td><td>稳定功能通道,不自动等于 Matter endpoint</td></tr><tr><td>min/max/step/unit</td><td>对应数值/String</td><td>数值型属性的约束,统一为对外业务单位</td></tr><tr><td>enumValues</td><td>List</td><td>枚举项,每项含 value 和 label</td></tr><tr><td>completionMode</td><td>String</td><td>STATE:新状态确认;ACK_ONLY:仅能确认协议接受</td></tr><tr><td>actions</td><td>List&lt;ActionSpec&gt;</td><td>无持久目标值的动作,例如停止;含 code、name、parameters、completionMode</td></tr></tbody></table></div><p>ActionSpec.parameters 按参数名列出 type、required 和适用的 min/max/step/enumValues;无参数动作使用空对象。请求中的参数必须符合该定义,不能让任意私有 JSON 绕过能力约束。</p><p>control-schema 的数值已经是业务值,scale=1;若物模型原始值存在倍率、偏移或反向含义,由平台云驱动或屏本地驱动转换,不能在两端重复转换。dpId 只是映射信息,不保证与 code 数值或格式一致。</p><p>请求必须携带所依据的 schemaRevision。版本变化后,旧参数不得照常发送,返回 SCHEMA_CHANGED 并重新读取。不支持的属性明确显示不可控,不猜测用途。</p></article><div class="chapter-pagination"><a href="#chapter-4"><small>上一章</small><span>← 家庭快照与事件同步</span></a><a class="next" href="#chapter-6"><small>下一章</small><span>控制命令与执行回执 →</span></a></div></section>
<section class="doc-page" id="chapter-6" data-title="控制命令与执行回执" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>控制命令与执行回执</h1><p class="page-description">提交、领取、执行许可和结果回报</p></div><nav class="on-this-page" aria-label="本页目录"><a href="#chapter-6-heading-1">1. 提交控制</a><a href="#chapter-6-heading-2">2. 查询控制结果</a><a href="#chapter-6-heading-3">3. 领取命令</a><a href="#chapter-6-heading-4">4. 申请执行</a><a href="#chapter-6-heading-5">5. 报告执行结果</a><a href="#chapter-6-heading-6">6. 屏上本地点击与回声处理</a></nav><article class="prose"><h3 id="chapter-6-heading-1">1. 提交控制<a class="anchor" href="#chapter-6-heading-1" aria-label="链接到1. 提交控制">#</a></h3><div class="endpoint" id="endpoint-12"><span class="method post">POST</span><code>/api/v1/devices/{deviceId}/commands</code><span class="badge extend">扩展建议</span><a class="endpoint-anchor" href="#endpoint-12" aria-label="定位 POST /api/v1/devices/{deviceId}/commands">#</a></div><p>鉴权:屏或会员身份,只能控制授权设备。路径沿用现有接口,补充以下约定。</p><p>参数:body: DeviceCommand</p><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">字段</th><th scope="col">类型</th><th scope="col">必填</th><th scope="col">说明</th></tr></thead><tbody><tr><td>commandType</td><td>String</td><td>是</td><td>set:设置属性;action:执行动作</td></tr><tr><td>params</td><td>Object</td><td>是</td><td>set 为属性 code 到值的映射;action 格式见下</td></tr><tr><td>timeout</td><td>Long</td><td>否</td><td>总有效时间毫秒,建议默认 30000,允许 1000–120000</td></tr><tr><td>schemaRevision</td><td>Long</td><td>是,扩展建议</td><td>客户端依据的控制模型版本</td></tr></tbody></table></div><details class="example json" id="example-13"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 13</span><span class="example-meta">JSON · 6 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-24" type="button" aria-label="复制 JSON 示例 13">复制</button></div><pre tabindex="0"><code id="code-chapter-6-24">{
<span class="json-key">&quot;commandType&quot;</span>: <span class="json-string">&quot;set&quot;</span>,
<span class="json-key">&quot;params&quot;</span>: {<span class="json-key">&quot;switch_1&quot;</span>: <span class="json-literal">false</span>},
<span class="json-key">&quot;timeout&quot;</span>: <span class="json-number">30000</span>,
<span class="json-key">&quot;schemaRevision&quot;</span>: <span class="json-number">2</span>
}</code></pre></details><p>action 请求示意:</p><details class="example json" id="example-14"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 14</span><span class="example-meta">JSON · 6 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-35" type="button" aria-label="复制 JSON 示例 14">复制</button></div><pre tabindex="0"><code id="code-chapter-6-35">{
<span class="json-key">&quot;commandType&quot;</span>: <span class="json-string">&quot;action&quot;</span>,
<span class="json-key">&quot;params&quot;</span>: {<span class="json-key">&quot;actionCode&quot;</span>: <span class="json-string">&quot;stop&quot;</span>, <span class="json-key">&quot;arguments&quot;</span>: {}},
<span class="json-key">&quot;timeout&quot;</span>: <span class="json-number">10000</span>,
<span class="json-key">&quot;schemaRevision&quot;</span>: <span class="json-number">2</span>
}</code></pre></details><p>动作必须在 schema.actions 中声明,示例 stop 不意味着所有设备都支持。</p><p>平台先原子记录幂等请求和命令,再调度执行;不得等待物理动作结束才生成 commandId。接受请求后返回 HTTP 202,data: CommandResult。</p><details class="example json" id="example-15"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 15</span><span class="example-meta">JSON · 17 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-59" type="button" aria-label="复制 JSON 示例 15">复制</button></div><pre tabindex="0"><code id="code-chapter-6-59">{
<span class="json-key">&quot;code&quot;</span>: <span class="json-number">0</span>,
<span class="json-key">&quot;message&quot;</span>: <span class="json-string">&quot;success&quot;</span>,
<span class="json-key">&quot;data&quot;</span>: {
<span class="json-key">&quot;commandId&quot;</span>: <span class="json-string">&quot;command-001&quot;</span>,
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;device-001&quot;</span>,
<span class="json-key">&quot;state&quot;</span>: <span class="json-string">&quot;QUEUED&quot;</span>,
<span class="json-key">&quot;terminal&quot;</span>: <span class="json-literal">false</span>,
<span class="json-key">&quot;executionSource&quot;</span>: <span class="json-string">&quot;LOCAL_PANEL&quot;</span>,
<span class="json-key">&quot;executorPanelId&quot;</span>: <span class="json-string">&quot;panel-001&quot;</span>,
<span class="json-key">&quot;routeRevision&quot;</span>: <span class="json-number">3</span>,
<span class="json-key">&quot;createdAt&quot;</span>: <span class="json-number">1792123200000</span>,
<span class="json-key">&quot;expiresAt&quot;</span>: <span class="json-number">1792123230000</span>,
<span class="json-key">&quot;completionMode&quot;</span>: <span class="json-string">&quot;STATE&quot;</span>,
<span class="json-key">&quot;result&quot;</span>: <span class="json-literal">null</span>
}
}</code></pre></details><p>executionSource 为 CLOUD 或 LOCAL_PANEL。CLOUD 由平台既有云驱动执行;LOCAL_PANEL 只进入指定屏的队列,不能同时调用云驱动。</p><p>提交时若本地执行屏已离线或设备已明确不可控,返回 DEVICE_OFFLINE,不建立等待日后联网自动执行的队列。在线检查后发生断网的已有命令按各自有效期处理。</p><p>同一 set 请求包含多个属性时,默认不保证物理原子性。平台先校验全部属性,然后逐项记录结果;部分完成返回 PARTIAL_FAILURE 及每项结果,不显示整体成功。涉及严格原子动作时应由设备原生服务另行声明。</p><h3 id="chapter-6-heading-2">2. 查询控制结果<a class="anchor" href="#chapter-6-heading-2" aria-label="链接到2. 查询控制结果">#</a></h3><div class="endpoint" id="endpoint-13"><span class="method get">GET</span><code>/api/v1/device-commands/{commandId}</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-13" aria-label="定位 GET /api/v1/device-commands/{commandId}">#</a></div><p>返回 CommandResult,只允许本命令关联家庭的合法调用者读取。</p><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">state</th><th scope="col">含义</th><th scope="col">terminal</th></tr></thead><tbody><tr><td>QUEUED</td><td>平台已建任务,尚未投递</td><td>false</td></tr><tr><td>DELIVERED</td><td>已投递,尚未获执行许可</td><td>false</td></tr><tr><td>EXECUTING</td><td>已授予执行权,可能即将或已经发送给设备</td><td>false</td></tr><tr><td>ACCEPTED</td><td>设备协议接受;未证明物理目标完成</td><td>STATE 时 false;ACK_ONLY 时 true</td></tr><tr><td>CONFIRMED</td><td>已取得满足目标的新状态或等价执行证据</td><td>true</td></tr><tr><td>PARTIAL_FAILURE</td><td>多属性/动作部分完成,部分失败或未知</td><td>true</td></tr><tr><td>FAILED</td><td>有明确拒绝或执行失败证据</td><td>true</td></tr><tr><td>EXPIRED</td><td>过期且确认没有进入 EXECUTING</td><td>true</td></tr><tr><td>CANCELLED</td><td>执行前撤销或归属失效,确认尚未开始</td><td>true</td></tr><tr><td>UNKNOWN</td><td>已进入执行阶段,但最终结果无法确定</td><td>true</td></tr></tbody></table></div><p>QUEUED/DELIVERED 超时后为 EXPIRED;EXECUTING 或等待目标回报超时后为 UNKNOWN,不宣称设备没有动作。终态不再自动重放。迟到状态仍可更新设备状态,并追加 lateEvidence,但不把过期命令重新排队。</p><h3 id="chapter-6-heading-3">3. 领取命令<a class="anchor" href="#chapter-6-heading-3" aria-label="链接到3. 领取命令">#</a></h3><div class="endpoint" id="endpoint-14"><span class="method post">POST</span><code>/api/v1/panels/{panelId}/commands/pull</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-14" aria-label="定位 POST /api/v1/panels/{panelId}/commands/pull">#</a></div><p>参数:body: CommandPullRequest</p><details class="example json" id="example-16"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 16</span><span class="example-meta">JSON · 5 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-100" type="button" aria-label="复制 JSON 示例 16">复制</button></div><pre tabindex="0"><code id="code-chapter-6-100">{
<span class="json-key">&quot;bootId&quot;</span>: <span class="json-string">&quot;boot-example-01&quot;</span>,
<span class="json-key">&quot;waitSeconds&quot;</span>: <span class="json-number">20</span>,
<span class="json-key">&quot;maxItems&quot;</span>: <span class="json-number">10</span>
}</code></pre></details><p>bootId 必填;maxItems 建议 1–20。每次新的领取使用新 Idempotency-Key;同一次请求网络重试使用原键。平台保留领取结果,不因响应丢失再次创建任务。</p><p>响应 data:</p><details class="example json" id="example-17"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 17</span><span class="example-meta">JSON · 18 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-125" type="button" aria-label="复制 JSON 示例 17">复制</button></div><pre tabindex="0"><code id="code-chapter-6-125">{
<span class="json-key">&quot;commands&quot;</span>: [
{
<span class="json-key">&quot;commandId&quot;</span>: <span class="json-string">&quot;command-001&quot;</span>,
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;device-001&quot;</span>,
<span class="json-key">&quot;localDeviceId&quot;</span>: <span class="json-string">&quot;local-device-001&quot;</span>,
<span class="json-key">&quot;commandType&quot;</span>: <span class="json-string">&quot;set&quot;</span>,
<span class="json-key">&quot;params&quot;</span>: {<span class="json-key">&quot;switch_1&quot;</span>: <span class="json-literal">false</span>},
<span class="json-key">&quot;schemaRevision&quot;</span>: <span class="json-number">2</span>,
<span class="json-key">&quot;routeRevision&quot;</span>: <span class="json-number">3</span>,
<span class="json-key">&quot;deliveryToken&quot;</span>: <span class="json-string">&quot;&lt;delivery-token&gt;&quot;</span>,
<span class="json-key">&quot;deliveryExpiresAt&quot;</span>: <span class="json-number">1792123210000</span>,
<span class="json-key">&quot;expiresAt&quot;</span>: <span class="json-number">1792123230000</span>,
<span class="json-key">&quot;completionMode&quot;</span>: <span class="json-string">&quot;STATE&quot;</span>
}
],
<span class="json-key">&quot;serverTime&quot;</span>: <span class="json-number">1792123200000</span>
}</code></pre></details><p>只投递当前归本屏负责且有效的命令。建议投递租期 10 秒,且不能晚于命令 expiresAt。屏未申请执行、投递租期已过时,可重新投递并更换 deliveryToken;旧 token 立即失效。已进入 EXECUTING 的命令不能重新投递给其他实例。</p><h3 id="chapter-6-heading-4">4. 申请执行<a class="anchor" href="#chapter-6-heading-4" aria-label="链接到4. 申请执行">#</a></h3><div class="endpoint" id="endpoint-15"><span class="method post">POST</span><code>/api/v1/panels/{panelId}/commands/{commandId}/start</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-15" aria-label="定位 POST /api/v1/panels/{panelId}/commands/{commandId}/start">#</a></div><details class="example json" id="example-18"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 18</span><span class="example-meta">JSON · 5 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-139" type="button" aria-label="复制 JSON 示例 18">复制</button></div><pre tabindex="0"><code id="code-chapter-6-139">{
<span class="json-key">&quot;bootId&quot;</span>: <span class="json-string">&quot;boot-example-01&quot;</span>,
<span class="json-key">&quot;deliveryToken&quot;</span>: <span class="json-string">&quot;&lt;delivery-token&gt;&quot;</span>,
<span class="json-key">&quot;routeRevision&quot;</span>: <span class="json-number">3</span>
}</code></pre></details><p>平台原子检查命令状态、有效期、最新执行归属、调用权限和 deliveryToken,成功转为 EXECUTING,返回:</p><details class="example json" id="example-19"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 19</span><span class="example-meta">JSON · 6 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-150" type="button" aria-label="复制 JSON 示例 19">复制</button></div><pre tabindex="0"><code id="code-chapter-6-150">{
<span class="json-key">&quot;executionToken&quot;</span>: <span class="json-string">&quot;&lt;execution-token&gt;&quot;</span>,
<span class="json-key">&quot;state&quot;</span>: <span class="json-string">&quot;EXECUTING&quot;</span>,
<span class="json-key">&quot;expiresAt&quot;</span>: <span class="json-number">1792123230000</span>,
<span class="json-key">&quot;serverTime&quot;</span>: <span class="json-number">1792123201000</span>
}</code></pre></details><p>屏收到有效执行许可后才能向设备发送。申请响应丢失时,以同一 Idempotency-Key 重试取得同一许可,平台仍检查当前身份、归属及有效期;屏重新核对截止时间,不能因响应超时自行执行。</p><p>在发送物理指令前,屏必须以 commandId 唯一约束原子占用本地执行记录,并先持久化 ATTEMPTING 标记;并发回调不能各执行一次。若进程在发送附近中断,重启后不再重发该动作,查询设备状态并报告 UNKNOWN 或可核实证据。分布式协议不能保证跨设备断电的物理“恰好一次”;本约定优先避免重复动作,并保留无法确定的结果。</p><h3 id="chapter-6-heading-5">5. 报告执行结果<a class="anchor" href="#chapter-6-heading-5" aria-label="链接到5. 报告执行结果">#</a></h3><div class="endpoint" id="endpoint-16"><span class="method post">POST</span><code>/api/v1/panels/{panelId}/commands/{commandId}/report</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-16" aria-label="定位 POST /api/v1/panels/{panelId}/commands/{commandId}/report">#</a></div><p>参数:body: CommandReport</p><details class="example json" id="example-20"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 20</span><span class="example-meta">JSON · 15 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-178" type="button" aria-label="复制 JSON 示例 20">复制</button></div><pre tabindex="0"><code id="code-chapter-6-178">{
<span class="json-key">&quot;executionToken&quot;</span>: <span class="json-string">&quot;&lt;execution-token&gt;&quot;</span>,
<span class="json-key">&quot;reportId&quot;</span>: <span class="json-string">&quot;command-report-001&quot;</span>,
<span class="json-key">&quot;reportSequence&quot;</span>: <span class="json-number">2</span>,
<span class="json-key">&quot;state&quot;</span>: <span class="json-string">&quot;CONFIRMED&quot;</span>,
<span class="json-key">&quot;observedAt&quot;</span>: <span class="json-number">1792123202500</span>,
<span class="json-key">&quot;items&quot;</span>: [
{
<span class="json-key">&quot;code&quot;</span>: <span class="json-string">&quot;switch_1&quot;</span>,
<span class="json-key">&quot;state&quot;</span>: <span class="json-string">&quot;CONFIRMED&quot;</span>,
<span class="json-key">&quot;reportedValue&quot;</span>: <span class="json-literal">false</span>,
<span class="json-key">&quot;evidence&quot;</span>: <span class="json-string">&quot;DEVICE_REPORT&quot;</span>
}
]
}</code></pre></details><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">字段</th><th scope="col">类型</th><th scope="col">必填</th><th scope="col">说明</th></tr></thead><tbody><tr><td>executionToken</td><td>String</td><td>是</td><td>绑定 commandId、panelId、执行实例和路由版本</td></tr><tr><td>reportId</td><td>String</td><td>是</td><td>唯一回报编号;重试不变</td></tr><tr><td>reportSequence</td><td>Long</td><td>是</td><td>同一命令内递增,避免旧回执覆盖新结果</td></tr><tr><td>state</td><td>String</td><td>是</td><td>ACCEPTED / CONFIRMED / PARTIAL_FAILURE / FAILED / UNKNOWN</td></tr><tr><td>observedAt</td><td>Long</td><td>否</td><td>设备结果观察时间;仅时间戳本身不构成完成证据</td></tr><tr><td>items</td><td>List</td><td>属性命令必填</td><td>各属性结果和证据</td></tr><tr><td>errorCode/message</td><td>String</td><td>失败或未知时</td><td>结构化原因和简短说明</td></tr></tbody></table></div><p>ACK_ONLY 动作可报告 ACCEPTED 作为终态,但界面仍显示“已接受”,不能显示“已确认机械停止”等未取得的结果。</p><p>平台核对目标、物模型允许的数值精度和执行归属,不接受用旧缓存值直接报告 CONFIRMED。可接受证据为实际设备回报,或指令之后完成且能证明新鲜度的读回;无证据时保留 ACCEPTED/UNKNOWN。命令回执不直接覆盖设备最新状态,设备状态另走第七节并按顺序合并。</p><p>响应 data:</p><details class="example json" id="example-21"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 21</span><span class="example-meta">JSON · 6 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-6-203" type="button" aria-label="复制 JSON 示例 21">复制</button></div><pre tabindex="0"><code id="code-chapter-6-203">{
<span class="json-key">&quot;commandId&quot;</span>: <span class="json-string">&quot;command-001&quot;</span>,
<span class="json-key">&quot;state&quot;</span>: <span class="json-string">&quot;CONFIRMED&quot;</span>,
<span class="json-key">&quot;terminal&quot;</span>: <span class="json-literal">true</span>,
<span class="json-key">&quot;duplicate&quot;</span>: <span class="json-literal">false</span>
}</code></pre></details><p>reportId 重复且内容相同返回同一结果;不同内容返回冲突。旧 reportSequence 不回退状态。中途会话刷新不影响有效 executionToken;新 bootId、撤权或路由变化后只能追加经平台核验的旧执行证据,不能凭旧 executionToken 获取新执行权。</p><h3 id="chapter-6-heading-6">6. 屏上本地点击与回声处理<a class="anchor" href="#chapter-6-heading-6" aria-label="链接到6. 屏上本地点击与回声处理">#</a></h3><p>负责该设备的屏可直接执行本地操作,随后上报状态及活动;这种操作不再同时向平台提交一条待下发控制命令。</p><p>本地操作以 localOperationId 标记来源。平台因该上报产生的事件只是同步,不重新生成控制命令。其他屏控制该设备时通过平台提交,仍由唯一执行屏领取。</p><p>同一屏对同一设备的本地操作与远程操作进入同一个串行执行入口;不能因来源不同并行修改同一属性。相互冲突的请求分别保留结果,不使用先前另一条命令的回报冒充当前命令完成。</p></article><div class="chapter-pagination"><a href="#chapter-5"><small>上一章</small><span>← 设备登记与标准控制模型</span></a><a class="next" href="#chapter-7"><small>下一章</small><span>状态与活动上报 →</span></a></div></section>
<section class="doc-page" id="chapter-7" data-title="状态与活动上报" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>状态与活动上报</h1><p class="page-description">状态顺序、活动与批量回报</p></div><article class="prose"><div class="endpoint" id="endpoint-17"><span class="method post">POST</span><code>/api/v1/panels/{panelId}/reports</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-17" aria-label="定位 POST /api/v1/panels/{panelId}/reports">#</a></div><p>此接口面向网关状态与本地活动,复用平台已有设备、数据和活动存储。屏使用此入口后,不再把同一条状态重复提交到其他状态接口。</p><p>参数:body: PanelReportBatch</p><details class="example json" id="example-22"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 22</span><span class="example-meta">JSON · 20 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-7-29" type="button" aria-label="复制 JSON 示例 22">复制</button></div><pre tabindex="0"><code id="code-chapter-7-29">{
<span class="json-key">&quot;bootId&quot;</span>: <span class="json-string">&quot;boot-example-01&quot;</span>,
<span class="json-key">&quot;stateEpoch&quot;</span>: <span class="json-number">12</span>,
<span class="json-key">&quot;reports&quot;</span>: [
{
<span class="json-key">&quot;reportId&quot;</span>: <span class="json-string">&quot;report-001&quot;</span>,
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;device-001&quot;</span>,
<span class="json-key">&quot;sequence&quot;</span>: <span class="json-number">18</span>,
<span class="json-key">&quot;schemaRevision&quot;</span>: <span class="json-number">2</span>,
<span class="json-key">&quot;routeRevision&quot;</span>: <span class="json-number">3</span>,
<span class="json-key">&quot;kind&quot;</span>: <span class="json-string">&quot;STATE&quot;</span>,
<span class="json-key">&quot;mode&quot;</span>: <span class="json-string">&quot;DELTA&quot;</span>,
<span class="json-key">&quot;online&quot;</span>: <span class="json-literal">true</span>,
<span class="json-key">&quot;observedAt&quot;</span>: <span class="json-number">1792123229500</span>,
<span class="json-key">&quot;properties&quot;</span>: {<span class="json-key">&quot;switch_1&quot;</span>: <span class="json-literal">false</span>},
<span class="json-key">&quot;quality&quot;</span>: <span class="json-string">&quot;GOOD&quot;</span>,
<span class="json-key">&quot;commandId&quot;</span>: <span class="json-string">&quot;command-001&quot;</span>
}
]
}</code></pre></details><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">字段</th><th scope="col">类型</th><th scope="col">必填</th><th scope="col">说明</th></tr></thead><tbody><tr><td>bootId/stateEpoch</td><td>String/Long</td><td>是</td><td>平台签发的当前启动世代</td></tr><tr><td>reports</td><td>List</td><td>是</td><td>建议每批 1–100 条</td></tr><tr><td>reportId</td><td>String</td><td>是</td><td>去重标识</td></tr><tr><td>deviceId</td><td>String</td><td>是</td><td>本屏负责的设备</td></tr><tr><td>sequence</td><td>Long</td><td>是</td><td>当前世代、当前设备内严格递增</td></tr><tr><td>schemaRevision/routeRevision</td><td>Long</td><td>是</td><td>防止错误物模型和旧执行归属上报</td></tr><tr><td>kind</td><td>String</td><td>是</td><td>STATE 或 ACTIVITY</td></tr><tr><td>mode</td><td>String</td><td>STATE 必填</td><td>FULL:所有当前可报告属性;DELTA:仅变化属性</td></tr><tr><td>online</td><td>Boolean</td><td>STATE 建议</td><td>本地实际连接状态</td></tr><tr><td>properties</td><td>Object</td><td>STATE 必填</td><td>code 到类型正确的值</td></tr><tr><td>observedAt</td><td>Long</td><td>否</td><td>屏观测时间;平台另记录 receivedAt</td></tr><tr><td>quality</td><td>String</td><td>STATE 必填</td><td>GOOD / STALE / UNAVAILABLE</td></tr><tr><td>commandId</td><td>String</td><td>否</td><td>远程控制关联;仅关联不等同于命令确认</td></tr><tr><td>localOperationId</td><td>String</td><td>ACTIVITY 必填</td><td>本地动作标识</td></tr><tr><td>action/detail</td><td>String/Object</td><td>ACTIVITY 必填</td><td>动作语义与必要明细,不含凭证</td></tr></tbody></table></div><p>平台只接受当前执行归属的上报。状态顺序按 stateEpoch、sequence 判断;不能以 HTTP 到达顺序决定新旧。bootId 改变后由平台分配新 stateEpoch,屏首次上报应为 FULL;旧世代补传只可留作历史,不覆盖新状态。</p><p>DELTA 需要按属性保存最近一次序号,避免序号较大的单个属性先到时,误丢另一个属性的较早有效变化。更旧的 FULL 不覆盖新属性,FULL 中未提供的属性只有在其序号比该属性更新时才标为未知;不能默认写成 0/false。</p><p>响应逐项给结果:</p><details class="example json" id="example-23"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 23</span><span class="example-meta">JSON · 16 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-7-72" type="button" aria-label="复制 JSON 示例 23">复制</button></div><pre tabindex="0"><code id="code-chapter-7-72">{
<span class="json-key">&quot;code&quot;</span>: <span class="json-number">0</span>,
<span class="json-key">&quot;message&quot;</span>: <span class="json-string">&quot;success&quot;</span>,
<span class="json-key">&quot;data&quot;</span>: {
<span class="json-key">&quot;results&quot;</span>: [
{
<span class="json-key">&quot;reportId&quot;</span>: <span class="json-string">&quot;report-001&quot;</span>,
<span class="json-key">&quot;accepted&quot;</span>: <span class="json-literal">true</span>,
<span class="json-key">&quot;duplicate&quot;</span>: <span class="json-literal">false</span>,
<span class="json-key">&quot;appliedToCurrentState&quot;</span>: <span class="json-literal">true</span>,
<span class="json-key">&quot;stateVersion&quot;</span>: <span class="json-number">81</span>
}
],
<span class="json-key">&quot;serverTime&quot;</span>: <span class="json-number">1792123230000</span>
}
}</code></pre></details><p>accepted 表示该记录已被持久接收;appliedToCurrentState 表示是否改变当前状态。旧记录可以 accepted=true、appliedToCurrentState=false。部分失败逐条返回 errorCode;屏只重试可重试项,不能因整批 code=0 删除失败记录。</p><p>云端来源设备由平台云驱动按同样的对外状态模型提供 stateVersion 和新鲜度。客户端不能把读接口的响应时间伪装成真实设备上报时间。</p></article><div class="chapter-pagination"><a href="#chapter-6"><small>上一章</small><span>← 控制命令与执行回执</span></a><a class="next" href="#chapter-8"><small>下一章</small><span>多屏、场景与 AI →</span></a></div></section>
<section class="doc-page" id="chapter-8" data-title="多屏、场景与 AI" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>多屏、场景与 AI</h1><p class="page-description">多屏归属、场景、AI 与配网</p></div><nav class="on-this-page" aria-label="本页目录"><a href="#chapter-8-heading-1">1. 多屏执行归属</a><a href="#chapter-8-heading-2">2. 平台场景和规则</a><a href="#chapter-8-heading-3">3. AI 控制</a><a href="#chapter-8-heading-4">4. 配网</a></nav><article class="prose"><h3 id="chapter-8-heading-1">1. 多屏执行归属<a class="anchor" href="#chapter-8-heading-1" aria-label="链接到1. 多屏执行归属">#</a></h3><p>DeviceRoute 由平台维护:</p><details class="example json" id="example-24"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 24</span><span class="example-meta">JSON · 6 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-8-13" type="button" aria-label="复制 JSON 示例 24">复制</button></div><pre tabindex="0"><code id="code-chapter-8-13">{
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;device-001&quot;</span>,
<span class="json-key">&quot;executionSource&quot;</span>: <span class="json-string">&quot;LOCAL_PANEL&quot;</span>,
<span class="json-key">&quot;executorPanelId&quot;</span>: <span class="json-string">&quot;panel-001&quot;</span>,
<span class="json-key">&quot;routeRevision&quot;</span>: <span class="json-number">3</span>
}</code></pre></details><ul><li>DISPLAY 屏读取同一家庭的状态并向平台提交控制,不取得其他屏的本地协议密钥。</li><li>一台设备只对应一个执行屏,不按谁先领取就临时决定归属。</li><li>屏离线不会自动把本地设备切换给另一屏;协议网络绑定不一定可转移。</li><li>归属变更必须完成明确交接并增加 routeRevision。旧执行屏仍可能离线控制时不得直接启用新执行方;在无法确认旧绑定已解除时停止交接。</li><li>旧 routeRevision 的新 start 请求拒绝;已经发到物理设备的动作无法由云端撤回,应保留结果不确定或迟到证据,不能虚报取消成功。</li></ul><h3 id="chapter-8-heading-2">2. 平台场景和规则<a class="anchor" href="#chapter-8-heading-2" aria-label="链接到2. 平台场景和规则">#</a></h3><p><strong>沿用</strong> scenes、scenes/v2、rules 的现有入口。家庭范围、动作执行与结果增加以下约定:</p><ul><li>每个场景或自动化配置明确 executionOwner 为 CLOUD 或指定 PANEL。</li><li>同一定时/自动化只由一个执行方调度;屏展示云场景不自动再建一份本地定时任务。</li><li>CLOUD 场景涉及本地设备时,动作转为第六节设备命令,经过所属执行屏。</li><li>PANEL 离线场景仅对本屏可执行的本地动作运行,并在恢复后补传运行记录;不回放已过期的云端动作。</li><li>场景执行接口建议返回 sceneRunId、state、terminal 及各动作 commandId;设备动作关联失败或未知时,场景不能整体标记成功。</li></ul><div class="endpoint" id="endpoint-18"><span class="method get">GET</span><code>/api/v1/scene-runs/{sceneRunId}</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-18" aria-label="定位 GET /api/v1/scene-runs/{sceneRunId}">#</a></div><p>响应 data:</p><details class="example json" id="example-25"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 25</span><span class="example-meta">JSON · 9 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-8-45" type="button" aria-label="复制 JSON 示例 25">复制</button></div><pre tabindex="0"><code id="code-chapter-8-45">{
<span class="json-key">&quot;sceneRunId&quot;</span>: <span class="json-string">&quot;scene-run-001&quot;</span>,
<span class="json-key">&quot;sceneId&quot;</span>: <span class="json-string">&quot;60001&quot;</span>,
<span class="json-key">&quot;state&quot;</span>: <span class="json-string">&quot;RUNNING&quot;</span>,
<span class="json-key">&quot;terminal&quot;</span>: <span class="json-literal">false</span>,
<span class="json-key">&quot;actions&quot;</span>: [
{<span class="json-key">&quot;index&quot;</span>: <span class="json-number">0</span>, <span class="json-key">&quot;commandId&quot;</span>: <span class="json-string">&quot;command-001&quot;</span>, <span class="json-key">&quot;state&quot;</span>: <span class="json-string">&quot;EXECUTING&quot;</span>}
]
}</code></pre></details><p>state 为 QUEUED/RUNNING/ACCEPTED/CONFIRMED/PARTIAL_FAILURE/FAILED/UNKNOWN。全部动作均确认时为 CONFIRMED;全部动作均已完成各自约定、但至少一项只能确认协议接受时,汇总为 ACCEPTED、terminal=true,不能把它改写成所有物理效果已确认。存在失败或未知时,按各项结果返回部分失败、失败或未知。</p><p>PANEL 执行的离线场景结果通过 diagnostics 的结构化事件记录可用于追踪;若需要平台场景历史与管理,则建议统一增加运行报告:</p><div class="endpoint" id="endpoint-19"><span class="method post">POST</span><code>/api/v1/panels/{panelId}/scene-runs/report</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-19" aria-label="定位 POST /api/v1/panels/{panelId}/scene-runs/report">#</a></div><p>请求含 sceneRunId(屏生成 UUID)、sceneId、configurationRevision、startedAt、finishedAt、state、actions(localOperationId 与各项结果);幂等按 sceneRunId,平台核对该规则当时的 PANEL 执行归属,只记录历史,不再执行动作。</p><h3 id="chapter-8-heading-3">3. AI 控制<a class="anchor" href="#chapter-8-heading-3" aria-label="链接到3. AI 控制">#</a></h3><div class="endpoint" id="endpoint-20"><span class="method post">POST</span><code>/api/v1/ai/command</code><span class="badge extend">扩展建议</span><a class="endpoint-anchor" href="#endpoint-20" aria-label="定位 POST /api/v1/ai/command">#</a></div><p>沿用 text、targetType、targetId 等现有请求字段。平台需要明确回复是只解释、等待确认,还是已经提交执行。</p><p>建议 data 包含 status(ANSWER/NEED_CONFIRMATION/SUBMITTED/REJECTED)、message,以及执行时的 commandIds 或 sceneRunId。NEED_CONFIRMATION 额外返回 confirmationId、候选目标及 expiresAt。</p><p>二次请求在既有目标字段之外携带 confirmationId,平台绑定原始请求、家庭、目标和有效期;同一确认只产生一次执行。屏收到 SUBMITTED 后只跟踪结果,不把同一动作再次交给本地解析器执行。</p><h3 id="chapter-8-heading-4">4. 配网<a class="anchor" href="#chapter-8-heading-4" aria-label="链接到4. 配网">#</a></h3><p>本地 Matter/Zigbee/BLE 配网继续由屏对应协议栈完成,成功后走设备登记。平台已有涂鸦 Wi-Fi/Matter/Zigbee 配网 Token 仅按其配套 SDK 和账号体系使用,不能当作通用本地协议凭证。</p></article><div class="chapter-pagination"><a href="#chapter-7"><small>上一章</small><span>← 状态与活动上报</span></a><a class="next" href="#chapter-9"><small>下一章</small><span>配置、内容、日志和 OTA →</span></a></div></section>
<section class="doc-page" id="chapter-9" data-title="配置、内容、日志和 OTA" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>配置、内容、日志和 OTA</h1><p class="page-description">运行配置、诊断事件与升级任务</p></div><nav class="on-this-page" aria-label="本页目录"><a href="#chapter-9-heading-1">1. 屏配置</a><a href="#chapter-9-heading-2">2. 诊断事件</a><a href="#chapter-9-heading-3">3. 屏查询升级任务</a><a href="#chapter-9-heading-4">4. 升级回报</a></nav><article class="prose"><h3 id="chapter-9-heading-1">1. 屏配置<a class="anchor" href="#chapter-9-heading-1" aria-label="链接到1. 屏配置">#</a></h3><div class="endpoint" id="endpoint-21"><span class="method get">GET</span><code>/api/v1/panels/{panelId}/configuration</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-21" aria-label="定位 GET /api/v1/panels/{panelId}/configuration">#</a></div><p>query: knownRevision(可选)。</p><p>响应 data:</p><details class="example json" id="example-26"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 26</span><span class="example-meta">JSON · 10 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-9-21" type="button" aria-label="复制 JSON 示例 26">复制</button></div><pre tabindex="0"><code id="code-chapter-9-21">{
<span class="json-key">&quot;revision&quot;</span>: <span class="json-number">4</span>,
<span class="json-key">&quot;unchanged&quot;</span>: <span class="json-literal">false</span>,
<span class="json-key">&quot;settings&quot;</span>: {
<span class="json-key">&quot;screenOffSeconds&quot;</span>: <span class="json-number">120</span>,
<span class="json-key">&quot;proximityWakeEnabled&quot;</span>: <span class="json-literal">true</span>
},
<span class="json-key">&quot;content&quot;</span>: [],
<span class="json-key">&quot;automationAssignments&quot;</span>: []
}</code></pre></details><p>本版本列出的 settings 为示例拟定键;平台与屏需要维护明确的支持键、类型和范围,不能把任意 JSON 当系统命令执行。content 每项包含 id、type、title、url、revision;automationAssignments 包含规则 ID、revision、executionOwner 及平台已定义的结构化条件/动作。</p><p>只有完整校验成功后才原子应用新配置,失败继续使用上一有效配置并上报原因。knownRevision 相同返回 unchanged=true,可省略主体。云端配置缺失不阻塞本地页面启动。</p><h3 id="chapter-9-heading-2">2. 诊断事件<a class="anchor" href="#chapter-9-heading-2" aria-label="链接到2. 诊断事件">#</a></h3><div class="endpoint" id="endpoint-22"><span class="method post">POST</span><code>/api/v1/panels/{panelId}/diagnostics</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-22" aria-label="定位 POST /api/v1/panels/{panelId}/diagnostics">#</a></div><p>参数:body 包含 events,建议每批最多 100 条:</p><details class="example json" id="example-27"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 27</span><span class="example-meta">JSON · 14 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-9-48" type="button" aria-label="复制 JSON 示例 27">复制</button></div><pre tabindex="0"><code id="code-chapter-9-48">{
<span class="json-key">&quot;events&quot;</span>: [
{
<span class="json-key">&quot;eventId&quot;</span>: <span class="json-string">&quot;diag-001&quot;</span>,
<span class="json-key">&quot;bootId&quot;</span>: <span class="json-string">&quot;boot-example-01&quot;</span>,
<span class="json-key">&quot;time&quot;</span>: <span class="json-number">1792123200000</span>,
<span class="json-key">&quot;category&quot;</span>: <span class="json-string">&quot;NETWORK&quot;</span>,
<span class="json-key">&quot;level&quot;</span>: <span class="json-string">&quot;WARN&quot;</span>,
<span class="json-key">&quot;code&quot;</span>: <span class="json-string">&quot;PLATFORM_UNREACHABLE&quot;</span>,
<span class="json-key">&quot;message&quot;</span>: <span class="json-string">&quot;平台连接暂时不可用&quot;</span>,
<span class="json-key">&quot;commandId&quot;</span>: <span class="json-literal">null</span>
}
]
}</code></pre></details><p>响应按 eventId 返回 accepted/duplicate/errorCode。不得包含 Token、Secret、Wi-Fi 密码、协议密钥或完整用户音频。诊断上传失败不影响本地控制。</p><h3 id="chapter-9-heading-3">3. 屏查询升级任务<a class="anchor" href="#chapter-9-heading-3" aria-label="链接到3. 屏查询升级任务">#</a></h3><div class="endpoint" id="endpoint-23"><span class="method get">GET</span><code>/api/v1/panels/{panelId}/ota-tasks/next</code><span class="badge new">新增建议</span><a class="endpoint-anchor" href="#endpoint-23" aria-label="定位 GET /api/v1/panels/{panelId}/ota-tasks/next">#</a></div><p>query: appVersionCode、systemVersion、supportedPackageTypes。平台只返回分配给该屏且适用的任务,无任务时 data=null。</p><details class="example json" id="example-28"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 28</span><span class="example-meta">JSON · 18 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-9-77" type="button" aria-label="复制 JSON 示例 28">复制</button></div><pre tabindex="0"><code id="code-chapter-9-77">{
<span class="json-key">&quot;taskId&quot;</span>: <span class="json-string">&quot;70001&quot;</span>,
<span class="json-key">&quot;attemptId&quot;</span>: <span class="json-string">&quot;ota-attempt-001&quot;</span>,
<span class="json-key">&quot;firmwareId&quot;</span>: <span class="json-string">&quot;80001&quot;</span>,
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;gateway-001&quot;</span>,
<span class="json-key">&quot;packageType&quot;</span>: <span class="json-string">&quot;APK&quot;</span>,
<span class="json-key">&quot;version&quot;</span>: <span class="json-string">&quot;1.0.1&quot;</span>,
<span class="json-key">&quot;versionCode&quot;</span>: <span class="json-number">101</span>,
<span class="json-key">&quot;targetModel&quot;</span>: <span class="json-string">&quot;RK3568-10INCH&quot;</span>,
<span class="json-key">&quot;downloadUrl&quot;</span>: <span class="json-string">&quot;https://mum.tonzou.cn/example-package.apk&quot;</span>,
<span class="json-key">&quot;size&quot;</span>: <span class="json-number">12345678</span>,
<span class="json-key">&quot;integrity&quot;</span>: {
<span class="json-key">&quot;algorithm&quot;</span>: <span class="json-string">&quot;SHA-256&quot;</span>,
<span class="json-key">&quot;digest&quot;</span>: <span class="json-string">&quot;&lt;package-digest&gt;&quot;</span>
},
<span class="json-key">&quot;signatureRequirement&quot;</span>: <span class="json-string">&quot;EXISTING_APP_SIGNATURE&quot;</span>,
<span class="json-key">&quot;expiresAt&quot;</span>: <span class="json-number">1792209600000</span>
}</code></pre></details><p>这是升级包元数据示例,不是可下载任务。重复查询返回同一任务的同一 attemptId,不因 GET 查询产生新的安装动作;平台明确安排新一轮重试时才分配新的 attemptId。packageType 至少区分 APK 与 ANDROID_AB;不兼容的系统更新形式返回 UNSUPPORTED。平台已有 fileMd5 可保留作兼容字段,实际包完整性与签名检查不能被任务查询成功替代。这里的包摘要只用于固件传输完整性,不扩展为普通业务文件的校验要求。</p><p>下载地址不得要求把屏 Bearer Token 转发给任意第三方域名。使用受控平台下载或明确的临时下载授权;系统包和 APK 保留各自的本地验证与安装流程。</p><h3 id="chapter-9-heading-4">4. 升级回报<a class="anchor" href="#chapter-9-heading-4" aria-label="链接到4. 升级回报">#</a></h3><div class="endpoint" id="endpoint-24"><span class="method post">POST</span><code>/api/v1/ota-tasks/{id}/report</code><span class="badge extend">扩展建议</span><a class="endpoint-anchor" href="#endpoint-24" aria-label="定位 POST /api/v1/ota-tasks/{id}/report">#</a></div><p>原 deviceId、success、version 用于最终结果;建议增加 attemptId、reportId、reportSequence、stage、progress、errorCode、message。</p><p>stage 为 DOWNLOADING/VERIFYING/INSTALLING/REBOOTING/SUCCEEDED/FAILED/CANCELLED。progress 为当前阶段的 0–100 进度。中间阶段 success 省略,最终成功/失败才填写;CANCELLED 不使用 success 冒充设备安装失败。以 attemptId、reportId 和递增 reportSequence 去重与排序,同阶段进度不回退,不允许旧轮次或迟到下载进度覆盖新的安装结果。</p><p>示例:</p><details class="example json" id="example-29"><summary><span class="example-icon" aria-hidden="true">⌘</span><span>查看 JSON 示例 29</span><span class="example-meta">JSON · 10 行</span></summary><div class="code-tools"><span>JSON 示例</span><button class="copy-code" data-copy="code-chapter-9-104" type="button" aria-label="复制 JSON 示例 29">复制</button></div><pre tabindex="0"><code id="code-chapter-9-104">{
<span class="json-key">&quot;deviceId&quot;</span>: <span class="json-string">&quot;gateway-001&quot;</span>,
<span class="json-key">&quot;attemptId&quot;</span>: <span class="json-string">&quot;ota-attempt-001&quot;</span>,
<span class="json-key">&quot;reportId&quot;</span>: <span class="json-string">&quot;ota-report-001&quot;</span>,
<span class="json-key">&quot;reportSequence&quot;</span>: <span class="json-number">5</span>,
<span class="json-key">&quot;stage&quot;</span>: <span class="json-string">&quot;SUCCEEDED&quot;</span>,
<span class="json-key">&quot;progress&quot;</span>: <span class="json-number">100</span>,
<span class="json-key">&quot;success&quot;</span>: <span class="json-literal">true</span>,
<span class="json-key">&quot;version&quot;</span>: <span class="json-string">&quot;1.0.1&quot;</span>
}</code></pre></details><p>升级任务和本地控制解耦。安装/重启条件需由屏核对;OTA 接口接通不表示可以跳过型号、包类型、签名或用户已确定的设备操作规则。</p></article><div class="chapter-pagination"><a href="#chapter-8"><small>上一章</small><span>← 多屏、场景与 AI</span></a><a class="next" href="#chapter-10"><small>下一章</small><span>建议错误码 →</span></a></div></section>
<section class="doc-page" id="chapter-10" data-title="建议错误码" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>建议错误码</h1><p class="page-description">HTTP 状态与业务错误处理</p></div><article class="prose"><p>新增接口建议使用下表。HTTP 状态与业务 code 为评审建议,平台既有错误码可映射保留,但同一含义应稳定一致。</p><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">HTTP</th><th scope="col">code</th><th scope="col">errorCode</th><th scope="col">屏端处理</th></tr></thead><tbody><tr><td>400</td><td>40001</td><td>INVALID_ARGUMENT</td><td>修正参数,不自动重试</td></tr><tr><td>400</td><td>40002</td><td>UNSUPPORTED_CAPABILITY</td><td>刷新能力,关闭对应无效操作</td></tr><tr><td>401</td><td>40101</td><td>TOKEN_EXPIRED</td><td>按身份刷新后重试同一逻辑请求</td></tr><tr><td>401</td><td>40102</td><td>INVALID_CREDENTIAL</td><td>停止自动循环,报告身份错误</td></tr><tr><td>403</td><td>40301</td><td>HOME_ACCESS_REVOKED</td><td>停止该家庭云操作,清理对应授权缓存</td></tr><tr><td>403</td><td>40302</td><td>DEVICE_OUT_OF_SCOPE</td><td>不执行跨家庭或非本屏任务</td></tr><tr><td>409</td><td>40901</td><td>IDEMPOTENCY_CONFLICT</td><td>不生成新键掩盖冲突</td></tr><tr><td>409</td><td>40902</td><td>SCHEMA_CHANGED</td><td>读取新模型,重新确认用户目标</td></tr><tr><td>409</td><td>40903</td><td>ROUTE_CHANGED</td><td>刷新归属;旧屏不执行</td></tr><tr><td>409</td><td>40904</td><td>DELIVERY_EXPIRED</td><td>本次投递无效,不执行</td></tr><tr><td>409</td><td>40905</td><td>EXECUTION_ALREADY_STARTED</td><td>查询原命令,不能重新操作</td></tr><tr><td>409</td><td>40906</td><td>DEVICE_OFFLINE</td><td>显示设备或执行屏离线</td></tr><tr><td>409</td><td>40907</td><td>STALE_SESSION</td><td>当前启动实例失效,停止领取</td></tr><tr><td>410</td><td>41001</td><td>CURSOR_EXPIRED</td><td>重新获取完整快照</td></tr><tr><td>410</td><td>41002</td><td>SNAPSHOT_EXPIRED</td><td>放弃未完成分页并重新开始</td></tr><tr><td>410</td><td>41003</td><td>COMMAND_EXPIRED</td><td>不执行</td></tr><tr><td>429</td><td>42901</td><td>RATE_LIMITED</td><td>按 Retry-After 等待;控制请求保持原幂等键与期限</td></tr><tr><td>503</td><td>50301</td><td>SERVICE_UNAVAILABLE</td><td>读取可退避重试;控制先查原结果</td></tr></tbody></table></div><p>建议退避加入随机抖动并有上限;恢复联网后不集中重放过期控制。业务过期处理独立于 HTTP 重试。</p></article><div class="chapter-pagination"><a href="#chapter-9"><small>上一章</small><span>← 配置、内容、日志和 OTA</span></a><a class="next" href="#chapter-11"><small>下一章</small><span>字段模型汇总 →</span></a></div></section>
<section class="doc-page" id="chapter-11" data-title="字段模型汇总" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>字段模型汇总</h1><p class="page-description">主要数据模型与字段入口</p></div><article class="prose"><div class="table-scroll" tabindex="0" aria-label="参数与约定表"><table><thead><tr><th scope="col">模型</th><th scope="col">核心字段</th><th scope="col">用途</th></tr></thead><tbody><tr><td>PanelSession</td><td>panelId、gatewayDeviceId、homeId、role、Token、stateEpoch、各时间参数</td><td>屏身份及运行参数</td></tr><tr><td>PanelHeartbeat</td><td>bootId、版本、网络、运行时间、协议状态</td><td>在线保持</td></tr><tr><td>DeviceRoute</td><td>executionSource、executorPanelId、routeRevision</td><td>唯一执行归属</td></tr><tr><td>ControlSchema</td><td>schemaRevision、properties、actions</td><td>统一控制模型</td></tr><tr><td>PropertySpec</td><td>code、type、access、semantic、channel、范围、completionMode</td><td>属性定义</td></tr><tr><td>CommandResult</td><td>commandId、state、terminal、有效期、result</td><td>控制生命周期</td></tr><tr><td>CommandDelivery</td><td>设备映射、参数、deliveryToken、有效期</td><td>命令投递</td></tr><tr><td>CommandReport</td><td>executionToken、reportId、reportSequence、state、items</td><td>执行证据</td></tr><tr><td>PanelReport</td><td>reportId、stateEpoch、sequence、属性、质量、来源</td><td>状态和本地活动</td></tr><tr><td>PanelEvent</td><td>eventId、type、来源、stateVersion、payload</td><td>家庭增量同步</td></tr><tr><td>PanelSnapshotPage</td><td>snapshotId、homeRevision、cursor、目录、nextPageToken</td><td>一致性初始化</td></tr><tr><td>SceneRun</td><td>sceneRunId、executionOwner、动作结果</td><td>场景执行追踪</td></tr><tr><td>OtaTask</td><td>taskId、包类型、型号、版本、下载与验证资料</td><td>屏升级任务</td></tr></tbody></table></div><p>字段在对应接口中规定。对外数据不包含底层协议密钥;原始厂商私有属性须有明确类型和权限后才暴露。</p></article><div class="chapter-pagination"><a href="#chapter-10"><small>上一章</small><span>← 建议错误码</span></a><a class="next" href="#chapter-12"><small>下一章</small><span>对接顺序与评审确认 →</span></a></div></section>
<section class="doc-page" id="chapter-12" data-title="对接顺序与评审确认" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>对接顺序与评审确认</h1><p class="page-description">实施依赖与双方评审事项</p></div><article class="prose"><p>建议按以下依赖顺序实现完整连接:</p><ol><li>终端身份与家庭范围,确认当前屏可读取的目录。</li><li>设备/物模型映射和初始化快照、增量状态。</li><li>云端设备控制,以及本地设备登记与上报。</li><li>本地网关命令领取、执行许可、回执和超时恢复。</li><li>多屏、场景、AI 及配置、诊断和 OTA 的相应接线。</li></ol><p>协议实施完成后统一从屏和手机/网页验证:同一家庭数据一致、命令只走一个执行来源、回执能证明实际结果、断线与重启不重复执行、旧状态不覆盖新状态;无外设只影响对应协议功能。</p><p>本稿请平台方重点确认:</p><ul><li>是否已有终端身份和网关协议,可用现有契约替换哪些“新增建议”路径。</li><li>是否接受屏受限身份访问既有业务接口,以及明确的家庭范围校验。</li><li>control-schema、通用控制参数和命令结果的最终字段。</li><li>执行归属、投递与结果状态、事件游标及状态序号的语义。</li><li>场景/AI/OTA 等已有实现如何对应本文模型及默认时间参数。</li></ul><p>确认后的路径和字段维护在同一份协议中,再按定稿实施。以上接口不是对现网行为的测试结论。</p></article><div class="chapter-pagination"><a href="#chapter-11"><small>上一章</small><span>← 字段模型汇总</span></a><a class="next" href="#chapter-13"><small>下一章</small><span>文档依据 →</span></a></div></section>
<section class="doc-page" id="chapter-13" data-title="文档依据" hidden><div class="page-heading"><div class="eyebrow">MUMIOT / PANEL API</div><h1>文档依据</h1><p class="page-description">原平台文档与协议依据</p></div><article class="prose"><p>现有路径与业务用途参考: <a href="https://mum.tonzou.cn/api/doc/index.html" target="_blank" rel="noopener noreferrer">概览<span class="external" aria-hidden="true">↗</span></a>、 <a href="https://mum.tonzou.cn/api/doc/partner.html" target="_blank" rel="noopener noreferrer">授权对接<span class="external" aria-hidden="true">↗</span></a>、 <a href="https://mum.tonzou.cn/api/doc/member.html" target="_blank" rel="noopener noreferrer">会员与家庭<span class="external" aria-hidden="true">↗</span></a>、 <a href="https://mum.tonzou.cn/api/doc/device.html" target="_blank" rel="noopener noreferrer">设备管理<span class="external" aria-hidden="true">↗</span></a>、 <a href="https://mum.tonzou.cn/api/doc/product.html" target="_blank" rel="noopener noreferrer">产品与物模型<span class="external" aria-hidden="true">↗</span></a>、 <a href="https://mum.tonzou.cn/api/doc/tuya.html" target="_blank" rel="noopener noreferrer">涂鸦云集成<span class="external" aria-hidden="true">↗</span></a>、 <a href="https://mum.tonzou.cn/api/doc/rule.html" target="_blank" rel="noopener noreferrer">规则与场景<span class="external" aria-hidden="true">↗</span></a>、 <a href="https://mum.tonzou.cn/api/doc/ai.html" target="_blank" rel="noopener noreferrer">AI 智能<span class="external" aria-hidden="true">↗</span></a>、 <a href="https://mum.tonzou.cn/api/doc/models.html" target="_blank" rel="noopener noreferrer">数据模型<span class="external" aria-hidden="true">↗</span></a>。</p></article><div class="chapter-pagination"><a href="#chapter-12"><small>上一章</small><span>← 对接顺序与评审确认</span></a></div></section><footer class="footer">MumIot · 10 寸中控屏直连接入协议 &nbsp; / &nbsp; v0.1 · 2026-10-16</footer></main></div><section id="search-panel" class="search-panel" aria-label="全文搜索结果" hidden><div class="search-summary"><span id="search-count" aria-live="polite"></span><button id="search-close" class="search-close" type="button" aria-label="关闭搜索结果">×</button></div><div id="search-results"></div></section><div class="toast" id="toast" role="status" hidden></div><script>const SEARCH_RECORDS=[{"id":"chapter-1","chapter":"chapter-1","label":"概览","kind":"章节","text":"\n 1. Base URL\n\n text\nhttps://mum.tonzou.cn\n \n\n本文接口路径统一包含 /api/v1 ,与 Base URL 拼接时不重复添加版本前缀。\n\n 2. 业务关系\n\n mermaid\nflowchart LR\n M[手机 / 网页] --> C[MumIot 平台]\n P[10 寸中控屏] --> 业务查询与云设备控制 C\n P --> 领取命令与同步事件 C\n P --> 本地协议 D[Matter / Zigbee / BLE 设备]\n P --> 登记与状态回报 C\n C --> V[平台已有云设备]\n \n\n- 账号、家庭、房间、平台设备和平台场景由 MumIot 管理。\n- 中控屏负责自己的界面、本地缓存、本地协议连接和实际执行。\n- 每台本地设备在平台上关联唯一执行屏;其他屏通过平台发起控制。\n- 本地断网控制保留,过期远程命令不在恢复联网后补执行。\n- 状态同步和执行命令是两种消息,收到同步事件不会自动再次执行设备动作。\n\n 3. 主通信方式\n\n本稿以 HTTPS REST 为主,命令领取和事件同步使用有时限的长轮询。屏主动建立连接,不需要平台从公网直接连接屏的地址。\n\n建议默认心跳间隔 30 秒、在线租期 90 秒、单次长轮询最多 20 秒。具体值由会话响应下发;客户端 HTTP 读取超时应大于等待时间并留有传输余量。命令处理、事件同步、心跳分别调度,长轮询不阻塞心跳或回执。\n\n若平台已有网关通信协议,可将本文的命令、状态和回执模型映射到已有通道;本稿不要求平台同时维护多套通信机制。\n\n 4. 接口目录\n\n 分类 方法与路径 标记 \n --- --- --- \n 屏会话 POST /api/v1/panels/session 新增建议 \n 刷新会话 POST /api/v1/panels/session/refresh 新增建议 \n 屏信息 GET /api/v1/panels/{panelId} 新增建议 \n 心跳 POST /api/v1/panels/{panelId}/heartbeat 新增建议 \n 家庭/房间 GET /api/v1/homes;GET /api/v1/homes/{homeId}/rooms 扩展建议:接受受限屏身份 \n 初始化快照 GET /api/v1/panels/{panelId}/snapshot 新增建议 \n 增量事件 GET /api/v1/panels/{panelId}/events 新增建议 \n 本地设备登记 POST /api/v1/panels/{panelId}/devices/register 新增建议 \n 本地设备能力更新 PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema 新增建议 \n 设备与物模型 GET /api/v1/devices/{deviceId};GET /api/v1/devices/{deviceId}/control-schema;GET /api/v1/products/{id}/spec 扩展建议:屏权限与模型约定 \n 控制提交 POST /api/v1/devices/{deviceId}/commands 扩展建议:幂等与命令结果 \n 控制查询 GET /api/v1/device-commands/{commandId} 新增建议 \n 屏领取命令 POST /api/v1/panels/{panelId}/commands/pull 新增建议 \n 屏申请执行 POST /api/v1/panels/{panelId}/commands/{commandId}/start 新增建议 \n 屏报告结果 POST /api/v1/panels/{panelId}/commands/{commandId}/report 新增建议 \n 屏上报状态 POST /api/v1/panels/{panelId}/reports 新增建议 \n 场景/规则/AI 现有 scenes、rules、ai 接口 沿用业务入口,扩展执行归属和回报 \n 场景运行查询 GET /api/v1/scene-runs/{sceneRunId} 新增建议 \n 本地场景结果上报 POST /api/v1/panels/{panelId}/scene-runs/report 新增建议 \n 屏配置/内容 GET /api/v1/panels/{panelId}/configuration 新增建议 \n 屏诊断事件 POST /api/v1/panels/{panelId}/diagnostics 新增建议 \n OTA 任务领取 GET /api/v1/panels/{panelId}/ota-tasks/next 新增建议 \n OTA 进度回报 POST /api/v1/ota-tasks/{id}/report 扩展建议 \n 天气/历史/告警 现有 weather、data、alarms 接口 沿用,并限制为当前屏授权范围 \n\n---\n\n","preview":"总体结构、通信方式与接口目录"},{"id":"chapter-1-heading-1","chapter":"chapter-1","label":"1. Base URL","kind":"概览","text":"1. Base URL\n\n text\nhttps://mum.tonzou.cn\n \n\n本文接口路径统一包含 /api/v1 ,与 Base URL 拼接时不重复添加版本前缀。\n\n","preview":"text\nhttps://mum.tonzou.cn\n \n\n本文接口路径统一包含 /api/v1 ,与 Base URL 拼接时不重复添加版本前缀。"},{"id":"chapter-1-heading-2","chapter":"chapter-1","label":"2. 业务关系","kind":"概览","text":"2. 业务关系\n\n mermaid\nflowchart LR\n M[手机 / 网页] --> C[MumIot 平台]\n P[10 寸中控屏] --> 业务查询与云设备控制 C\n P --> 领取命令与同步事件 C\n P --> 本地协议 D[Matter / Zigbee / BLE 设备]\n P --> 登记与状态回报 C\n C --> V[平台已有云设备]\n \n\n- 账号、家庭、房间、平台设备和平台场景由 MumIot 管理。\n- 中控屏负责自己的界面、本地缓存、本地协议连接和实际执行。\n- 每台本地设备在平台上关联唯一执行屏;其他屏通过平台发起控制。\n- 本地断网控制保留,过期远程命令不在恢复联网后补执行。\n- 状态同步和执行命令是两种消息,收到同步事件不会自动再次执行设备动作。\n\n","preview":"mermaid\nflowchart LR\n M[手机 / 网页] --> C[MumIot 平台]\n P[10 寸中控屏] --> 业务查询与云设备控制 C\n P --> 领取命令与同步事件 C\n P --> 本地协议 D[Matter / Zigb"},{"id":"chapter-1-heading-3","chapter":"chapter-1","label":"3. 主通信方式","kind":"概览","text":"3. 主通信方式\n\n本稿以 HTTPS REST 为主,命令领取和事件同步使用有时限的长轮询。屏主动建立连接,不需要平台从公网直接连接屏的地址。\n\n建议默认心跳间隔 30 秒、在线租期 90 秒、单次长轮询最多 20 秒。具体值由会话响应下发;客户端 HTTP 读取超时应大于等待时间并留有传输余量。命令处理、事件同步、心跳分别调度,长轮询不阻塞心跳或回执。\n\n若平台已有网关通信协议,可将本文的命令、状态和回执模型映射到已有通道;本稿不要求平台同时维护多套通信机制。\n\n","preview":"本稿以 HTTPS REST 为主,命令领取和事件同步使用有时限的长轮询。屏主动建立连接,不需要平台从公网直接连接屏的地址。\n\n建议默认心跳间隔 30 秒、在线租期 90 秒、单次长轮询最多 20 秒。具体值由会话响应下发;客户端 HTTP 读取超时应大于等待时间并留有传输余量。"},{"id":"chapter-1-heading-4","chapter":"chapter-1","label":"4. 接口目录","kind":"概览","text":"4. 接口目录\n\n 分类 方法与路径 标记 \n --- --- --- \n 屏会话 POST /api/v1/panels/session 新增建议 \n 刷新会话 POST /api/v1/panels/session/refresh 新增建议 \n 屏信息 GET /api/v1/panels/{panelId} 新增建议 \n 心跳 POST /api/v1/panels/{panelId}/heartbeat 新增建议 \n 家庭/房间 GET /api/v1/homes;GET /api/v1/homes/{homeId}/rooms 扩展建议:接受受限屏身份 \n 初始化快照 GET /api/v1/panels/{panelId}/snapshot 新增建议 \n 增量事件 GET /api/v1/panels/{panelId}/events 新增建议 \n 本地设备登记 POST /api/v1/panels/{panelId}/devices/register 新增建议 \n 本地设备能力更新 PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema 新增建议 \n 设备与物模型 GET /api/v1/devices/{deviceId};GET /api/v1/devices/{deviceId}/control-schema;GET /api/v1/products/{id}/spec 扩展建议:屏权限与模型约定 \n 控制提交 POST /api/v1/devices/{deviceId}/commands 扩展建议:幂等与命令结果 \n 控制查询 GET /api/v1/device-commands/{commandId} 新增建议 \n 屏领取命令 POST /api/v1/panels/{panelId}/commands/pull 新增建议 \n 屏申请执行 POST /api/v1/panels/{panelId}/commands/{commandId}/start 新增建议 \n 屏报告结果 POST /api/v1/panels/{panelId}/commands/{commandId}/report 新增建议 \n 屏上报状态 POST /api/v1/panels/{panelId}/reports 新增建议 \n 场景/规则/AI 现有 scenes、rules、ai 接口 沿用业务入口,扩展执行归属和回报 \n 场景运行查询 GET /api/v1/scene-runs/{sceneRunId} 新增建议 \n 本地场景结果上报 POST /api/v1/panels/{panelId}/scene-runs/report 新增建议 \n 屏配置/内容 GET /api/v1/panels/{panelId}/configuration 新增建议 \n 屏诊断事件 POST /api/v1/panels/{panelId}/diagnostics 新增建议 \n OTA 任务领取 GET /api/v1/panels/{panelId}/ota-tasks/next 新增建议 \n OTA 进度回报 POST /api/v1/ota-tasks/{id}/report 扩展建议 \n 天气/历史/告警 现有 weather、data、alarms 接口 沿用,并限制为当前屏授权范围 \n\n---\n\n","preview":"分类 方法与路径 标记 \n --- --- --- \n 屏会话 POST /api/v1/panels/session 新增建议 \n 刷新会话 POST /api/v1/panels/session/refresh 新增建议 \n 屏信息 G"},{"id":"chapter-2","chapter":"chapter-2","label":"通用约定","kind":"章节","text":"\n 1. 请求头\n\n 名称 类型 必填 说明 \n --- --- --- --- \n Authorization String 是,除另有说明 Bearer 加中控 Access Token \n Content-Type String 有 JSON 请求体时 application/json \n X-MUM-Protocol String 本文新增接口及采用扩展契约的请求 固定为 panel-v1,用于识别本文的请求和响应约定 \n X-Request-Id String 建议 单次请求的跟踪编号,不承担业务去重 \n Idempotency-Key String 本文所有 POST,以及扩展后的业务写请求 一次逻辑操作的唯一编号;重试时保持不变 \n\n幂等范围为当前租户、调用主体、方法、规范化资源路径及 Idempotency-Key。相同键与相同业务内容返回同一操作结果;相同键但不同内容返回 IDEMPOTENCY_CONFLICT 。服务端在产生副作用前原子登记该键,并发重试也只产生一项业务操作。\n\n建议业务幂等记录至少保留 7 天;超过保留期的旧控制请求不得作为自动重试重新提交。命令执行记录与幂等记录的保留期均由平台明确。身份类响应含凭证,按第三节规定处理,不作为普通日志保存。\n\n认证和当前资源权限检查先于幂等结果返回;撤权后不能借旧幂等键取回受限结果或执行许可。长轮询返回命令或事件前也要重查身份,不在等待期间把已经失效的访问继续放行。\n\n对于已有路径,本文新增的必填字段、HTTP 202 和响应模型仅用于明确声明 panel-v1 的调用。原手机/网页请求格式由平台保留兼容,并在内部归入同一设备路由和命令记录;不能直接把新必填字段施加到所有旧客户端。旧调用若没有稳定请求键,不能声称具备本文定义的客户端重试去重保证。\n\n 2. 成功响应\n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {}\n}\n \n\n单条结果在 data;分页业务沿用现有 PageResult。中控快照、事件、命令领取及批量上报采用各自定义的数据结构,不强套 PageResult。\n\n业务 code=0 只表示该接口请求处理成功。命令是否执行,以 data.state、data.terminal 及执行证据为准。\n\n 3. 错误响应\n\n json\n{\n \"code\": 40901,\n \"message\": \"请求编号已用于另一项操作\",\n \"data\": {\n \"errorCode\": \"IDEMPOTENCY_CONFLICT\",\n \"requestId\": \"req-example-01\",\n \"retryable\": false\n }\n}\n \n\n错误使用对应非 2xx HTTP 状态。若现有业务接口采用其他错误结构,由平台提供准确映射;屏不能仅凭 HTTP 200 判定设备控制成功。\n\n 4. 标识与时间\n\n 类型 约定 \n --- --- \n homeId、memberId、productId、数据库 id 本文新增响应统一为十进制字符串;现有 Long 字段由客户端按原契约无损解析 \n deviceId 平台业务设备 ID;与数据库主键 id 分开 \n panelId 平台中控 ID \n localDeviceId 屏内持久化设备 ID;重启不变,与本地协议绑定关联 \n installationId 当前应用安装实例 ID;重装后的恢复需验证既有终端身份 \n bootId 每次进程启动生成的新 UUID \n requestId、commandId、reportId、eventId 字符串,不依赖可解析的内部格式 \n revision、sequence 非负整数;事件游标为不透明字符串,不能由客户端计算 \n 时间戳 Unix 毫秒,Long;expiresIn、waitSeconds、leaseSeconds 为秒 \n\n设备离线、平台连接中断和状态陈旧分别表示,不互相替代。平台响应中的 serverTime 用于估计时差,执行截止时间在屏内换算为单调时钟,防止系统时间跳变使过期命令继续执行。\n\n---\n\n","preview":"请求头、幂等、响应和标识"},{"id":"chapter-2-heading-1","chapter":"chapter-2","label":"1. 请求头","kind":"通用约定","text":"1. 请求头\n\n 名称 类型 必填 说明 \n --- --- --- --- \n Authorization String 是,除另有说明 Bearer 加中控 Access Token \n Content-Type String 有 JSON 请求体时 application/json \n X-MUM-Protocol String 本文新增接口及采用扩展契约的请求 固定为 panel-v1,用于识别本文的请求和响应约定 \n X-Request-Id String 建议 单次请求的跟踪编号,不承担业务去重 \n Idempotency-Key String 本文所有 POST,以及扩展后的业务写请求 一次逻辑操作的唯一编号;重试时保持不变 \n\n幂等范围为当前租户、调用主体、方法、规范化资源路径及 Idempotency-Key。相同键与相同业务内容返回同一操作结果;相同键但不同内容返回 IDEMPOTENCY_CONFLICT 。服务端在产生副作用前原子登记该键,并发重试也只产生一项业务操作。\n\n建议业务幂等记录至少保留 7 天;超过保留期的旧控制请求不得作为自动重试重新提交。命令执行记录与幂等记录的保留期均由平台明确。身份类响应含凭证,按第三节规定处理,不作为普通日志保存。\n\n认证和当前资源权限检查先于幂等结果返回;撤权后不能借旧幂等键取回受限结果或执行许可。长轮询返回命令或事件前也要重查身份,不在等待期间把已经失效的访问继续放行。\n\n对于已有路径,本文新增的必填字段、HTTP 202 和响应模型仅用于明确声明 panel-v1 的调用。原手机/网页请求格式由平台保留兼容,并在内部归入同一设备路由和命令记录;不能直接把新必填字段施加到所有旧客户端。旧调用若没有稳定请求键,不能声称具备本文定义的客户端重试去重保证。\n\n","preview":"名称 类型 必填 说明 \n --- --- --- --- \n Authorization String 是,除另有说明 Bearer 加中控 Access Token \n Content-Type String 有 JSON 请求体时 "},{"id":"chapter-2-heading-2","chapter":"chapter-2","label":"2. 成功响应","kind":"通用约定","text":"2. 成功响应\n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {}\n}\n \n\n单条结果在 data;分页业务沿用现有 PageResult。中控快照、事件、命令领取及批量上报采用各自定义的数据结构,不强套 PageResult。\n\n业务 code=0 只表示该接口请求处理成功。命令是否执行,以 data.state、data.terminal 及执行证据为准。\n\n","preview":"json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {}\n}\n \n\n单条结果在 data;分页业务沿用现有 PageResult。中控快照、事件、命令领取及批量上报采用各自定义的数据结构,不强套 PageResult。\n"},{"id":"chapter-2-heading-3","chapter":"chapter-2","label":"3. 错误响应","kind":"通用约定","text":"3. 错误响应\n\n json\n{\n \"code\": 40901,\n \"message\": \"请求编号已用于另一项操作\",\n \"data\": {\n \"errorCode\": \"IDEMPOTENCY_CONFLICT\",\n \"requestId\": \"req-example-01\",\n \"retryable\": false\n }\n}\n \n\n错误使用对应非 2xx HTTP 状态。若现有业务接口采用其他错误结构,由平台提供准确映射;屏不能仅凭 HTTP 200 判定设备控制成功。\n\n","preview":"json\n{\n \"code\": 40901,\n \"message\": \"请求编号已用于另一项操作\",\n \"data\": {\n \"errorCode\": \"IDEMPOTENCY_CONFLICT\",\n \"requestId\": \"req-example-01\","},{"id":"chapter-2-heading-4","chapter":"chapter-2","label":"4. 标识与时间","kind":"通用约定","text":"4. 标识与时间\n\n 类型 约定 \n --- --- \n homeId、memberId、productId、数据库 id 本文新增响应统一为十进制字符串;现有 Long 字段由客户端按原契约无损解析 \n deviceId 平台业务设备 ID;与数据库主键 id 分开 \n panelId 平台中控 ID \n localDeviceId 屏内持久化设备 ID;重启不变,与本地协议绑定关联 \n installationId 当前应用安装实例 ID;重装后的恢复需验证既有终端身份 \n bootId 每次进程启动生成的新 UUID \n requestId、commandId、reportId、eventId 字符串,不依赖可解析的内部格式 \n revision、sequence 非负整数;事件游标为不透明字符串,不能由客户端计算 \n 时间戳 Unix 毫秒,Long;expiresIn、waitSeconds、leaseSeconds 为秒 \n\n设备离线、平台连接中断和状态陈旧分别表示,不互相替代。平台响应中的 serverTime 用于估计时差,执行截止时间在屏内换算为单调时钟,防止系统时间跳变使过期命令继续执行。\n\n---\n\n","preview":"类型 约定 \n --- --- \n homeId、memberId、productId、数据库 id 本文新增响应统一为十进制字符串;现有 Long 字段由客户端按原契约无损解析 \n deviceId 平台业务设备 ID;与数据库主键 id 分开 \n pa"},{"id":"chapter-3","chapter":"chapter-3","label":"中控身份与会话","kind":"章节","text":"\n 1. 身份模型\n\n 新增建议。 平台为屏配置独立的终端身份及家庭关联,签发 ROLE_PANEL 身份。AppSecret 继续留在平台服务端;屏端不持有全应用共享的长期 Secret。\n\n开发阶段为已配置测试板预置终端身份与测试家庭,联网后自动建立会话,不增加用户输入激活码或人工审批。预置终端凭证与对应平台记录由平台方通过受控配置交付;序列号仅用于定位和核对,不能单凭序列号获得授权。终端凭证不能直接固化成所有 APK 共用值。\n\n家庭、会员关联及可执行设备范围由平台决定,不能由屏在请求里任意指定。设备、家庭或成员关系失效后,平台在每次敏感调用及消息投递时重新检查。\n\n 建议权限 可执行内容 \n --- --- \n panel.read 读取自身信息与当前家庭快照 \n panel.report 上报自身心跳及负责设备的状态 \n device.read 读取已授权家庭设备与物模型 \n device.control 控制授权范围内设备 \n device.manage 管理被授予的设备与房间操作 \n gateway.execute 领取并执行明确分配给本屏的本地设备命令 \n scene.use 查看、执行授权场景;编辑权限另由平台控制 \n ota.read / ota.report 查询自己的升级任务并回报 \n\n本稿建议允许受限屏身份调用既有家庭、设备、场景和必要涂鸦/AI 业务入口。平台负责将屏身份关联到当前家庭的业务上下文,不能把屏 Token 当成管理员 Token,也不要求把会员密码下发到屏。\n\n 2. 建立会话\n\n POST /api/v1/panels/session — 新增建议 \n\n鉴权: Authorization: PanelBootstrap \u003c终端初始化凭证> \n\n参数:body: PanelSessionRequest\n\n 字段 类型 必填 说明 \n --- --- --- --- \n serialNumber String 是 与预置终端记录核对 \n installationId String 是 安装实例 \n bootId String 是 当前启动实例 \n model String 是 屏型号 \n appVersion String 是 应用版本 \n protocolVersion String 是 本文建议为 1 \n features List\u003cString> 是 本屏实际支持的协议/功能,不等同于外设在线 \n\n json\n{\n \"serialNumber\": \"PANEL-DEMO-001\",\n \"installationId\": \"install-example-01\",\n \"bootId\": \"boot-example-01\",\n \"model\": \"RK3568-10INCH\",\n \"appVersion\": \"1.0.0\",\n \"protocolVersion\": \"1\",\n \"features\": [\"MATTER\", \"ZIGBEE\", \"BLE_GATT\"]\n}\n \n\n响应 data: PanelSession\n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {\n \"panelId\": \"panel-001\",\n \"gatewayDeviceId\": \"gateway-001\",\n \"homeId\": \"10001\",\n \"memberId\": \"20001\",\n \"role\": \"GATEWAY\",\n \"accessToken\": \"\u003cpanel-access-token>\",\n \"refreshToken\": \"\u003cpanel-refresh-token>\",\n \"tokenType\": \"Bearer\",\n \"expiresIn\": 3600,\n \"refreshExpiresIn\": 2592000,\n \"stateEpoch\": 12,\n \"heartbeatIntervalSeconds\": 30,\n \"leaseSeconds\": 90,\n \"maxWaitSeconds\": 20,\n \"serverTime\": 1792123200000\n }\n}\n \n\n上述有效期仅为建议示例,客户端按实际响应处理。role 为 GATEWAY 或 DISPLAY;同一家庭可有多个网关,但每台本地设备只有一个 executorPanelId。\n\nstateEpoch 是平台为当前 bootId 分配的状态世代。同一 bootId 重试建立会话得到同一世代;新的 bootId 建立有效会话后,旧会话不能继续领取新命令或覆盖当前状态。设备执行中的旧记录按第六节处理,不自动交给新实例重做。\n\n 3. 刷新会话\n\n POST /api/v1/panels/session/refresh — 新增建议 \n\n鉴权:请求体中的 refreshToken;不要求尚未过期的 Access Token。\n\n 字段 类型 必填 说明 \n --- --- --- --- \n refreshToken String 是 当前刷新凭证 \n bootId String 是 必须与当前有效启动实例一致 \n\n json\n{\n \"refreshToken\": \"\u003cpanel-refresh-token>\",\n \"bootId\": \"boot-example-01\"\n}\n \n\n成功 data 返回新的 accessToken、refreshToken、expiresIn、refreshExpiresIn 和 serverTime,保留原 panelId/homeId/stateEpoch。\n\n建议刷新凭证轮换,并为同一旧凭证与同一 Idempotency-Key 保留 120 秒的加密响应重放窗口。刷新响应丢失时可用原请求取回同一结果;不同内容不得复用该键。窗口外或凭证撤销后返回明确错误,不放宽家庭范围。\n\n 4. 屏信息与心跳\n\n GET /api/v1/panels/{panelId} — 新增建议 \n\n鉴权:Bearer;panelId 必须为自身。响应含 panelId、gatewayDeviceId、homeId、role、enabled、online、lastSeenAt、features。\n\n POST /api/v1/panels/{panelId}/heartbeat — 新增建议 \n\n参数:body: PanelHeartbeat\n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"appVersion\": \"1.0.0\",\n \"networkType\": \"ETHERNET\",\n \"uptimeSeconds\": 120,\n \"deviceCounts\": {\"total\": 6, \"online\": 5},\n \"protocols\": {\n \"matter\": \"READY\",\n \"zigbee\": \"NOT_CONNECTED\",\n \"ble\": \"READY\"\n }\n}\n \n\nbootId 必填;其余运行字段建议上报。协议状态为 READY、NOT_CONNECTED、ERROR 或 UNSUPPORTED。缺少外设只影响相应协议,不使整个屏启动失败。\n\n响应 data:\n\n json\n{\n \"serverTime\": 1792123230000,\n \"leaseSeconds\": 90,\n \"configurationRevision\": 4,\n \"homeRevision\": 28\n}\n \n\n租期从平台收到有效心跳时计算。领取命令或其他 API 调用不隐式替代心跳。屏离线后,平台拒绝新的远程本地设备控制;本地离线执行不以云端租期为前提。\n\n---\n\n","preview":"终端身份、会话、续期与心跳"},{"id":"chapter-3-heading-1","chapter":"chapter-3","label":"1. 身份模型","kind":"中控身份与会话","text":"1. 身份模型\n\n 新增建议。 平台为屏配置独立的终端身份及家庭关联,签发 ROLE_PANEL 身份。AppSecret 继续留在平台服务端;屏端不持有全应用共享的长期 Secret。\n\n开发阶段为已配置测试板预置终端身份与测试家庭,联网后自动建立会话,不增加用户输入激活码或人工审批。预置终端凭证与对应平台记录由平台方通过受控配置交付;序列号仅用于定位和核对,不能单凭序列号获得授权。终端凭证不能直接固化成所有 APK 共用值。\n\n家庭、会员关联及可执行设备范围由平台决定,不能由屏在请求里任意指定。设备、家庭或成员关系失效后,平台在每次敏感调用及消息投递时重新检查。\n\n 建议权限 可执行内容 \n --- --- \n panel.read 读取自身信息与当前家庭快照 \n panel.report 上报自身心跳及负责设备的状态 \n device.read 读取已授权家庭设备与物模型 \n device.control 控制授权范围内设备 \n device.manage 管理被授予的设备与房间操作 \n gateway.execute 领取并执行明确分配给本屏的本地设备命令 \n scene.use 查看、执行授权场景;编辑权限另由平台控制 \n ota.read / ota.report 查询自己的升级任务并回报 \n\n本稿建议允许受限屏身份调用既有家庭、设备、场景和必要涂鸦/AI 业务入口。平台负责将屏身份关联到当前家庭的业务上下文,不能把屏 Token 当成管理员 Token,也不要求把会员密码下发到屏。\n\n","preview":"新增建议。 平台为屏配置独立的终端身份及家庭关联,签发 ROLE_PANEL 身份。AppSecret 继续留在平台服务端;屏端不持有全应用共享的长期 Secret。\n\n开发阶段为已配置测试板预置终端身份与测试家庭,联网后自动建立会话,不增加用户输入激活码或人工审批。预置"},{"id":"chapter-3-heading-2","chapter":"chapter-3","label":"2. 建立会话","kind":"中控身份与会话","text":"2. 建立会话\n\n POST /api/v1/panels/session — 新增建议 \n\n鉴权: Authorization: PanelBootstrap \u003c终端初始化凭证> \n\n参数:body: PanelSessionRequest\n\n 字段 类型 必填 说明 \n --- --- --- --- \n serialNumber String 是 与预置终端记录核对 \n installationId String 是 安装实例 \n bootId String 是 当前启动实例 \n model String 是 屏型号 \n appVersion String 是 应用版本 \n protocolVersion String 是 本文建议为 1 \n features List\u003cString> 是 本屏实际支持的协议/功能,不等同于外设在线 \n\n json\n{\n \"serialNumber\": \"PANEL-DEMO-001\",\n \"installationId\": \"install-example-01\",\n \"bootId\": \"boot-example-01\",\n \"model\": \"RK3568-10INCH\",\n \"appVersion\": \"1.0.0\",\n \"protocolVersion\": \"1\",\n \"features\": [\"MATTER\", \"ZIGBEE\", \"BLE_GATT\"]\n}\n \n\n响应 data: PanelSession\n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {\n \"panelId\": \"panel-001\",\n \"gatewayDeviceId\": \"gateway-001\",\n \"homeId\": \"10001\",\n \"memberId\": \"20001\",\n \"role\": \"GATEWAY\",\n \"accessToken\": \"\u003cpanel-access-token>\",\n \"refreshToken\": \"\u003cpanel-refresh-token>\",\n \"tokenType\": \"Bearer\",\n \"expiresIn\": 3600,\n \"refreshExpiresIn\": 2592000,\n \"stateEpoch\": 12,\n \"heartbeatIntervalSeconds\": 30,\n \"leaseSeconds\": 90,\n \"maxWaitSeconds\": 20,\n \"serverTime\": 1792123200000\n }\n}\n \n\n上述有效期仅为建议示例,客户端按实际响应处理。role 为 GATEWAY 或 DISPLAY;同一家庭可有多个网关,但每台本地设备只有一个 executorPanelId。\n\nstateEpoch 是平台为当前 bootId 分配的状态世代。同一 bootId 重试建立会话得到同一世代;新的 bootId 建立有效会话后,旧会话不能继续领取新命令或覆盖当前状态。设备执行中的旧记录按第六节处理,不自动交给新实例重做。\n\n","preview":"POST /api/v1/panels/session — 新增建议 \n\n鉴权: Authorization: PanelBootstrap \u003c终端初始化凭证> \n\n参数:body: PanelSessionRequest\n\n 字段 类型 必填 说明 \n ---"},{"id":"chapter-3-heading-3","chapter":"chapter-3","label":"3. 刷新会话","kind":"中控身份与会话","text":"3. 刷新会话\n\n POST /api/v1/panels/session/refresh — 新增建议 \n\n鉴权:请求体中的 refreshToken;不要求尚未过期的 Access Token。\n\n 字段 类型 必填 说明 \n --- --- --- --- \n refreshToken String 是 当前刷新凭证 \n bootId String 是 必须与当前有效启动实例一致 \n\n json\n{\n \"refreshToken\": \"\u003cpanel-refresh-token>\",\n \"bootId\": \"boot-example-01\"\n}\n \n\n成功 data 返回新的 accessToken、refreshToken、expiresIn、refreshExpiresIn 和 serverTime,保留原 panelId/homeId/stateEpoch。\n\n建议刷新凭证轮换,并为同一旧凭证与同一 Idempotency-Key 保留 120 秒的加密响应重放窗口。刷新响应丢失时可用原请求取回同一结果;不同内容不得复用该键。窗口外或凭证撤销后返回明确错误,不放宽家庭范围。\n\n","preview":"POST /api/v1/panels/session/refresh — 新增建议 \n\n鉴权:请求体中的 refreshToken;不要求尚未过期的 Access Token。\n\n 字段 类型 必填 说明 \n --- --- --- --- \n refres"},{"id":"chapter-3-heading-4","chapter":"chapter-3","label":"4. 屏信息与心跳","kind":"中控身份与会话","text":"4. 屏信息与心跳\n\n GET /api/v1/panels/{panelId} — 新增建议 \n\n鉴权:Bearer;panelId 必须为自身。响应含 panelId、gatewayDeviceId、homeId、role、enabled、online、lastSeenAt、features。\n\n POST /api/v1/panels/{panelId}/heartbeat — 新增建议 \n\n参数:body: PanelHeartbeat\n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"appVersion\": \"1.0.0\",\n \"networkType\": \"ETHERNET\",\n \"uptimeSeconds\": 120,\n \"deviceCounts\": {\"total\": 6, \"online\": 5},\n \"protocols\": {\n \"matter\": \"READY\",\n \"zigbee\": \"NOT_CONNECTED\",\n \"ble\": \"READY\"\n }\n}\n \n\nbootId 必填;其余运行字段建议上报。协议状态为 READY、NOT_CONNECTED、ERROR 或 UNSUPPORTED。缺少外设只影响相应协议,不使整个屏启动失败。\n\n响应 data:\n\n json\n{\n \"serverTime\": 1792123230000,\n \"leaseSeconds\": 90,\n \"configurationRevision\": 4,\n \"homeRevision\": 28\n}\n \n\n租期从平台收到有效心跳时计算。领取命令或其他 API 调用不隐式替代心跳。屏离线后,平台拒绝新的远程本地设备控制;本地离线执行不以云端租期为前提。\n\n---\n\n","preview":"GET /api/v1/panels/{panelId} — 新增建议 \n\n鉴权:Bearer;panelId 必须为自身。响应含 panelId、gatewayDeviceId、homeId、role、enabled、online、lastSeenAt、features。\n\n"},{"id":"chapter-4","chapter":"chapter-4","label":"家庭快照与事件同步","kind":"章节","text":"\n 1. 既有家庭、房间接口\n\n GET /api/v1/homes \n GET /api/v1/homes/{homeId}/rooms \n\n标记:扩展建议,仅补充屏身份访问规则。\n\n屏只能读取会话绑定的家庭。既有 memberId 等参数即使保留,也必须与屏身份关联校验,不能作为越权选择器。改名、房间编辑等沿用现有接口并受相应管理权限约束。\n\n 2. 初始化快照\n\n GET /api/v1/panels/{panelId}/snapshot — 新增建议 \n\n参数:query: pageToken(首请求省略)。\n\n响应 data: PanelSnapshotPage\n\n json\n{\n \"snapshotId\": \"snapshot-001\",\n \"homeId\": \"10001\",\n \"homeName\": \"示例家庭\",\n \"homeRevision\": 28,\n \"cursor\": \"cursor-snapshot-001\",\n \"serverTime\": 1792123200000,\n \"expiresIn\": 120,\n \"rooms\": [\n {\"id\": \"30001\", \"name\": \"客厅\", \"sortOrder\": 0}\n ],\n \"devices\": [\n {\n \"id\": \"40001\",\n \"deviceId\": \"device-001\",\n \"deviceName\": \"客厅灯\",\n \"homeId\": \"10001\",\n \"roomId\": \"30001\",\n \"productId\": \"50001\",\n \"platform\": \"local\",\n \"deviceType\": \"light\",\n \"executionSource\": \"LOCAL_PANEL\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevision\": 3,\n \"schemaRevision\": 2,\n \"online\": true,\n \"stateFresh\": true,\n \"properties\": {\"switch_1\": true},\n \"stateVersion\": 80,\n \"reportedAt\": 1792123195000,\n \"receivedAt\": 1792123195100,\n \"quality\": \"GOOD\"\n }\n ],\n \"scenes\": [],\n \"nextPageToken\": null\n}\n \n\n 字段 类型 说明 \n --- --- --- \n snapshotId String 本轮一致性快照标识 \n homeRevision Long 家庭目录与配置版本,不作为设备状态序号 \n cursor String 快照对应事件位置;后续只应用该位置之后的事件 \n rooms/devices/scenes List 本页包含的对象,可能为空 \n nextPageToken String/null 后续页;null 表示已结束 \n\n平台需在同一逻辑版本上构造所有分页,并使 cursor 与该快照一致,避免分页期间丢失变化。建议单快照缓存 120 秒、每页最多 200 个对象。屏全部拉取成功后原子替换对应家庭的云缓存;失败时保留上一份可用数据。分页过期返回 SNAPSHOT_EXPIRED,重新开始。\n\n删除平台缓存不自动清除屏的本地配网密钥。完整快照里没出现的平台设备可以退出云目录;本地物理解绑仍需明确动作。\n\n 3. 增量事件\n\n GET /api/v1/panels/{panelId}/events — 新增建议 \n\n 查询参数 类型 必填 说明 \n --- --- --- --- \n after String 是 上次已持久化的游标,首次用快照 cursor \n waitSeconds Integer 否 默认 20,范围 0–20 \n limit Integer 否 默认 100,上限 200 \n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {\n \"events\": [\n {\n \"eventId\": \"event-001\",\n \"type\": \"DEVICE_STATE\",\n \"homeId\": \"10001\",\n \"deviceId\": \"device-001\",\n \"createdAt\": 1792123230000,\n \"originPanelId\": \"panel-001\",\n \"reportId\": \"report-001\",\n \"stateVersion\": 81,\n \"payload\": {\n \"online\": true,\n \"properties\": {\"switch_1\": false},\n \"reportedAt\": 1792123229500,\n \"receivedAt\": 1792123230000,\n \"quality\": \"GOOD\"\n }\n }\n ],\n \"nextCursor\": \"cursor-event-001\",\n \"hasMore\": false,\n \"serverTime\": 1792123230000\n }\n}\n \n\n事件类型建议包括 ROOM_UPSERT、ROOM_DELETED、DEVICE_UPSERT、DEVICE_DELETED、DEVICE_STATE、SCENE_UPSERT、SCENE_DELETED、COMMAND_UPDATED、CONFIGURATION_CHANGED、OTA_TASK_AVAILABLE、HOME_ACCESS_REVOKED。\n\nUPSERT 携带更新后的完整公开对象;DELETED 携带对应 ID 和 revision;STATE 按第七节合并;COMMAND_UPDATED 携带 CommandResult;配置与 OTA 事件只提示重新读取相应接口。权限撤销事件可尽力通知,后续请求仍须独立拒绝,不能依赖客户端收到事件才生效。\n\n平台按家庭顺序产生事件,屏落库和推进游标在同一事务中完成;重复 eventId 不重复应用。建议游标保留至少 24 小时,过期返回 HTTP 410 / CURSOR_EXPIRED,屏重新拉完整快照。没有事件时 events=[],nextCursor 保持已确认位置。hasMore=true 时立即继续获取,不再等待。\n\n---\n\n","preview":"家庭快照、分页与增量事件"},{"id":"chapter-4-heading-1","chapter":"chapter-4","label":"1. 既有家庭、房间接口","kind":"家庭快照与事件同步","text":"1. 既有家庭、房间接口\n\n GET /api/v1/homes \n GET /api/v1/homes/{homeId}/rooms \n\n标记:扩展建议,仅补充屏身份访问规则。\n\n屏只能读取会话绑定的家庭。既有 memberId 等参数即使保留,也必须与屏身份关联校验,不能作为越权选择器。改名、房间编辑等沿用现有接口并受相应管理权限约束。\n\n","preview":"GET /api/v1/homes \n GET /api/v1/homes/{homeId}/rooms \n\n标记:扩展建议,仅补充屏身份访问规则。\n\n屏只能读取会话绑定的家庭。既有 memberId 等参数即使保留,也必须与屏身份关联校验,不能作为越权选择器。改名、房"},{"id":"chapter-4-heading-2","chapter":"chapter-4","label":"2. 初始化快照","kind":"家庭快照与事件同步","text":"2. 初始化快照\n\n GET /api/v1/panels/{panelId}/snapshot — 新增建议 \n\n参数:query: pageToken(首请求省略)。\n\n响应 data: PanelSnapshotPage\n\n json\n{\n \"snapshotId\": \"snapshot-001\",\n \"homeId\": \"10001\",\n \"homeName\": \"示例家庭\",\n \"homeRevision\": 28,\n \"cursor\": \"cursor-snapshot-001\",\n \"serverTime\": 1792123200000,\n \"expiresIn\": 120,\n \"rooms\": [\n {\"id\": \"30001\", \"name\": \"客厅\", \"sortOrder\": 0}\n ],\n \"devices\": [\n {\n \"id\": \"40001\",\n \"deviceId\": \"device-001\",\n \"deviceName\": \"客厅灯\",\n \"homeId\": \"10001\",\n \"roomId\": \"30001\",\n \"productId\": \"50001\",\n \"platform\": \"local\",\n \"deviceType\": \"light\",\n \"executionSource\": \"LOCAL_PANEL\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevision\": 3,\n \"schemaRevision\": 2,\n \"online\": true,\n \"stateFresh\": true,\n \"properties\": {\"switch_1\": true},\n \"stateVersion\": 80,\n \"reportedAt\": 1792123195000,\n \"receivedAt\": 1792123195100,\n \"quality\": \"GOOD\"\n }\n ],\n \"scenes\": [],\n \"nextPageToken\": null\n}\n \n\n 字段 类型 说明 \n --- --- --- \n snapshotId String 本轮一致性快照标识 \n homeRevision Long 家庭目录与配置版本,不作为设备状态序号 \n cursor String 快照对应事件位置;后续只应用该位置之后的事件 \n rooms/devices/scenes List 本页包含的对象,可能为空 \n nextPageToken String/null 后续页;null 表示已结束 \n\n平台需在同一逻辑版本上构造所有分页,并使 cursor 与该快照一致,避免分页期间丢失变化。建议单快照缓存 120 秒、每页最多 200 个对象。屏全部拉取成功后原子替换对应家庭的云缓存;失败时保留上一份可用数据。分页过期返回 SNAPSHOT_EXPIRED,重新开始。\n\n删除平台缓存不自动清除屏的本地配网密钥。完整快照里没出现的平台设备可以退出云目录;本地物理解绑仍需明确动作。\n\n","preview":"GET /api/v1/panels/{panelId}/snapshot — 新增建议 \n\n参数:query: pageToken(首请求省略)。\n\n响应 data: PanelSnapshotPage\n\n json\n{\n \"snapshotId\": \"snapshot"},{"id":"chapter-4-heading-3","chapter":"chapter-4","label":"3. 增量事件","kind":"家庭快照与事件同步","text":"3. 增量事件\n\n GET /api/v1/panels/{panelId}/events — 新增建议 \n\n 查询参数 类型 必填 说明 \n --- --- --- --- \n after String 是 上次已持久化的游标,首次用快照 cursor \n waitSeconds Integer 否 默认 20,范围 0–20 \n limit Integer 否 默认 100,上限 200 \n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {\n \"events\": [\n {\n \"eventId\": \"event-001\",\n \"type\": \"DEVICE_STATE\",\n \"homeId\": \"10001\",\n \"deviceId\": \"device-001\",\n \"createdAt\": 1792123230000,\n \"originPanelId\": \"panel-001\",\n \"reportId\": \"report-001\",\n \"stateVersion\": 81,\n \"payload\": {\n \"online\": true,\n \"properties\": {\"switch_1\": false},\n \"reportedAt\": 1792123229500,\n \"receivedAt\": 1792123230000,\n \"quality\": \"GOOD\"\n }\n }\n ],\n \"nextCursor\": \"cursor-event-001\",\n \"hasMore\": false,\n \"serverTime\": 1792123230000\n }\n}\n \n\n事件类型建议包括 ROOM_UPSERT、ROOM_DELETED、DEVICE_UPSERT、DEVICE_DELETED、DEVICE_STATE、SCENE_UPSERT、SCENE_DELETED、COMMAND_UPDATED、CONFIGURATION_CHANGED、OTA_TASK_AVAILABLE、HOME_ACCESS_REVOKED。\n\nUPSERT 携带更新后的完整公开对象;DELETED 携带对应 ID 和 revision;STATE 按第七节合并;COMMAND_UPDATED 携带 CommandResult;配置与 OTA 事件只提示重新读取相应接口。权限撤销事件可尽力通知,后续请求仍须独立拒绝,不能依赖客户端收到事件才生效。\n\n平台按家庭顺序产生事件,屏落库和推进游标在同一事务中完成;重复 eventId 不重复应用。建议游标保留至少 24 小时,过期返回 HTTP 410 / CURSOR_EXPIRED,屏重新拉完整快照。没有事件时 events=[],nextCursor 保持已确认位置。hasMore=true 时立即继续获取,不再等待。\n\n---\n\n","preview":"GET /api/v1/panels/{panelId}/events — 新增建议 \n\n 查询参数 类型 必填 说明 \n --- --- --- --- \n after String 是 上次已持久化的游标,首次用快照 cursor \n wa"},{"id":"chapter-5","chapter":"chapter-5","label":"设备登记与标准控制模型","kind":"章节","text":"\n 1. 本地设备登记\n\n POST /api/v1/panels/{panelId}/devices/register — 新增建议 \n\n鉴权:本屏 gateway.execute 及被授予的登记权限。只登记实际归本屏执行的设备。\n\n参数:body: LocalDeviceRegistration\n\n 字段 类型 必填 说明 \n --- --- --- --- \n localDeviceId String 是 屏内稳定设备 ID \n deviceName String 是 名称 \n protocol String 是 MATTER / ZIGBEE / BLE_GATT / BLUETOOTH_MESH \n deviceType String 是 平台约定的设备分类 \n roomId String 否 当前家庭内房间 \n manufacturer/model String 否 型号资料 \n productId String 否 已有平台产品;必须属于允许的产品范围 \n schema Object 是 包含 properties/actions;登记时不指定平台 schemaRevision,不上报推测能力 \n\n json\n{\n \"localDeviceId\": \"local-device-001\",\n \"deviceName\": \"客厅灯\",\n \"protocol\": \"MATTER\",\n \"deviceType\": \"light\",\n \"roomId\": \"30001\",\n \"manufacturer\": \"Example\",\n \"model\": \"LIGHT-A\",\n \"schema\": {\n \"properties\": [\n {\n \"code\": \"switch_1\",\n \"name\": \"开关\",\n \"type\": \"bool\",\n \"access\": [\"read\", \"write\", \"notify\"],\n \"semantic\": \"ON_OFF\",\n \"channel\": \"1\",\n \"completionMode\": \"STATE\"\n }\n ],\n \"actions\": []\n }\n}\n \n\n响应 data:\n\n json\n{\n \"id\": \"40001\",\n \"deviceId\": \"device-001\",\n \"productId\": \"50001\",\n \"homeId\": \"10001\",\n \"parentDeviceId\": \"gateway-001\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevision\": 3,\n \"schemaRevision\": 2,\n \"duplicate\": false\n}\n \n\n平台以 panelId + localDeviceId 识别同一登记,不重复创建设备。重复登记相同资料返回同一映射;已登记后名称/房间等资料通过现有设备更新接口处理,schema 通过下方能力更新接口处理,不能借重新登记覆盖正在使用的物模型。没有 productId 时平台按已配置产品模板或确认后的 schema 建立对应产品;无法匹配则返回明确错误,不把空能力设备标成可控。\n\n平台维护设备 id、deviceId、父网关和执行归属,屏不传本地网络密钥、Matter Fabric 密钥或用户口令。端点、属性码和本地协议地址的映射持久化在屏内。\n\n PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema — 新增建议 \n\n请求体含 expectedSchemaRevision、routeRevision、properties、actions;properties/actions 使用下方定义。平台核对执行归属及期望版本,原子保存并返回新的 schemaRevision。同一幂等请求返回同一版本,版本不符返回 SCHEMA_CHANGED。\n\n更新影响尚未开始的旧模型命令时,旧命令在执行前失败并给出 SCHEMA_CHANGED;已经开始的命令按原模型快照保留执行证据。旧模型状态不得覆盖语义已变化的新属性。\n\n 2. 控制模型\n\n GET /api/v1/devices/{deviceId}/control-schema — 扩展建议 \n\n路径沿用;以下 data 结构为补充建议,需要与平台实际模型核对。\n\n json\n{\n \"deviceId\": \"device-001\",\n \"productId\": \"50001\",\n \"schemaRevision\": 2,\n \"properties\": [\n {\n \"code\": \"switch_1\",\n \"dpId\": \"1\",\n \"name\": \"开关\",\n \"type\": \"bool\",\n \"access\": [\"read\", \"write\", \"notify\"],\n \"semantic\": \"ON_OFF\",\n \"channel\": \"1\",\n \"completionMode\": \"STATE\"\n },\n {\n \"code\": \"brightness\",\n \"name\": \"亮度\",\n \"type\": \"int\",\n \"access\": [\"read\", \"write\", \"notify\"],\n \"semantic\": \"BRIGHTNESS\",\n \"channel\": \"1\",\n \"min\": 0,\n \"max\": 100,\n \"step\": 1,\n \"unit\": \"%\",\n \"completionMode\": \"STATE\"\n }\n ],\n \"actions\": []\n}\n \n\n 字段 类型 说明 \n --- --- --- \n code String 设备内唯一业务属性码,命令使用该值 \n dpId String 可选,平台原始功能点标识 \n type String bool / int / float / enum / string / json \n access List\u003cString> read、write、notify;产品旧模型若是字符串,由平台明确转换 \n semantic String 可选的标准能力语义,如 ON_OFF、BRIGHTNESS \n channel String 稳定功能通道,不自动等于 Matter endpoint \n min/max/step/unit 对应数值/String 数值型属性的约束,统一为对外业务单位 \n enumValues List 枚举项,每项含 value 和 label \n completionMode String STATE:新状态确认;ACK_ONLY:仅能确认协议接受 \n actions List\u003cActionSpec> 无持久目标值的动作,例如停止;含 code、name、parameters、completionMode \n\nActionSpec.parameters 按参数名列出 type、required 和适用的 min/max/step/enumValues;无参数动作使用空对象。请求中的参数必须符合该定义,不能让任意私有 JSON 绕过能力约束。\n\ncontrol-schema 的数值已经是业务值,scale=1;若物模型原始值存在倍率、偏移或反向含义,由平台云驱动或屏本地驱动转换,不能在两端重复转换。dpId 只是映射信息,不保证与 code 数值或格式一致。\n\n请求必须携带所依据的 schemaRevision。版本变化后,旧参数不得照常发送,返回 SCHEMA_CHANGED 并重新读取。不支持的属性明确显示不可控,不猜测用途。\n\n---\n\n","preview":"设备登记、能力更新与控制模型"},{"id":"chapter-5-heading-1","chapter":"chapter-5","label":"1. 本地设备登记","kind":"设备登记与标准控制模型","text":"1. 本地设备登记\n\n POST /api/v1/panels/{panelId}/devices/register — 新增建议 \n\n鉴权:本屏 gateway.execute 及被授予的登记权限。只登记实际归本屏执行的设备。\n\n参数:body: LocalDeviceRegistration\n\n 字段 类型 必填 说明 \n --- --- --- --- \n localDeviceId String 是 屏内稳定设备 ID \n deviceName String 是 名称 \n protocol String 是 MATTER / ZIGBEE / BLE_GATT / BLUETOOTH_MESH \n deviceType String 是 平台约定的设备分类 \n roomId String 否 当前家庭内房间 \n manufacturer/model String 否 型号资料 \n productId String 否 已有平台产品;必须属于允许的产品范围 \n schema Object 是 包含 properties/actions;登记时不指定平台 schemaRevision,不上报推测能力 \n\n json\n{\n \"localDeviceId\": \"local-device-001\",\n \"deviceName\": \"客厅灯\",\n \"protocol\": \"MATTER\",\n \"deviceType\": \"light\",\n \"roomId\": \"30001\",\n \"manufacturer\": \"Example\",\n \"model\": \"LIGHT-A\",\n \"schema\": {\n \"properties\": [\n {\n \"code\": \"switch_1\",\n \"name\": \"开关\",\n \"type\": \"bool\",\n \"access\": [\"read\", \"write\", \"notify\"],\n \"semantic\": \"ON_OFF\",\n \"channel\": \"1\",\n \"completionMode\": \"STATE\"\n }\n ],\n \"actions\": []\n }\n}\n \n\n响应 data:\n\n json\n{\n \"id\": \"40001\",\n \"deviceId\": \"device-001\",\n \"productId\": \"50001\",\n \"homeId\": \"10001\",\n \"parentDeviceId\": \"gateway-001\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevision\": 3,\n \"schemaRevision\": 2,\n \"duplicate\": false\n}\n \n\n平台以 panelId + localDeviceId 识别同一登记,不重复创建设备。重复登记相同资料返回同一映射;已登记后名称/房间等资料通过现有设备更新接口处理,schema 通过下方能力更新接口处理,不能借重新登记覆盖正在使用的物模型。没有 productId 时平台按已配置产品模板或确认后的 schema 建立对应产品;无法匹配则返回明确错误,不把空能力设备标成可控。\n\n平台维护设备 id、deviceId、父网关和执行归属,屏不传本地网络密钥、Matter Fabric 密钥或用户口令。端点、属性码和本地协议地址的映射持久化在屏内。\n\n PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema — 新增建议 \n\n请求体含 expectedSchemaRevision、routeRevision、properties、actions;properties/actions 使用下方定义。平台核对执行归属及期望版本,原子保存并返回新的 schemaRevision。同一幂等请求返回同一版本,版本不符返回 SCHEMA_CHANGED。\n\n更新影响尚未开始的旧模型命令时,旧命令在执行前失败并给出 SCHEMA_CHANGED;已经开始的命令按原模型快照保留执行证据。旧模型状态不得覆盖语义已变化的新属性。\n\n","preview":"POST /api/v1/panels/{panelId}/devices/register — 新增建议 \n\n鉴权:本屏 gateway.execute 及被授予的登记权限。只登记实际归本屏执行的设备。\n\n参数:body: LocalDeviceRegistration\n\n "},{"id":"chapter-5-heading-2","chapter":"chapter-5","label":"2. 控制模型","kind":"设备登记与标准控制模型","text":"2. 控制模型\n\n GET /api/v1/devices/{deviceId}/control-schema — 扩展建议 \n\n路径沿用;以下 data 结构为补充建议,需要与平台实际模型核对。\n\n json\n{\n \"deviceId\": \"device-001\",\n \"productId\": \"50001\",\n \"schemaRevision\": 2,\n \"properties\": [\n {\n \"code\": \"switch_1\",\n \"dpId\": \"1\",\n \"name\": \"开关\",\n \"type\": \"bool\",\n \"access\": [\"read\", \"write\", \"notify\"],\n \"semantic\": \"ON_OFF\",\n \"channel\": \"1\",\n \"completionMode\": \"STATE\"\n },\n {\n \"code\": \"brightness\",\n \"name\": \"亮度\",\n \"type\": \"int\",\n \"access\": [\"read\", \"write\", \"notify\"],\n \"semantic\": \"BRIGHTNESS\",\n \"channel\": \"1\",\n \"min\": 0,\n \"max\": 100,\n \"step\": 1,\n \"unit\": \"%\",\n \"completionMode\": \"STATE\"\n }\n ],\n \"actions\": []\n}\n \n\n 字段 类型 说明 \n --- --- --- \n code String 设备内唯一业务属性码,命令使用该值 \n dpId String 可选,平台原始功能点标识 \n type String bool / int / float / enum / string / json \n access List\u003cString> read、write、notify;产品旧模型若是字符串,由平台明确转换 \n semantic String 可选的标准能力语义,如 ON_OFF、BRIGHTNESS \n channel String 稳定功能通道,不自动等于 Matter endpoint \n min/max/step/unit 对应数值/String 数值型属性的约束,统一为对外业务单位 \n enumValues List 枚举项,每项含 value 和 label \n completionMode String STATE:新状态确认;ACK_ONLY:仅能确认协议接受 \n actions List\u003cActionSpec> 无持久目标值的动作,例如停止;含 code、name、parameters、completionMode \n\nActionSpec.parameters 按参数名列出 type、required 和适用的 min/max/step/enumValues;无参数动作使用空对象。请求中的参数必须符合该定义,不能让任意私有 JSON 绕过能力约束。\n\ncontrol-schema 的数值已经是业务值,scale=1;若物模型原始值存在倍率、偏移或反向含义,由平台云驱动或屏本地驱动转换,不能在两端重复转换。dpId 只是映射信息,不保证与 code 数值或格式一致。\n\n请求必须携带所依据的 schemaRevision。版本变化后,旧参数不得照常发送,返回 SCHEMA_CHANGED 并重新读取。不支持的属性明确显示不可控,不猜测用途。\n\n---\n\n","preview":"GET /api/v1/devices/{deviceId}/control-schema — 扩展建议 \n\n路径沿用;以下 data 结构为补充建议,需要与平台实际模型核对。\n\n json\n{\n \"deviceId\": \"device-001\",\n \"productI"},{"id":"chapter-6","chapter":"chapter-6","label":"控制命令与执行回执","kind":"章节","text":"\n 1. 提交控制\n\n POST /api/v1/devices/{deviceId}/commands — 扩展建议 \n\n鉴权:屏或会员身份,只能控制授权设备。路径沿用现有接口,补充以下约定。\n\n参数:body: DeviceCommand\n\n 字段 类型 必填 说明 \n --- --- --- --- \n commandType String 是 set:设置属性;action:执行动作 \n params Object 是 set 为属性 code 到值的映射;action 格式见下 \n timeout Long 否 总有效时间毫秒,建议默认 30000,允许 1000–120000 \n schemaRevision Long 是,扩展建议 客户端依据的控制模型版本 \n\n json\n{\n \"commandType\": \"set\",\n \"params\": {\"switch_1\": false},\n \"timeout\": 30000,\n \"schemaRevision\": 2\n}\n \n\naction 请求示意:\n\n json\n{\n \"commandType\": \"action\",\n \"params\": {\"actionCode\": \"stop\", \"arguments\": {}},\n \"timeout\": 10000,\n \"schemaRevision\": 2\n}\n \n\n动作必须在 schema.actions 中声明,示例 stop 不意味着所有设备都支持。\n\n平台先原子记录幂等请求和命令,再调度执行;不得等待物理动作结束才生成 commandId。接受请求后返回 HTTP 202,data: CommandResult。\n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {\n \"commandId\": \"command-001\",\n \"deviceId\": \"device-001\",\n \"state\": \"QUEUED\",\n \"terminal\": false,\n \"executionSource\": \"LOCAL_PANEL\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevision\": 3,\n \"createdAt\": 1792123200000,\n \"expiresAt\": 1792123230000,\n \"completionMode\": \"STATE\",\n \"result\": null\n }\n}\n \n\nexecutionSource 为 CLOUD 或 LOCAL_PANEL。CLOUD 由平台既有云驱动执行;LOCAL_PANEL 只进入指定屏的队列,不能同时调用云驱动。\n\n提交时若本地执行屏已离线或设备已明确不可控,返回 DEVICE_OFFLINE,不建立等待日后联网自动执行的队列。在线检查后发生断网的已有命令按各自有效期处理。\n\n同一 set 请求包含多个属性时,默认不保证物理原子性。平台先校验全部属性,然后逐项记录结果;部分完成返回 PARTIAL_FAILURE 及每项结果,不显示整体成功。涉及严格原子动作时应由设备原生服务另行声明。\n\n 2. 查询控制结果\n\n GET /api/v1/device-commands/{commandId} — 新增建议 \n\n返回 CommandResult,只允许本命令关联家庭的合法调用者读取。\n\n state 含义 terminal \n --- --- --- \n QUEUED 平台已建任务,尚未投递 false \n DELIVERED 已投递,尚未获执行许可 false \n EXECUTING 已授予执行权,可能即将或已经发送给设备 false \n ACCEPTED 设备协议接受;未证明物理目标完成 STATE 时 false;ACK_ONLY 时 true \n CONFIRMED 已取得满足目标的新状态或等价执行证据 true \n PARTIAL_FAILURE 多属性/动作部分完成,部分失败或未知 true \n FAILED 有明确拒绝或执行失败证据 true \n EXPIRED 过期且确认没有进入 EXECUTING true \n CANCELLED 执行前撤销或归属失效,确认尚未开始 true \n UNKNOWN 已进入执行阶段,但最终结果无法确定 true \n\nQUEUED/DELIVERED 超时后为 EXPIRED;EXECUTING 或等待目标回报超时后为 UNKNOWN,不宣称设备没有动作。终态不再自动重放。迟到状态仍可更新设备状态,并追加 lateEvidence,但不把过期命令重新排队。\n\n 3. 领取命令\n\n POST /api/v1/panels/{panelId}/commands/pull — 新增建议 \n\n参数:body: CommandPullRequest\n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"waitSeconds\": 20,\n \"maxItems\": 10\n}\n \n\nbootId 必填;maxItems 建议 1–20。每次新的领取使用新 Idempotency-Key;同一次请求网络重试使用原键。平台保留领取结果,不因响应丢失再次创建任务。\n\n响应 data:\n\n json\n{\n \"commands\": [\n {\n \"commandId\": \"command-001\",\n \"deviceId\": \"device-001\",\n \"localDeviceId\": \"local-device-001\",\n \"commandType\": \"set\",\n \"params\": {\"switch_1\": false},\n \"schemaRevision\": 2,\n \"routeRevision\": 3,\n \"deliveryToken\": \"\u003cdelivery-token>\",\n \"deliveryExpiresAt\": 1792123210000,\n \"expiresAt\": 1792123230000,\n \"completionMode\": \"STATE\"\n }\n ],\n \"serverTime\": 1792123200000\n}\n \n\n只投递当前归本屏负责且有效的命令。建议投递租期 10 秒,且不能晚于命令 expiresAt。屏未申请执行、投递租期已过时,可重新投递并更换 deliveryToken;旧 token 立即失效。已进入 EXECUTING 的命令不能重新投递给其他实例。\n\n 4. 申请执行\n\n POST /api/v1/panels/{panelId}/commands/{commandId}/start — 新增建议 \n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"deliveryToken\": \"\u003cdelivery-token>\",\n \"routeRevision\": 3\n}\n \n\n平台原子检查命令状态、有效期、最新执行归属、调用权限和 deliveryToken,成功转为 EXECUTING,返回:\n\n json\n{\n \"executionToken\": \"\u003cexecution-token>\",\n \"state\": \"EXECUTING\",\n \"expiresAt\": 1792123230000,\n \"serverTime\": 1792123201000\n}\n \n\n屏收到有效执行许可后才能向设备发送。申请响应丢失时,以同一 Idempotency-Key 重试取得同一许可,平台仍检查当前身份、归属及有效期;屏重新核对截止时间,不能因响应超时自行执行。\n\n在发送物理指令前,屏必须以 commandId 唯一约束原子占用本地执行记录,并先持久化 ATTEMPTING 标记;并发回调不能各执行一次。若进程在发送附近中断,重启后不再重发该动作,查询设备状态并报告 UNKNOWN 或可核实证据。分布式协议不能保证跨设备断电的物理“恰好一次”;本约定优先避免重复动作,并保留无法确定的结果。\n\n 5. 报告执行结果\n\n POST /api/v1/panels/{panelId}/commands/{commandId}/report — 新增建议 \n\n参数:body: CommandReport\n\n json\n{\n \"executionToken\": \"\u003cexecution-token>\",\n \"reportId\": \"command-report-001\",\n \"reportSequence\": 2,\n \"state\": \"CONFIRMED\",\n \"observedAt\": 1792123202500,\n \"items\": [\n {\n \"code\": \"switch_1\",\n \"state\": \"CONFIRMED\",\n \"reportedValue\": false,\n \"evidence\": \"DEVICE_REPORT\"\n }\n ]\n}\n \n\n 字段 类型 必填 说明 \n --- --- --- --- \n executionToken String 是 绑定 commandId、panelId、执行实例和路由版本 \n reportId String 是 唯一回报编号;重试不变 \n reportSequence Long 是 同一命令内递增,避免旧回执覆盖新结果 \n state String 是 ACCEPTED / CONFIRMED / PARTIAL_FAILURE / FAILED / UNKNOWN \n observedAt Long 否 设备结果观察时间;仅时间戳本身不构成完成证据 \n items List 属性命令必填 各属性结果和证据 \n errorCode/message String 失败或未知时 结构化原因和简短说明 \n\nACK_ONLY 动作可报告 ACCEPTED 作为终态,但界面仍显示“已接受”,不能显示“已确认机械停止”等未取得的结果。\n\n平台核对目标、物模型允许的数值精度和执行归属,不接受用旧缓存值直接报告 CONFIRMED。可接受证据为实际设备回报,或指令之后完成且能证明新鲜度的读回;无证据时保留 ACCEPTED/UNKNOWN。命令回执不直接覆盖设备最新状态,设备状态另走第七节并按顺序合并。\n\n响应 data:\n\n json\n{\n \"commandId\": \"command-001\",\n \"state\": \"CONFIRMED\",\n \"terminal\": true,\n \"duplicate\": false\n}\n \n\nreportId 重复且内容相同返回同一结果;不同内容返回冲突。旧 reportSequence 不回退状态。中途会话刷新不影响有效 executionToken;新 bootId、撤权或路由变化后只能追加经平台核验的旧执行证据,不能凭旧 executionToken 获取新执行权。\n\n 6. 屏上本地点击与回声处理\n\n负责该设备的屏可直接执行本地操作,随后上报状态及活动;这种操作不再同时向平台提交一条待下发控制命令。\n\n本地操作以 localOperationId 标记来源。平台因该上报产生的事件只是同步,不重新生成控制命令。其他屏控制该设备时通过平台提交,仍由唯一执行屏领取。\n\n同一屏对同一设备的本地操作与远程操作进入同一个串行执行入口;不能因来源不同并行修改同一属性。相互冲突的请求分别保留结果,不使用先前另一条命令的回报冒充当前命令完成。\n\n---\n\n","preview":"提交、领取、执行许可和结果回报"},{"id":"chapter-6-heading-1","chapter":"chapter-6","label":"1. 提交控制","kind":"控制命令与执行回执","text":"1. 提交控制\n\n POST /api/v1/devices/{deviceId}/commands — 扩展建议 \n\n鉴权:屏或会员身份,只能控制授权设备。路径沿用现有接口,补充以下约定。\n\n参数:body: DeviceCommand\n\n 字段 类型 必填 说明 \n --- --- --- --- \n commandType String 是 set:设置属性;action:执行动作 \n params Object 是 set 为属性 code 到值的映射;action 格式见下 \n timeout Long 否 总有效时间毫秒,建议默认 30000,允许 1000–120000 \n schemaRevision Long 是,扩展建议 客户端依据的控制模型版本 \n\n json\n{\n \"commandType\": \"set\",\n \"params\": {\"switch_1\": false},\n \"timeout\": 30000,\n \"schemaRevision\": 2\n}\n \n\naction 请求示意:\n\n json\n{\n \"commandType\": \"action\",\n \"params\": {\"actionCode\": \"stop\", \"arguments\": {}},\n \"timeout\": 10000,\n \"schemaRevision\": 2\n}\n \n\n动作必须在 schema.actions 中声明,示例 stop 不意味着所有设备都支持。\n\n平台先原子记录幂等请求和命令,再调度执行;不得等待物理动作结束才生成 commandId。接受请求后返回 HTTP 202,data: CommandResult。\n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {\n \"commandId\": \"command-001\",\n \"deviceId\": \"device-001\",\n \"state\": \"QUEUED\",\n \"terminal\": false,\n \"executionSource\": \"LOCAL_PANEL\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevision\": 3,\n \"createdAt\": 1792123200000,\n \"expiresAt\": 1792123230000,\n \"completionMode\": \"STATE\",\n \"result\": null\n }\n}\n \n\nexecutionSource 为 CLOUD 或 LOCAL_PANEL。CLOUD 由平台既有云驱动执行;LOCAL_PANEL 只进入指定屏的队列,不能同时调用云驱动。\n\n提交时若本地执行屏已离线或设备已明确不可控,返回 DEVICE_OFFLINE,不建立等待日后联网自动执行的队列。在线检查后发生断网的已有命令按各自有效期处理。\n\n同一 set 请求包含多个属性时,默认不保证物理原子性。平台先校验全部属性,然后逐项记录结果;部分完成返回 PARTIAL_FAILURE 及每项结果,不显示整体成功。涉及严格原子动作时应由设备原生服务另行声明。\n\n","preview":"POST /api/v1/devices/{deviceId}/commands — 扩展建议 \n\n鉴权:屏或会员身份,只能控制授权设备。路径沿用现有接口,补充以下约定。\n\n参数:body: DeviceCommand\n\n 字段 类型 必填 说明 \n --- -"},{"id":"chapter-6-heading-2","chapter":"chapter-6","label":"2. 查询控制结果","kind":"控制命令与执行回执","text":"2. 查询控制结果\n\n GET /api/v1/device-commands/{commandId} — 新增建议 \n\n返回 CommandResult,只允许本命令关联家庭的合法调用者读取。\n\n state 含义 terminal \n --- --- --- \n QUEUED 平台已建任务,尚未投递 false \n DELIVERED 已投递,尚未获执行许可 false \n EXECUTING 已授予执行权,可能即将或已经发送给设备 false \n ACCEPTED 设备协议接受;未证明物理目标完成 STATE 时 false;ACK_ONLY 时 true \n CONFIRMED 已取得满足目标的新状态或等价执行证据 true \n PARTIAL_FAILURE 多属性/动作部分完成,部分失败或未知 true \n FAILED 有明确拒绝或执行失败证据 true \n EXPIRED 过期且确认没有进入 EXECUTING true \n CANCELLED 执行前撤销或归属失效,确认尚未开始 true \n UNKNOWN 已进入执行阶段,但最终结果无法确定 true \n\nQUEUED/DELIVERED 超时后为 EXPIRED;EXECUTING 或等待目标回报超时后为 UNKNOWN,不宣称设备没有动作。终态不再自动重放。迟到状态仍可更新设备状态,并追加 lateEvidence,但不把过期命令重新排队。\n\n","preview":"GET /api/v1/device-commands/{commandId} — 新增建议 \n\n返回 CommandResult,只允许本命令关联家庭的合法调用者读取。\n\n state 含义 terminal \n --- --- --- \n QUEUED 平"},{"id":"chapter-6-heading-3","chapter":"chapter-6","label":"3. 领取命令","kind":"控制命令与执行回执","text":"3. 领取命令\n\n POST /api/v1/panels/{panelId}/commands/pull — 新增建议 \n\n参数:body: CommandPullRequest\n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"waitSeconds\": 20,\n \"maxItems\": 10\n}\n \n\nbootId 必填;maxItems 建议 1–20。每次新的领取使用新 Idempotency-Key;同一次请求网络重试使用原键。平台保留领取结果,不因响应丢失再次创建任务。\n\n响应 data:\n\n json\n{\n \"commands\": [\n {\n \"commandId\": \"command-001\",\n \"deviceId\": \"device-001\",\n \"localDeviceId\": \"local-device-001\",\n \"commandType\": \"set\",\n \"params\": {\"switch_1\": false},\n \"schemaRevision\": 2,\n \"routeRevision\": 3,\n \"deliveryToken\": \"\u003cdelivery-token>\",\n \"deliveryExpiresAt\": 1792123210000,\n \"expiresAt\": 1792123230000,\n \"completionMode\": \"STATE\"\n }\n ],\n \"serverTime\": 1792123200000\n}\n \n\n只投递当前归本屏负责且有效的命令。建议投递租期 10 秒,且不能晚于命令 expiresAt。屏未申请执行、投递租期已过时,可重新投递并更换 deliveryToken;旧 token 立即失效。已进入 EXECUTING 的命令不能重新投递给其他实例。\n\n","preview":"POST /api/v1/panels/{panelId}/commands/pull — 新增建议 \n\n参数:body: CommandPullRequest\n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"waitSeconds\":"},{"id":"chapter-6-heading-4","chapter":"chapter-6","label":"4. 申请执行","kind":"控制命令与执行回执","text":"4. 申请执行\n\n POST /api/v1/panels/{panelId}/commands/{commandId}/start — 新增建议 \n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"deliveryToken\": \"\u003cdelivery-token>\",\n \"routeRevision\": 3\n}\n \n\n平台原子检查命令状态、有效期、最新执行归属、调用权限和 deliveryToken,成功转为 EXECUTING,返回:\n\n json\n{\n \"executionToken\": \"\u003cexecution-token>\",\n \"state\": \"EXECUTING\",\n \"expiresAt\": 1792123230000,\n \"serverTime\": 1792123201000\n}\n \n\n屏收到有效执行许可后才能向设备发送。申请响应丢失时,以同一 Idempotency-Key 重试取得同一许可,平台仍检查当前身份、归属及有效期;屏重新核对截止时间,不能因响应超时自行执行。\n\n在发送物理指令前,屏必须以 commandId 唯一约束原子占用本地执行记录,并先持久化 ATTEMPTING 标记;并发回调不能各执行一次。若进程在发送附近中断,重启后不再重发该动作,查询设备状态并报告 UNKNOWN 或可核实证据。分布式协议不能保证跨设备断电的物理“恰好一次”;本约定优先避免重复动作,并保留无法确定的结果。\n\n","preview":"POST /api/v1/panels/{panelId}/commands/{commandId}/start — 新增建议 \n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"deliveryToken\": \"\u003cdelivery-to"},{"id":"chapter-6-heading-5","chapter":"chapter-6","label":"5. 报告执行结果","kind":"控制命令与执行回执","text":"5. 报告执行结果\n\n POST /api/v1/panels/{panelId}/commands/{commandId}/report — 新增建议 \n\n参数:body: CommandReport\n\n json\n{\n \"executionToken\": \"\u003cexecution-token>\",\n \"reportId\": \"command-report-001\",\n \"reportSequence\": 2,\n \"state\": \"CONFIRMED\",\n \"observedAt\": 1792123202500,\n \"items\": [\n {\n \"code\": \"switch_1\",\n \"state\": \"CONFIRMED\",\n \"reportedValue\": false,\n \"evidence\": \"DEVICE_REPORT\"\n }\n ]\n}\n \n\n 字段 类型 必填 说明 \n --- --- --- --- \n executionToken String 是 绑定 commandId、panelId、执行实例和路由版本 \n reportId String 是 唯一回报编号;重试不变 \n reportSequence Long 是 同一命令内递增,避免旧回执覆盖新结果 \n state String 是 ACCEPTED / CONFIRMED / PARTIAL_FAILURE / FAILED / UNKNOWN \n observedAt Long 否 设备结果观察时间;仅时间戳本身不构成完成证据 \n items List 属性命令必填 各属性结果和证据 \n errorCode/message String 失败或未知时 结构化原因和简短说明 \n\nACK_ONLY 动作可报告 ACCEPTED 作为终态,但界面仍显示“已接受”,不能显示“已确认机械停止”等未取得的结果。\n\n平台核对目标、物模型允许的数值精度和执行归属,不接受用旧缓存值直接报告 CONFIRMED。可接受证据为实际设备回报,或指令之后完成且能证明新鲜度的读回;无证据时保留 ACCEPTED/UNKNOWN。命令回执不直接覆盖设备最新状态,设备状态另走第七节并按顺序合并。\n\n响应 data:\n\n json\n{\n \"commandId\": \"command-001\",\n \"state\": \"CONFIRMED\",\n \"terminal\": true,\n \"duplicate\": false\n}\n \n\nreportId 重复且内容相同返回同一结果;不同内容返回冲突。旧 reportSequence 不回退状态。中途会话刷新不影响有效 executionToken;新 bootId、撤权或路由变化后只能追加经平台核验的旧执行证据,不能凭旧 executionToken 获取新执行权。\n\n","preview":"POST /api/v1/panels/{panelId}/commands/{commandId}/report — 新增建议 \n\n参数:body: CommandReport\n\n json\n{\n \"executionToken\": \"\u003cexecution-token>"},{"id":"chapter-6-heading-6","chapter":"chapter-6","label":"6. 屏上本地点击与回声处理","kind":"控制命令与执行回执","text":"6. 屏上本地点击与回声处理\n\n负责该设备的屏可直接执行本地操作,随后上报状态及活动;这种操作不再同时向平台提交一条待下发控制命令。\n\n本地操作以 localOperationId 标记来源。平台因该上报产生的事件只是同步,不重新生成控制命令。其他屏控制该设备时通过平台提交,仍由唯一执行屏领取。\n\n同一屏对同一设备的本地操作与远程操作进入同一个串行执行入口;不能因来源不同并行修改同一属性。相互冲突的请求分别保留结果,不使用先前另一条命令的回报冒充当前命令完成。\n\n---\n\n","preview":"负责该设备的屏可直接执行本地操作,随后上报状态及活动;这种操作不再同时向平台提交一条待下发控制命令。\n\n本地操作以 localOperationId 标记来源。平台因该上报产生的事件只是同步,不重新生成控制命令。其他屏控制该设备时通过平台提交,仍由唯一执行屏领取。\n\n同一屏对同一"},{"id":"chapter-7","chapter":"chapter-7","label":"状态与活动上报","kind":"章节","text":"\n POST /api/v1/panels/{panelId}/reports — 新增建议 \n\n此接口面向网关状态与本地活动,复用平台已有设备、数据和活动存储。屏使用此入口后,不再把同一条状态重复提交到其他状态接口。\n\n参数:body: PanelReportBatch\n\n json\n{\n \"bootId\": \"boot-example-01\",\n \"stateEpoch\": 12,\n \"reports\": [\n {\n \"reportId\": \"report-001\",\n \"deviceId\": \"device-001\",\n \"sequence\": 18,\n \"schemaRevision\": 2,\n \"routeRevision\": 3,\n \"kind\": \"STATE\",\n \"mode\": \"DELTA\",\n \"online\": true,\n \"observedAt\": 1792123229500,\n \"properties\": {\"switch_1\": false},\n \"quality\": \"GOOD\",\n \"commandId\": \"command-001\"\n }\n ]\n}\n \n\n 字段 类型 必填 说明 \n --- --- --- --- \n bootId/stateEpoch String/Long 是 平台签发的当前启动世代 \n reports List 是 建议每批 1–100 条 \n reportId String 是 去重标识 \n deviceId String 是 本屏负责的设备 \n sequence Long 是 当前世代、当前设备内严格递增 \n schemaRevision/routeRevision Long 是 防止错误物模型和旧执行归属上报 \n kind String 是 STATE 或 ACTIVITY \n mode String STATE 必填 FULL:所有当前可报告属性;DELTA:仅变化属性 \n online Boolean STATE 建议 本地实际连接状态 \n properties Object STATE 必填 code 到类型正确的值 \n observedAt Long 否 屏观测时间;平台另记录 receivedAt \n quality String STATE 必填 GOOD / STALE / UNAVAILABLE \n commandId String 否 远程控制关联;仅关联不等同于命令确认 \n localOperationId String ACTIVITY 必填 本地动作标识 \n action/detail String/Object ACTIVITY 必填 动作语义与必要明细,不含凭证 \n\n平台只接受当前执行归属的上报。状态顺序按 stateEpoch、sequence 判断;不能以 HTTP 到达顺序决定新旧。bootId 改变后由平台分配新 stateEpoch,屏首次上报应为 FULL;旧世代补传只可留作历史,不覆盖新状态。\n\nDELTA 需要按属性保存最近一次序号,避免序号较大的单个属性先到时,误丢另一个属性的较早有效变化。更旧的 FULL 不覆盖新属性,FULL 中未提供的属性只有在其序号比该属性更新时才标为未知;不能默认写成 0/false。\n\n响应逐项给结果:\n\n json\n{\n \"code\": 0,\n \"message\": \"success\",\n \"data\": {\n \"results\": [\n {\n \"reportId\": \"report-001\",\n \"accepted\": true,\n \"duplicate\": false,\n \"appliedToCurrentState\": true,\n \"stateVersion\": 81\n }\n ],\n \"serverTime\": 1792123230000\n }\n}\n \n\naccepted 表示该记录已被持久接收;appliedToCurrentState 表示是否改变当前状态。旧记录可以 accepted=true、appliedToCurrentState=false。部分失败逐条返回 errorCode;屏只重试可重试项,不能因整批 code=0 删除失败记录。\n\n云端来源设备由平台云驱动按同样的对外状态模型提供 stateVersion 和新鲜度。客户端不能把读接口的响应时间伪装成真实设备上报时间。\n\n---\n\n","preview":"状态顺序、活动与批量回报"},{"id":"chapter-8","chapter":"chapter-8","label":"多屏、场景与 AI","kind":"章节","text":"\n 1. 多屏执行归属\n\nDeviceRoute 由平台维护:\n\n json\n{\n \"deviceId\": \"device-001\",\n \"executionSource\": \"LOCAL_PANEL\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevision\": 3\n}\n \n\n- DISPLAY 屏读取同一家庭的状态并向平台提交控制,不取得其他屏的本地协议密钥。\n- 一台设备只对应一个执行屏,不按谁先领取就临时决定归属。\n- 屏离线不会自动把本地设备切换给另一屏;协议网络绑定不一定可转移。\n- 归属变更必须完成明确交接并增加 routeRevision。旧执行屏仍可能离线控制时不得直接启用新执行方;在无法确认旧绑定已解除时停止交接。\n- 旧 routeRevision 的新 start 请求拒绝;已经发到物理设备的动作无法由云端撤回,应保留结果不确定或迟到证据,不能虚报取消成功。\n\n 2. 平台场景和规则\n\n 沿用 scenes、scenes/v2、rules 的现有入口。家庭范围、动作执行与结果增加以下约定:\n\n- 每个场景或自动化配置明确 executionOwner 为 CLOUD 或指定 PANEL。\n- 同一定时/自动化只由一个执行方调度;屏展示云场景不自动再建一份本地定时任务。\n- CLOUD 场景涉及本地设备时,动作转为第六节设备命令,经过所属执行屏。\n- PANEL 离线场景仅对本屏可执行的本地动作运行,并在恢复后补传运行记录;不回放已过期的云端动作。\n- 场景执行接口建议返回 sceneRunId、state、terminal 及各动作 commandId;设备动作关联失败或未知时,场景不能整体标记成功。\n\n 新增建议:GET /api/v1/scene-runs/{sceneRunId} \n\n响应 data:\n\n json\n{\n \"sceneRunId\": \"scene-run-001\",\n \"sceneId\": \"60001\",\n \"state\": \"RUNNING\",\n \"terminal\": false,\n \"actions\": [\n {\"index\": 0, \"commandId\": \"command-001\", \"state\": \"EXECUTING\"}\n ]\n}\n \n\nstate 为 QUEUED/RUNNING/ACCEPTED/CONFIRMED/PARTIAL_FAILURE/FAILED/UNKNOWN。全部动作均确认时为 CONFIRMED;全部动作均已完成各自约定、但至少一项只能确认协议接受时,汇总为 ACCEPTED、terminal=true,不能把它改写成所有物理效果已确认。存在失败或未知时,按各项结果返回部分失败、失败或未知。\n\nPANEL 执行的离线场景结果通过 diagnostics 的结构化事件记录可用于追踪;若需要平台场景历史与管理,则建议统一增加运行报告:\n\n 新增建议:POST /api/v1/panels/{panelId}/scene-runs/report \n\n请求含 sceneRunId(屏生成 UUID)、sceneId、configurationRevision、startedAt、finishedAt、state、actions(localOperationId 与各项结果);幂等按 sceneRunId,平台核对该规则当时的 PANEL 执行归属,只记录历史,不再执行动作。\n\n 3. AI 控制\n\n POST /api/v1/ai/command — 扩展建议 \n\n沿用 text、targetType、targetId 等现有请求字段。平台需要明确回复是只解释、等待确认,还是已经提交执行。\n\n建议 data 包含 status(ANSWER/NEED_CONFIRMATION/SUBMITTED/REJECTED)、message,以及执行时的 commandIds 或 sceneRunId。NEED_CONFIRMATION 额外返回 confirmationId、候选目标及 expiresAt。\n\n二次请求在既有目标字段之外携带 confirmationId,平台绑定原始请求、家庭、目标和有效期;同一确认只产生一次执行。屏收到 SUBMITTED 后只跟踪结果,不把同一动作再次交给本地解析器执行。\n\n 4. 配网\n\n本地 Matter/Zigbee/BLE 配网继续由屏对应协议栈完成,成功后走设备登记。平台已有涂鸦 Wi-Fi/Matter/Zigbee 配网 Token 仅按其配套 SDK 和账号体系使用,不能当作通用本地协议凭证。\n\n---\n\n","preview":"多屏归属、场景、AI 与配网"},{"id":"chapter-8-heading-1","chapter":"chapter-8","label":"1. 多屏执行归属","kind":"多屏、场景与 AI","text":"1. 多屏执行归属\n\nDeviceRoute 由平台维护:\n\n json\n{\n \"deviceId\": \"device-001\",\n \"executionSource\": \"LOCAL_PANEL\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevision\": 3\n}\n \n\n- DISPLAY 屏读取同一家庭的状态并向平台提交控制,不取得其他屏的本地协议密钥。\n- 一台设备只对应一个执行屏,不按谁先领取就临时决定归属。\n- 屏离线不会自动把本地设备切换给另一屏;协议网络绑定不一定可转移。\n- 归属变更必须完成明确交接并增加 routeRevision。旧执行屏仍可能离线控制时不得直接启用新执行方;在无法确认旧绑定已解除时停止交接。\n- 旧 routeRevision 的新 start 请求拒绝;已经发到物理设备的动作无法由云端撤回,应保留结果不确定或迟到证据,不能虚报取消成功。\n\n","preview":"DeviceRoute 由平台维护:\n\n json\n{\n \"deviceId\": \"device-001\",\n \"executionSource\": \"LOCAL_PANEL\",\n \"executorPanelId\": \"panel-001\",\n \"routeRevi"},{"id":"chapter-8-heading-2","chapter":"chapter-8","label":"2. 平台场景和规则","kind":"多屏、场景与 AI","text":"2. 平台场景和规则\n\n 沿用 scenes、scenes/v2、rules 的现有入口。家庭范围、动作执行与结果增加以下约定:\n\n- 每个场景或自动化配置明确 executionOwner 为 CLOUD 或指定 PANEL。\n- 同一定时/自动化只由一个执行方调度;屏展示云场景不自动再建一份本地定时任务。\n- CLOUD 场景涉及本地设备时,动作转为第六节设备命令,经过所属执行屏。\n- PANEL 离线场景仅对本屏可执行的本地动作运行,并在恢复后补传运行记录;不回放已过期的云端动作。\n- 场景执行接口建议返回 sceneRunId、state、terminal 及各动作 commandId;设备动作关联失败或未知时,场景不能整体标记成功。\n\n 新增建议:GET /api/v1/scene-runs/{sceneRunId} \n\n响应 data:\n\n json\n{\n \"sceneRunId\": \"scene-run-001\",\n \"sceneId\": \"60001\",\n \"state\": \"RUNNING\",\n \"terminal\": false,\n \"actions\": [\n {\"index\": 0, \"commandId\": \"command-001\", \"state\": \"EXECUTING\"}\n ]\n}\n \n\nstate 为 QUEUED/RUNNING/ACCEPTED/CONFIRMED/PARTIAL_FAILURE/FAILED/UNKNOWN。全部动作均确认时为 CONFIRMED;全部动作均已完成各自约定、但至少一项只能确认协议接受时,汇总为 ACCEPTED、terminal=true,不能把它改写成所有物理效果已确认。存在失败或未知时,按各项结果返回部分失败、失败或未知。\n\nPANEL 执行的离线场景结果通过 diagnostics 的结构化事件记录可用于追踪;若需要平台场景历史与管理,则建议统一增加运行报告:\n\n 新增建议:POST /api/v1/panels/{panelId}/scene-runs/report \n\n请求含 sceneRunId(屏生成 UUID)、sceneId、configurationRevision、startedAt、finishedAt、state、actions(localOperationId 与各项结果);幂等按 sceneRunId,平台核对该规则当时的 PANEL 执行归属,只记录历史,不再执行动作。\n\n","preview":"沿用 scenes、scenes/v2、rules 的现有入口。家庭范围、动作执行与结果增加以下约定:\n\n- 每个场景或自动化配置明确 executionOwner 为 CLOUD 或指定 PANEL。\n- 同一定时/自动化只由一个执行方调度;屏展示云场景不自动再建一份本地定"},{"id":"chapter-8-heading-3","chapter":"chapter-8","label":"3. AI 控制","kind":"多屏、场景与 AI","text":"3. AI 控制\n\n POST /api/v1/ai/command — 扩展建议 \n\n沿用 text、targetType、targetId 等现有请求字段。平台需要明确回复是只解释、等待确认,还是已经提交执行。\n\n建议 data 包含 status(ANSWER/NEED_CONFIRMATION/SUBMITTED/REJECTED)、message,以及执行时的 commandIds 或 sceneRunId。NEED_CONFIRMATION 额外返回 confirmationId、候选目标及 expiresAt。\n\n二次请求在既有目标字段之外携带 confirmationId,平台绑定原始请求、家庭、目标和有效期;同一确认只产生一次执行。屏收到 SUBMITTED 后只跟踪结果,不把同一动作再次交给本地解析器执行。\n\n","preview":"POST /api/v1/ai/command — 扩展建议 \n\n沿用 text、targetType、targetId 等现有请求字段。平台需要明确回复是只解释、等待确认,还是已经提交执行。\n\n建议 data 包含 status(ANSWER/NEED_CONFIRMATIO"},{"id":"chapter-8-heading-4","chapter":"chapter-8","label":"4. 配网","kind":"多屏、场景与 AI","text":"4. 配网\n\n本地 Matter/Zigbee/BLE 配网继续由屏对应协议栈完成,成功后走设备登记。平台已有涂鸦 Wi-Fi/Matter/Zigbee 配网 Token 仅按其配套 SDK 和账号体系使用,不能当作通用本地协议凭证。\n\n---\n\n","preview":"本地 Matter/Zigbee/BLE 配网继续由屏对应协议栈完成,成功后走设备登记。平台已有涂鸦 Wi-Fi/Matter/Zigbee 配网 Token 仅按其配套 SDK 和账号体系使用,不能当作通用本地协议凭证。\n\n---"},{"id":"chapter-9","chapter":"chapter-9","label":"配置、内容、日志和 OTA","kind":"章节","text":"\n 1. 屏配置\n\n GET /api/v1/panels/{panelId}/configuration — 新增建议 \n\nquery: knownRevision(可选)。\n\n响应 data:\n\n json\n{\n \"revision\": 4,\n \"unchanged\": false,\n \"settings\": {\n \"screenOffSeconds\": 120,\n \"proximityWakeEnabled\": true\n },\n \"content\": [],\n \"automationAssignments\": []\n}\n \n\n本版本列出的 settings 为示例拟定键;平台与屏需要维护明确的支持键、类型和范围,不能把任意 JSON 当系统命令执行。content 每项包含 id、type、title、url、revision;automationAssignments 包含规则 ID、revision、executionOwner 及平台已定义的结构化条件/动作。\n\n只有完整校验成功后才原子应用新配置,失败继续使用上一有效配置并上报原因。knownRevision 相同返回 unchanged=true,可省略主体。云端配置缺失不阻塞本地页面启动。\n\n 2. 诊断事件\n\n POST /api/v1/panels/{panelId}/diagnostics — 新增建议 \n\n参数:body 包含 events,建议每批最多 100 条:\n\n json\n{\n \"events\": [\n {\n \"eventId\": \"diag-001\",\n \"bootId\": \"boot-example-01\",\n \"time\": 1792123200000,\n \"category\": \"NETWORK\",\n \"level\": \"WARN\",\n \"code\": \"PLATFORM_UNREACHABLE\",\n \"message\": \"平台连接暂时不可用\",\n \"commandId\": null\n }\n ]\n}\n \n\n响应按 eventId 返回 accepted/duplicate/errorCode。不得包含 Token、Secret、Wi-Fi 密码、协议密钥或完整用户音频。诊断上传失败不影响本地控制。\n\n 3. 屏查询升级任务\n\n GET /api/v1/panels/{panelId}/ota-tasks/next — 新增建议 \n\nquery: appVersionCode、systemVersion、supportedPackageTypes。平台只返回分配给该屏且适用的任务,无任务时 data=null。\n\n json\n{\n \"taskId\": \"70001\",\n \"attemptId\": \"ota-attempt-001\",\n \"firmwareId\": \"80001\",\n \"deviceId\": \"gateway-001\",\n \"packageType\": \"APK\",\n \"version\": \"1.0.1\",\n \"versionCode\": 101,\n \"targetModel\": \"RK3568-10INCH\",\n \"downloadUrl\": \"https://mum.tonzou.cn/example-package.apk\",\n \"size\": 12345678,\n \"integrity\": {\n \"algorithm\": \"SHA-256\",\n \"digest\": \"\u003cpackage-digest>\"\n },\n \"signatureRequirement\": \"EXISTING_APP_SIGNATURE\",\n \"expiresAt\": 1792209600000\n}\n \n\n这是升级包元数据示例,不是可下载任务。重复查询返回同一任务的同一 attemptId,不因 GET 查询产生新的安装动作;平台明确安排新一轮重试时才分配新的 attemptId。packageType 至少区分 APK 与 ANDROID_AB;不兼容的系统更新形式返回 UNSUPPORTED。平台已有 fileMd5 可保留作兼容字段,实际包完整性与签名检查不能被任务查询成功替代。这里的包摘要只用于固件传输完整性,不扩展为普通业务文件的校验要求。\n\n下载地址不得要求把屏 Bearer Token 转发给任意第三方域名。使用受控平台下载或明确的临时下载授权;系统包和 APK 保留各自的本地验证与安装流程。\n\n 4. 升级回报\n\n POST /api/v1/ota-tasks/{id}/report — 扩展建议 \n\n原 deviceId、success、version 用于最终结果;建议增加 attemptId、reportId、reportSequence、stage、progress、errorCode、message。\n\nstage 为 DOWNLOADING/VERIFYING/INSTALLING/REBOOTING/SUCCEEDED/FAILED/CANCELLED。progress 为当前阶段的 0–100 进度。中间阶段 success 省略,最终成功/失败才填写;CANCELLED 不使用 success 冒充设备安装失败。以 attemptId、reportId 和递增 reportSequence 去重与排序,同阶段进度不回退,不允许旧轮次或迟到下载进度覆盖新的安装结果。\n\n示例:\n\n json\n{\n \"deviceId\": \"gateway-001\",\n \"attemptId\": \"ota-attempt-001\",\n \"reportId\": \"ota-report-001\",\n \"reportSequence\": 5,\n \"stage\": \"SUCCEEDED\",\n \"progress\": 100,\n \"success\": true,\n \"version\": \"1.0.1\"\n}\n \n\n升级任务和本地控制解耦。安装/重启条件需由屏核对;OTA 接口接通不表示可以跳过型号、包类型、签名或用户已确定的设备操作规则。\n\n---\n\n","preview":"运行配置、诊断事件与升级任务"},{"id":"chapter-9-heading-1","chapter":"chapter-9","label":"1. 屏配置","kind":"配置、内容、日志和 OTA","text":"1. 屏配置\n\n GET /api/v1/panels/{panelId}/configuration — 新增建议 \n\nquery: knownRevision(可选)。\n\n响应 data:\n\n json\n{\n \"revision\": 4,\n \"unchanged\": false,\n \"settings\": {\n \"screenOffSeconds\": 120,\n \"proximityWakeEnabled\": true\n },\n \"content\": [],\n \"automationAssignments\": []\n}\n \n\n本版本列出的 settings 为示例拟定键;平台与屏需要维护明确的支持键、类型和范围,不能把任意 JSON 当系统命令执行。content 每项包含 id、type、title、url、revision;automationAssignments 包含规则 ID、revision、executionOwner 及平台已定义的结构化条件/动作。\n\n只有完整校验成功后才原子应用新配置,失败继续使用上一有效配置并上报原因。knownRevision 相同返回 unchanged=true,可省略主体。云端配置缺失不阻塞本地页面启动。\n\n","preview":"GET /api/v1/panels/{panelId}/configuration — 新增建议 \n\nquery: knownRevision(可选)。\n\n响应 data:\n\n json\n{\n \"revision\": 4,\n \"unchanged\": false,\n "},{"id":"chapter-9-heading-2","chapter":"chapter-9","label":"2. 诊断事件","kind":"配置、内容、日志和 OTA","text":"2. 诊断事件\n\n POST /api/v1/panels/{panelId}/diagnostics — 新增建议 \n\n参数:body 包含 events,建议每批最多 100 条:\n\n json\n{\n \"events\": [\n {\n \"eventId\": \"diag-001\",\n \"bootId\": \"boot-example-01\",\n \"time\": 1792123200000,\n \"category\": \"NETWORK\",\n \"level\": \"WARN\",\n \"code\": \"PLATFORM_UNREACHABLE\",\n \"message\": \"平台连接暂时不可用\",\n \"commandId\": null\n }\n ]\n}\n \n\n响应按 eventId 返回 accepted/duplicate/errorCode。不得包含 Token、Secret、Wi-Fi 密码、协议密钥或完整用户音频。诊断上传失败不影响本地控制。\n\n","preview":"POST /api/v1/panels/{panelId}/diagnostics — 新增建议 \n\n参数:body 包含 events,建议每批最多 100 条:\n\n json\n{\n \"events\": [\n {\n \"eventId\": \"diag-00"},{"id":"chapter-9-heading-3","chapter":"chapter-9","label":"3. 屏查询升级任务","kind":"配置、内容、日志和 OTA","text":"3. 屏查询升级任务\n\n GET /api/v1/panels/{panelId}/ota-tasks/next — 新增建议 \n\nquery: appVersionCode、systemVersion、supportedPackageTypes。平台只返回分配给该屏且适用的任务,无任务时 data=null。\n\n json\n{\n \"taskId\": \"70001\",\n \"attemptId\": \"ota-attempt-001\",\n \"firmwareId\": \"80001\",\n \"deviceId\": \"gateway-001\",\n \"packageType\": \"APK\",\n \"version\": \"1.0.1\",\n \"versionCode\": 101,\n \"targetModel\": \"RK3568-10INCH\",\n \"downloadUrl\": \"https://mum.tonzou.cn/example-package.apk\",\n \"size\": 12345678,\n \"integrity\": {\n \"algorithm\": \"SHA-256\",\n \"digest\": \"\u003cpackage-digest>\"\n },\n \"signatureRequirement\": \"EXISTING_APP_SIGNATURE\",\n \"expiresAt\": 1792209600000\n}\n \n\n这是升级包元数据示例,不是可下载任务。重复查询返回同一任务的同一 attemptId,不因 GET 查询产生新的安装动作;平台明确安排新一轮重试时才分配新的 attemptId。packageType 至少区分 APK 与 ANDROID_AB;不兼容的系统更新形式返回 UNSUPPORTED。平台已有 fileMd5 可保留作兼容字段,实际包完整性与签名检查不能被任务查询成功替代。这里的包摘要只用于固件传输完整性,不扩展为普通业务文件的校验要求。\n\n下载地址不得要求把屏 Bearer Token 转发给任意第三方域名。使用受控平台下载或明确的临时下载授权;系统包和 APK 保留各自的本地验证与安装流程。\n\n","preview":"GET /api/v1/panels/{panelId}/ota-tasks/next — 新增建议 \n\nquery: appVersionCode、systemVersion、supportedPackageTypes。平台只返回分配给该屏且适用的任务,无任务时 data=n"},{"id":"chapter-9-heading-4","chapter":"chapter-9","label":"4. 升级回报","kind":"配置、内容、日志和 OTA","text":"4. 升级回报\n\n POST /api/v1/ota-tasks/{id}/report — 扩展建议 \n\n原 deviceId、success、version 用于最终结果;建议增加 attemptId、reportId、reportSequence、stage、progress、errorCode、message。\n\nstage 为 DOWNLOADING/VERIFYING/INSTALLING/REBOOTING/SUCCEEDED/FAILED/CANCELLED。progress 为当前阶段的 0–100 进度。中间阶段 success 省略,最终成功/失败才填写;CANCELLED 不使用 success 冒充设备安装失败。以 attemptId、reportId 和递增 reportSequence 去重与排序,同阶段进度不回退,不允许旧轮次或迟到下载进度覆盖新的安装结果。\n\n示例:\n\n json\n{\n \"deviceId\": \"gateway-001\",\n \"attemptId\": \"ota-attempt-001\",\n \"reportId\": \"ota-report-001\",\n \"reportSequence\": 5,\n \"stage\": \"SUCCEEDED\",\n \"progress\": 100,\n \"success\": true,\n \"version\": \"1.0.1\"\n}\n \n\n升级任务和本地控制解耦。安装/重启条件需由屏核对;OTA 接口接通不表示可以跳过型号、包类型、签名或用户已确定的设备操作规则。\n\n---\n\n","preview":"POST /api/v1/ota-tasks/{id}/report — 扩展建议 \n\n原 deviceId、success、version 用于最终结果;建议增加 attemptId、reportId、reportSequence、stage、progress、errorCo"},{"id":"chapter-10","chapter":"chapter-10","label":"建议错误码","kind":"章节","text":"\n新增接口建议使用下表。HTTP 状态与业务 code 为评审建议,平台既有错误码可映射保留,但同一含义应稳定一致。\n\n HTTP code errorCode 屏端处理 \n --- --- --- --- \n 400 40001 INVALID_ARGUMENT 修正参数,不自动重试 \n 400 40002 UNSUPPORTED_CAPABILITY 刷新能力,关闭对应无效操作 \n 401 40101 TOKEN_EXPIRED 按身份刷新后重试同一逻辑请求 \n 401 40102 INVALID_CREDENTIAL 停止自动循环,报告身份错误 \n 403 40301 HOME_ACCESS_REVOKED 停止该家庭云操作,清理对应授权缓存 \n 403 40302 DEVICE_OUT_OF_SCOPE 不执行跨家庭或非本屏任务 \n 409 40901 IDEMPOTENCY_CONFLICT 不生成新键掩盖冲突 \n 409 40902 SCHEMA_CHANGED 读取新模型,重新确认用户目标 \n 409 40903 ROUTE_CHANGED 刷新归属;旧屏不执行 \n 409 40904 DELIVERY_EXPIRED 本次投递无效,不执行 \n 409 40905 EXECUTION_ALREADY_STARTED 查询原命令,不能重新操作 \n 409 40906 DEVICE_OFFLINE 显示设备或执行屏离线 \n 409 40907 STALE_SESSION 当前启动实例失效,停止领取 \n 410 41001 CURSOR_EXPIRED 重新获取完整快照 \n 410 41002 SNAPSHOT_EXPIRED 放弃未完成分页并重新开始 \n 410 41003 COMMAND_EXPIRED 不执行 \n 429 42901 RATE_LIMITED 按 Retry-After 等待;控制请求保持原幂等键与期限 \n 503 50301 SERVICE_UNAVAILABLE 读取可退避重试;控制先查原结果 \n\n建议退避加入随机抖动并有上限;恢复联网后不集中重放过期控制。业务过期处理独立于 HTTP 重试。\n\n---\n\n","preview":"HTTP 状态与业务错误处理"},{"id":"chapter-11","chapter":"chapter-11","label":"字段模型汇总","kind":"章节","text":"\n 模型 核心字段 用途 \n --- --- --- \n PanelSession panelId、gatewayDeviceId、homeId、role、Token、stateEpoch、各时间参数 屏身份及运行参数 \n PanelHeartbeat bootId、版本、网络、运行时间、协议状态 在线保持 \n DeviceRoute executionSource、executorPanelId、routeRevision 唯一执行归属 \n ControlSchema schemaRevision、properties、actions 统一控制模型 \n PropertySpec code、type、access、semantic、channel、范围、completionMode 属性定义 \n CommandResult commandId、state、terminal、有效期、result 控制生命周期 \n CommandDelivery 设备映射、参数、deliveryToken、有效期 命令投递 \n CommandReport executionToken、reportId、reportSequence、state、items 执行证据 \n PanelReport reportId、stateEpoch、sequence、属性、质量、来源 状态和本地活动 \n PanelEvent eventId、type、来源、stateVersion、payload 家庭增量同步 \n PanelSnapshotPage snapshotId、homeRevision、cursor、目录、nextPageToken 一致性初始化 \n SceneRun sceneRunId、executionOwner、动作结果 场景执行追踪 \n OtaTask taskId、包类型、型号、版本、下载与验证资料 屏升级任务 \n\n字段在对应接口中规定。对外数据不包含底层协议密钥;原始厂商私有属性须有明确类型和权限后才暴露。\n\n---\n\n","preview":"主要数据模型与字段入口"},{"id":"chapter-12","chapter":"chapter-12","label":"对接顺序与评审确认","kind":"章节","text":"\n建议按以下依赖顺序实现完整连接:\n\n1. 终端身份与家庭范围,确认当前屏可读取的目录。\n2. 设备/物模型映射和初始化快照、增量状态。\n3. 云端设备控制,以及本地设备登记与上报。\n4. 本地网关命令领取、执行许可、回执和超时恢复。\n5. 多屏、场景、AI 及配置、诊断和 OTA 的相应接线。\n\n协议实施完成后统一从屏和手机/网页验证:同一家庭数据一致、命令只走一个执行来源、回执能证明实际结果、断线与重启不重复执行、旧状态不覆盖新状态;无外设只影响对应协议功能。\n\n本稿请平台方重点确认:\n\n- 是否已有终端身份和网关协议,可用现有契约替换哪些“新增建议”路径。\n- 是否接受屏受限身份访问既有业务接口,以及明确的家庭范围校验。\n- control-schema、通用控制参数和命令结果的最终字段。\n- 执行归属、投递与结果状态、事件游标及状态序号的语义。\n- 场景/AI/OTA 等已有实现如何对应本文模型及默认时间参数。\n\n确认后的路径和字段维护在同一份协议中,再按定稿实施。以上接口不是对现网行为的测试结论。\n\n","preview":"实施依赖与双方评审事项"},{"id":"chapter-13","chapter":"chapter-13","label":"文档依据","kind":"章节","text":"\n现有路径与业务用途参考:\n[概览](https://mum.tonzou.cn/api/doc/index.html)、\n[授权对接](https://mum.tonzou.cn/api/doc/partner.html)、\n[会员与家庭](https://mum.tonzou.cn/api/doc/member.html)、\n[设备管理](https://mum.tonzou.cn/api/doc/device.html)、\n[产品与物模型](https://mum.tonzou.cn/api/doc/product.html)、\n[涂鸦云集成](https://mum.tonzou.cn/api/doc/tuya.html)、\n[规则与场景](https://mum.tonzou.cn/api/doc/rule.html)、\n[AI 智能](https://mum.tonzou.cn/api/doc/ai.html)、\n[数据模型](https://mum.tonzou.cn/api/doc/models.html)。\n\n","preview":"原平台文档与协议依据"},{"id":"endpoint-1","chapter":"chapter-3","label":"POST /api/v1/panels/session","kind":"新增建议","text":"POST /api/v1/panels/session 新增建议","preview":"中控身份与会话"},{"id":"endpoint-2","chapter":"chapter-3","label":"POST /api/v1/panels/session/refresh","kind":"新增建议","text":"POST /api/v1/panels/session/refresh 新增建议","preview":"中控身份与会话"},{"id":"endpoint-3","chapter":"chapter-3","label":"GET /api/v1/panels/{panelId}","kind":"新增建议","text":"GET /api/v1/panels/{panelId} 新增建议","preview":"中控身份与会话"},{"id":"endpoint-4","chapter":"chapter-3","label":"POST /api/v1/panels/{panelId}/heartbeat","kind":"新增建议","text":"POST /api/v1/panels/{panelId}/heartbeat 新增建议","preview":"中控身份与会话"},{"id":"endpoint-5","chapter":"chapter-4","label":"GET /api/v1/homes","kind":"接口","text":"GET /api/v1/homes ","preview":"家庭快照与事件同步"},{"id":"endpoint-6","chapter":"chapter-4","label":"GET /api/v1/homes/{homeId}/rooms","kind":"接口","text":"GET /api/v1/homes/{homeId}/rooms ","preview":"家庭快照与事件同步"},{"id":"endpoint-7","chapter":"chapter-4","label":"GET /api/v1/panels/{panelId}/snapshot","kind":"新增建议","text":"GET /api/v1/panels/{panelId}/snapshot 新增建议","preview":"家庭快照与事件同步"},{"id":"endpoint-8","chapter":"chapter-4","label":"GET /api/v1/panels/{panelId}/events","kind":"新增建议","text":"GET /api/v1/panels/{panelId}/events 新增建议","preview":"家庭快照与事件同步"},{"id":"endpoint-9","chapter":"chapter-5","label":"POST /api/v1/panels/{panelId}/devices/register","kind":"新增建议","text":"POST /api/v1/panels/{panelId}/devices/register 新增建议","preview":"设备登记与标准控制模型"},{"id":"endpoint-10","chapter":"chapter-5","label":"PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema","kind":"新增建议","text":"PUT /api/v1/panels/{panelId}/devices/{deviceId}/schema 新增建议","preview":"设备登记与标准控制模型"},{"id":"endpoint-11","chapter":"chapter-5","label":"GET /api/v1/devices/{deviceId}/control-schema","kind":"扩展建议","text":"GET /api/v1/devices/{deviceId}/control-schema 扩展建议","preview":"设备登记与标准控制模型"},{"id":"endpoint-12","chapter":"chapter-6","label":"POST /api/v1/devices/{deviceId}/commands","kind":"扩展建议","text":"POST /api/v1/devices/{deviceId}/commands 扩展建议","preview":"控制命令与执行回执"},{"id":"endpoint-13","chapter":"chapter-6","label":"GET /api/v1/device-commands/{commandId}","kind":"新增建议","text":"GET /api/v1/device-commands/{commandId} 新增建议","preview":"控制命令与执行回执"},{"id":"endpoint-14","chapter":"chapter-6","label":"POST /api/v1/panels/{panelId}/commands/pull","kind":"新增建议","text":"POST /api/v1/panels/{panelId}/commands/pull 新增建议","preview":"控制命令与执行回执"},{"id":"endpoint-15","chapter":"chapter-6","label":"POST /api/v1/panels/{panelId}/commands/{commandId}/start","kind":"新增建议","text":"POST /api/v1/panels/{panelId}/commands/{commandId}/start 新增建议","preview":"控制命令与执行回执"},{"id":"endpoint-16","chapter":"chapter-6","label":"POST /api/v1/panels/{panelId}/commands/{commandId}/report","kind":"新增建议","text":"POST /api/v1/panels/{panelId}/commands/{commandId}/report 新增建议","preview":"控制命令与执行回执"},{"id":"endpoint-17","chapter":"chapter-7","label":"POST /api/v1/panels/{panelId}/reports","kind":"新增建议","text":"POST /api/v1/panels/{panelId}/reports 新增建议","preview":"状态与活动上报"},{"id":"endpoint-18","chapter":"chapter-8","label":"GET /api/v1/scene-runs/{sceneRunId}","kind":"新增建议","text":"GET /api/v1/scene-runs/{sceneRunId} 新增建议","preview":"多屏、场景与 AI"},{"id":"endpoint-19","chapter":"chapter-8","label":"POST /api/v1/panels/{panelId}/scene-runs/report","kind":"新增建议","text":"POST /api/v1/panels/{panelId}/scene-runs/report 新增建议","preview":"多屏、场景与 AI"},{"id":"endpoint-20","chapter":"chapter-8","label":"POST /api/v1/ai/command","kind":"扩展建议","text":"POST /api/v1/ai/command 扩展建议","preview":"多屏、场景与 AI"},{"id":"endpoint-21","chapter":"chapter-9","label":"GET /api/v1/panels/{panelId}/configuration","kind":"新增建议","text":"GET /api/v1/panels/{panelId}/configuration 新增建议","preview":"配置、内容、日志和 OTA"},{"id":"endpoint-22","chapter":"chapter-9","label":"POST /api/v1/panels/{panelId}/diagnostics","kind":"新增建议","text":"POST /api/v1/panels/{panelId}/diagnostics 新增建议","preview":"配置、内容、日志和 OTA"},{"id":"endpoint-23","chapter":"chapter-9","label":"GET /api/v1/panels/{panelId}/ota-tasks/next","kind":"新增建议","text":"GET /api/v1/panels/{panelId}/ota-tasks/next 新增建议","preview":"配置、内容、日志和 OTA"},{"id":"endpoint-24","chapter":"chapter-9","label":"POST /api/v1/ota-tasks/{id}/report","kind":"扩展建议","text":"POST /api/v1/ota-tasks/{id}/report 扩展建议","preview":"配置、内容、日志和 OTA"}];
const pages=[...document.querySelectorAll(".doc-page")], navItems=[...document.querySelectorAll("[data-chapter]")];
const input=document.getElementById("search-input"), searchPanel=document.getElementById("search-panel"),results=document.getElementById("search-results"),summary=document.getElementById("search-count");
const expand=document.getElementById("expand-examples");let current="chapter-1",timer=0;
const escapeHtml=s=>String(s).replace(/[&<>"]/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c]));
function setExpandLabel(){const all=[...document.getElementById(current).querySelectorAll("details.json")];expand.disabled=!all.length;expand.textContent=!all.length||all.some(x=>!x.open)?"展开示例":"收起示例";}
function navigate(id,scroll=true){const node=document.getElementById(id)||document.getElementById("chapter-1");if(node.id==="main-content"){node.tabIndex=-1;node.focus();window.scrollTo({top:0,behavior:"instant"});return;}const page=node.classList.contains("doc-page")?node:node.closest(".doc-page");if(!page)return;current=page.id;pages.forEach(p=>p.hidden=p!==page);navItems.forEach(a=>{if(a.dataset.chapter===current)a.setAttribute("aria-current","page");else a.removeAttribute("aria-current");});document.getElementById("current-page").textContent=page.dataset.title;document.title=page.dataset.title+" · MumIot 中控屏 API";setExpandLabel();document.body.classList.remove("nav-open");document.getElementById("menu-toggle").setAttribute("aria-expanded","false");if(scroll)requestAnimationFrame(()=>{if(node===page)window.scrollTo({top:0,behavior:"instant"});else node.scrollIntoView({block:"start",behavior:"instant"});});}
function closeSearch(){searchPanel.hidden=true;input.setAttribute("aria-expanded","false");}
function search(){const q=input.value.trim().toLowerCase();if(!q){closeSearch();return;}const terms=q.split(/\s+/).filter(Boolean);const found=SEARCH_RECORDS.map(record=>{const target=(record.label+" "+record.text).toLowerCase();if(!terms.every(term=>target.includes(term)))return null;const score=(record.label.toLowerCase()===q?100:0)+(record.label.toLowerCase().includes(q)?30:0)+(record.label.startsWith("GET ")||record.label.startsWith("POST ")||record.label.startsWith("PUT ")?10:0);return {record,score};}).filter(Boolean).sort((a,b)=>b.score-a.score);summary.textContent=found.length?found.length+" 条匹配 · 全文搜索":"没有找到匹配内容";results.innerHTML=found.slice(0,40).map(({record})=>{const pos=record.text.toLowerCase().indexOf(terms[0]);const start=Math.max(0,pos-32);const excerpt=record.text.slice(start,start+150).replace(/\s+/g," ");return '<a class="search-result" href="#'+record.id+'"><div class="result-kind">'+escapeHtml(record.kind)+'</div><strong>'+escapeHtml(record.label)+'</strong><p>'+(start?"…":"")+escapeHtml(excerpt)+(record.text.length>start+150?"…":"")+'</p></a>';}).join("")||'<div class="search-empty">换一个接口路径、字段或关键词试试。</div>';searchPanel.hidden=false;input.setAttribute("aria-expanded","true");}
input.addEventListener("input",search);input.addEventListener("focus",()=>{if(input.value.trim())search();});document.getElementById("search-close").addEventListener("click",closeSearch);
document.addEventListener("click",event=>{const link=event.target.closest('a[href^="#"]');if(link){const id=decodeURIComponent(link.getAttribute("href").slice(1));if(!document.getElementById(id))return;event.preventDefault();closeSearch();if(location.hash==="#"+id)navigate(id);else location.hash=id;}if(!event.target.closest(".search-box")&&!event.target.closest(".search-panel"))closeSearch();});
window.addEventListener("hashchange",()=>navigate(decodeURIComponent(location.hash.slice(1))));
document.addEventListener("keydown",event=>{if(event.key==="Escape"){closeSearch();document.body.classList.remove("nav-open");document.getElementById("menu-toggle").setAttribute("aria-expanded","false");}if(event.key==="/"&&!/INPUT|TEXTAREA/.test(document.activeElement.tagName)){event.preventDefault();input.focus();}});
expand.addEventListener("click",()=>{const all=[...document.getElementById(current).querySelectorAll("details.json")];const open=all.some(x=>!x.open);all.forEach(x=>x.open=open);setExpandLabel();});
document.querySelectorAll("details.json").forEach(detail=>detail.addEventListener("toggle",setExpandLabel));
document.getElementById("menu-toggle").addEventListener("click",()=>{const open=document.body.classList.toggle("nav-open");document.getElementById("menu-toggle").setAttribute("aria-expanded",String(open));});
document.getElementById("nav-backdrop").addEventListener("click",()=>{document.body.classList.remove("nav-open");document.getElementById("menu-toggle").setAttribute("aria-expanded","false");});
function toast(message){const element=document.getElementById("toast");element.textContent=message;element.hidden=false;clearTimeout(timer);timer=setTimeout(()=>element.hidden=true,2200);}
async function copy(value){if(navigator.clipboard&&window.isSecureContext){try{await navigator.clipboard.writeText(value);return;}catch(error){}}const area=document.createElement("textarea");area.value=value;area.setAttribute("readonly","");area.style.cssText="position:fixed;left:-9999px;top:0";document.body.appendChild(area);area.select();let copied=false;try{copied=document.execCommand("copy");}finally{area.remove();}if(!copied)throw new Error("copy unavailable");}
document.querySelectorAll(".copy-code").forEach(button=>button.addEventListener("click",async()=>{try{await copy(document.getElementById(button.dataset.copy).textContent);toast("示例已复制");}catch(error){toast("复制未完成,请选中示例文本复制");}}));
document.getElementById("copy-base").addEventListener("click",async()=>{try{await copy("https://mum.tonzou.cn");toast("Base URL 已复制");}catch(error){toast("复制未完成,请手动复制地址");}});
let printDetails=[];window.addEventListener("beforeprint",()=>{printDetails=[...document.querySelectorAll("details")].map(x=>[x,x.open]);printDetails.forEach(([x])=>x.open=true);});window.addEventListener("afterprint",()=>{printDetails.forEach(([x,open])=>x.open=open);printDetails=[];setExpandLabel();});document.getElementById("print-document").addEventListener("click",()=>window.print());
navigate(decodeURIComponent(location.hash.slice(1))||"chapter-1",Boolean(location.hash));
</script></body></html>