【踩坑系列】Home Assistant无法识别Aqara无线开关(贴墙式)

想起上次做光感+人体感应自动控灯的时候,用aqara无线开关(贴墙式)控灯遇到个小坑:像zigbee墙壁开关,接入空调伴侣后,HA会实例化switch.wall_switch_xxxxxxxx,但没找到无线开关。于是又苦逼去读代码了,还算顺利解决,惯例把解决过程理一理做个总结分享下。


1.环境

  • Ubuntu18.04 + HA 0.71.0
  • aqara空调伴侣升级版(网关)
  • aqara无线开关(贴墙式)
  • python模块目录,即/srv/homeassistant/homeassistant_venv/lib/python3.6/site-packages/,后面用{[python模块目录]}代替。

2.过程

Step1

首先查看看HA日志,发现有错误信息:

ERROR (SyncWorker_8) [xiaomi_gateway] Unsupported device found! Please create an issue at https://github.com/Danielhiversen/PyXiaomiGateway/issues and provide the following data: {‘cmd’: ‘read_rsp’, ‘model’: ‘sensor_86sw1’,……

明显是说不支持sensor_86sw1的类型,还注意信息中的“[xiaomi_gateway]”说明是xiaomi_gateway模块。找到{[python模块目录]}/xiaomi_gateway/init.py,可以看到支持的设备类型是在device_types = {}中定义的,我们把sensor_86sw1、sensor_86sw2(双键开关)加进去。

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
#……省略……#
class XiaomiGateway(object):
#……省略……#
def _discover_devices(self):
#……省略……#
device_types = {
'sensor': ['sensor_ht', 'gateway', 'gateway.v3', 'weather',
'weather.v1', 'sensor_motion.aq2', 'acpartner.v3'],
'binary_sensor': ['magnet', 'sensor_magnet', 'sensor_magnet.aq2',
'motion', 'sensor_motion', 'sensor_motion.aq2',
'switch', 'sensor_switch', 'sensor_switch.aq2', 'sensor_switch.aq3',
'86sw1', 'sensor_86sw1.aq1', 'sensor_86sw1',
'86sw2', 'sensor_86sw2.aq1', 'sensor_86sw2',
'cube', 'sensor_cube', 'sensor_cube.aqgl01',
'smoke', 'sensor_smoke',
'natgas', 'sensor_natgas',
'sensor_wleak.aq1'],
'switch': ['plug',
'ctrl_neutral1', 'ctrl_neutral1.aq1',
'ctrl_neutral2', 'ctrl_neutral2.aq1',
'ctrl_ln1', 'ctrl_ln1.aq1',
'ctrl_ln2', 'ctrl_ln2.aq1',
'86plug', 'ctrl_86plug', 'ctrl_86plug.aq1'],
'light': ['gateway', 'gateway.v3'],
'cover': ['curtain'],
'lock': ['lock.aq1']}
#……省略……#

Step2

xiaomi_gateway模块只是完成把设备信息和网关关联起来,正式的创建设备是在{[python模块目录]}/homeassistant/components/binary_sensor/xiaomi_aqara.py中实现的。所以打开看里面代码可以看到无线开关是在elif model in [‘86sw1’, ‘sensor_86sw1.aq1’]、elif model in [‘86sw2’, ‘sensor_86sw2.aq1’]里面的,确是没有“sensor_86sw1”,那继续添加。

INFO:单键开关是实例化一个XiaomiButton的,双开关是实例化成三个XiaomiButton的(一个同时按双键触发)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#……省略……#
def setup_platform(hass, config, add_devices, discovery_info=None):
#……省略……#
if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']:
#……省略……#
elif model in ['86sw1', 'sensor_86sw1.aq1', 'sensor_86sw1']:
devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
hass, gateway))
elif model in ['86sw2', 'sensor_86sw2.aq1', 'sensor_86sw1']:
devices.append(XiaomiButton(device, 'Wall Switch (Left)',
'channel_0', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
'channel_1', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Both)',
'dual_channel', hass, gateway))
#……省略……#

Step3

重启,查看HA可以成功识别出无线开关了。

但又有新问题:点击按钮什么反应都没有,看回调方法中所执行的parse_data(),正常是会有click事件生成的。

1
2
3
4
5
6
7
8
9
10
#……省略……#
class XiaomiButton(XiaomiBinarySensor):
#……省略……#
def parse_data(self, data, raw_data):
#……省略……#
self._hass.bus.fire('click', {
'entity_id': self.entity_id,
'click_type': click_type
})
#……省略……#

Step4

看了下aqara网关局域网通讯协议发现,无线开关的click_type是button_0、button_1,而不是channel_0、channel_1。所以要把Step2的修改的代码恢复,重新新增sensor_86sw1、sensor_86sw2设备类型的处理代码。

重启测试,日志有EVENT消息,可以正常使用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
……省略……
elif model in ['86sw1', 'sensor_86sw1.aq1']:
devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
hass, gateway))
elif model in ['sensor_86sw1']: #增加sensor_86sw1
devices.append(XiaomiButton(device, 'Wall Switch', 'button_0',
hass, gateway))
elif model in ['86sw2', 'sensor_86sw2.aq1']:
devices.append(XiaomiButton(device, 'Wall Switch (Left)',
'channel_0', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
'channel_1', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Both)',
'dual_channel', hass, gateway))
elif model in ['sensor_86sw2']: #增加sensor_86sw2
devices.append(XiaomiButton(device, 'Wall Switch (Left)',
'button_0', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
'button_1', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Both)',
'dual_channel', hass, gateway))
……省略……


3.小结

最后简单介绍下我所理解的网关工作流程吧,相关的工作是由{[python模块目录]}/homeassistant/components/xiaomi_aqara.py来完成的。

1)读取配置文件xiaomi_aqara:的信息,初始化网关发现对象XiaomiGatewayDiscovery({[python模块目录]}/xiaomi_gateway/init.py),注意HA也用了全局变量hass.data[PY_XIAOMI_GATEWAY]存储所找到的网关,后续注册设备会用到。

1
2
xiaomi = hass.data[PY_XIAOMI_GATEWAY] = XiaomiGatewayDiscovery(
hass.add_job, gateways, interface)

2)通过udp通信发现、注册网关,同时读取网关的设备列表。这时候,网关对象已经存有设备信息,供后续不同组件(component)注册设备使用。

1
xiaomi.discover_gateways()

3)监听udp通信端口,网关接收到数据包会调用push_data()进行解析,再根据数据包中设备sid调用相应设备所定义的push_data()实现对应设备的回调处理。设备的push_data()会再调用parse_data()(不同的设备会定义不同的方法)来更新数据、调用parse_voltage(通用)来更新电量,最后调用async_schedule_update_ha_state()更新状态。这部分是实现和设备的通信。

1
xiaomi.listen()

4)调用不同组件(component)的setup_platform注册设备。(好像是通过discovery.load_platform()触发EVENT_PLATFORM_DISCOVERED事件,后续HA自行处理,具体会处理我也没了解。)

1
2
for component in ['binary_sensor', 'sensor', 'switch', 'light', 'cover', 'lock']:
discovery.load_platform(hass, component, DOMAIN, {}, config)