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