无论是否常(实)用、不管是虚拟的还是实体的,语音助手,无疑正在成为一股潮流。本次 WWDC 一个看点也是苹果会否推出智能音箱,或是对 Siri 叕一次升级,不过在此之前,我们依然可以做些什么,让 Siri 对你「言听计从」(目前不包括 Mac 上的 Siri,因为没有接入 HomeKit)
这不是一篇详尽的教程,而是想和大家交流一些想法,DIY 玩起来(当然干货也是有的,提供了一个 Homebridge 插件,可以在部署了 Node.js 及 Homebridge 的环境下安装)
先看一个例子
- 对 Siri 下达「睡眠电脑」,屏幕就会进入睡眠状态
或者复杂一点的
- 对 Siri 下达「更新树莓派」,树莓派会执行
sudo apt-get update
,并且通过 Telegram 推送执行结果,如果有待更新软件包,还会新建一个 Todoist 任务,评论内容为软件包信息 - 对 Siri 下达「升级树莓派」,会执行
sudo apt-get dist-upgrade
,推送成功,并完成 Todoist 任务
通过 HomeKit 执行 Shell 脚本
熟悉 Homebridge 的朋友当然了解它的强大与灵活,我们可以利用提供的接口编写插件,将原本并不智能的设备纳入到 HomeKit 的管理中,或者做任何想做的事,这个实现也不例外
直观的方案
我们要做的,是扩展 HomeKit 的功能,使之能执行自定义的命令
其实社区不乏这样的实现,我们可以很容易地虚拟一个开关之类的设备,然后打开它的时候去执行预设的 Shell 脚本(能执行 Shell 就约等于能做任何事)
这个方法很直观,但是我们当然不希望每个 Shell 脚本都需要添加一个虚拟开关,并且在使用 Siri 的时候用「打开 / 关闭 xxx」这样的句式来下发命令
转换思路
其实想一下,我们并不是必须靠「开关」来执行命令,我们需要的是改变虚拟设备的状态,就触发相应的脚本,所以接下来就顺理成章了,HomeKit 里智能灯拥有最多的可控状态,1 个智能灯,通过改变亮度(0 - 100)就可以对应约 100 个命令
HomeKit 中智能灯采用 HSV 色彩空间,也就是说除了「亮度 V」,还有「色相 H」、「饱和度 S」可以利用,但是根据 Homebridge 接口的特点,实现各属性的乘数关系比较复杂,简单与直观起见,只用亮度通常就足够了
这样,我们就可以设定亮度为 1 时,执行某个脚本;设定亮度为 2 时,执行另一个脚本等等,极大地减少了添加虚拟设备的数量
HomeKit 中「场景」的用途
滑动操作的缺点
前面我们设计通过改变智能灯的亮度来执行 Shell 脚本,但是你一定不想在 UI 上滑动亮度条,鬼知道「沿途」会触发多少目标之外的脚本,而且每个亮度对应的脚本具体是什么也不容易记忆,这时候「场景」就派上用场了
场景与命令映射
我们可以设定多个场景,每个场景中智能灯的亮度不同,进而对应不同的脚本,这样去点按场景就很方便了,而且与「给每个命令都映射一个开关」的方法相比,虚拟一个设备而设定多个场景的方式显得更「优雅」一些
用语音去设定场景,就好像在「执行语音命令」
自然地,我们会给这些场景起一些有含义的名字,比如上面的例子。至此,我们「执行语音命令」的目标其实就已经达成了
最初想到可以这样做是缘于一次搞怪:
当 HomeKit 接入了一些设备,比如智能灯(真正的),我们自然会想要捉弄下 Siri,于是我说:要有光。不出意外,Siri 没那么「聪明」。不过如果这样设置,我们对《最后的问题》的致敬就可以完成:
显然,Siri 对场景名称是敏感的。正如系统建议的「出门」、「到家」、「晚安」、「早上好」那几个场景一样,我们只要用想要执行的命令名称(或任何话,只要 Siri 能正确「听写」)来创建场景,比如「睡眠电脑」、「关闭电脑」等等,然后去编写对应的 Shell 脚本就好
刚设置好的场景,可能要等一会儿 Siri 才能正确索引和识别
几个体验上的问题 & 细节
原理很简单,交流几个应该考虑的问题:
- 屏蔽误触。毕竟是添加了一个设备,有时会不小心开关它,我们可以编写一些策略来屏蔽它,比如响应「开 / 关」操作时,异步还原它原本的开关状态;只有「修改亮度」操作才真正进行处理,同时将开关状态置为「开」
- 忽略模糊指令。同样,毕竟是添加了一个「灯」,如果对 Siri 下达类似这样的指令:「将灯亮度调到 x」,这会改变所有智能灯的亮度,而如果 x 恰好对应了一个 Shell 的话……这一定不是我们期望的。这个问题解决起来麻烦一些,一个可行的方案是再添加一个虚拟灯,如果我们发现这两个灯「同时」收到命令的话(实际上是两条并发的命令,但中间会有一点时间差),就可以认为该命令是模糊指令,然后忽略它
插件使用说明
需要先行配置好 Node.js 及 Homebridge,请参考相关教程
下面是我写的一个 Homebridge 插件,仅供参考。安装命令:
npm install -g homebridge-command-bulb
插件配置
默认不需要配置。如果想要修改 Shell 脚本存放路径,或者需要 Telegram 推送功能,Homebridge 配置如下(如果有其它插件配置,注意合并):
{
"platforms": [
{
"platform": "CommandPlatform",
"directory": "~/.homebridge/commands",
"tg_token": "",
"tg_chat_id": "",
"proxy": "http://localhost:8888"
}
]
}
- 需要自己创建 Shell 脚本目录,默认为
~/.homebridge/commands
- Telegram token 需要通过 @BotFather 申请;chat id 可以通过 @get_id_bot 获取
- 插件连接 Telegram 通常需要 proxy,请科学解决。如果是 socks proxy 的话可以用 privoxy 转换为 http proxy
重启 Homebridge 后会添加两个灯,「Command Bulb」和「Probe Bulb」,前者用于执行命令,后者用于排除模糊指令干扰(没有其它用处,请无视它的存在)
脚本规则
脚本应具有可执行权限,约定先于配置,脚本前缀、后缀采用如下规则
- 前缀(前两位,01 - 99)用于映射亮度,比如「01」对应亮度为 1,此外,所有前缀为「01」的脚本都会被执行,可以一次执行多个独立脚本。预留了亮度为 0 和 100 两个值用于标识「成功 / 失败」,所以不要用「00」作为前缀
- 后缀用于辅助功能,目前是用于 Telegram 推送消息(在配置了相关参数的情况下)
- 「.ok」表示执行结果为成功时推送「Command: xxx OK!」消息。注意,默认情况下,执行过程中存在 stderr 不会被认为是失败
- 「.out」表示推送 stdout
- 「.err」表示推送 stderr,并且执行过程中存在 stderr 会被认为是失败
- 如果失败,总是会推送「Command: xxx Failed!」消息,无需后缀
- 多个后缀可组合,如「.ok.out」,会推送成功和标准输出两条消息
- 其它未定义的及「.sh」可有可无,会忽略
一些使用建议
- 不要将两个虚拟灯加入个人收藏,最好新建一个房间来摆放它们,尽量减少直接操作它们的机会
- 新建两个场景「成功」、「失败」(「成功」对应于「Command Bulb」关闭状态,「失败」对应于其亮度为 100 的状态),用于标识执行结果,可以添加到个人收藏充当信号灯;而其他命令的场景最好不要添加到个人收藏(太多会显得乱,而且我们倾向于用 Siri 来触发而不是点按)
- 有一个家庭中枢会方便很多,同时也使得外出时 HomeKit 依然可用。如果用闲置的 iPad 作为中枢,将「自动锁定」关闭可以保持 HomeKit 总是可用,亮度可以调到最低
控制 Mac 的方法
通过 SSH 控制 Mac 执行命令
原理是在运行 Homebridge 及插件的服务器上,通过 SSH 连接 Mac 执行命令,类似这样:
ssh user@ip 'shell 命令'
所以需要在 Mac 打开系统偏好设置 -> 共享 -> 远程登录
其中 user 为 Mac 的用户名(在终端中运行whoami
即是),ip 为 Mac 的地址,可以在路由器中绑定,或者用共享设置页面显示的类似 xxx.local 的地址,更加灵活
设置 Mac SSH 免密登录
在服务器上 SSH 到 Mac 还需要输入密码,所以要设置免密登录(只对该服务器有效)
在服务器上运行ssh-keygen -t rsa
,一路回车,会在服务器的 ~/.ssh 目录下生成两个文件,把其中 id_rsa.pub 中的内容粘贴到 Mac 的 ~/.ssh/authorized_keys 文件中即可
示例
- 睡眠电脑(屏幕)
ssh user@ip 'pmset displaysleepnow'
- 关闭电脑
ssh user@ip 'osascript <<EOF tell application "Finder" to shut down EOF'
it works 还是 it just works
整个方案是可行的,对原本 HomeKit 的「侵入性」相对比较小,就我个人体验来说还不错,不过当然也有无法克服的缺点:
- 需要自己编写 Shell 脚本
- 终究是显式地引入了虚拟设备,也为此不得不考虑容错机制。不过增加的设备也可以成为功能的扩展点
- 语音与命令终究是静态映射,不能动态解析和响应,所以需要手动设定较多场景
- 受制于 HomeKit 接口的开放 / 破解程度,未来可能会失效
- Siri 对场景的名称并不总能正确解读。比如设置了一个「关闭电脑」命令,因为包含了 Siri 指令系统的关键字「关闭」,它就不干(不过如果说「请关闭电脑」,却能正确识别,迷之傲娇)
采用这个方案,我们实际上并没有让 Siri 更「聪明」,而是用一种比较 tricky 的方式,让 Siri 更「听话」,有一说一,说一不二,说二就听不懂了
这自然不是最佳的体验,不管是语音助手,还是智能家居,都应该有更加直观的界面、更加动态的实现。期待本次 WWDC 能给我们带来更多「it just works」的特性,也期待被收购的 Workflow 能早日加入 Siri 的支持