搬到新加坡以后,我拥有了很多张手机卡:
相对应的,我需要有备用机来运行这些号码。正好手上有一台旧的iPhone XR,于是我就把其中两张卡插到了这个手机里。但随之而来的是另一个问题,手机长期插电对电池非常不友好,很有可能导致备用机电池彻底废掉。
垃圾佬的家里从来不缺少设备,很快我翻出一个之前从国内带来的米家智能插座2,前几年买来当智能网关用的。但来新加坡以后家里基本没有智能设备了,所以一直在吃灰。
我完整的预想是,将备用机插在智能插座上,然后通过iOS的Shortcuts来监控手机电量——当手机电量小于30%的时候自动开启插座;当手机电量充到80%的时候自动关闭插座。
说干就干,查了下资料,网上对米家生态的协议、工具其实都分析的比较完善了,没有遇到太多困难。
首先,使用https://github.com/PiotrMachowski/Xiaomi-cloud-tokens-extractor.git这个Python脚本获取我的小米账户绑定的智能设备:
可见,我只有3个智能设备,第一个就是米家智能插座2(“Mi Smart Power Plug 2”)。
这个插座信息里包含了其ID,Mac地址,IP地址,通信使用的Token和其设备型号(Model):
NAME: Mi Smart Power Plug 2 (Wi-Fi and Bluetooth Gateway)
ID: xxxxx
MAC: 12:34:56:78:90:AB
IP: 192.168.1.187
TOKEN: token....
MODEL: chuangmi.plug.212a01
米家设备通信端口是54321,协议是UDP,所以想知道自己家里的米家设备地址,也可以用nmap扫描这个端口,比如:
nmap -sU -p54321 --open 192.168.1.0/24
拿到IP地址和token以后,我们可以使用python-miio这个工具来访问我们的设备。
miiocli chuangmiplug --ip 192.168.1.187 --token xxxxx info
尝试一下后发现工具报错,并不识别我的插座model:
原因可能是我的设备型号比较小众,所以命令没有添加进来。问题不大,到这个网站使用Model名称是可以搜索到这款产品的:
点击进去,就能看到米家智能插座2全部功能的通信协议属性:
还挺全的,其中我最需要的当然是第一个“Switch”。
看到其中的SIID和PIID了吗?Switch功能的SIID是2,开关on的PIID是1,所以发送如下信息就可以开启开关:
[{'did': 'MYDID', 'siid': 2, 'piid': 1, 'value':True}]
同理,发送如下信息可以关闭开关:
[{'did': 'MYDID', 'siid': 2, 'piid': 1, 'value':False}]
参考这个issue,使用如下命令来发送raw message:
miiocli device --ip 192.168.1.187 --token xxxxx raw_command set_properties "[{'did': 'MYDID', 'siid': 2, 'piid': 1, 'value':True}]"
成功点亮。
在网上浏览一番,发现有一个系统Home Assistant,基本可以完全实现自动化,且不需要编程。不过我家里智能设备少得可怜,杀鸡焉用牛刀呢?如果我只为了控制开关,其实简单写个脚本就行了。
家里正好有一个树莓派上面运行着一个DNS服务器,是PHP写的,所以我也用PHP写了一个简陋的脚本丢在树莓派Web目录下:
<?php
header('content-type: text/plain');
if (empty($_SERVER["HTTP_X_CSRF_TOKEN"]) || $_SERVER["HTTP_X_CSRF_TOKEN"] != "xxxxxx") {
echo "csrf check error\n";
exit;
}
define('TOKEN', 'token');
define('IP', '192.168.1.187');
define('PUSHDEER_KEY', 'pushdeer key');
$action = $_POST['action'] ?? '';
$battery = $_POST['battery'] ?? '';
if ($action === 'on') {
$result = shell_exec('miiocli device --ip '.IP.' --token '.TOKEN.' raw_command set_properties "[{\'did\': \'MYDID\', \'siid\': 2, \'piid\': 1, \'value\':True}]"');
$message = "当前电量:${battery}%,已开始充电";
} else if ($action === 'off') {
$result = shell_exec('miiocli device --ip '.IP.' --token '.TOKEN.' raw_command set_properties "[{\'did\': \'MYDID\', \'siid\': 2, \'piid\': 1, \'value\':False}]"');
$message = "当前电量:${battery}%,已停止充电";
}
echo $result;
file_get_contents('https://api2.pushdeer.com/message/push?pushkey='.PUSHDEER_KEY.'&text=' . urlencode($message));
echo "done\n";
前两行主要是为了防止CSRF漏洞,不要正常上网的时候插座被其他人控制。
后面的代码就非常简单了,主要就是调用miiocli的命令开关插座,并且在开启和关闭时,通过pushdeer给我发送通知。
使用burp来测试一下效果,实测可以正常开启插座:
我的主力机也收到了pushdeer的推送:
现在完成了服务端,还需要定制一下客户端,用于触发开关。
首先添加一个开启插座的快捷指令,其内容是发送HTTP请求给树莓派:
然后在捷径里增加一个Automation,选择在电量小于30%时触发:
找到刚才添加的Shortcut,组成一个完整的指令:
注意,要把Ask Before Running关掉,否则不能实现全过程自动化。
同理,增加一个电量大于80%时自动关闭的自动化。
另外,我还增加了一个定时任务,每天检查一次一次当前手机电量,如果发现大于90%或小于20%,说明前面的动作没成功,那么再重启关闭或开启一次开关。这是为了防止某一天网络不问题,开关没有成功开启或关闭的情况下,能够有兜底机制。
不知道为何苹果没有提供小时级别的定时任务,否则这个方案会更完美一些。
最后,花了大概3个小时时间,把整个流程搞完了,又等了一天测试,完美运行,非常顺滑,妈妈再也不用担心我的电池~
从成本上来讲,这个方案其实还是有点浪费,智能插座的大部分功能实际上并没有用上(虽然它本身也是吃灰)。以后有机会,可以研究研究更廉价的方案,早日实现多备用机同时待机。