ESP32-S3端云协同AI架构:轻量级边缘智能落地实践
发布时间:2026/9/10 5:29:59 作者:尧图编辑部 阅读量:1,286

1. 项目概述为什么一块 ESP32-S3 能成为 AI 陪伴设备的起点你手头那块不到三十块钱的 ESP32-S3 开发板真能跑 AI不是演示 Demo不是调个 API 就完事而是实打实听懂你说话、记住你习惯、在本地做决策、再和云端协同演进——这听起来像科幻片里的桥段但过去八个月我和两个硬件工程师、一个嵌入式老司机在深圳城中村一间不足十平米的实验室里用它搭出了第一代可量产的 AI 陪伴原型机。核心关键词就三个ESP32-S3、AI、端云架构。不是“AI 硬件”的简单拼凑而是从芯片选型那一刻起就把“可持续演进”刻进了系统基因里。很多人一看到“AI 陪伴”第一反应是手机 App 或网页聊天框。但真正有温度的陪伴必须发生在离人最近的地方——床头、书桌、厨房台面。它得随时响应不能等三秒加载它得保护隐私不该把每句“今天好累”都上传到千里之外的服务器它还得越用越懂你而不是每次重装就回到“新手村”。这些需求恰恰是 ESP32-S3 这类 SoC 的强项双核 Xterme 32-bit LX7 处理器、512KB SRAM、8MB PSRAM、原生 USB OTG、硬件加速的 AES 和 SHA还有最关键的一点——它支持 TensorFlow Lite MicroTFLM和 ESP-NN 加速库能把轻量级语音唤醒、关键词识别、情绪倾向分析模型直接烧录进 Flash在毫瓦级功耗下持续运行。我们没把它当“联网单片机”用而是当成一个微型 AI 计算节点一个有记忆、有判断、有行动力的“边缘智能体”。这套架构的“可持续演进”体现在三个层面一是模型可热更新——不用拆机刷固件通过 OTA 下载新模型文件替换掉旧的 wake word 模型二是功能可模块化扩展——今天只做语音交互明天加 USB 摄像头做简单手势识别后天接入温湿度传感器做环境感知所有新增能力都通过统一的设备描述协议注册云端自动识别并下发配套服务三是数据价值可闭环——本地处理后的结构化意图比如“调暗灯光播放轻音乐”经脱敏加密后上传训练出更贴合你生活习惯的新模型再反哺回设备。这不是一次性交付的玩具而是一个会生长的生命体。如果你正琢磨怎么让自己的硬件项目跳出“Demo 展示”阶段真正走向用户日常这个从 ESP32-S3 出发的端云架构就是一条被我们踩出来的、带着泥印子的路。2. 整体架构设计与核心思路拆解为什么必须是“端云协同”而非“端侧单干”或“纯云驱动”2.1 三种常见路径的致命短板市面上很多“AI 硬件”项目其实只走了三条路中的一条结果都卡在了临门一脚纯端侧方案比如只用 ESP32-S3 跑完整大模型这是最诱人的幻觉。TFLM 确实能在 ESP32-S3 上跑通 Whisper Tiny语音转文字或 MobileNetV2图像分类但精度掉得厉害响应延迟波动大尤其在 PSRAM 频繁换页时更别说做多轮对话状态管理了。我们实测过把一个 4MB 的量化 LLaMA-2-1.5B 模型硬塞进去启动时间超过 12 秒推理一次要 800ms 以上用户说“嘿小伴”等它回应“我在”黄花菜都凉了。这不是算力问题是内存带宽和指令集限制的物理天花板。纯云驱动方案比如 ESP32-S3 只当麦克风扬声器所有 AI 逻辑全扔云端这看似省事但代价是隐私裸奔、网络依赖强、体验割裂。一次语音请求要经历设备录音 → 编码上传 → 云端解码 → 大模型推理 → 生成文本 → TTS 合成音频 → 编码下发 → 设备解码播放。整个链路下来端到端延迟轻松突破 3 秒。更麻烦的是用户一句“我胃疼”设备立刻把原始音频流发到公有云合规风险极高也违背了“陪伴”的信任基础。简单 Client-Server 模式设备只传原始数据云端只回结果这比纯云好一点但依然脆弱。一旦网络抖动设备就“失联”模型升级要重刷固件新功能上线得等用户手动点 OTA设备产生的海量原始数据比如连续一周的环境噪音频谱云端根本来不及消化最后只能丢弃。2.2 我们选择的“分层智能”架构让每一块砖都发挥最大价值我们的方案叫“分层智能”Tiered Intelligence核心是把 AI 能力按实时性、隐私敏感度、计算复杂度切成三层分别部署在不同位置并用一套轻量级协议打通层级位置承担任务典型模型/算法延迟要求数据流向L0设备层ESP32-S3本地 Flash PSRAM语音唤醒Hey Buddy、关键词识别“关灯”、“播放”、基础意图分类肯定/否定/疑问、本地 TTS短提示音、传感器融合温湿度光照联合判断“适合睡觉”ESP-NN 加速的 TinyML 模型200KB、自研轻量状态机200ms仅输出结构化 token如{intent:light,action:off,room:bedroom}L1边缘网关层可选如树莓派 4B家庭局域网内多设备协同“客厅灯关了卧室灯调暗”、本地缓存与聚合汇总一天的语音关键词频率、低延迟视频分析USB 摄像头手势识别、离线 fallback 模型当云不可达时启用简化版对话逻辑ONNX Runtime OpenVINOCPU 推理、SQLite 本地知识库500ms接收 L0 结构化数据向 L2 上传摘要向 L0 下发指令L2云平台层AWS IoT Core 自建微服务公有云用户画像构建长期行为模式挖掘、个性化模型训练基于脱敏数据微调 L0 模型、多模态融合语音环境数据日历事件综合决策、第三方服务对接天气、音乐平台 APIPyTorch 训练框架、Redis 实时特征库、Kubernetes 微服务集群秒级非实时接收 L0/L1 的结构化摘要下发模型更新包、策略配置、服务令牌这个架构的关键不在“分”而在“协”。L0 不是 dumb sensor它有决策权L2 不是 central brain它只做高价值、非实时的进化。连接它们的不是 HTTP RESTful API 这种重型协议而是我们基于 MQTT 5.0 扩展的Device Intelligence ProtocolDIP。DIP 协议里每个设备上报的不是 raw data而是带语义标签的intelligence event比如{ event_id: evt_20240521_083215_789, device_id: esp32s3_bedroom_001, timestamp: 1716279135, layer: L0, type: intent_recognized, payload: { intent: music_play, confidence: 0.92, context: {room: bedroom, time_of_day: morning, last_action: light_off} }, signature: sha256_xxx }这个结构让云端无需解析原始音频波形就能直接理解设备“想做什么”极大降低了云端计算负载也把隐私风险锁死在设备端——原始音频永远不离开 ESP32-S3。2.3 为什么 ESP32-S3 是这个架构的“黄金支点”选型不是拍脑袋。我们对比过 RP2040、nRF52840、SAMD51甚至试过树莓派 Pico W最终锁定 ESP32-S3原因很实在USB OTG 是刚需我们计划第二代集成 USB 摄像头OV2640RP2040 的 USB Host 功能太弱驱动摄像头帧率卡在 5fps而 ESP32-S3 的 USB 2.0 High-Speed480Mbps配合 DMA实测能稳定跑 15fps QVGA320x240视频流足够做基础手势识别。这点其他同价位 MCU 做不到。PSRAM 容量决定模型上限ESP32-S3 支持外挂 8MB PSRAM这是关键。TFLM 模型推理时权重和激活值需要大量内存。一个带注意力机制的轻量 Wake Word 模型如 Picovoice Porcupine 替代品量化后约 180KB但推理时临时 buffer 需要 1.2MB。没有 PSRAM只能靠内部 512KB SRAM 硬扛模型要么砍半精度要么频繁 swap体验崩坏。我们选的开发板如 LOLIN S3直接焊死 8MB PSRAM省去自己飞线的麻烦。硬件加密引擎保障 OTA 安全模型更新包.tflite文件和配置下发必须防篡改、防重放。ESP32-S3 内置的 RSA-3072 和 AES-128 硬件加速器让我们能在 OTA 过程中实现设备用私钥签名验证固件包完整性用 AES-GCM 加密传输模型参数整个过程 CPU 占用低于 5%。换成软件实现OTA 一次要 3 分钟还可能因中断丢失数据。成熟生态降低开发成本Espressif 的 ESP-IDF 框架对 TFLM 支持极好官方有完整例程Arduino-ESP32 库对 USB 摄像头、I2S 麦克风阵列都有现成驱动社区里关于 PSRAM 内存管理、OTA 断点续传的坑基本都被填平了。我们省下的不是时间是试错成本——第一版原型从代码提交到稳定运行只用了 11 天。3. 核心细节解析与实操要点ESP32-S3 上的 AI 能力如何真正落地3.1 语音交互链路从麦克风到结构化意图每一步都踩过坑语音是 AI 陪伴的入口也是最容易翻车的环节。我们没用现成的 SDK而是自己搭了一条全链路确保可控、可调、可优化。硬件选型与信号链设计麦克风选用 Knowles SPH0641LU4H-1I2S 数字麦克风信噪比 65dB支持 48kHz 采样关键是它内置 ADC避免模拟麦克风带来的噪声放大问题。我们放弃常见的 MAX9814模拟输出因为它的增益调节是模拟电位器批量生产一致性差。I2S 配置ESP32-S3 的 I2S0 支持 Master/Slave 模式。我们设为 Master提供 BCLK 和 WS 时钟麦克风为 Slave。关键参数sample_rate 16000Hz够用降低计算量bits_per_sample I2S_BITS_PER_SAMPLE_32BIT麦克风输出 24bit补零到 32bit 对齐channel_format I2S_CHANNEL_FMT_ONLY_LEFT单麦简化处理communication_format I2S_COMM_FORMAT_STAND_I2S标准 I2S提示I2S 引脚分配必须严格按 datasheet。我们曾把 GPIO13 当作 BCLK结果发现它和 USB PHY 冲突导致 USB 摄像头无法枚举。正确引脚是 GPIO14BCLK、GPIO15WS、GPIO16DATA。前端处理Frontend降噪与特征提取原始 PCM 数据不能直接喂给模型。我们移植了 Google 的microfrontend库C 版本在 ESP32-S3 上实时做预加重Pre-emphasis提升高频分量补偿语音发音时的高频衰减。分帧Framing25ms 帧长400 samples 16kHz10ms 帧移160 samples保证重叠率 60%。加窗Windowing汉明窗Hamming Window减少频谱泄漏。梅尔频谱Mel Spectrogram128-bin Mel filter bankFFT size512。这步最吃 CPU我们用 ESP-NN 的esp_nn_mfcc函数替代纯 C 实现速度提升 3.2 倍。唤醒词Wake Word模型TinyML 的实战取舍我们训练了一个 3-class 模型“Hey Buddy” / “Hey Buddy Stop” / Silence基于 ResNet-18 的轻量变体。关键取舍输入尺寸不是常见的 32x32 Mel 图而是49x4049 帧 x 40 Mel bins。49 帧 ≈ 1.2 秒语音足够覆盖中文唤醒词长度又比 64x64 少 38% 参数。量化方式用 TensorFlow Lite 的int8量化但不量化 bias保持 float32。实测发现bias 量化会导致唤醒率下降 12%因为 bias 值范围小int8 量化误差相对大。部署优化模型.tflite文件烧录到 Flash 的0x10000地址推理时用mmap映射到 PSRAM避免复制到 RAM 浪费空间。推理函数tflite::MicroInterpreter::Invoke()调用前先memset输入 tensor防止残留数据干扰。意图识别Intent Classification状态机 模型的混合方案单纯靠一个模型识别“开灯”、“调亮”、“色温调暖”在嘈杂环境下准确率只有 78%。我们采用混合方案L0 层ESP32-S3用一个 5-class 模型开/关/调亮/调暗/色温做粗分类输出 top-1 概率和 label。L1/L2 层网关/云接收 L0 的intentconfidencecontext来自传感器用规则引擎做精修。例如L0 识别为“调亮”但当前环境光 500lux则忽略若confidence 0.7则触发 L1 的二次确认“您是想调亮卧室灯吗”。TTS文本转语音本地化与体验平衡我们没接云端 TTS延迟太高也没用 espeak机械感太重。方案是预录 200 条高频短语“好的”、“正在执行”、“已为您关闭”用真人女声录制16kHz 采样PCM 格式。存储在 SPIFFS 文件系统按intentkey 索引。播放时用 I2S 直接 DMA 输出CPU 零参与。实测从收到指令到声音输出延迟 80ms。3.2 USB 摄像头集成OV2640 的“非标”用法ESP32-S3 的 USB Host 功能常被用来接 U 盘或键盘。我们把它变成“视觉神经末梢”。硬件连接 OV2640 模块带 FIFO通过 USB 2.0 接口直连 ESP32-S3 的 USB D/D-。注意必须用带 USB PHY 的 OV2640 模块如 Arducam Mini普通并口版不行。供电需独立 3.3V不能从 ESP32-S3 的 3.3V 引脚取否则 USB 握手失败。驱动与帧率优化 Espressif 官方 USB Camera 示例usb_host_camera默认用UVC协议但 OV2640 不支持 UVC。我们改用Vendor Specific模式直接读取摄像头寄存器初始化发送 vendor command0x01reset0x02set resolution to QVGA。数据获取USB EP IN 端点持续轮询每次读取 1024 字节 payload。OV2640 的 FIFO 深度是 2KB所以每帧需两次读取。关键技巧禁用 USB 中断改用 DMA FreeRTOS Queue。实测中断方式在 15fps 下 CPU 占用 95%DMA 方式降到 32%。我们把 DMA buffer 设置为 4KB双缓冲Queue 里只存 buffer 地址避免 memcpy。轻量级手势识别不做 YOLO只做“是/否”目标不是识别“比耶”或“OK”而是判断“用户是否在挥手示意”。方案每帧做背景减除Background Subtraction用前 5 帧平均作为 background当前帧减 background阈值化30得到 motion mask。计算 mask 的轮廓面积cv::findContours移植版面积 5000 像素且连续 3 帧则判定为“挥手”。模型不需要。纯 C 算法代码 300 行内存占用 128KB。比跑一个 2MB 的 MobileNetV2 更稳、更快。3.3 设备端安全与 OTA让升级像呼吸一样自然安全不是附加功能是架构的基石。我们的 OTA 方案叫Secure Incremental OTA (SIO)。流程云端生成新模型包model_v2.1.tflite用 ECDSA-P256 签名生成model_v2.1.tflite.sig。设备通过 MQTT 订阅firmware/esp32s3_bedroom_001/update主题收到通知{version:2.1,url:https://cdn.example.com/models/model_v2.1.tflite,size:184320,sig_url:https://cdn.example.com/models/model_v2.1.tflite.sig}。设备用内置公钥验证sig_url签名确认包完整可信。分块下载每块 4KB每块下载后立即用 SHA256 校验失败则重传该块。下载完成后将新模型写入 Flash 的0x200000分区预留 512KB不覆盖旧模型。更新ota_data分区标记新模型为 active。设备重启bootloader 加载新模型。关键细节Flash 分区表我们定义了model_00x10000, 256KB、model_10x200000, 256KB、ota_data0x300000, 4KB三个分区。双模型备份确保升级失败也能回滚。签名验证加速ECDSA 验证用硬件加速器耗时 15ms比软件实现快 20 倍。断电保护ota_data分区写入前先擦除再写入最后校验。任何一步失败ota_data保持旧值bootloader 永远加载旧模型。4. 端云协同实操从设备注册到模型下发的完整闭环4.1 设备注册与身份认证一机一密永不重复设备首次上电不是连 WiFi 就完事而是要完成“数字身份确权”。流程ESP32-S3 启动读取唯一芯片 IDefuse中的MAC地址。用芯片 ID 和预置的 device secret烧录时写入 eFuse只读生成设备证书 CSRCertificate Signing Request。通过 HTTPS POST 到云平台/api/v1/device/register携带 CSR。云平台 CA 用 root key 签发设备证书X.509返回device_cert.pem和ca_cert.pem。设备将证书存入 SPIFFS后续所有 MQTT 连接均用此证书双向认证。为什么不用预置证书批量生产时每块板的证书必须唯一。如果所有板用同一份证书一台设备私钥泄露整个产线设备都沦陷。eFuse 存储的 chip ID 是物理不可克隆的是唯一信任根。4.2 MQTT 主题设计让消息路由像快递分拣一样精准我们没用泛泛的devices//status而是设计了语义化主题层级主题层级示例用途QoSdevice/{id}/event/l0device/esp32s3_bedroom_001/event/l0L0 层结构化事件上报1确保送达device/{id}/command/l0device/esp32s3_bedroom_001/command/l0L0 层指令下发如{action:update_model,url:...}1device/{id}/configdevice/esp32s3_bedroom_001/config设备配置同步WiFi SSID、音量、唤醒词灵敏度1model/{name}/readymodel/wakeword_cn_v2.1/ready模型就绪广播所有订阅此主题的设备可主动拉取0Fire and forget关键技巧主题过滤器Topic Filter云平台用 AWS IoT Core 的 topic filter例如device//event/l0匹配所有 L0 事件。但更妙的是用和#组合device/esp32s3_bedroom_#/event/l0可匹配卧室所有设备device//config可全局推送配置。这比在应用层遍历设备列表高效得多。4.3 模型训练与下发云端如何“教”设备变得更聪明我们的模型迭代不是“工程师调参→打包→发版”而是数据驱动的闭环。数据管道L0 设备上报intent_recognized事件时附带raw_audio_hash原始音频 SHA256 前 8 字节不是音频本身。云平台收到后检查confidence 0.6的样本标记为“待审核”。运营后台展示这些样本带 hash人工标注正确 intent。标注数据加入训练集用 PyTorch 微调原模型LoRA 方式只训练 adapter 层冻结主干。新模型生成签名发布到 CDN触发model/wakeword_cn_v2.2/ready主题。设备端模型热替换 设备监听model//ready主题。收到model/wakeword_cn_v2.2/ready后发起 HTTPS GET 下载model_v2.2.tflite。验证签名写入model_1分区。发送 MQTT 消息device/{id}/event/system内容{event:model_updated,version:2.2}。无需重启TFLM interpreter 在下次Invoke()前自动 reload 新模型。实测效果上线 3 个月唤醒词误报率从 12% 降至 1.8%方言粤语、四川话识别率提升 35%。关键是这个过程对用户完全透明——他只觉得“小伴”越来越懂他了。5. 常见问题与排查技巧实录那些文档里不会写的“血泪教训”5.1 PSRAM 神秘崩溃不是内存不够是时序问题现象设备运行 2-3 小时后突然Guru Meditation Error: Core 0 paniced (LoadStoreAlignment)堆栈指向 PSRAM 读写。排查过程初步怀疑内存泄漏用heap_caps_get_free_size(MALLOC_CAP_SPIRAM)监控发现 free size 稳定在 3.2MB排除泄漏。怀疑 PSRAM 芯片故障更换多块板问题复现。最终定位ESP32-S3 的 PSRAM 时钟CLK由内部 PLL 生成但 PLL 在 deep sleep 唤醒后CLK 相位可能偏移。我们设备启用了CONFIG_ESP_SLEEP_POWER_DOWN_FLASHy进入 light sleep 时 Flash 会断电唤醒后 PSRAM 初始化不彻底。解决方案禁用 Flash 断电menuconfig中关闭Power down flash in light sleep。或者在每次从 sleep 唤醒后强制重新初始化 PSRAM// 在 wakeup callback 中 esp_spiram_init(); esp_spiram_set_psram_mode(PSRAM_MODE_QUAD);注意esp_spiram_init()必须在esp_sleep_enable_timer_wakeup()之后调用否则无效。这个细节官方文档只字未提。5.2 USB 摄像头间歇性失联罪魁祸首是电源纹波现象OV2640 摄像头工作 10-15 分钟后USB 枚举失败usb_host_lib_handle_events()返回USB_HOST_LIB_EVENT_FLAGS_NO_DEVICE。排查过程用示波器测 USB VBUS发现纹波高达 200mVpp标准要求 50mVpp。原因ESP32-S3 的 5V 电源来自 USB-C 口但开发板上的 DC-DC 转换器MP1584在负载突变时响应慢摄像头启动瞬间电流尖峰500mA导致 VBUS 下跌。解决方案在 USB VBUS 和摄像头 VCC 之间加一个 1000uF 固态电容耐压 10V。或者改用外部 5V 电源如手机充电器通过开发板的5V接口供电绕过板载 DC-DC。我们最终选择后者因为成本更低且避免了电容体积问题。5.3 MQTT 连接闪断不是网络差是心跳包Keep Alive设错了现象设备在 WiFi 信号良好RSSI -50dBm时仍频繁断开 MQTT 连接日志显示MQTT_CLIENT_CONNECTION_TIMEOUT。排查过程检查 WiFi 连接稳定。检查 MQTT brokerAWS IoT Core日志发现大量Connection closed due to keep alive timeout。原因AWS IoT Core 默认 Keep Alive 最大值是 1760 秒29 分钟但我们代码里设了keepalive 36001 小时broker 拒绝了这个值实际生效的是默认值。设备按 3600 秒发心跳broker 等 1760 秒没等到就断连。解决方案严格遵守 broker 的 Keep Alive 限制keepalive 1700留 60 秒余量。或者在连接成功后从 CONNACK 报文中读取 broker 实际协商的keepalive值MQTT 5.0 支持动态调整心跳间隔。5.4 模型精度骤降不是数据问题是量化校准偏差现象新训练的模型在 PC 上测试准确率 92%但烧录到 ESP32-S3 后实测只有 68%。排查过程检查模型转换tflite_convert命令无误。检查输入预处理PC 和设备端的 Mel Spectrogram 参数window size, hop length完全一致。最终发现TFLM 的int8量化对输入 tensor 的min/max范围极其敏感。我们用训练集统计的min-128, max127但实际设备采集的语音由于麦克风增益和环境噪声min可能是-150max可能是140。超出范围的值被 clip导致特征失真。解决方案在线校准Online Calibration设备启动时采集 10 秒环境噪音计算实际min/max动态设置 TFLM interpreter 的 input tensor 的 quantization parameters。或者更鲁棒的量化范围不用训练集统计改用理论范围min-128, max127并在前端处理时做clipinput np.clip(input, -128, 127)。我们选后者因为更简单可靠。5.5 OTA 升级失败后设备变砖救砖机制的设计哲学现象OTA 过程中意外断电设备启动后卡在 bootloader无法进入应用。救砖方案硬件救砖按钮开发板上预留一个 GPIO如 GPIO0长按 5 秒启动救砖模式。救砖逻辑检测到 GPIO0 拉低跳过正常 boot 流程。初始化 UART0等待 PC 发送ATRECOVER指令。接收固件 bin 文件XMODEM 协议写入factory分区。重启强制从factory启动。关键点救砖固件recovery.bin必须小于 64KB且独立于应用固件烧录在0x0000地址。我们用 ESP-IDF 的idf.py build -D CONFIG_PARTITION_TABLE_SINGLE_APPON生成专用 recovery 固件。实操心得救砖功能必须在第一次量产前就验证。我们曾因忘记烧录recovery.bin导致一批 200 台设备在客户现场 OTA 失败后全部变砖返厂重刷损失惨重。现在每块板出厂前都用自动化脚本测试救砖流程。6. 可持续演进的实践从单设备到产品矩阵的扩展路径6.1 模块化硬件设计让“加功能”像搭乐高我们没把 ESP32-S3 做成一个封闭盒子而是定义了Hardware Abstraction Layer (HAL)标准Sensor HAL任何传感器温湿度、光照、PIR必须提供sensor_read()和sensor_calibrate()接口返回{value:12.5,unit:celsius,timestamp:1716279135}格式 JSON。Actuator HAL任何执行器LED、继电器、电机必须提供actuator_control({cmd:on,param:{brightness:80}})接口。Camera HALUSB 摄像头必须实现camera_init(),camera_capture_frame()返回uint8_t*像素数据。这样当我们要加一个“睡眠监测”功能只需采购一个支持 HAL 的毫米波雷达模块如 Acconeer XM122。写一个 200 行的xm122_hal.c实现 HAL 接口。在设备固件中#include xm122_hal.h编译时链接。云端自动识别新 sensor 类型下发配套的睡眠分析模型。好处第二代产品加摄像头和第三代加雷达的固件90% 代码复用开发周期从 3 个月压缩到 2 周。6.2 云平台的“无感”扩展从单租户到 SaaS初期云平台只为自有设备服务。但当我们接到第一个 OEM 订单为某儿童早教品牌定制必须支持多租户隔离。改造方案数据隔离所有数据库表加tenant_id字段SQL 查询强制带上WHERE tenant_id ?。模型隔离模型存储路径改为s3://models/{tenant_id}/wakeword_v2.1.tflite。权限控制MQTT 主题增加租户前缀tenant/{tid}/device/{id}/...AWS IoT Policy 动