本文编写于 616 天前,最后修改于 614 天前,其中某些信息可能已经过时。

趁着有点闲钱,打算继续改改家中的电器。
首先记录两个重要步骤:

获取设备的token

所有米家智能产品通信都在54321/udp上,通过token认证交互,而这个token目前无法直接获取了,不过可以通过以下方法获得:

数据库法

推荐使用安卓模拟器,因为今后的调试中涉及抓包,方便起见。
使用的米家版本需要是5.0.19左右的版本,在此之后的版本已经不会把token存到本地了。下载:ApkMirror
下载,安装,登陆账号,等待米家同步所有设备。
这时可以使用文件管理器打开/data/data/com.xiaomi.smarthome/databases/miio2.db移动到计算机中,使用网上的工具,例如:https://miio.loli.ren/index.php 来读取,也可以直接使用sqlite工具打开。


我们需要的是这个ip和token。

建议在路由器端为智能产品固定IP地址

好了,经过测试,其他方法失效(哭

抓包解包

​‌‌‌​‌​​​​​‌‌​‌​‍​‌​‌‌‌​​‌‌‌‌​‌​‍​‌​​‌​​​‌​​​‌‌​‍​​‌‌​​​‌​​‌‌​‌​‍​‌‌‌‌‌‌​​​​‌​‌​‌‍‌​‌​‌​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​‌‌​‌​‍‌​‌‌‌‌​‌‍‌​​‌​​‌‌‍‌​​‌​​​​‍‌​​‌‌​​​‍​​‌‌​‌​​​​​‌​​‌‍​‌​‌‌‌​‌​‌‌​‌‌​‍​‌‌‌​​​​‌​​​‌​‌‌‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​‌‌​​​​​‍​​‌​‌‌‌‌‌‌‌‌​​​‍​‌‌​​‌‌‌​‌‌​​‌‌‌‍​‌‌​​​‌‌‌​​​‌​‌‍​​‌‌‌‌‌‌‌‌​​‌‌‍​​‌‌​​‌‌​​​​‌​‌‍​‌‌‌‌‌‌‌​​​​​​‌​‍​‌​​‌​​​‌​​‌​​‌‍​‌​​​‌‌‌​‌‌‌​‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​​‌‌​‌‌‌‍​‌‌‌​​​​​​‌‌​​​‌‍​​‌‌​‌​​‌​​‌‌‌‌‍​​‌‌‌‌‌‌‌‌‌‌​‌‍​​‌‌‌‌‌‌‌‌​​‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​​​‌​‌​‌‌​​‌‌‌​‍‌​​‌​​​​‍‌​​​‌​‌​‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​‌‌​‌​‍​‌‌​​​‌​‌‌‌​​​‌‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​‌​​‌​‍‌‌​​‌‌‌‌‍‌‌​​‌‌​​‍‌‌​‌​​‌​‍‌‌​​‌‌​‌‍‌‌​​‌​​‌‍​​‌‌‌​‌‌​‌‌‌‌‌‌‍​‌​‌‌‌​​‌‌​​‌‌​‍​​​​​​​​‌‌‌‌​​‌‌‍​‌​‌‌​​​‌‌​​​​​‍​​‌‌​‌​​‌‌‌‌​​​‍​‌​‌​​​‌‌​​‌‌‌‌‍​‌​‌​​​‌​‌‌‌‌‌‌‍​​​​​​​​‌‌‌​​‌​‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​​‌​‌‌‍‌​​​‌‌‌‌‍‌​​​‌‌​​‍‌‌​​​‌​‌‍‌​‌​​​‌‌‍‌​‌​​​‌‌‍‌​​‌​‌‌​‍‌​​‌​​‌​‍‌​​​‌‌​‌‍‌​​‌‌‌​‌‍‌​​​‌‌‌​‍‌‌​‌​​​‌‍‌​​‌‌‌​​‍‌​​‌​​​‌‍‌​‌​​​‌‌‍‌​​‌‌​‌​‍‌​​​​‌‌‌‍‌​​​‌‌‌‌‍‌​‌​​​‌‌‍‌​​‌​‌‌‌‍‌​​‌​​​​‍‌​​‌​​‌​‍‌​​‌‌​‌​‍‌​​‌​‌​​‍‌​​‌​‌‌​‍‌​​​‌​‌‌‍‌​‌​​​​​‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​​‌‌​‌‍‌‌​​‌‌‌‌‍‌‌​‌​​​‌‍‌​​‌​‌‌‌‍‌​​​‌​‌‌‍‌​​‌​​‌​‍‌​​‌​​‌‌

首先你需要在服务器端安装miio,npm i miio -g,然后启动米家APP进行抓包,你需要知道需要抓包的智能产品的IP地址,开始抓包后在米家APP对智能产品进行操作,尽可能的覆盖每一个操作。为避免影响,比如空调伴侣,可以把空调插头先拔下来。
我在此使用WireShark进行操作,涉及的操作大同小异。
使用过滤条件:ip.addr == 智能产品的IP 如果有必要可以过滤udp包出来,然后File->Export Packet Dissections->As JSON,直接命名保存就可以。


然后我们得到了这样的JSON:

[
  {
    "_index": "packets-2020-03-25",
    "_type": "pcap_file",
    "_score": null,
    "_source": {
      "layers": {
        "frame": {
          "frame.interface_id": "0",
          "frame.interface_id_tree": {
            "frame.interface_name": "\\Device\\NPF_{2333333333333}"
          },
          "frame.encap_type": "1",
          "frame.time": "Mar 25, 2020 17:46:40.839379000 \344230b4",
          "frame.offset_shift": "0.000000000",
          "frame.time_epoch": "1585129600.839379000",
          "frame.time_delta": "0.051667000",
          "frame.time_delta_displayed": "0.000000000",
          "frame.time_relative": "5.302513000",
          "frame.number": "189",
          "frame.len": "74",
          "frame.cap_len": "74",
          "frame.marked": "0",
          "frame.ignored": "0",
          "frame.protocols": "eth:ethertype:ip:udp:data",
          "frame.coloring_rule.name": "UDP",
          "frame.coloring_rule.string": "udp"
        },
        "eth": {
          "eth.dst": "502343444",
          "eth.dst_tree": {
            "eth.dst_resolved": "50:e23424:72333:43",
            "eth.addr": "50:ec:23333:02343:43",
            "eth.addr_resolved": "502434243",
            "eth.lg": "0",
            "eth.ig": "0"
          },

然后执行 miio protocol json-dump JSON文件 --token 设备的TOKEN就可以自动解包,我们就会获得如下信息:

 ->  192.168.31.145 data= N/A
 <-  192.168.31.68 data= N/A
 ->  192.168.31.145 data= {"id":9552,"method":"get_ac_model","params":[]}
 <-  192.168.31.68 data= {"id":9552,"result":[192,9377,1],"exe_time":0}
 ->  192.168.31.145 data= N/A
 <-  192.168.31.68 data= N/A
 ->  192.168.31.145 data= {"id":9553,"method":"get_prop","params":["ac_mode","ac_state","load_power","en_nnlight","quick_cool_state","sleep_state","list_crc32"]}
 <-  192.168.31.68 data= {"id":9553,"result":[0,"P1_M0_T22_S3_D0",0.00,0,0,0,187445932],"exe_time":0}
 ->  192.168.31.145 data= {"id":9555,"method":"get_prop","params":["ac_mode","ac_state","load_power","en_nnlight","quick_cool_state","sleep_state","list_crc32"]}
 <-  192.168.31.68 data= {"id":9555,"result":[0,"P1_M0_T22_S3_D0",0.00,0,0,0,187445932],"exe_time":0}

像这样就是米家产品的交互过程,我们只需要知道它发送什么,返回什么就行,miio能让我们直接使用。

空调

空调我也不记得是什么时候买的了,不过好在小米杂货铺牛逼!
花65买了一个空调伴侣2,东西真的很小,大概长这样:


设计的很聪明,那一圈黑色的是全向红外发射器,然后自带功率计,这个价格很不错,详细的功能官网有,我不多做赘述。

米家APP张这个样子,功能很足,主要是码库是真的全,点赞!
然后自然是接入到HomeBridge让我等穷人和Siri多多接触。但是很遗憾,在npm搜了一下,只有三个是空调伴侣的插件,并且基本是3年前左右的代码,经过测试,完全不可用。
看了一下,原来是空调伴侣的控制码变了,原来是编码组合,现在是单个命令,感兴趣可以看看之前解码部分中的示例,那个是最新版本空调伴侣的抓包。
我也找来了一份旧版的抓包:

->  192.168.199.154 data= {"id":9460,"method":"get_model_and_state","params":[]                                                                             }
 <-  192.168.199.240 data= {"result":["010500978022222102","0111201E0280222221",                                                                             "1148"],"id":9460}
 ->  192.168.199.154 data= {"id":9461,"method":"get_model_and_state","params":[]                                                                             }
 <-  192.168.199.240 data= {"result":["010500978022222102","0111201E0280222221",                                                                             "1148"],"id":9461}
 ->  192.168.199.154 data= {"id":9462,"method":"send_cmd","params":["01802222210                                                                             1201e02",1]}
 <-  192.168.199.240 data= {"result":["ok"],"id":9462}
 ->  192.168.199.154 data= {"id":9463,"method":"get_model_and_state","params":[]                                                                             }
 <-  192.168.199.240 data= {"result":["010500978022222102","0101201E0280222221",                                                                             "1148"],"id":9463}
 ->  192.168.199.154 data= N/A
 <-  192.168.199.240 data= N/A
 ->  192.168.199.154 data= {"id":9464,"method":"get_model_and_state","params":[]                                                                             }
 <-  192.168.199.240 data= {"result":["010500978022222102","0101201E0280222221",                                                                             "1148"],"id":9464}
 ->  192.168.199.154 data= {"id":9465,"method":"send_cmd","params":["01802222211                                                                             1201e02",1]}
 <-  192.168.199.240 data= {"result":["ok"],"id":9465}

那怎么能放弃呢哈哈哈哈
故研究一下新的编码,和目前的程序,经过测试,新版编码中ac_state参数会直接返回空调目前的信息:

- P 电源状态,0 开 1关
- S 风速 0 自动 1 低 2 中 3 高
- D 扫风 1 关 0 开
- M 模式 0 制冷 1 制热 2 自动 3 通风 4 除湿
- T 温度 跟值

这些是可以通过操作米家APP整出来的,有了这些信息就可以修改代码了。
我使用的模块是homebridge-mi-acpartner,Github在 https://github.com/LASER-Yi/homebridge-mi-acpartner
我修改可用的代码没有公开,毕竟是为了用而糊的,没有从0开发也就很多地方考虑不到,怕出事...

修改

首先是获取信息的逻辑,原来的代码会请求get_model_and_state这里会直接返回所有信息,不过没有人类可读性,程序里面使用substr分割,不过我目前解读了ac_state的信息就直接替换即可,不过需要注意的是,ac_state里面0是开,1是关,需要修改相关代码,并且模式的数字也不一样,制冷和制热的代码有调换。
解决完信息获取,就是发送命令了。因为目前发送命令不像之前是一次性给状态,而是挨个设置,我推测是新版固件有计时器,会等待一段时间接收指令再一次发送,这个工作之前应该是交给APP的,现在给伴侣自己了。
所以为了发送命令,我在每次获取信息时创建一次信息备份,在发送命令时与备份信息比对,有变动就发送。(原来是更新状态后一次性执行函数,由函数组合状态成控制码发送)这样就解决了发送命令的问题。
现在已经完全可在家庭APP中使用,顺带带上玄学聪明的Siri:


效果很满意哈哈哈哈

温度计

买了一个温湿度计,挺便宜


不过它本身不支持Wifi连接,需要网关。正巧,我家之前在餐厅安装了米家吸顶灯,他是支持作为蓝牙网关使用的,中间隔了一堵墙和衣柜,居然还有信号,也是很厉害了。

不过我想弄到homebridge上就有点麻烦,npm上我搜索是只搜索到一个通过蓝牙连接的,可..我机器哪来的蓝牙,想了想应该还是得靠网关。
抓包无果..不太清楚客户端是怎么向网关要数据的,不过我还是尝试翻找了前人的代码,是这么写的:

//Update CurrentTemperature
        const p1 = this.outerSensor && this.platform.devices[this.deviceIndex].call('get_device_prop_exp', [
            [this.outerSensor, "temperature", "humidity"]
        ])

这个this.outerSensor是蓝牙设备的id,写了一个js试一下:

const miio = require('miio位置');
miio.device({ address: '网关ip', token: "网关token" })
    .then((retDevice) => {
        console.log('Connected to', retDevice);
        retDevice.call('get_device_prop_exp', [
            ["传感器ID", "temperature", "humidity"]
        ]).then((sendRet) => {
            console.log(sendRet);
            retDevice.destroy();
        })
    })
    .catch(err => console.log('Error to', err));

不过我家吸顶灯的网络位置真的是差到离谱啊啊啊!
不知道是不是应为贴天花板的原因,wifi奇差,丢包率能高到10%。当然在app里也不好使,我估计网关应该是自动找传感器要数据然后定时上报,不然不可能每次这么快就能读数据...(我说的)
基本上就是只有这两种结果:

Connected to MiioDevice {
  model=yeelink.light.ceiling22,
  types=miio,
  capabilities=
}
(node:29924) UnhandledPromiseRejectionWarning: Error: Call to device timed out
    at Timeout.retry [as _onTimeout] (/www/server/nodejs/lib/node_modules/miio/lib/network.js:487:18)
    at listOnTimeout (timers.js:327:15)
    at processTimers (timers.js:271:5)
(node:29924) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:29924) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Error to { Error: Could not connect to device, handshake timeout
    at Timeout.handshakeTimeout.setTimeout [as _onTimeout] (/www/server/nodejs/lib/node_modules/miio/lib/network.js:427:17)
    at listOnTimeout (timers.js:327:15)
    at processTimers (timers.js:271:5) code: 'timeout', device: null }

告辞,尝试无果,阶段性放弃。
等大佬反馈结果...

门禁

淘宝的wifi继电器,不到20块钱:


功能很强,支持接入米家,天猫精灵等智能家具产品。

找线

拆开家里的门禁


一共四根线,喊好友来当苦力在楼下试线hhhh
结果,左1红色线是在线信号线,黄色是门禁状态线,绿色和黑色搞不清楚,反正经过测试,少了哪一根线都不能完成开门(日
并且我家门禁需要:有人按我家门铃,摘下电话机,按下开锁按钮,才能操作开门...
开始是想最坏的设计也就是长通电话线,然后操作开门..
现在一来,估计只能换别的办法了,因为要同时保活电话机的响应能力,不然有没有人按门铃都不知道。
所以我打算找时间把板子卸下来,看能不能焊一下2333

好了,这个东西先鸽...我顺便想想继电器还能用在哪儿(哭)