ESPectre 资源化设备 API 架构决策(ADR)深度解析:从 RPC 到 /espectre/v1 的演进与实现
2026/9/16 16:39:48 网站建设 项目流程

ESPectre 资源化设备 API 架构决策(ADR)深度解析:从 RPC 到 /espectre/v1 的演进与实现

【免费下载链接】espectreWi-Fi CSI motion sensing for ESP32. C++ SDK, ESPHome, Native, and Matter frontends, browser tools, and a CLI for the full device lifecycle. GPLv3 and commercial licensing.项目地址: https://gitcode.com/GitHub_Trending/es/espectre

本文基于 ESPectre 仓库的架构决策记录 docs/adr/2026-09-03-adopt-resource-oriented-device-api.md(状态:Accepted),完整剖析该 ADR 的前因后果:为何放弃单一的POST /requestRPC 端点,如何用/espectre/v1下的资源化 Direct HTTP 重写读写与动作语义,以及如何在 HTTP 与 MQTT 之间保持单一规范资源/操作模型。读完本文,你将掌握 ESPectre 3.0 时代设备 API 的资源划分、HTTP 方法语义、GET /csi独占集合生命周期、MQTT 保留策略与可执行契约(C++ 协议注册表 + 命令引擎)的实际落地方式,并能据此为浏览器工具、CLI、benchmark 与 SDK 客户端编写兼容的调用代码。

一、背景:POST /requestRPC 模型的四大痛点

在资源化 API 之前,第一版 Direct API 把所有读取、变更与动作都压缩到唯一一个POST /requestRPC 端点里。该 ADR 记录了这一模型在多传输、多前端环境下的系统性缺陷:

  1. 生命周期概念重复:原始 CSI 采集需要显式的 start/stop 命令、一个 bearer 绑定以及 bind 超时,客户端才能打开二进制响应流——协议自造了一套"会话状态机",而不是依赖传输层已有的连接语义。
  2. 字段冗余:每条消息都重复携带身份(identity)与版本(version)字段,payload 臃肿且容易漂移。
  3. 浏览器缓存困难:所有请求都是 POST,浏览器无法按资源做条件请求或缓存控制。
  4. 传输机制泄漏为应用命令:传输层的细节(如 session、bearer、bind timeout)以应用命令的形式暴露在协议表面,HTTP 与 MQTT 的行为因此难以对齐。

与此同时,MQTT 侧也积累了一批与资源归属不匹配的查询命令和 topic payload。诊断、发现、配置、可用性与高帧率传感事件需要不同的保留(retention)与安全策略:例如传感事件是高频、可丢弃的,而配置与可用性状态必须是持久的、受控的。单一命令模型无法表达这些差异,这正是推动资源化重构的根本原因。

该 ADR 部分取代了三份先前的决策记录:

  • docs/adr/2026-07-02-use-one-message-model-and-command-engine-across-transports.md(单一消息模型与跨传输命令引擎,其"单一模型多传输"的结论被保留,但旧的查询/变更命令名被资源目录取代);
  • docs/adr/2026-07-03-unify-raw-csi-collection-over-http.md(原始 CSI 统一走 HTTP,二进制帧、排序、来源与固定环形缓冲行为继续生效,但显式命令建会话与 bearer 绑定被GET /csi连接生命周期取代);
  • docs/adr/2026-08-17-adopt-improv-serial-and-direct-http-for-local-control.md(Improv Serial 配网 + Direct HTTP 本地控制,其传输选择、端口、CORS 边界与发现服务继续生效,但/requestRPC 被资源 API 取代)。

二、核心决策:资源化 Direct HTTP 与 HTTP 方法语义

ADR 的关键决策是:保留应用版本1.0,但用一个尚未发布的资源化 Direct HTTP 取代旧 API,基础路径为/espectre/v1。语义如下:

HTTP 方法语义在 API 中的典型用途
GET读取资源healthdevicecapabilitiessensingwifidiagnosticseventscsi
PATCH部分更新资源device(label)、sensing(阈值/检测器/命中次数等)、mqtt
PUT替换式设置wifi/bssid(BSSID 固定选择,整体替换)
DELETE移除资源wifi/bssidwifi/credentialsmqtt
POST子资源动作sensing/calibrationswifi/scans、OTA 的checks/updates

关键约束:不存在/request路由,也没有任何兼容别名。旧客户端的POST /request在 3.0 起直接失效,必须迁移。

源码中的可执行路由表

资源化 API 并非停留在文档层面,而是由一个 C++ 常量路由表直接驱动。在 src/cpp/runtime/espectre_protocol.cpp 中,kApiRoutes定义了完整的基础路由,每条路由携带{http_method, path, name, command, method, kind, asynchronous}

{"GET", "/espectre/v1/health", "health", "health", ..., RESOURCE, ...}, {"GET", "/espectre/v1/device", "device", "device", ..., RESOURCE, ...}, {"GET", "/espectre/v1/capabilities", "capabilities", "capabilities", ..., RESOURCE, ...}, {"GET", "/espectre/v1/sensing", "sensing", "sensing", ..., RESOURCE, ...}, {"GET", "/espectre/v1/wifi", "wifi", "wifi", ..., RESOURCE, ...}, {"GET", "/espectre/v1/wifi/access-points", "wifi/access-points", "wifi_access_points", ..., RESOURCE, ...}, {"GET", "/espectre/v1/mqtt", "mqtt", "mqtt", ..., RESOURCE, ...}, {"GET", "/espectre/v1/diagnostics", "diagnostics", "read_diagnostics", ..., RESOURCE, ...}, {"GET", "/espectre/v1/devices", "devices", "devices", ..., RESOURCE, ...}, {"PATCH", "/espectre/v1/device", "update_device", "update_device", ..., OPERATION, ...}, {"PATCH", "/espectre/v1/sensing", "update_sensing", "update_sensing", ..., OPERATION, ...}, {"PATCH", "/espectre/v1/mqtt", "update_mqtt", "update_mqtt", ..., OPERATION, ...}, {"POST", "/espectre/v1/sensing/calibrations", "recalibrate", "recalibrate", ..., OPERATION, true}, {"POST", "/espectre/v1/wifi/scans", "scan_wifi", "scan_wifi", ..., OPERATION, true}, {"PUT", "/espectre/v1/wifi/bssid", "set_wifi_bssid", "set_wifi_bssid", ..., OPERATION, true}, {"DELETE", "/espectre/v1/wifi/bssid", "clear_wifi_bssid", "clear_wifi_bssid", ..., OPERATION, true}, {"DELETE", "/espectre/v1/wifi/credentials","clear_wifi_credentials", "clear_wifi_credentials", ..., OPERATION, true}, {"DELETE", "/espectre/v1/mqtt", "clear_mqtt", "clear_mqtt", ..., OPERATION, ...}, {"GET", "/espectre/v1/events", "events", "", ..., STREAM, ...}, {"GET", "/espectre/v1/csi", "csi", "", ..., STREAM, ...},

从这张表可以看到三个设计要点:

  • 资源(RESOURCE)全部走 GET,操作(OPERATION)全部是 PATCH/POST/PUT/DELETE,两个流式端点(STREAM:/eventsSSE、/csi二进制)也是 GET。
  • OTA 不在基础路由表中。根据 docs/API.md,OTA 是前端拥有的"规范消息模型的扩展":Native 通过同一个扩展目录(EspectreProtocolExtension)向 Direct HTTP 与 MQTT 注册自己的资源、操作、事件与参数校验。validate_protocol_extension强制扩展路由必须以/espectre/v1/开头、与基础路由不得冲突(见 src/cpp/runtime/espectre_protocol.cpp)。这样感测 SDK(sensing SDK)不需要实现固件升级。
  • diagnostics路由是唯一的"带参数 GET":HTTP 端既支持在 GET body 中携带 JSON 参数对象,也支持浏览器友好形式GET /espectre/v1/diagnostics?fields=%5B%22traffic_tx_pps%22%5D(URL 编码的 JSON 数组)。路由解析器对 query string 做了严格限制:只有 diagnostics、只有 GET、且不能与 body 同时使用,其他路径带 query 一律拒绝(见 src/cpp/runtime/direct_http_protocol.cpp)。

端点与请求/响应框架

Direct HTTP 监听 TCP 端口62587(即0xF47B,字符 U+1F47B GHOST 的低 16 位,定义于 src/cpp/runtime/direct_http_protocol.h)。客户端从GET /espectre/v1/capabilities开始,用返回的resourcesoperations目录推断支持面,而不是凭前端名称硬编码——这是"能力协商优先"的核心原则。

协议框架约定(详见 docs/API.md):

  • 成功的 GET 直接返回资源对象;请求体(若有)必须是 JSON 对象,空 body 等价于{};C++ 前端最多接受 2,048 字节请求体;非空 body 必须带Content-Type: application/json
  • 变更操作返回统一结果对象:{"accepted":true,"code":"ok","message":"operation accepted","data":{}},其中data可选。
  • 同步变更返回 HTTP200;异步与破坏性路由在请求通过应用分发后返回 HTTP202
  • 校验、能力、冲突类错误使用 docs/API.md 中的状态码(400 invalid_params404 unsupported409 busy/conflict/busy_raw_collection等);分发前被 HTTP 服务拒绝的失败(如413超限、415非 JSON、429限流、503停止中)不是规范结果对象,客户端必须先检查状态码与Content-Type再解析 JSON。

路由解析的具体实现可见 src/cpp/runtime/direct_http_protocol.cpp:parse_direct_http_request先分离 query string,再在基础路由表与扩展路由中按(http_method, resource)精确匹配,匹配失败即返回"unsupported Direct resource or method";随后做 body 大小检查与 JSON 对象字段解析(重复字段会被拒绝,测试 test/cpp/suites/runtime/test_espectre_protocol.cpp 验证了这两类错误)。HTTP 服务端在 src/cpp/runtime/esp_idf/direct_http_service_esp_idf.cpp 中按GET/POST/PUT/DELETE四种方法注册/espectre/v1/*通配 URI(对应测试 test/cpp/suites/runtime/test_direct_http_service.cpp 断言了注册结果)。

三、跨传输的单一资源与操作模型

ADR 明确要求:HTTP 与 MQTT 共享同一套规范资源与操作模型——资源 payload、操作名、校验规则与结果码完全一致,只有传输封装与投递策略不同(这一结论继承自已部分取代的 docs/adr/2026-07-02-use-one-message-model-and-command-engine-across-transports.md)。

版本与身份字段的收敛

为了消除冗余字段,协议对protocol_versiondevice_id的出现位置做了硬性限定:

  • protocol_version只出现在capabilities资源与发现(discovery)元数据中,普通应用 JSON 不再携带。
  • device_id只出现在device资源、多设备发现结果以及既有的二进制 CSI 记录元数据中
  • MQTT 命令请求(commands/request)顶层只有command_idcommand,参数也在顶层,既无protocol_version也无device_id

capabilities资源承载协商所需的一切:protocol_version(当前1.0)、resources(GET 可用的资源名数组)、operations{name, method, path}数组)、events(事件名数组)、features.csi(是否支持GET /csi)以及可选的csi对象。客户端必须容忍资源的增量式扩展(additive fields)。

MQTT 保留与可用性策略

MQTT 侧的主题布局与保留策略同样按资源边界划分(docs/API.md):

Topic 后缀Retained用途
health健康、可用性与 Last Will
device设备身份与构建信息
capabilities协商与支持面
sensing感测状态与调参
wifi脱敏后的无线状态
otaOTA 状态
motion每次检测评估的运动事件
fault运行时故障
commands/result关联的命令结果

要点:

  • health兼作 Home Assistant 可用性主题与 MQTT Last Will:设备非优雅断开后由 broker 发布 retained 的离线health。除了 MQTT keepalive 之外不再有应用层心跳。
  • 设备状态按资源 retained 发布(device/capabilities/sensing/wifi/ota),而每个评估周期的motionfault与关联命令结果都不保留——高频、可替换的事件不应占用 broker 存储。
  • wifi的 retained payload 做了脱敏:只含configuredconnectedbandchannelrssi_dbmapply_stateapply_message不发布 SSID、BSSID 与 IP
  • MQTT 不提供 MQTT 配置、诊断、发现与 CSI 相关主题;read_diagnostics的结果通过commands/result.data返回。诊断走"关联响应"而非事件流。

MQTT 命令集与 HTTP 操作一一对应:update_devicePATCH /deviceupdate_sensingPATCH /sensingrecalibratePOST /sensing/calibrationsread_diagnosticsGET /diagnosticscheck_ota/start_otaPOST /ota/checks//ota/updates。命令 payload 限制 2,048 字节,command_id为 1–64 字符(ASCII 字母、数字与_-.:)。MQTT 没有 HTTP 状态码,订阅者依据commands/result中的acceptedcode判断;不在已发布命令集内的命令返回forbidden

三个 C++ 前端共享一个分发器

"单一模型多传输"在代码上的落点,是所有 C++ 前端(Native、ESPHome、Matter)共用同一个FrontendCommandEngine(src/cpp/runtime/frontend_command_engine.cpp 的execute入口)与同一个 CSI 会话控制器。各适配器只负责传输帧、来源(Origin)与访问策略,随后在前端任务上串行执行规范命令。ESPHome 实体写入与 Direct 变更最终收敛到同一份权威运行时状态。

四、GET /csi:独占集合生命周期

资源化 API 最激进的一处变化,是用连接生命周期取代显式命令管理的会话。ADR 的表述是:

GET /csi拥有整个独占集合生命周期的所有权:TCP 响应开始集合,其关闭停止集合,客户端采用第一帧二进制数据的会话 ID。

具体协议行为(详见 docs/API.md):

  • GET /espectre/v1/csi打开唯一的独占二进制 CSI 集合会话。没有setup 请求、bearer token、会话删除或 bind 超时;关闭 TCP 响应即结束集合。
  • C++ 运行时要求在集合开始前感测服务已武装(armed)。若感测被禁用或因重配置/维护而挂起,打开集合会失败,且不会偷偷重新启用 CSI 采集或流量生成。
  • 集合期间暂停所有传输上的派生感测事件(motion 及未来所有派生事件),但控制与资源事件保持活跃;集合结束(含需重新校准时)后先恢复感测与 readiness,派生事件才恢复。
  • 集合期间第二次/csi请求,以及感测、Wi-Fi、OTA 变更,都返回409
  • 每个 CSI V8 记录保留既有的 60 字节小端 HTTP 前缀;客户端采用第一帧的 16 字节会话标识符,同一连接内变更即拒绝。生产方保序,固定环形缓冲的丢弃在传输计数器中可观察(fresh_record_total + raw_drop_total == classified_frames_offered_to_raw)。
  • 原始工作线程在无可用 CSI 记录时检测到客户端断开,会直接关闭会话,不合成记录

源码佐证:显式命令已彻底消失

src/cpp/runtime/esp_idf/raw_csi_session_controller.cpp 中的handle_command展示了这一转变的直接后果:旧的"开始/停止集合"命令处理函数被保留为一个空壳,任何到达的命令都返回code = "unsupported",消息固定为"CSI collection is opened with GET /csi"。也就是说,从协议层面就杜绝了经由命令引擎打开 CSI 会话的路径——集合只能由GET /csi发起。会话开始时会用esp_fill_random生成 16 字节会话 ID(src/cpp/runtime/esp_idf/raw_csi_session_controller.cpp),并在begin()中校验能力(supports_raw_csi)与互斥状态(RAW_COLLECTION),确保独占性。

外部 CSI 流量

external模式下,ESPHome、Native 与 Matter 接受发往设备的 UDP 标记与单播 ICMP Echo Request。UDP 可用设备 IP 或csi_traffic_multicast_group(默认239.255.0.1,置空则关闭组播加入、保留单播接收);UDP 监听端口5555,只接受恰好四字节的 UTF-8 标记F0 9F 91 BB。外部主机负责两种协议的节奏控制;./espectre collect会持续选择external并导入标准库实现的ExternalTrafficGenerator(tools/espectre_traffic_generator.py),其--pps只控制该 UDP 生成器与数据集来源记录。详细命令见 docs/CLI.md,投递限制见 docs/CSI.md。

五、规范拆分与可执行契约

ADR 将公共规范一分为二:

  • docs/API.md:拥有 Direct HTTP、SSE 与 MQTT 的公开应用契约,包括全部资源 schema、操作参数、事件定义、CSI 集合、错误码与安全版本策略;
  • docs/DISCOVERY.md:拥有 DNS-SD/mDNS、浏览器引导发现与/devices资源(TXT 记录、bootstrap 行为、/devices扫描 schema 与序列化限制)。

两者通过 TXT 记录中的protovers=1.0path=/espectre/v1衔接:客户端从发现拿到基础路径后,再通过GET /capabilities协商精确表面。

C++ 注册表与命令引擎是可执行契约

"可执行契约"(executable contract)的含义是:文档描述行为,但真正裁决行为的是代码kApiRoutes路由表(src/cpp/runtime/espectre_protocol.cpp)、参数校验(validate_sdk_command_parameters)与FrontendCommandEngine共同构成权威实现;HTTP 与 MQTT 的映射关系由 C++ 专门测试维护,espectre_transport_mapping_payload()直接序列化该映射供跨语言对账。测试 test/cpp/suites/runtime/test_espectre_protocol.cpp 覆盖了规范消息解析、未知资源返回"unsupported Direct resource or method"、畸形 JSON 与重复字段拒绝;test/cpp/suites/runtime/test_direct_http_service.cpp 则验证了扩展路由(vendor 资源/动作)通过EspectreProtocolExtension注入的机制。

Micro-ESPectre:只读交集

由于 MicroPython 无法共享 C++ 实现,Micro-ESPectre 维护等价的注册表与分发器,但只发布受支持的只读交集。从 src/python/micro_espectre/direct_api.py 可以看到其实质:

  • _status()/_config()/_wifi()/_diagnostics()提供healthsensingwifidiagnostics的只读快照;_config()输出与 C++ 同构的sensingschema(enabled/ready/calibrating/mode/threshold/detector/motion_on_hits/motion_off_hits/csi_traffic_mode/traffic_generator_mode/csi_target_pps);
  • 唯一支持的变更动作是校准:take_recalibration_request()认领一个排队的 Direct 校准请求,complete_recalibration()允许 Direct worker 接受下一个请求——对应POST /sensing/calibrations(HTTP202,繁忙时409 busy);
  • publish_motion()在存在事件客户端时发布规范 motion 事件(stateidlemotion,携带score)。

根据 docs/API.md 的支持矩阵,Micro 支持全部 GET 资源(含calibrations),但不支持wifi/access-pointsdevicescsimqttota及一切 PATCH/PUT/DELETE 变更。不支持的资源与方法组合统一返回 HTTP404——未受支持的前端资源不会出现在 capabilities 中,且返回 404,这是 ADR 后果清单里明确的一条。

六、后果与迁移影响

ADR 的 Consequences 可以归纳为六点,逐一与代码对应:

  1. 浏览器、CLI、benchmark 与 SDK 客户端操作显式资源。Web 侧 docs/web/content/tools/monitor.html(Monitor)、docs/web/content/tools/device-settings.html(设备设置)、docs/web/content/tools/csi-visualizer.html(CSI 可视化)以及 tools/benchmark_firmware.py 都按资源直接请求所需字段;通用 CLI 的read_diagnostics默认["*"],显式传fields: []可查看目录。
  2. 三个 C++ 前端共享一个分发器与一个 CSI 会话控制器FrontendCommandEngineRawCsiSessionController是共享实现,前端只提供传输适配。
  3. MQTT 的保留、可用性与命令安全遵循资源边界。retained 策略、healthLast Will、wifi脱敏、commands/result不保留,均按上表执行。
  4. 显式原始会话命令、bearer 绑定、每消息版本字段与冗余身份字段消失raw_csi_session_controllerhandle_command直接返回unsupported即为代码级证明;protocol_version/device_id的位置约束写入契约。
  5. 未受支持的前端资源不在 capabilities 中并返回404。能力协商(GET /capabilities)是权威来源,自定义构建禁用某特性时同样生效。
  6. 使用旧 RPC 模型的预发布客户端必须迁移,且没有兼容期。API.md 明确"不存在旧路由、命令、主题或别名";在 3.0.0 release-candidate 阶段,协议契约保持1.0(Direct/espectre/v1、MQTT 前缀espectre/v1/devices、DNS-SDprotovers=1.0),诊断字段选择行为也在该预发布契约内演化——使用匹配版本的固件与客户端,没有到旧诊断响应的自动回退。

七、总结与阅读路径

资源化设备 API 是 ESPectre 从"单端点 RPC + 命令会话"走向"资源化、能力协商、跨传输一致"的关键架构转折。它以最小的字段占用(版本与身份只在必要处出现)、显式的资源所有权(GET /csi独占集合)、按资源划分的保留与安全策略,统一了 Native、ESPHome、Matter 与 Micro 四类前端的可编程表面,并把可执行契约交给 C++ 注册表与命令引擎,而不是停留在文档层面。

若想继续深入,建议按以下顺序阅读仓库内容:

  1. 本 ADR 及其决策谱系:docs/adr/2026-07-02-use-one-message-model-and-command-engine-across-transports.md、docs/adr/2026-07-03-unify-raw-csi-collection-over-http.md、docs/adr/2026-08-17-adopt-improv-serial-and-direct-http-for-local-control.md;
  2. 完整契约:docs/API.md 与 docs/DISCOVERY.md;
  3. 可执行实现:src/cpp/runtime/espectre_protocol.cpp、src/cpp/runtime/direct_http_protocol.cpp、src/cpp/runtime/frontend_command_engine.cpp、src/cpp/runtime/esp_idf/raw_csi_session_controller.cpp;
  4. 只读 Micro 实现:src/python/micro_espectre/direct_api.py;
  5. 契约测试:test/cpp/suites/runtime/test_espectre_protocol.cpp、test/cpp/suites/runtime/test_direct_http_service.cpp;
  6. 相关底层协议:docs/CSI.md、docs/CLI.md。

【免费下载链接】espectreWi-Fi CSI motion sensing for ESP32. C++ SDK, ESPHome, Native, and Matter frontends, browser tools, and a CLI for the full device lifecycle. GPLv3 and commercial licensing.项目地址: https://gitcode.com/GitHub_Trending/es/espectre

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询