Tim's blah blah blah

Cloudless Xiaomi Smart Plug

I want to measure the energy consumption of my devices at >10A, but in a cloudless, local Linux setup. Unfortunately, I didn’t get these plugs to work without the app, so I gave up and moved to a new hardware version instead.

I have a Xiamo (Mi) Smart Plug which when connected advertises itself as chuangmi-plug-hmi206_miapffff, with the last 4 characters linking to the BSSID of the device, in my case 46:23:7c:ab:ff:ff. The corresponding MAC address is 44:23:7c:ab:ff:ff.

Prepare static IP

We know the MAC, so we can inform the router to give it a static IP, making it easier to find back.

/ip dhcp-server lease add address=172.16.20.23 server=IoT_DHCP mac-address=44:23:7c:ab:8f:88 comment="mi-plug-8f88"
/ip dns static add address=172.16.20.23 name="mi-plug-8f88" place-before=0 ttl="01:00:00" comment="infrastructure"
/ip dns static add address=172.16.20.23 name="mi-plug-8f88.lan" place-before=0 ttl="01:00:00" comment="infrastructure"

Getting a token

With the app

Unfortunately you need the app to retrieve the token, meaning phoning home at least once is not preventable (let me know if you did!).

Without the app (fails - token changes after setup)

Getting token

First you need to have a security token, unique to each device. See here and here for more details

Gives: 21310020000000000e254488000003a3ffffffffffffffffffffffffffffffff

Check this works:

miiocli device --ip 192.168.4.1 --token ffffffffffffffffffffffffffffffff info
Model: chuangmi.plug.hmi206
Hardware version: ESP8266
Firmware version: 1.3.9_0005

Connecting to WiFi

We can either use the app here, or we can use the python-miio package to do this ourselves. I prefer the latter because I can prevent it phoning home, and can directly attach it to a non-internet controlled VLAN. Instead of using a VLAN, one could block access to Xiaomi’s servers, but I’m not sure which those are.

python-miio’s module has a method to connect to wifi, but this is not exposed to the CLI, so we use Python instead.

python3 -c "
import miio
device = miio.Device(ip='192.168.4.1', token='d3394594e386aefcb6042989d0aac160')
device.configure_wifi(ssid='Oberon-IoT', password='TJ3rNlLRjLVkPcVvvM29a')
"

This gives an error, but the command seems to have succeeded as it shows up on my router.

In [3]: device.configure_wifi(ssid='Oberon-IoT', password='TJ3rNlLRjLVkPcVvvM29a
   ...: ')                                                                      
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-fb4c60736e51> in <module>
----> 1 device.configure_wifi(ssid='Oberon-IoT', password='TJ3rNlLRjLVkPcVvvM29a')

~/Library/Python/3.7/lib/python/site-packages/miio/device.py in configure_wifi(self, ssid, password, uid, extra_params)
    247         params = {"ssid": ssid, "passwd": password, "uid": uid, **extra_params}
    248 
--> 249         return self.send("miIO.config_router", params)[0]
    250 
    251     def get_properties(

TypeError: 'int' object is not subscriptable

Once connected to WiFi, the token appears to be reset:

miiocli chuangmiplug --ip 172.16.20.100 --token d3394594e386aefcb6042989d0aac160 info
Error: Got checksum error which indicates use of an invalid token. Please check your token!

So I tried to got a new token, which failed and returned only 0s

echo -ne '\x21\x31\x00\x20\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' | nc  -u 172.16.20.100 54321 > xiaomi_answer && cat xiaomi_answer | xxd -p

Gives 21310020000000000e2544880000027000000000000000000000000000000000

Sources

#security #server #unix