0%

使用 Python 控制米家设备

拿到了一个 米家智能插座 3,用了一下,发现米家 app 必须连接互联网才能控制设备,在断网情况下,即使 app 和设备在同一个局域网也无法控制。
发现了 python-miio 项目,可以通过 Python 指定 ip 控制设备。

先安装环境:

1
2
sudo apt install pipx git -y
pipx install -i https://pypi.tuna.tsinghua.edu.cn/simple git+https://github.com/rytilahti/python-miio.git

首先获取设备 ip:

1
2
3
4
5
$ miiocli discover
INFO:miio.miioprotocol:Sending discovery to <broadcast> with timeout of 5s..
INFO:miio.miioprotocol: IP 192.168.1.20 (ID: cbe43460) - token: b'ffffffffffffffffffffffffffffffff'
INFO:miio.miioprotocol:Discovery done
INFO:miio.discovery:Discovering devices with mDNS for 5 seconds...

配置完成的设备,获取到的 token 全是 ff,无法使用。
连接重置后设备开放的 WiFi 也可以使用 miiocli discover 获取到 Token,但是无法对设备进行管理,也无法修改连接的 WiFi。

在重置设备后,按照米家 app 流程配置设备并添加到小米账号里,然后使用 miiocli cloud 登录账号获取 Token,获取的数据如下(Token 和 mac 已替换为假数据):

1
2
3
4
5
Model: cuco.plug.v3
Hardware version: ESP32C3
Firmware version: 1.0.7
Token: c7c3b0c463e50f373aba1481ca0681f4
mac: 1D:D0:5E:21:0E:80

查看设备状态:

1
2
3
4
5
6
7
8
9
10
$ miiocli genericmiot --ip 192.168.1.20 --token c7c3b0c463e50f373aba1481ca0681f4 status
Running command status
Service [bold]Switch (switch)[/bold]
Device Fault (switch:fault, access: R): No Faults (value: 0)
Service [bold]Power Consumption (power-consumption)[/bold]
Power Consumption (power-consumption:power-consumption, access: R): 0 None
Electric Power (power-consumption:electric-power, access: R): 60 None
Service [bold]on-off-count (on-off-count)[/bold]
on-off-count (on-off-count:on-off-count, access: R): 0 None
temperature (on-off-count:temperature, access: R): 36 None

可以成功获取到状态。

参考 Xiaomi Miot Spec: 小米 / 米家产品库 找到实时功率项的 SIID 和 PIID 分别是 11 和 2。
用命令获取实时数据:

1
2
3
$ miiocli genericmiot --ip 192.168.1.20 --token c7c3b0c463e50f373aba1481ca0681f4 get_property_by 11 2
Running command get_property_by
[{'did': '11-2', 'siid': 11, 'piid': 2, 'code': 0, 'value': 55}]

可以看到实时功率是 55w。

也可以通过命令控制开关:

1
2
3
4
# 打开
miiocli genericmiot --ip 192.168.1.20 --token c7c3b0c463e50f373aba1481ca0681f4 set_property_by 2 1 True bool
# 关闭
miiocli genericmiot --ip 192.168.1.20 --token c7c3b0c463e50f373aba1481ca0681f4 set_property_by 2 1 False bool

或者通过 miiocli genericmiot actions 获取支持的 call,例如这个插座就可以通过 miiocli genericmiot call switch:toggle 切换开关状态。

可以正常操作插座后,试着写了一个收集实时功率数据的脚本。
首先配置脚本用的环境:

1
2
3
4
5
6
7
8
9
10
# 安装 python3 虚拟环境支持
sudo apt install python3-venv
# 创建虚拟环境
python3 -m venv /opt/miio
# 启用虚拟环境
source /opt/miio/bin/activate
# 在虚拟环境内安装 python-miio
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple git+https://github.com/rytilahti/python-miio.git
# 退出虚拟环境
deactivate

编写脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/opt/miio/bin/python3
from time import sleep, time
from csv import DictWriter
from miio import Device
from os.path import isfile

filename = "power_and_temperature_data.csv"
fieldnames = ['time', 'power', 'temperature']


class MiPlug(Device):
def set(self, SIID, PIID, VALUE):
return self.send(
"set_properties",
[{'did': f'set-{SIID}-{PIID}', 'piid': PIID, 'siid': SIID, 'value': VALUE}]
)

def get(self, SIID, PIID):
return self.send(
"get_properties",
[{'did': f'set-{SIID}-{PIID}', 'piid': PIID, 'siid': SIID}]
)

def on(self): # 打开开关
return self.set(2, 1, True)

def off(self): # 关闭开关
return self.set(2, 1, False)

def lock(self): # 启用物理锁
return self.set(7, 1, True)

def unlock(self): # 解除物理锁
return self.set(7, 1, False)

def electric(self): # 功率
data = self.get(11, 2)
if data:
return data[0]['value']

def temperature(self): # 温度
data = self.get(12, 2)
if data:
return data[0]['value']


dev = MiPlug("192.168.1.20", "c7c3b0c463e50f373aba1481ca0681f4")

if not isfile(filename):
with open(filename, 'w') as file:
writer = DictWriter(file, fieldnames=fieldnames)
writer.writeheader()

data = []
while True:
data.append({
'time': time(),
'power': dev.electric(),
'temperature': dev.temperature()
})
# sleep(5)

if len(data) >= 10: # 每 10 条数据保存一次
with open(filename, mode='a') as csv_file:
writer = DictWriter(csv_file, fieldnames=fieldnames)
for row in data:
writer.writerow(row)
data.clear()

脚本开头指定了脚本的解释器是虚拟环境里的,可以无需加载虚拟环境直接运行脚本。
运行后会把功率和温度的数据收集到 power_and_temperature_data.csv 文件。

官方米家程序并没有提供修改 WiFi 的设置,如果需要更换,则只能重置插座后重新连接。python-miio 已经实现了,但好像没有提供直接的调用命令。
参考了文档源码,可以手动构建命令修改:

1
miiocli genericmiot --ip 192.168.1.20 --token c7c3b0c463e50f373aba1481ca0681f4 raw_command miIO.config_router "{'ssid': 'name', 'passwd': 'password', 'uid': '0'}"

修改会马上生效。