前言
在Home Assistant 折腾笔记一文中,我们曾提及Home Assistant Cloud。
Home Assistant Cloud提供安全的远程连接,允许你远程控制设备(在你外出的时候),也允许Amazon Alexa and Google Assistant控制Home Assistant实例.
目前该服务由Home Assistant的合作伙伴Nabu Casa, Inc提供。Nabu Casa, Inc由Home Assistant 和 Hass.io的联合创始人创建。
Home Assistant Cloud目前由Nabu Casa, Inc提供,
nabucasa client是开源的:nabucasa(我当前本地安装的版本是0.14),对于hass_nabucasa的交互逻辑,可以从测试代码中学习:test_cloud_api.py。
但在国内使用,延迟比较严重,哪天被墙了也难说。本文试图对Home Assistant Cloud 进行分析,为今后自行构建Home Assistant Cloud server提供参考,这个工作颇似我们此前对Scratch做的分析。
思路
为了理解Home Assistant Cloud的工作原理,我们将从前端开始,观察通信过程,之后跟踪到Home Assistant后端源码里(nabucasa组件是核心部分),最后对Home Assistant Cloud server进行推断。
前端分析(通信过程)
首先打开Home Assistant Cloud: http://127.0.0.1:8123/config/cloud/account
我们从登陆开始:
登陆
POST: http://127.0.0.1:8123/api/cloud/login
Request Payload: {"email":"EMAIL","password":"PASSWORD"}
Response: {"success": true}
登陆完成之后, websocket发送message:{"type":"cloud/status","id":18}
收到:
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
|
{
"success": true,
"id": 18,
"result": {
"logged_in": true,
"prefs": {
"alexa_enabled": false,
"google_secure_devices_pin": null,
"google_enabled": false,
"remote_enabled": false,
"cloudhooks": {
"xxx": {
"cloudhook_id": "xxx",
"managed": true,
"cloudhook_url": "https://hooks.nabu.casa/xxxxxx",
"webhook_id": "xxx"
}
},
"cloud_user": "xx",
"google_entity_configs": {}
},
"email": "EMAIL",
"remote_certificate": null,
"alexa_entities": {
"include_entities": [],
"exclude_domains": [],
"include_domains": [],
"exclude_entities": []
},
"cloud": "connecting",
"remote_connected": false,
"alexa_domains": [
"fan",
"automation",
"group",
"alert",
"cover",
"lock",
"script",
"sensor",
"binary_sensor",
"climate",
"scene",
"input_boolean",
"switch",
"media_player",
"light"
],
"remote_domain": null,
"google_entities": {
"include_entities": [],
"exclude_domains": [],
"include_domains": [],
"exclude_entities": []
}
},
"type": "result"
}
|
之后前端继续发送两条websocket消息:
{"type":"webhook/list","id":19}, 返回:
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"success": true,
"id": 19,
"result": [
{
"domain": "locative",
"name": "Locative",
"webhook_id": "xxxx"
}
],
"type": "result"
}
|
{"type":"cloud/subscription","id":20}, 返回:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
{
"success": true,
"id": 20,
"result": {
"customer_exists": true,
"renewal_active": false,
"status": 200,
"subscription": {
"canceled_at": 1560481412,
"cancel_at_period_end": true,
"trial_end": 1563148800,
"status": "trialing",
"current_period_end": 1563148800
},
"human_description": "Trial user. Trial expires Jul 15, 2019.",
"delete_requested": false,
"provider": null,
"source": null,
"plan_renewal_date": 1563148800,
"billing_plan_type": "trial"
},
"type": "result"
}
|
开启Remote Control
点击Remote Control 右边的按钮,将打开远程控制模式。点击之后前端发送websocket消息:
{"type":"cloud/remote/connect","id":20}, 返回内容同前头{"type":"cloud/status","id":18}相似
{"type":"cloud/status","id":21}, 返回内容同前头{"type":"cloud/status","id":18}相似
关闭Remote Control
{"type":"cloud/remote/disconnect","id":22}, 返回内容同前头{"type":"cloud/status","id":18}相似
打开显示在页面的链接:https://xxx.ui.nabu.casa, 可以进行远程控制了!控制界面完全相同,目前猜测是建立了一个透明管道,就像ngrok那样
开启alax
{"type":"cloud/update_prefs","alexa_enabled":true,"id":24}, 返回{"success": true, "id": 24, "result": null, "type": "result"}
后端分析
根据前端的websocket message,逆向找到对应的后端源码是比较简单的一件事。
我们重点关注一下handle{"type":"cloud/remote/connect","id":20}消息的后端源码
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@websocket_api.require_admin
@_require_cloud_login
@websocket_api.async_response
@_ws_handle_cloud_errors
@websocket_api.websocket_command({
'type': 'cloud/remote/connect'
})
async def websocket_remote_connect(hass, connection, msg):
"""Handle request for connect remote."""
cloud = hass.data[DOMAIN] #
await cloud.client.prefs.async_update(remote_enabled=True)
await cloud.remote.connect()
connection.send_result(msg['id'], _account_data(cloud))
|
nabucasa client部分对应的源码
阅读RemoteUI class可以发现cloud非常值得关注,cloud作为RemoteUI初始化参数传入,我们跟踪RemoteUI的初始化过程,可以追溯到:self.remote = RemoteUI(self), nabucasa client与云相关的所有秘密都可以从这儿找到。
云端分析
从前头的讨论我们感觉Home Assistant Cloud有些像ngrok server。使用ngrok好像也完全做得到这些事。 本质是把局域网服务暴露到外网。
远程页面如何通信
Remote Control现实的公网控制页面https://xxx.ui.nabu.casa/ 是如何控制我们的设备的呢,可以发现,它使用的websocket通道为 wss://xxx.ui.nabu.casa/api/websocket,采用的message和本地完全一样。
看起来是个透明代理。
从证书信息和后端源码可知证书使用 letsencrypt.org(有效期为3个月,会自动更新)
隐藏目录
登陆之后,观察.homeassistant/.cloud目录, 有以下文件:
- acme_account.pem
- acme_reg.json
- production_auth.json
- remote_fullchain.pem
- remote_private.pem
.storage/cloud内容为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
{
"data": {
"alexa_enabled": false,
"cloud_user": "xxx",
"cloudhooks": {
"xxx": {
"cloudhook_id": "xxx",
"cloudhook_url": "https://hooks.nabu.casa/xxx",
"managed": true,
"webhook_id": "xxx"
}
},
"google_enabled": false,
"google_secure_devices_pin": null,
"remote_enabled": true
},
"key": "cloud",
"version": 1
}
|
参考