Home Assistant 与 Yeelight彩光灯泡的通信过程分析
文章目录
Awaken your home.
前言
Home Assistant是一个隐私优先的智慧家庭系统,支持本地化部署,甚至允许在没有断网的情况下使用。
我是Home Assistant的铁杆粉,用它构建了一些好玩的东西,正打算基于它创建一些更有想象力的项目。
近期基本读完了Home Assistant所有的文档,喜欢它的架构设计。在架构上,codelab-adapter和它有许多相似之处。
Home Assistant的核心源码此前也大体上读了一遍,代码写得非常漂亮,堪称Python Asyncio的典范案例。
Home Assistant近期发布了0.94版本, 为1.0版本做好了准备,预计今年发布1.0版本。
考虑到项目已经基本上稳定下来,API不会再有大的变动。于是我近期准备重新审视一遍Home Assistant,在源码和架构层面对其做一些解读和梳理。
本文焦点
将硬件接入Home Assistant有许多种方式。正如大多数物联网项目一样,接入一款硬件是很容易的。MQTT这类的基础协议提供了充分的自由度。
但我们要知道此类系统往往会随着业务而发展,不断生长,如果没有精心设计的架构,很容易被后期的复杂度压垮。在操作系统的发展史上,太多这样的先烈了。
Home Assistant已经展示出其架构的高度灵活性(至今已经接入了1400+设备),也许有望成为物联网领域的linux。
往系统中硬编码接入一款硬件总是容易的,但随着系统的生长,这种生态十分容易溃败。所以本文关注Home Assistant社区是如何考虑设备接入这件事的,如何应对组件增长带来的复杂度升高。
我们将分析Home Assistant接入Yeelight彩光灯泡的细节,以此为例,对系统做深入了解。
本文将通过实验来跟踪通信的细节。
Yeelight彩光灯泡
Yeelight彩光灯泡搭载了wifi芯片。为了将其接入WiFi,你需要使用官方APP来为它配网。
由于我们准备用Home Assistant控制它,所以需要在APP中做些设置,使其接受来自局域网的控制指令。
实验
实验环境
- MacOS 10.13.5
- Python 3.7.2
- homeassistant==0.94.2
运行 homeassistant
我在虚拟环境里安装homeassistant,为了不与系统中的homeassistant冲突,我选择在当前的虚拟python环境下运行homeassistant: python -m homeassistant
配置目录
第一次运行,会创建配置目录
Config directory: /Users/wuwenjie/.homeassistant
目录内容为:
|
|
从命名可以看出configuration.yaml
是配置文件。
值得一提的是,如果你采用hass.io安装homeassistant,那么你永远不需要在命令行里进入配置目录,可以直接在网页上编辑,这对非技术用户很友好。
由于防火墙的存在,在国内安装hass.io非常痛苦,由于防火墙的存在,技术/科研人员的日常大多时候都是痛苦的。
fuck the GFW.
其他依赖
运行python -m homeassistant
的时候,还将安装其他依赖,一些值得留意的依赖包括netdisco==2.6.0
.
将灯泡信息添加到configuration.yaml
重启时,发现安装了yeelight==0.5.0
。
在Yeelight APP找到Yeelight彩光灯泡的IP地址,将其配置到configuration.yaml
|
|
重启home-assistant服务,即可看到灯泡已经出现在用户面板里。控制开关和调节色温一切正常。
开始分析
在接下来的分析里,我们关注
- 彩灯设备的发现和加载
- 控制指令如何生效(通信细节)
意外发现
Yeelight是home-assistant支持的components之一,home-assistant目前支持1400+设备。
技术层面,components的代码见components。在探索如何接入新的components时,意外发现Integration Services已经完美解决了我们前头提到的home-assistant如何接入新设备
的问题。
但我们仍然准备继续分析,深入到代码层面的细节。
分析工具
我使用sourcegraph的chrome插件来阅读和跟踪源码,这个工具让我们在代码森林中穿梭变得自如。
程序入口
从setup.py中可以看到程序的入口点: hass = homeassistant.__main__:main
其中ensure_config_file值得关注,这个函数的指责是确保配置文件的正常。如果不存在配置文件则帮助我们创建所需的配置文件(默认),后文提及的configuration.yaml就是由它创建的,如果你对初始化的配置文件感兴趣,从它入手可能是个好主意。更多细节:
- async_ensure_config_exists
- hass.async_add_executor_job(_write_default_config, config_dir)
- hass.async_add_executor_job是个很棒的通用函数。
- _write_default_config
此外,try_to_restart的机制怪有趣的,home-assistant是生产级的代码,很多细节考虑得非常周到,很有借鉴价值。
彩灯设备的发现和加载
我们进入到前头提到的分析目标之一:彩灯设备的发现和加载
在components/yeelight/init.py中发现了Integration Services文档描述的内容:def setup(hass, config), 由此可知官方默认支持的components,与用户临时接入的组件,都遵循同一套接入机制。系统的同构性有利于健壮性和灵活性。
还记得我们在configuration.yaml
做的设置吗?
|
|
这个设置是如何被源码使用的呢?在def setup(hass, config)中看得很清楚:
|
|
彩灯设备的发现和加载机制,这段代码已经很清晰展示了。
def setup(hass, config)
中的细节(如track_time_interval)值得深入学习,留待之后有空进一步追踪。
控制指令如何生效(通信细节)
为了理解控制指令如何生效,我们可以跟踪用户操作过程中发生的交互。
从控制台可以看出开关等过程中并不发生http(ajax)交互,于是可以断定是使用websocket交互。
通过观察过程中发生的交互,我们可以使用其他客户端来模拟。下边使用Pharo来替代网页,与Yeelight进行交互。
|
|
让我们进一步跟踪websocket消息是如何生效的。
追踪代码可以发现websocket_api竟是作为components在homeassistant中运行!homeassistant的同构性太惊人了,我非常喜欢这种高度一致的设计。
我们前头发送了开灯命令:
|
|
handle这份命令的代码为:
|
|
至此我们弄清楚了本文感兴趣的所有问题。
todo
- 对多个组件的并行机制做了解
参考
文章作者 种瓜
上次更新 2019-06-12