iOS备用机自动充电方案

搬到新加坡以后,我拥有了很多张手机卡:

  • 1张国内的电话卡,回国时使用
  • 1张新加坡电话卡,本地使用
  • 1张马来西亚电话卡,去马来西亚旅行时使用
  • 1张英国电话卡,去欧洲旅行时使用

相对应的,我需要有备用机来运行这些号码。正好手上有一台旧的iPhone XR,于是我就把其中两张卡插到了这个手机里。但随之而来的是另一个问题,手机长期插电对电池非常不友好,很有可能导致备用机电池彻底废掉。

垃圾佬的家里从来不缺少设备,很快我翻出一个之前从国内带来的米家智能插座2,前几年买来当智能网关用的。但来新加坡以后家里基本没有智能设备了,所以一直在吃灰。

我完整的预想是,将备用机插在智能插座上,然后通过iOS的Shortcuts来监控手机电量——当手机电量小于30%的时候自动开启插座;当手机电量充到80%的时候自动关闭插座。

控制米家智能设备

说干就干,查了下资料,网上对米家生态的协议、工具其实都分析的比较完善了,没有遇到太多困难。

首先,使用https://github.com/PiotrMachowski/Xiaomi-cloud-tokens-extractor.git这个Python脚本获取我的小米账户绑定的智能设备:

image.png

可见,我只有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

image.png

拿到IP地址和token以后,我们可以使用python-miio这个工具来访问我们的设备。

miiocli chuangmiplug --ip 192.168.1.187 --token xxxxx info

尝试一下后发现工具报错,并不识别我的插座model:

image.png

原因可能是我的设备型号比较小众,所以命令没有添加进来。问题不大,到这个网站使用Model名称是可以搜索到这款产品的:

image.png

点击进去,就能看到米家智能插座2全部功能的通信协议属性:

image.png

还挺全的,其中我最需要的当然是第一个“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来测试一下效果,实测可以正常开启插座:

image.png

我的主力机也收到了pushdeer的推送:

image-20221213000420980.png

集成iOS快捷指令

现在完成了服务端,还需要定制一下客户端,用于触发开关。

首先添加一个开启插座的快捷指令,其内容是发送HTTP请求给树莓派:

image.png

然后在捷径里增加一个Automation,选择在电量小于30%时触发:

image.png

找到刚才添加的Shortcut,组成一个完整的指令:

image.png

注意,要把Ask Before Running关掉,否则不能实现全过程自动化。

同理,增加一个电量大于80%时自动关闭的自动化。

另外,我还增加了一个定时任务,每天检查一次一次当前手机电量,如果发现大于90%或小于20%,说明前面的动作没成功,那么再重启关闭或开启一次开关。这是为了防止某一天网络不问题,开关没有成功开启或关闭的情况下,能够有兜底机制。

不知道为何苹果没有提供小时级别的定时任务,否则这个方案会更完美一些。

总结

最后,花了大概3个小时时间,把整个流程搞完了,又等了一天测试,完美运行,非常顺滑,妈妈再也不用担心我的电池~

从成本上来讲,这个方案其实还是有点浪费,智能插座的大部分功能实际上并没有用上(虽然它本身也是吃灰)。以后有机会,可以研究研究更廉价的方案,早日实现多备用机同时待机。

赞赏

喜欢这篇文章?打赏1元

评论

skip 回复

看了这篇文章,我也想试一下家里的米家设备能不能用代码来操作。文章里只说了调用 raw_command set_properties ,为了找调用Actions的方法我找了很多,也试了很多次,https://github.com/rytilahti/python-miio/issues/901 这里介绍了 get_properties 和 get_properties 来设置 属性 Properties,但是没有说如何使用方法。

如果使用 raw_command action 的话后面的 params 不能是列表而是字典格式,就是说要把外面的方括号要去掉。

`miiocli device --ip [ip] --token [token] raw_command action "{'did': 'start-sweep', 'aiid': 1, 'siid': 3 }"

试了好多的可能性,太惨了。看到这里我才意识到哪里不对。https://github.com/rytilahti/python-miio/issues/1181#issuecomment-963903615

坝北 回复

感觉玩到最后都要玩到硬件上

L1k 回复

@坝北 硬件只是需要的时候玩,比如openwrt这些软路由,有需要才会去搞,没事少折腾硬件,砖一次能花你一周时间

captcha