绕过米家云:用 python-miio 本地直连小米设备,秒级拿到传感器数据

折腾 Home Assistant + 米家设备时,最让人头疼的就是"数据延迟"。官方集成走米家云,温湿度 2-3 分钟才更新一次,偶尔云端数据还会"抽风"报错。本文记录我用 python-miio 绕过米家云、局域网直连除湿机,把数据采集频率做到秒级、并接入 HA 的完整实战。 一、痛点:为什么需要本地直连 先看一张真实对比(同一台除湿机,同一时刻): 数据来源 当前值 状态 米家云(官方集成) 30 °F ❌ 云端数据坏了,实际应是 84 °F 本地直连(python-miio) 29.5 °C ✅ 准确 官方 HA 米家集成的工作方式是 MQTT 订阅推送: 设备属性变化 → 设备上报米家云 → 米家云 MQTT 推送 → HA ```plain 理论上 HA 能"第一时间"收到,但实际依赖两点: 1. **设备主动上报**(米家 WiFi 设备为省电,通常 2-3 分钟才上报一次传感器数据) 2. **推送可靠性**(官方仓库 Issue 里提到 WiFi 设备推送丢失率接近 30%) 结果就是:**延迟大、还不一定准**。 而**本地直连**的路径是: ```plain 你的脚本 → 局域网直连设备(miio 协议)→ 秒级返回 ```plain 不经过米家云,不依赖设备上报节奏,你想读就读,又快又准。 --- ## 二、原理:米家设备的本地通信 小米的 WiFi 设备(除湿机、空气净化器、扫地机等)在局域网内会开放一个 **miio 协议**(UDP 54321 端口)。只要你有设备的 **token**(一个 32 位十六进制密钥),就能在局域网内直接和设备通信。 ```plain ┌──────────┐ 局域网 (UDP 54321) ┌──────────┐ │ 你的脚本 │ ──── miio 协议 ──────▶│ 小米设备 │ │(python-miio)│ ◀── 加密响应 ────── │ │ └──────────┘ └──────────┘ ```plain 关键点: - **token** 是设备配对时生成的密钥,控制设备的"钥匙" - miio 协议是加密的,token 用于加解密 - [python-miio](https://github.com/rytilahti/python-miio) 是开源的 Python 库,封装了这套协议 - MIoT(小米新一代物模型)设备用 `siid/piid`(服务ID/属性ID)寻址属性 --- ## 三、实战:除湿机本地直连全流程 ### 第 1 步:获取设备 token token 是本地直连的必需品。获取方式有几种: **方法 A:从 Home Assistant 官方集成配置里提取(最省事)** 如果你已经在 HA 装了官方米家集成,token 已经明文存在配置里: ```plain /config/.storage/xiaomi_home/miot_devices/<你的uid>_cn.dict ```plain 这个文件是 JSON,里面每个设备都带 `token` 字段。 **方法 B:用旧版米家 APP 提取数据库** 安卓米家 APP(旧版 5.4.54)的数据库里有 token,用 [mihome-binary-extractor](https://github.com/Maxmudjon/Get_MiHome_devices_token) 提取。 **方法 C:在线工具**(不推荐,有账号泄露风险) > ⚠️ **token 等于设备控制权**,千万不要提交到公开仓库或发到群里。 ### 第 2 步:找到设备局域网 IP 本地直连需要设备的局域网 IP。几种方式: - 路由器后台看 DHCP 客户端列表 - `ip neigh` / `arp -a` 看 ARP 表 - python-miio 的发现功能 但设备 MAC 可能随机化,最稳的办法是**用 token 遍历候选 IP 试连**: ```python from miio import MiotDevice token = "你的token" for ip in ["192.168.1.98", "192.168.1.107", "192.168.1.89"]: try: dev = MiotDevice(ip=ip, token=token, mapping={}) info = dev.info() print(f"✅ {ip}: {info.model}") except Exception: print(f"❌ {ip}") ```plain 哪个 IP 返回了 `model`,就是它(我的除湿机是 `192.168.1.89`,model `xiaomi.derh.13l`)。 ### 第 3 步:读数据 —— 理解 MIoT 的 siid/piid MIoT 设备用**服务(siid) + 属性(piid)** 来组织功能。比如除湿机: - `siid=3, piid=2` → 温度 - `siid=3, piid=1` → 湿度 - `siid=2, piid=5` → 目标湿度 读取用 `get_property_by(siid, piid)`: ```python from miio import MiotDevice dev = MiotDevice(ip="192.168.1.89", token="你的token", mapping={}) temp = dev.get_property_by(3, 2)[0]["value"] # 温度 humidity = dev.get_property_by(3, 1)[0]["value"] # 湿度 print(f"温度 {temp}°C, 湿度 {humidity}%") # 输出: 温度 29.5°C, 湿度 58% ```plain **怎么知道哪个 siid/piid 是温度?** —— 扫描: ```python for siid in range(1, 9): for piid in range(1, 8): try: r = dev.get_property_by(siid, piid)[0] if r.get("code") == 0 and r.get("value") is not None: print(f"siid={siid} piid={piid}: {r['value']}") except Exception: pass ```plain 看输出里哪个值像温度(20-30)、哪个像湿度(40-70),就能对上号。也可以查设备的 [MIoT Spec](https://home.miot-spec.com/)。 ### 第 4 步:MQTT 推送到 HA,秒级显示 光自己读到数据还不够,要让它进 HA、能在仪表盘显示,最优雅的方式是 **MQTT Discovery**:你的脚本发布一个 discovery 消息,HA 自动创建对应传感器。 架构: ```plain 除湿机 ──python-miio──▶ 采集脚本 ──paho-mqtt──▶ mosquitto ──discovery──▶ HA ```plain 完整采集脚本: ```python import time, json import paho.mqtt.client as mqtt from miio import MiotDevice MQTT_HOST = "127.0.0.1" DEV_IP = "192.168.1.89" DEV_TOKEN = "你的token" INTERVAL = 10 # 每10秒采集一次 # 连 MQTT client = mqtt.Client() client.connect(MQTT_HOST, 1883, 60) client.loop_start() # 发布 discovery,HA 会自动创建传感器 discovery = { "name": "除湿机温度(本地)", "state_topic": "dehumi_local/state", "value_template": "{{ value_json.temperature }}", "unit_of_measurement": "°C", "device_class": "temperature", "state_class": "measurement", "unique_id": "dehumi_local_temp_001", } client.publish("homeassistant/sensor/dehumi_local_temp/config", json.dumps(discovery), retain=True) # 湿度同理,略 dev = MiotDevice(ip=DEV_IP, token=DEV_TOKEN, mapping={}) while True: try: temp = dev.get_property_by(3, 2)[0]["value"] humid = dev.get_property_by(3, 1)[0]["value"] # 一次发一个 JSON,HA 用 value_template 拆分 payload = json.dumps({"temperature": temp, "humidity": humid}) client.publish("dehumi_local/state", payload, retain=True) print(f"{time.strftime('%H:%M:%S')} 温度={temp}°C 湿度={humid}%") except Exception as e: print(f"采集失败: {e}") time.sleep(INTERVAL) ```plain HA 那边只要装了 MQTT 集成(连同一个 broker),几秒内就会自动出现「除湿机温度(本地)」「除湿机湿度(本地)」两个传感器,**10 秒刷新一次**。 --- ## 四、效果对比 | | 官方集成(云端) | 本地直连(python-miio+MQTT) | |--|--|--| | 更新频率 | 2-3 分钟 | **10 秒(可调更短)** | | 数据准确性 | 依赖云端(偶发错误) | **直读设备,准** | | 断网能否工作 | ❌ | ✅ | | 响应延迟 | 分钟级 | **秒级** | | 部署复杂度 | 一键装集成 | 需要写脚本 + broker | --- ## 五、踩坑记录 1. **netifaces 编译失败**:python-miio 依赖 netifaces,在 `python:3-slim` 镜像里没有 gcc 会编译失败。解决:用完整的 `python:3` 镜像,或装 `gcc python3-dev`。 2. **新版 python-miio 的 API 变了**:网上的老教程用 `get_properties_by`,新版改成了 `get_property_by(siid, piid)`,而且 `MiotDevice` 创建时必须传 `mapping={}`(否则报 "Neither the class nor the parameter defines the mapping")。 3. **`miiocli` 命令找不到**:pip 装完 python-miio 后,`miiocli` 有时不在这个 PATH。直接用 Python API 调用更稳。 4. **MQTT 集成配置方式**:新版 HA 不能在 `configuration.yaml` 里写 `mqtt: broker: ...` 了(会报 invalid option),必须通过「设置 → 集成 → 添加 MQTT」用 UI 配置,或直接写 config_entries。 5. **discovery 的 entity_id 是中文名 slugify**:如果 discovery 里 `name` 写中文,HA 生成的 entity_id 会是拼音(如 `chu_shi_ji_wen_du`),自动化里引用要注意。 6. **设备 MAC 随机化**:现在很多设备开启了 MAC 随机化,没法靠厂商 OUI 识别。用 token 遍历 IP 试连最靠谱。 --- ## 六、总结 本地直连的本质就三件事:**拿到 token → 找到 IP → 用 miio 协议读写**。 适合的场景: - 对数据实时性要求高(自动化、监控) - 米家云数据不稳/不准 - 想完全本地化、断网可控 不适合的场景: - 蓝牙/Zigbee 设备(不是 IP 设备,走不了 miio) - 不想折腾、一键党(直接用官方集成就行) 如果你的核心诉求是"数据又快又准",这套方案值得搞。**我的除湿机从 2-3 分钟更新一次,变成了 10 秒一次,而且云端报错温度时本地依然准确** —— 这就是本地直连的价值。 --- **参考资料** - [python-miio](https://github.com/rytilahti/python-miio) - [MIoT Spec 查询](https://home.miot-spec.com/) - [HA MQTT Discovery 文档](https://www.home-assistant.io/docs/mqtt/discovery/) - [米家官方 HA 集成](https://github.com/XiaoMi/ha_xiaomi_home)

August 12, 2026 · 4 min · 669 words

你好,Hugo!

第一篇 Hugo 博客文章。

August 3, 2026 · 1 min · 28 words