借助 Home Assistant,做一个能「看见」我赖床的智能闹钟 | 2018 年度征文

编注:本文是「我的 2018 年度关键词」年度征文活动的第 5 篇入围文章,本文仅代表作者本人观点,少数派对标题和排版略作调整。
想了解如何参与本次征文,赢取各种丰厚奖品,你可以 点此查看 活动规则和奖品清单。


前言

在2017年年底,我给自己的2018年定了一个挑战,那就是尝试学习一门编程语言。在之前,这对我是一片空白。这个个人挑战,既不追求有什么具体的目标,也不会对我的工作有什么直接的帮助,纯粹是要逼自己锻炼自学的能力,拓展一下 scope。

18年年底逐渐来临,一个 idea 逐渐孕育。

作为一个晚睡癌者,虽然我已经特地租到离公司只有15分钟步行时间的地方,但随着我睡过闹钟的病症越来越严重,我有了一个想法:

能不能有这样一个闹钟?设定在每天的起床 deadline 上,到点就能够智能检测我是不是在赖床;一旦发现我又要迟到,就放摇滚音乐把我叫醒。

在2018年的 Black Friday,我托朋友海淘了一个 Amazon Echo Dot,燃起了我要 hack 一下这个小东西的热情。在18年最后的两个星期,我决定抡起袖子开干。

中途一度觉得搞不定,但熬了一轮夜、掉了一轮头发之后,搞出来的效果居然还不错,效果请戳:

今天就和广大晚睡癌病友们分享下这个项目。

方案研究

一开始的方案设想很简单:

作为独居青年我没有办法能拍到自己真实睡觉的样子,请大家接受我的自拍谢谢
作为独居青年我没有办法能拍到自己真实睡觉的样子,请大家接受我的自拍谢谢

但是,经过一番研究,我还是没有找到方法 hack 进 Alexa。不过,作为一名灵活的产品经理,我决定 work around 一下,改成下面这样子依然能上线跑噢:

最终可以执行的流程,因为 Hack 不进 Alexa
最终可以执行的流程,因为 Hack 不进 Alexa

经过一番研究,我确定了这个项目所需的软硬件方案。基于 MVP 原则(简单来说就是万一搞不成了不要浪费太多钱和时间的原则),我从床底下拿出了尘封已久的安卓机皇:oneplus one

下面就是初步列出的方案:

模块实现方式所需硬件/软件
整体流程控制Home Assistant一台能够运行 Linux 的机器,例如一台安卓手机
视觉识别摄像头(local)+深度学习模型接口(server)一台有摄像头的机器,例如一台安卓手机;一台能够部署模型并暴露接口的VPS
音频Text-to-speech带麦克风的机器,例如一台安卓手机

于是流程图,又可以继续细化。

加上了硬件模块划分的流程图
加上了硬件模块划分的流程图

这个在2018年年尾才确定的项目,我觉得是一个很好的选择。因为麻雀虽小,但贯穿了过去一年的所有重要知识点。

模块知识点技能点
部署 Home AssistantLinux 基础知识开源项目理解,以及文档查阅能力
Home Assistant 自动化开发Python + 硬件基础理解业务流程设计能力
图像识别深度学习模型训练机器学习的基础理解,以及开源的优秀框架运用
Client-Server 交互深度学习模型部署API接口对API接口交互的理解

像很多新手一样,2017年底刚开始学习时,我模糊地以为我的学习重点是 Python 这门语言。但慢慢走下去,才发现具体的语言本身毫不重要,像上面所展示的知识点,完全可以把语言抽离出来,更多是编程利于的框架性理解。一年过后更重要的收获,是我对互联网在实现层面的理解更深了。

分享科技相关资讯的网站有不少,但我一直觉得少数派还是一个小小的极客社区:对比较 hardcore 而相对枯燥的内容很宽容,社区内也很鼓励动手能力。我不相信编程是一种囿于职业、专业的技能。只要愿意学习,每个人都几乎可以学习任何事情,编程自然也绝不例外。不过,虽然编程不是少数人的特权,但肯学习和动手实践的人总是少数派。

这次项目的分享,会分为两篇,可能还是会比较 hardcore 和枯燥:

  1. 第一篇围绕 Home Assistant 的搭建;
  2. 第二篇围绕用深度学习模型完成图像分类预测的实现

本文为第一篇,大部分的篇幅会和 Home Assistant 相关。我会在前半部分介绍比较浅显和有趣的内容,尽量面向所有读者,感兴趣的朋友看完前半部分,有了大体的了解就差不多了。而后半部分则会比较复杂,涉及 Home Assitant 的开发部分,代码也会出现比较多。普通读者可能会感到头疼,这不要紧,因为只是供打算动手做设计的朋友参考的。


什么是 Home Assistant?

Home Assistant 是一个开源的智能家居自动化平台。它特点是运行在局域网内的设备上,相对比较注重隐私。

智能家居的最大问题之一,就是标准无法统一。在开源社区的努力下,已经支持1000多种硬件和软件接入。接入的软硬件,可以统一串联起来,设置自动化流程。包括我手头上零零碎碎的硬件也是各个品牌的,基于这个平台的特点,我选择使用该开源项目作为主要的开发平台。

Home Assistant 核心概念理解:组件、服务、状态

在 Home Assistant 设计自动化中,我们总是在和这几个关键概念打交道:

  1. 组件:可以理解为 HA 允许接入的硬件或者软件服务,例如小米的灯(硬件)、摄像头(硬件)、麦克风(硬件)、脚本传感器(软件)、甚至自己编写的 custom component(软件,这个也是我们这次项目中主要使用到的能串起整个流程的核心组件)
  2. 服务:一个组件,会对外提供服务,例如一盏灯可以提供开关灯的服务,一个麦克风可以提供说话的服务。自己编写的一些脚本组件,也可以对外提供触发的服务
  3. 状态:一盏灯在当前是否开启着的,这是一个状态;

智能家居是一个非常复杂的场景,Home Assistant 作为一个智能家居平台,在设计之初就要考虑到开放性和复杂性。组件、服务、状态,这三个概念相当简洁,又全面,确实是非常优雅的设计。

HA 模块拆解

承接上面对核心概念的概念,我们终于可以把项目中设计到的功能点,在 Home Assistant 的框架中进行拆解。

模块组件(component)涉及服务
在面板上可以输入时间的地方Input Datetime(输入时间)不涉及
判断当前时间是否为工作日&当前时间Shell Command(命令行)暴露一个检查当前时间的服务
判断&闹钟主体(视觉部分)Android IP webcam(摄像头)snapshot 拍照服务
判断&闹钟主体(闹钟部分)TTS (text-to-speech) + Kodi(麦克风)google.say 用上google 文字转音频服务

敲定软件方案,数据流转也已经清晰确定
敲定软件方案,数据流转也已经清晰确定


实战篇

现在开始进入到复杂的部分,会设计到 Home Assistant 的开发模块,也会涉及比较多的代码。抛砖引玉,供想要动手调教的朋友参考。

如何部署 Home Assistant?

本质上来讲,可以部署在任何 Linux 的环境中,包括树莓派、Macbook、以及任意可以运行 Linux 的设备上。如果你使用树莓派,建议参阅 @墨澜 之前分享的一系列教程,特别是 Home Assistant + 树莓派:强大的智能家居系统 · 安装篇

我手上刚好有一台旧的安卓手机,就想办法直接在上面部署了,参考分享帖 Ubuntu安装HomeAssistant教程 步骤如下:

  1. 安装 Linux Deploy APP,在家里的 Wifi 下启动
  2. 使用你的电脑 ssh 连入安卓终端,安装 Python3、pip 等工具
  3. 激活一个 Python3 的虚拟环境
  4. 安装 Home Assistant
    pip install homeassistant
    
  5. 启动 Home Assistant
    hass --open-ui
    

    然后,用你的浏览器访问 <存管服务器的IP>:8123 应该就可以正常访问。

我的 Home Assistant 面板
我的 Home Assistant 面板

文档研究基础篇:如何查询、添加组件

当 HA 成功运行后,要添加组件都在主目录的 configuration.yaml 中进行编辑和添加。
朋友们,让我们打开文档官方文档:组件,可以在这里搜索上千样软硬件的接入方法。

例如,Text-to-speech,只需要在configuration.yaml中添加下面这些代码:

# yaml 文件的关键点在于缩进要正确
# Text to speech
tts: - platform: google

配置我的麦克风,只需要这样添加:

# Host 是 IP, 要看你的设备当前在局域网内是啥 IP
# Media player
media_player: - platform: kodi name: speaker host: 192.168.0.106

文档中还会介绍如何接入小米、Echo等设备,大家可以多多探索。

文档研究高级篇:如何用 script 写稍微复杂的自动化

一般的自动化,我们可以使用 HA 自带的自动化模块进行设计,这里也可以参考文档 官方文档:自动化

配置-自动化-新建自动化,可以直接创建简单的自动化流程
配置-自动化-新建自动化,可以直接创建简单的自动化流程

但在我们这个场景下,自动化流程过于复杂了,所以我们要开始了解如何用编写脚本的方式完成自动化。

  • Shell command 命令行

这个组件,可以把命令行封装成一个服务。命令行可以执行脚本,这样一来,我们就可以执行复杂一点的流程了。而且,更重要的是,我们可以通过这个形式,突破 HA 的沙盒模型,例如导入第三方 Python 包,以实现各种可能性。

命令行相关的组件以及其功能

组件作用
Shell Command简单地提供一个 Service,调用之后可以执行输入的命令行
Command Line Binary Sensor本质上是一个二元传感器(就是说只有On/Off两种状态),根据命令行返回的结果,设置传感器的状态
Command Line Cover本质上应该是一个类似车库门这样的可以上下左右控制的东西,但没有怎么了解,因为我首先还没有车…
Command Line Notify本质上是一个消息服务,不过可以使用命令行
Command Line Sensor本质上是一个传感器,可以通过命令行返回的内容定制各种各样的东西
Command Line Switch本质上是一个开关,当它开启或者关闭的时候,可以触发不同的命令行

关于 Shell command 的高级用法还可以参考:Home Assistant + 树莓派:强大的智能家居系统 · 高级篇一

这里顺带提一下 Python Scripts 这个组件,它其实和 Shell Command 比较像,就是直接把使用 Python 写的脚本封装成一个可以调用的服务,对于习惯写 Python 的朋友应该稍微有好点。但请注意,这里是在 沙盒环境(sandbox environment) 中,不能直接调用第三方包。

  • Custom component 定制化组件

使用 Custom component 可以按照自己的流程,去定制流程。
以这个项目为例,我将会定制一个叫做 Smart Alarm 的组件,并接入系统,它如果被触发:

  1. 首先,会开启摄像头拍一张照片
  2. 其次,会分析这张照片中,我是否还在床上
  3. 如果是,则让麦克风说话;如果不是,就保持沉默

这样一段流程,用定制化的方式来写,就非常非常简单了。不过,它的缺点也很明显:

  1. 语言是基于 Python 的,对 Python 的要求比较高
  2. 位于沙盒模型中,只能使用 HA 内部的 API

因此,不作赘述,感兴趣的同学可以自己阅读文档:官方文档:自己动手创建一个虚拟组件

文档研究超级复杂篇:什么是沙盒环境,以及脚本如何跨设备联动

这一小节的内容非常 hardcore 和复杂,仅仅是描述下自己踩过的坑,感兴趣的同学可以留心下。在使用 Python 编写脚本的时候,非常重要的一点,是要知道现在是否位于沙盒环境中。这是一种安全策略,在这个环境内,基本上意味着你只能使用 HA 内部定义的 API,不能导入第三方。

组件环境局限性跨设备联动方案
Shell Command(以及上面列出的相关的组件)无限制,当前 Linux 所在的环境脚本执行是一次性的,没有什么逻辑判断在里面,无法定义复杂的自动化利用 HA 对外提供的 REST API 服务
Python Scripts沙盒环境只能使用 HA 自己的 API利用 HA 对内的 API 服务
Custom component沙盒环境只能使用 HA 自己的 API利用 HA 对内的 API 服务

划重点:而在这个智能闹钟项目中,判断当前是否在工作日会需要导入 chinese_calendar 这个包,以及触发照片判断会用到 requests,都一定会使用到第三方包,所以一定要把他们用 Shell Command 封装成两个单独的服务!

# command line sensor,工作日和时间判断
binary_sensor: - platform: command_line command: 'python3 /home/android/.homeassistant/python_scripts/working_time_check.py' name: if_worktime_check payload_on: 'Working time' payload_off: 'Resting time' # command line script,触发照片判断
shell_command: alarm_check: 'python3 /home/android/.homeassistant/python_scripts/alarm_check.py '

HA 内部 API

该内部方法,可以允许 HA 内沙盒环境内的脚本与 HA 的设备进行交互。

HA 外部 REST API

这个方法,允许 HA 体系外的脚本,能够与 HA 体系内的设备进行交互。但是,这里一定要提前获取令牌,不然请求会失败。实际上,这也是一种安全的措施。你肯定不想自己的设备被其他人入侵。查看官方文档:发出请求前要先授权

首先是授权,获取令牌

点击左上角头像,拉到最底下可以看到长期令牌
点击左上角头像,拉到最底下可以看到长期令牌

请求时,该令牌会需要放置在请求头中,参数名为 Authorization

# 官方样例,这里的 ABCDEFGH 就是令牌内容,Bearer 请保留。创建你个人令牌后,请如法炮制。
import requests url = "https://your.awesome.home/api/error/all" headers = { 'Authorization': "Bearer ABCDEFGH",
} response = requests.request('GET', url, headers=headers) print(response.text)

因为我也是新手,对文档的理解也很浅。所以只能在这里简单分享下我整理的信息,以及常见的几种场景,感兴趣的同学可以再去钻研文档:

HA 内部 APIHA 外部 REST API
使用方法使用 hass 对象用 requests 的 GET/POST 处理
注意事项除了 hass 对象以外,还有一堆帮助方法,例如监听某个状态是否有变更请求头中,要带上提前获取的 Token
文档地址官方文档:Hass对象入门官方文档:外部REST API
  1. 获取状态

    • 内部 API:hass.states.get(entity_id)

      # 例如:获取前端设置的时间值
      TIMING = hass.states.get('input_datetime.smart_alarm_timing')
      
    • 外部 REST API:用get请求 http://localhost:8123/api/states/entity_id

      # 例如:获取前端设置的时间值
      headers = { 'Accept': '*/*', 'Content-Type': 'application/json;charset=UTF-8', 'Authorization': 'Bearer 后面接着前面所说的Token',
      }
      response = requests.get('http://localhost:8123/api/states/input_datetime.smart_alarm_timing', headers=headers)
      
  2. 改变状态

    • 内部 API:hass.states.set(entity_id, state)

      # 例如:恢复传感器 am_I_still_in_bed 状态为 'empty'
      hass.states.set('sensor.am_I_still_in_bed', 'empty')
      
    • 外部 REST API:用post带上要设置的状态参数请求 http://localhost:8123/api/states/entity_id

      # 例如:设置传感器 am_I_still_in_bed 状态为 'occupied'
      headers = {
      'Content-Type': 'application/json;charset=UTF-8',
      'Authorization': 'Bearer 后面接着前面所说的Token',
      }
      data = '{"state": "occupied"}'
      response = requests.post(
      'http://localhost:8123/api/states/sensor.am_I_still_in_bed',
      headers=headers,
      data=data)
      

      我还要特别推荐两个官方用例:
      例子一、用 Python 编写虚拟组件
      例子二、有人侵入时触发警报灯

HA 部分代码分享

我还得先想想,这里怎么分享。

感谢

作为面向 Google 编程的选手,我必须要感谢下社区内分享过大量 HA 经验的作者 @墨澜 ,我在文中频繁地引用了她的文章,就是因为她的一系列文章都非常全面,还翻译了 HA 的中文文档。Hats off to her!

下一篇,会围绕项目中另一块硬骨头:视觉识别模型。

欢迎在评论留下你的看法,谢谢大家。

关联阅读:下篇•用深度学习为闹钟提供视觉能力

> 下载少数派 客户端、关注 少数派公众号,找到数字时代更好的生活方式 🥳
> 特惠、好用的硬件产品,尽在 少数派sspai官方店铺🛒


今年,你除了可以参加年度征文活动,赢取 iPad Pro、Kindle Oasis、戴森吸尘器、HomePod 等丰厚奖品,也可以去微博参与「我的 2018 年度瞬间」有奖摄影活动,从今年拍摄的照片里,选出令你印象最深刻的一张。我们同样准备了 GoPro Hero 7 Black、富士 instax 照片打印机等奖品等你来拿。