米家智能插座逆向研究
Soybean收拾屋子发现了个闲置了几个月的米家智能插座,就这东西

好像是去年在米家看着好玩随手买的,冬天拿来定时关电热毯来着。心想下次用到估计还是冬天,不如研究下?随手网上搜了下,发现数码之家有人试着把自己的拆了,虽然拆坏了但是提供了个很重要的信息:主控是 ESP32.
瞬时激动,这不是个自制开源拖拉机的好机会?二话不说找撬棒拆

有先烈的指导就是好,好歹没拆坏。然后烙铁点开中间两个电极的焊点拆下主板,详细地分析了下元件。正面没啥东西,左侧主控,右侧非隔离式开关电源,顶部继电器和驱动电路。

主控是 ESP32-D0WD,双核240MHz,5x5小封装版本,市面上最常见的 ESP32。右侧有一个按钮两个状态指示灯(在按钮后面被挡住)。

反面东西比较多:a. BL0937 功率计芯片,配合正面的 2mΩ 电阻测量实时功率;b. EON25QH32,4MB SPI Flash,存储固件代码;c:5V to 3.3V LDO;d. BP2525F 非隔离降压芯片,内置 500V 6.5Ω MOSFET。存储芯片下面还有 UART 的焊盘方便刷固件真好。拿万用表戳戳戳简单逆向了一下设计:

相当简单,小学生都会做,感觉这个开源拖拉机很轻松嘛。开关在 GPIO0,按着按键上电就是 download mode,简直不能更适合开发 IoT 了。好,github 抓 Arduino-ESP32 环境下来,上传个 Hello world 试试先。

咦,开机报错,提示这是个单核 CPU? 明明确认过是 ESP32-D0WD的啊?检查下编译环境再看看:

还真是单核,看参数非常奇怪,这明显是改了 efuse。pip 下了整个 esptool 看看怎么回事

虽然大多数存储加密相关的 efuse 没被爆,但是明显还是有 efuse 被改过了,再看看 esp32 的开发文档

GG,小米还真把 disable_app_cpu 的 fuse 给爆了。ESP32 的 efuse 一旦写1就没法重置,除非换芯片。好吧,再去 github 找了个 ESP32-solo 的开发环境,毕竟 ESP32 本身是有单核版本的。

GG,还是不能启动,ESP32 的 bootloader 是闭源的,检测到 MAC CRC 错误直接拒绝启动。回头看了眼 efuse,MAC CRC 的确是错的,什么鬼,赶紧把备份的原厂固件刷回去……刷回去一切正常????等等,ESP 的芯片的 MAC 和对应的 CRC 都是芯片出厂时写入的,为什么 CRC 会不对????
*更新:注意 ESP32 芯片特写照第二行 XMC01ESP,这是只出现在小米 IoT 设备的料号,小米 IoT 里的 ESP32 疑似都是单核。至于 bootrom,看了下文档应该是厂商写在 SoC 芯片里面的(类似树莓派)
总结:
小米明显很清楚开源芯片有被用户拿来改着玩的风险,就找厂商定制了不能跑开源固件的芯片。efuse 中文档查不到的 chip revision,无法解释的 efuse block3,都从侧面证明了小米作为大陆 IoT 一哥,乐鑫(Espressif)这种小芯片厂真是什么都愿意做。从 ESP 社区的信息看来,ESP32 的双核设计在开源社区并不吃香,ESP 自己也有出出厂爆掉 fuse 的 ESP32-S0WD;同时,小米这 PCB 正面的大 LDO 空焊盘也说明小米内部设计很可能也是从双核开始的。个人感觉小米原厂固件使用时发热也并不低,谁知道小米都做了什么……
==============================================
这里开始是一周之后的事情了……
折腾了大半天只是发个文真是心不甘,决定去网上买 ESP32 换上。第一次买的时候脑抽买成了 ESP32-WROOM-32,拆开发现封装不对,又下单买的 ESP32-WROOM-32D (为什么不买 v3 芯片的 32E?哪有钱,你老母)

话说 Espressif 也够抠的,官方模块连着两个拆开都是不知什么山寨 Flash,记得以前玩 ESP8266 被山寨 Flash 的虚标寿命折腾得够呛,最后全换 Winbond……
*更新:查了下是 XM25QH32CHIG,来自武汉新芯(XMC)

焊上 Serial,检查了下 fuse,是空的没问题

Arduino 瞎糊的固件立即刷上,简单测试了下这 TSMC 40LP 的芯片双核 240Mhz 发热真的感人(担心供电不足提前换过 LDO,好像是 RT9013)。固件设定里频率降到 160MHz 还是热,后来又降到 80MHz。 实际使用 80MHz + SDK 默认的 WiFi 节能策略,性能足够发热也基本感觉不到。

实际测试的时候发现虽然万用表测出 IO19 连接驱动继电器的 SS8050,但 IO19 并不能改变继电器状态。写了个固件把 GPIO 一个一个测,发现实际控制继电器的是 IO26 和 IO27。继电器驱动旁边那个 SOT23-5 芯片是 SN74LVC1G80 ,TI 的 D 型触发器,IO26 是 D,IO27 是 CLK,IO27 每个上升沿 IO26 的状态被 buffer 反相之后输出,即使 ESP32 重启也不会影响继电器状态(怪不得 OTA 都不会影响插座开关)。而 IO19 是个 input,检测 buffer 的输出电平,用来检测重启之后继电器是不是开着。

至于功率计的 BL0937,CF 脚输出功率脉冲,CF1 和 SEL 配合输出电压和电流 RMS 倒是没啥兴趣。用 ESP32 的外部中断跟踪 CF 脚,然后 ticker 在固定时间数 CF 有多少个脉冲,倒是也不难(试过 ESP32 的 PNCT,不知为什么触发的时候 millis() 也被清零了很郁闷)。文档上并没有提供什么直接的输出和功率的换算公式,反而是建议客户对实际产品进行校准,手头并没有什么可靠的交流负载,加上 PF 肯定影响读数,倒是带来不少困难。

下面是个 demo 程序,业余水平,有兴趣的可以自己刷来玩玩。
https://pastebin.com/uFaJHAym 密码 hmHd2B7ngY
==============================================
生命不息折腾不止,都拆到这个地步了不来点硬件 mod 真是太无趣了。仔细分析了下板子,板上有几个可以自己随便玩的 GPIO 焊盘(注意 IO2/5/21 在上电时涉及状态检测,IO16/17 是 UART1 没有这些问题)

随便挑了个 GPIO 连接一个 NMOS 控制一个 IR LED,我们就得到了一个开源小米空调开关伴侣。一直很烦租房的垃圾空调待机功耗高得离谱,但是小米空调伴侣本身不带开关(空调压缩机工作时关开关容易烧了继电器,尤其是大功率空调),房东装的空调功率小用 10A 插头也不方便插空调伴侣,这下正好解决了问题。

插座开关和 IR 发射集成在一个程序里正好也避免了烧继电器。推荐 Arduino IRremote 这个库,收发 IR 都很方便,不过空调的码往往很长,读码最好用个性能别太差的单片机。
==============================================
另外拆了个米家空调伴侣2,一套东西换汤不换药。连供电都一样是 5V(TPA3) 非隔离电源 + 3.3V(TPA24) 降压,开关电源电容是 TEAPO 不是前面的杂牌,降压不是 LDO 而是 TPS560200 同步 buck。PCB 应该还是四层,不过没有沉金直接喷锡。

小米也在用山寨闪存啊,深圳芯天下

主控依然是小米定制版 ESP32。TPA1 是 UART,分别是 RX/TX/?/GND/?,开关不是 IO0,是 RGB LED 旁边的 TPA12。插脚焊点看起来比前面的 WiFi 开关焊得更死,就不再拆了。