-----------帖子还在编辑中--------------
【从入门到放弃】
绿米局域网通信协议、PyXiaomiGateway库、
HA原生xiaomi_aqara组件、Homekit组件自学过程
Lumi
Zigbee
PyXiaomiGateway
xiaomi_aqara
homekit
局域网通信协议
前言
作为论坛里面一枚小萌新,其实我的小米设备并不多。前期呢,我也和大家一样,跟着论坛里的大佬们搞定了很多小米设备接入和使用。但是随着接触的东西越来越多,战线越拉越长,这些小米设备也被我忽视掉了,再也没有好好研究过小米网关局域网通信协议
、PyXiaomiGateway库
、xiaomi_aqara组件
、xiaomi_miio
、homekit组件
这些东西。其实,想必很多人都是和我一样,抱着如何将自家的小米设备接入homekit,被那句用siri控制一切带进Hassbian这个论坛开始学习的吧,这就是所谓的“初心”吧。所以我决定沉下心来,挤点时间,不忘初心,拾起学习。同时,楼主我也感觉把学习过程记录下来是一件有意义的事,对刚入坑的坛友来讲,或许是帮助和借鉴;对自己则是一种任务牵引,则是总结和梳理,积累经验展示成果,更重要的是还可以向论坛里各位大佬学习体会、交流经验。故而立帖(挖坑,没错就是挖坑哈)。
楼主我本着边搬运边学习的想法,一开始可能自己的理解比较少,搬运的内容比较多,请理解。后期慢慢加入自己的理解体会,把学习过程转变为学习心得,最后变成教程,谢谢大家。
储备知识
- 网络基础知识,TCP/IP协议架构,UDP组播,Socket。
- HA的基本架构,组件结构,物联网组件的结构。
- Python编程基础,懂基本的数据结构和代码含义。
- (待添加)
总原理图
(待添加)
1. 绿米局域网通信协议
绿米局域网通信协议就是一套API,用于对网关及其下属ZigBee子设备(传感器、控制器等)进行管理。绿米局域网通信协议有两个互不兼容的版本:
- 绿米局域网通信协议1.X,最新版本为1.1.1(2017.12.21)。
- 绿米局域网通信协议2.X,最新版本为2.0.1(2018.05.18)。
按照一般理解,1.X版本的协议,主要用于米家品牌的设备;而2.X版本的协议,主要用于Aqara品牌的设备。目前来说,这两个协议,其实差别并不大,所以PyXiaomiGateway同时提供了对这两个协议的支持。
PS:绿米(lumi)即深圳绿米联创科技有限公司,其自有产品的品牌为Aqara,给小米代工产品的品牌为米家。2015年1月发布小米多功能网关,2016年3月发布小米多功能网关2代(升级版),2016年11月29日,由小米正式更名为米家。
【表1】绿米1.X协议文档列出的设备类型及对应模型名(model)
小米多功能网关:gateway
米家多功能网关升级版:gateway.v3
米家窗磁传感器:magnet
米家人体传感器:motion
米家无线开关传感器:switch
米家智能插座:plug
Aqara 86单火开关单键:ctrl_neutral1
Aqara 86单火开关双键:ctrl_neutral2
Aqara 86无线开关单键:86sw1
Aqara 86无线开关双键:86sw2
米家温湿度传感器:sensor_ht
米家魔方传感器:cube
Aqara窗帘:curtain
Aqara 86零火墙壁开关单键:ctrl_ln1、ctrl_ln1.aq1
Aqara 86零火墙壁开关双键:ctrl_ln2、ctrl_ln2.aq1
Aqara 墙壁插座:86plug、ctrl_86plug.aq1
米家天然气报警器:natgas
米家烟雾报警器:smoke
Aqara门磁传感器:sensor_magnet.aq2
Aqara人体照度传感器:sensor_motion.aq2
Aqara无线开关传感器:sensor_switch.aq2
Aqara温湿度气压传感器:weather.v1
Aqara水浸传感器:sensor_wleak.aq1
Aqara门锁:lock.aq1
【表2】绿米2.X协议文档列出的设备类型及对应模型名(model)
Aqara空调伴侣升级版:acpartner.v3
智能插座:plug
墙壁插座:ctrl_86plug、ctrl_86plug.aq1
墙壁开关(零火单键):ctrl_ln1、ctrl_ln1.aq1
墙壁开关(零火双键):ctrl_ln2
墙壁开关(单火单键):ctrl_neutral1
墙壁开关(单火双键):ctrl_neutral2
窗帘电机:curtain
双路控制器:lumi.ctrl_dualchn
门窗传感器:sensor_magnet.aq2
人体传感器:sensor_motion.aq2
温湿度传感器:weather
水浸传感器:sensor_wleak.aq1
无线开关:sensor_switch.aq2
无线开关(升级版):sensor_switch.aq3
86无线开关单键:sensor_86sw1.aq1
86无线开关双键:sensor_86sw2.aq1
魔方传感器:sensor_cube.aqgl01
【表3】不支持任何局域网通信协议的设备对应模型名(model)
Aqara空调伴侣:acpartner.v1
米家空调伴侣:acpartner.v2
注意:开启局域网通信协议并拥有该网关的KEY后,才能与该网关进行局域网完全通信。
注意:有些设备3个表中都没有给出,一般属于支持1.X的设备,也可能不支持任何协议。
注意:唯一支持2.X协议的网关是Aqara空调伴侣升级版(方形那个),小米多功能网关/米家多功能网关升级版均为1.X协议。
注意:Aqara魔方传感器仅能接入Aqara空调伴侣升级版才能使用局域网协议控制。
疑问:米家空调伴侣由于硬件性能关系,存在但没有公开局域网通信协议。
猜测:如果你的设备在表2而不在表1中,说明很可能只支持2.x协议,必须接入Aqara空调伴侣升级版;如果你的设备在表1而不在表2中,则说明支持1.X协议,并且按绿米的说法,也能接入Aqara空调伴侣升级版(这货可能2个协议都能兼容)。如果在表1又在表2中,那么就要看固件了,老固件支持1.X,新固件是全支持还是只支持2.X未知。
8.14日更新:今天重新读到这里,感觉以前对子设备的协议支持理解有偏差,实际上协议只是网关的协议,是否支持协议并不应该是子设备的问题,等下步分析zigbee通讯时再做修订。
1.1 局域网通信协议主要功能(目前)
- 发现与查询设备
- 设备状态上报
- 对设备进行读写操作
- 设备心跳上报
1.2 发现与查询设备
首先,为了方便,我将局域网内需要与小米网关及下属设备建立通信的主机(比如HA所在主机)简称为服务器,IP记作$Host,小米网关IP记作$Hub(下同,只是方便文章编写,和协议本身以及编程毫无关系),服务器可以是电脑也可以是手机等等,必须按照网关所支持的局域网通信协议,通过自己编写或者通过库函数编写,以及现成的软件、APP,才能完成相应的通讯。
①服务器发现网关(全文默认以2.X协议为例):
- 服务器向局域网发送UDP组播报文(IP:224.0.0.50,端口:4321),内容为
{"cmd":"whois"}
- 即【服务器>>组播(224.0.0.50:4321){"cmd":"whois"}】下写法同理。
- 网关收到信息后通过UDP单播报文回复服务器:
- 【网关>>单播($Host:4321){"cmd":"report",...}】
- report的具体内容举例:
{
"cmd":"iam",
"ip":"192.168.0.42", //网关IP地址
"protocal":"UDP",
"port":"9898",
"model":"gateway.aq1", //网关设备类型
......
}
②服务器查询网关设备
- 【服务器>>单播($Hub:9898){"cmd":"discovery"}】
- 【网关>>单播($Host:9898){"cmd":"discovery_rsp",...}】
- discovery_rsp具体内容举例:
{
"cmd":"discovery_rsp",
"sid":"158d323123c9d9", //sid为网关did
"token":"TahkC7dalbIhXG22", //网关生成的随机字符串
"dev_list":[{"sid":"158d0000f1a750","model":"plug"},
{"sid":"158d00010fd645","model":"sensor_switch.aq2"}] //sid为子设备did
}
注意:“token”为网关生成的随机字符串,每10s刷新一次,在未收到设备心跳上报的token前,用户可用此token来生成写设备时的“key”。
1.3 设备查询上报
- 当设备状态发生变化时,【网关>>组播(224.0.0.50:9898){"cmd":"report",...}】
- report具体内容举例:
{
"cmd":"report",
"model":"sensor_magnet.aq2",
"sid":"158d0000123456",
"params":[{"window_status":"open"}]
}
1.4 设备心跳上报
①网关心跳上报
- 网关每10秒钟发送一次心跳报文,用来告诉服务器网关正常工作。若间隔65s以上未收到心跳包即表示网关处于离线状态。网关设备心跳格式如下:
- 【网关>>组播(224.0.0.50:9898){"cmd":"heartbeat",...}】
-
heartbeat具体内容举例:
{
"cmd":"heartbeat",
"model":"gateway.v3",
"sid":"f0b429b3c9d965",
"token":"1234567890abcdef", //网关生成的随机字符串
"params":[{"ip":"172.22.4.130"}] //网关IP地址
}
②子设备心跳上报
- 子设备通过心跳告诉PC:子设备正常工作(心跳上报频率:睡眠设备是每60分钟一次,插电设备是每10分钟一次)。子设备心跳格式如下:
- 【网关>>组播(224.0.0.50:9898){"cmd":"heartbeat",...}】
- heartbeat具体内容举例:
{
"cmd":"heartbeat",
"model":"sensor_magnet.aq2",
"sid":"158d000065a271",
"params":[{"window_status":"open"}]
}
1.5 AES-CBC 128加密
开启局域网通信协议后,服务器可以获取网关信息(接收网关上报、回应信息,对网关进行读操作),但无法控制网关(对网关进行写操作)。
要对网关及其下属设备进行管理和控制,必须拥有该网关的KEY,我把它叫做网关KEY。网关KEY可在APP上取得,为16个字节长度的字符串。
网关KEY并非直接用于控制网关,而是作为加密的密钥。服务器收到网关心跳“heartbeat”里的16个字节的“token”字符串之后,使用网关KEY对token的字符串进行AES-CBC 128加密,生成16个字节的密文,再转换为32字节的ASCII码,这才是通讯时用到的工作KEY。
AES-CBC 128初始向量定义为:unsigned char const AES_KEY_IV[16] = {0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e}。
1.6 设备读写操作
①读设备
服务器向网关发送读命令,网关向服务器回复读取结果。
- 【服务器>>单播($Hub:9898){"cmd":"read",...}】
- read具体内容举例:
{
"cmd":"read",
"sid":"158d0000123456" //墙壁开关did
}
- 【网关>>单播($Host:9898){"cmd":"read_rsp",...}】
- read_rsp具体内容举例:
{
"cmd":"read",
"sid":"158d0000123456" //墙壁开关did
}
{
"cmd":"write_rsp",
"model":"ctrl_neutral1",
"sid":"158d0000123456",
"params":[{"channel_0":"on"}]
}
②写设备
服务器向网关发送写命令,网关向服务器发送回复,表示知悉。
- 【服务器>>单播($Hub:9898){"cmd":"write",...}】
- write具体内容举例:
{
"cmd":"write",
"model":"ctrl_neutral1",
"sid":"158d0000123456",
"key":"3EB43E37C20AFF4C5872CC0D04D81314",
"params":[{"channel_0":"off"}]
}
- 【网关>>单播($Host:9898){"cmd":"write_rsp",...}】
- write_rsp具体内容举例:
{
"cmd":"write_rsp",
"model":"ctrl_neutral1",
"sid":"158d0000123456",
"params":[{"channel_0":"on"}]
}
该“write_rsp”只代表网关收到了write命令,params里的属性状态为当前的设备最新状态,不是write之后的最终设备状态。最终的设备状态靠report报文进行上报。
1.7 两个协议格式之间的区别与联系
讨论问题:绿米局域网通信协议能否跨越局域网使用?
2.PyXiaomiGateway库
上面学习了绿米局域网通信协议,但是光有通信协议还不够,要把这些协议具体化为实现方法才行。你可以自己按照协议写程序,也可以调用别人已经写好的库函数,比如PyXiaomiGateway,这是一个HA组件正在使用的现成的库函数,目前版本为0.9.5。
库的源代码地址:
https://github.com/Danielhiversen/PyXiaomiGateway
该库提供了两个类
- XiaomiGatewayDiscovery
- XiaomiGateway
XiaomiGateway类
- 用法:XiaomiGateway(ip_adress, port, sid, key, sock, proto=None)
参数ip_adress:网关的ip地址;
参数port:网关的通信端口,9898;
参数sid:网关的sid;
参数key:网关的key,16位;
参数sock:传输用的socket;
参数proto:网关的协议版本;
调用这个类后,会发生这些事:
1.若没有指定协议版本,则向网关发送read命令,获取网关的协议版本。
2.向网关发送discovery(get_id_list)命令,搜索网关及其下属设备的sid(设备编号),并保存到sids(设备编号列表)中,同时保存网关的token。
3.根据sids中的设备编号,向每个设备都发送read命令,获取所有设备的信息,若设备的model在支持列表内,则将此设备的信息按照类型,注册到self.devices中。
所以,将这个类实例化后,顺利的话,可从对象的devices属性中获取设备信息。
XiaomiGatewayDiscovery类
- 用法:XiaomiGatewayDiscovery(callback_func, gateways_config, interface)
参数callback_func:指定回传函数;
参数gateways_config:指定网关配置,可以多个网关;
参数interface:绑定的服务器IP,可填"any"。
调用这个类后,会发生这些事:
1.自动配置基本参数(组播地址,端口,UDP协议等);
2.初始化socket,绑定interface。
这个类还有一下几个方法可供调用:
- discover_gateways 发现网关
- listen 监听端口
- stop-listen 关闭监听
discover_gateways方法
调用这个方法后,会发生这些事:
1.建立socket,绑定interface。
2.若gateways_config中存在完整配置项,则读取网关配置,转换主机名为ip地址等, 并调用XiaomiGateway类,保存为self.gateways[ip_adress]。
3.服务器向网内UDP组播,发送whois命令,通过网关回应,补充网关配置,并调用XiaomiGateway类,保存为self.gateways[ip_adress]
3.xiaomi_aqara组件
小米aqara平台
https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/xiaomi_aqara.py
xiao_aqara组件是hass与小米网关通讯的重要模块,目前包含:
核心:xiaomi_aqara.py
平台:binary_sensor/xiaomi_aqara.py, sensor/xiaomi_aqara.py, switch/xiaomi_aqara.py, light/xiaomi_aqara.py, cover/xiaomi_aqara.py, lock/xiaomi_aqara.py。
核心xiaomi_aqara.py
该文件主要包含两个部分:
组件加载方法Setup()与小米设备类库XiaomiDevice()
Setup()主要负责接收用户配置,并初始化和启动小米组件,尝试通过PyXiaomiGateway通讯库与小米网关通信并取得网关对象。
同时定义并注册了4项服务:播放网关音乐、停止播放网关音乐、添加Zigbee子设备,移除Zigbee子设备。
XiaomiDevice()主要负责通过PyXiaomiGateway通讯库,建立小米设备在hass架构下的实体模型,实现通用的功能逻辑,构建了以下属性和方法:
属性:设备名name、唯一识别码unique_id(uid)、反馈特性available、轮询特性should_poll、设备自属性device_state_attributes。
方法:
async_added_to_hass:跟踪可反馈的设备。
push_data:更新Hass实体状态
parse_voltage:解析电压
parse_data:解析数据
平台XXXX/xiaomi_aqara.py
各类平台py文件,通过小米设备类库XiaomiDevice()与平台对应的核心类库生成相应的小米平台类。
从而实现对应的设备的完整功能。
4.homekit组件