利用 midea_ac_lan 开源库控制美的空调

前言

美的智能家居平台的接口是非开放的。不管是在 HomeAssistant 还是 HomeBridge 中接入美的设备,都是靠伟大无私的开源社区提供解决方案。

在之前我自己的基于 Python 实现的智能家居控制系统 里,我用到的是 midea-ac-py

由于美的空调的 token、key 在每次重新供电时都会变化,而这个库没有将 discover 功能集成进来——库作者额外提供了一个命令行工具(
midea-msmart
)发现局域网内的美的设备,导致更新 token 流程的自动化程度不高,每次都要人工复制。

我曾经尝试把 midea-msmart 集成进我的系统,通过可编程方式更新 token,总会遇到各种奇怪问题,比如帐密验证失败、超时等等,所以一直在寻找可替代库。终于让我发现了 midea_ac_lan

midea_ac_lan 使用方法

midea_ac_lan 也是一个 HA 组件,它同时把 discover 能力也集成了进去,可以很方便地以编程方式获取最新的 token、key。 下面详细介绍 midea_ac_lan 的低层接口的使用方法。

0. 设置美的美居 APP

下载 App,注册账号,然后把相关设备添加到 App 中。discover 是通过访问美的服务器获取 token、key 的。

1. 发现所有设备

1
2
3
from midea_ac_lan.midea.core.discover import discover

print(discover())

如果你在路由器设置里把每台设备的 ip 固定下来(强烈建议),这个步骤通常只需要在设备有变化时(新增、移除)执行一次,会列出所有设备的 ip、端口、设备 id(恒值,永不会变)等信息,记录下来即可。

2. 获取 token

1
2
3
4
5
session = aiohttp.ClientSession()
cloud = MideaCloud(session, 'Your Account', 'Your Password', 'cn')
await cloud.login()
token, key = await cloud.get_token(device_id, True) # 注意第二个参数为 True
await session.close()

device_id 即为步骤 1 中获取的设备 id。注意 get_token 的第二个参数,表示应以大端还是小端字节序解释 device_id,默认值是 False,而我实测 True 才是对的。

3. 连接设备

1
2
d = MideaACDevice('', device_id, ip, port, token, key, 3, '', '')
d.connect()

MideaACDevice 是空调类型子类,也可通过父类 MiedaDevice 创建实例。

4. 控制设备

每个类型设备的可操作属性,都在 DeviceAttributes 类中列出。以空调为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
class DeviceAttributes(StrEnum):
prompt_tone = "prompt_tone"

power = "power"
mode = "mode"
target_temperature = "target_temperature"
fan_speed = "fan_speed"
swing_vertical = "swing_vertical"
swing_horizontal = "swing_horizontal"
boost_mode = "boost_mode"
smart_eye = "smart_eye"
dry = "dry"
# ...省略其它属性

以最简单的开/关为例:

1
2
d.set_attribute('power', True)  # 打开空调
d.get_attribute('power') # 获取开关状态

可能一个典型的应用场景是这样的:每次设置属性值后,遍历获取所有属性的最新值,返回给前端。要做到这点需要调用额外的接口。
因为 midea_ac_lan 默认每 30s 同步一次设备状态,所以如果像上边代码一样,set 之后马上 get,大概率得到的还是旧值。可以主动调用 MiedaDevice.refresh_status() 同步状态:

1
d.refresh_status(True)

参数值必须为 True,否则它将不等待数据到来直接返回。

但我实测 refresh_status 有概率会因为没有数据返回而触发如下异常:

1
2
3
4
# def refresh_status(self, wait_response=False):
msg = self._socket.recv(512)
if len(msg) == 0:
raise socket.error

建议做下异常保护。

后记

对 midea_ac_lan 完整的使用方式,可以在我的 SmartHome 项目中找到。

评论