merdok / homebridge-miot

Homebridge plugin for devices supporting the Xiaomi miot protocol
MIT License
394 stars 63 forks source link

Add support for Mi Smart Microwave Oven #48

Closed ahelpingchip closed 3 years ago

ahelpingchip commented 3 years ago

Hello,

Would it be possible to add support for the Mi Smart Microwave Oven?

Model: chunmi.microwave.n20l01 SKU: MWBLXE1ACM Link: https://www.mi.com/buy/detail?product_id=10330

When setup in the plugin, it seems to connect successfully and is seen as a switch, but there are no additional controls. Toggling the switch also does nothing, and it does not reflect the current state of the microwave.

I understand this isn't the usual type of device controlled by HomeKit, so I don't expect support for every feature, but being able to at least start or stop it would be nice.

If there is anything I can provide (logs, testing, etc.), I would be happy to help the best I can.

merdok commented 3 years ago

Hello, yes, that should be possible :) It looks pretty straight forward! I will try to include it in the next update and i will let you know when the update is ready.

merdok commented 3 years ago

Please try version 0.9.6. The device should now be supported.

ahelpingchip commented 3 years ago

Thank you very much for adding the device.

So I tried it out, and it is properly detected and classified. Unfortunately, it doesn't seem to reflect the state of the Microwave. Toggling the control also does nothing.

One interesting behavior is the first time i try to toggle the switch on in the Home app, it toggles itself back off. After that it just sticks to the set state (but does not affect the Microwave).

I tried enabling the Enable Deep Debug Log checkbox, but I didn't see any additional messages in the Homebridge UI log window.

Forcing to use MiCloud results in the polling timing out in the logs. There is no effect on the microwave operation.

I tried restarting Homebridge while the microwave is on, but it still initialized as off.

I know my setup and your plugin works, because I have a dmaker.fan.1c that responds perfectly fine.

If you have any additional diagnostic tips, please let me know, and thank you again for maintaining this plugin!

ahelpingchip commented 3 years ago

Sorry, didn't realize I have to run homebridge -D for debug logs to show up. 😅

Here are what I believe are the relevant logs:

Init

[6/28/2021, 8:17:12 PM] [miot] [Microwave] Got device configuration, initializing device with name: Microwave
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Found cached device information: chunmi.microwave.n20l01
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Device model known: chunmi.microwave.n20l01! Creating device!
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Creating miot device!
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Device Factory - Creating device instance by model: chunmi.microwave.n20l01!
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Initializing device properties
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Device properties: [
  "child_lock",
  "left_time",
  "status",
  "heat_level"
]
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Initializing device actions
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Device actions: [
  "pause",
  "start_cook",
  "cancel_cooking"
]
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Device successfully created! It is a Oven device!
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Initializing accessory!
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Accessory Factory - Creating Oven accessory for device Microwave!
[6/28/2021, 8:17:12 PM] [miot] [Microwave] Accessory successfully initialized!

Connect

[6/28/2021, 8:17:13 PM] [miot] [Microwave] Connected to device: chunmi.microwave.n20l01
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Setting up device!
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Fetching device info.
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Doing device specific setup
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Doing initial property fetch
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Splitting properties into chunks. Number of chunks: 1. Chunk size: 14
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Chunks:  [
 [
  "child_lock",
  "left_time",
  "status",
  "heat_level"
 ]
]
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Device setup finished! Device ready, you can now control your device!
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Device connected!
[6/28/2021, 8:17:13 PM] [miot] [Tall Fan] Successfully saved device info!
[6/28/2021, 8:17:13 PM] [miot] [Microwave] Successfully saved device info!
[6/28/2021, 8:17:13 PM] [miot] [Tall Fan] Got device info! Device firmware: 2.0.4
[6/28/2021, 8:17:14 PM] [miot] [Microwave] Got device info! Device firmware: 2.0.7
[6/28/2021, 8:17:14 PM] [miot] [Microwave] Full device info:
 {
  "life": REDACTED,
  "uid": REDACTED,
  "model": "chunmi.microwave.n20l01",
  "token": "REDACTED",
  "ipflag": 1,
  "fw_ver": "2.0.7",
  "mcu_fw_ver": "0010",
  "miio_ver": "0.0.8",
  "hw_ver": "esp8266",
  "mmfree": 25572,
  "mac": "REDACTED",
  "wifi_fw_ver": "2709610",
  "ap": {
    "ssid": "REDACTED",
    "bssid": "REDACTED",
    "rssi": -39,
    "primary": 1
  },
  "netif": {
    "localIp": "REDACTED",
    "mask": "REDACTED",
    "gw": "REDACTED"
  },
  "config_type": "app"
}

Response Parsing

[6/28/2021, 8:17:14 PM] [miot] [Microwave] Error while parsing response from device for property child_lock. Response: {"did":"REDACTED","siid":3,"piid":1,"code":-4004}
[6/28/2021, 8:17:14 PM] [miot] [Microwave] Error while parsing response from device for property left_time. Response: {"did":"REDACTED","siid":2,"piid":1,"code":-4004}
[6/28/2021, 8:17:14 PM] [miot] [Microwave] Error while parsing response from device for property status. Response: {"did":"REDACTED","siid":2,"piid":3,"code":-4004}
[6/28/2021, 8:17:14 PM] [miot] [Microwave] Error while parsing response from device for property heat_level. Response: {"did":"REDACTED","siid":2,"piid":3,"code":-4004}
[6/28/2021, 8:17:14 PM] [miot] [Microwave] Got initial device properties:
 [
  {
    "name": "child_lock",
    "value": false
  },
  {
    "name": "left_time",
    "value": 0
  },
  {
    "name": "status",
    "value": 0
  },
  {
    "name": "heat_level",
    "value": 0
  }
]

The error above repeats several times, probably with every time the polling occurs.

Control

When I try to toggle the Microwave state in the Home app, this appears:

[6/28/2021, 8:22:11 PM] [miot] [Microwave] Send action! RAW: {"did":"REDACTED","siid":2,"aiid":2,"in":[]}
[6/28/2021, 8:22:12 PM] [miot] [Microwave] Error while executing action start_cook with params ! Error: user ack invalid

and then the switch state resets to Off.

Let me know if I can provide anything else!

merdok commented 3 years ago

Hi,

it seems that your device does not accept the commands, this can have multiple reasons. First off, if the device is indeed a miot device then you do not have to use micloud. If the device does not accept both MiCloud and local commands then there is something wrong.

Make sure your token and did are correct. If you have did specified in your config then remove it, it will be fetched automatically when needed.

Also I see a error message: "user ack invalid" not sure what that means but maybe something is disabled to prevent remote controlling?

ahelpingchip commented 3 years ago

The token and did are indeed correct, I checked several times (and the auto-fetch got the same token).

I did a little bit of digging, and tried using python-miio to talk to the microwave directly. I always got a response of "code":-4004 / (Other internal errors), and read somewhere that if you consistently get this, it probably means the device doesn't support LAN communication and only supports cloud.

Additionally, on the app, it showed on the status that it was using "remote communication" despite my phone being on the same network:

image

So I tried forcing MiCloud and checking the debug log again (sorry I didn't check this the first time) —

[6/28/2021, 10:48:34 PM] [miot] [Microwave] MiCloud - Login step 1
[6/28/2021, 10:48:34 PM] [miot] [Microwave] MiCloud - Login step 1 result: OK - REDACTED
[6/28/2021, 10:48:34 PM] [miot] [Microwave] Got device info! Device firmware: 2.0.7
[6/28/2021, 10:48:34 PM] [miot] [Microwave] Full device info: REDACTED
... (omitted) 
[6/28/2021, 10:48:35 PM] [miot] [Microwave] MiCloud - Login step 3 result: OK - ok

now the parsing error looks different:

[6/28/2021, 10:48:40 PM] [miot] [Microwave] MiCloud request https://api.io.mi.com/app/miotspec/prop/get - {"_nonce":"REDACTED","data":"{\"params\":[{\"did\":\"REDACTED\",\"siid\":3,\"piid\":1},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":1},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":3},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":3}]}","signature":"REDACTED"}
[6/28/2021, 10:48:42 PM] [miot] [Microwave] Error while parsing response from device for property status. Response: {"did":"REDACTED","siid":2,"piid":3,"code":-706012014}
[6/28/2021, 10:48:42 PM] [miot] [Microwave] Error while parsing response from device for property heat_level. Response: {"did":"REDACTED","siid":2,"piid":3,"code":-706012014}

child_lock and left_time now seem correctly parsed (which we'll see later). status and heat_level have a strange response, however. I tried looking it up on MIOT docs and couldn't make sense of it.

trying to control was also interesting:

[6/28/2021, 10:50:33 PM] [miot] [Microwave] Send action! RAW: {"did":"REDACTED","siid":2,"aiid":2,"in":[]}
[6/28/2021, 10:50:33 PM] [miot] [Microwave] MiCloud request https://api.io.mi.com/app/miotspec/action - {"_nonce":"REDACTED","data":"{\"params\":{\"did\":\"REDACTED\",\"siid\":2,\"aiid\":2,\"in\":[]}}","signature":"REDACTED"}
... (omitted)
[6/28/2021, 10:50:35 PM] [miot] [Microwave] Error while executing action start_cook with params ! Error: Invalid response. Response: {"did":"REDACTED","miid":0,"siid":2,"aiid":2,"code":-704040999,"exe_time":0,"net_cost":0,"ot_cost":0,"otlocalts":0,"oa_cost":0,"_oa_rpc_cost":0}

now it's a different response, and this error code (704040999) we can actually parse according to MIOT docs: (client issue, feature is offline). not sure why, though.

when i tried starting the microwave manually, the "light sensor" actually updated with the time remaining in seconds, which was really cool to see! the power status remained "off", however. (which makes sense as it's not being parsed correctly)

image

[6/28/2021, 10:51:15 PM] [miot] [Microwave] MiCloud request https://api.io.mi.com/app/miotspec/prop/get - {"_nonce":"REDACTED","data":"{\"params\":[{\"did\":\"REDACTED\",\"siid\":3,\"piid\":1},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":1},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":3},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":3}]}","signature":"REDACTED"}
[6/28/2021, 10:51:16 PM] [miot] [Microwave] Error while parsing response from device for property status. Response: {"did":"REDACTED","siid":2,"piid":3,"code":-706012014}
[6/28/2021, 10:51:16 PM] [miot] [Microwave] Error while parsing response from device for property heat_level. Response: {"did":"REDACTED","siid":2,"piid":3,"code":-706012014}
[6/28/2021, 10:51:16 PM] [miot] [Microwave] Device properties updated:
 [
  {
    "name": "child_lock",
    "value": false
  },
  {
    "name": "left_time",
    "value": 97
  },
  {
    "name": "status",
    "value": 0
  },
  {
    "name": "heat_level",
    "value": 0
  }
]

trying to toggle the switch while the microwave is active still results in the same error, though.

[6/28/2021, 10:51:27 PM] [miot] [Microwave] Send action! RAW: {"did":"REDACTED","siid":2,"aiid":2,"in":[]}
[6/28/2021, 10:51:27 PM] [miot] [Microwave] MiCloud request https://api.io.mi.com/app/miotspec/action - {"_nonce":"REDACTED","data":"{\"params\":{\"did\":\"REDACTED\",\"siid\":2,\"aiid\":2,\"in\":[]}}","signature":"REDACTED"}
[6/28/2021, 10:51:29 PM] [miot] [Microwave] Error while executing action start_cook with params ! Error: Invalid response. Response: {"did":"REDACTED","miid":0,"siid":2,"aiid":2,"code":-704040999,"exe_time":0,"net_cost":0,"ot_cost":0,"otlocalts":0,"oa_cost":0,"_oa_rpc_cost":0}

one thing i noticed with the request being made:

https://api.io.mi.com/app/miotspec/prop/get - {"_nonce":"REDACTED","data":"{\"params\":[{\"did\":\"REDACTED\",\"siid\":3,\"piid\":1},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":1},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":3},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":3}]}","signature":"REDACTED"}

why does the data payload have two copies of the 3rd and 4th object? maybe there's a bug in the constructor of the request.

{
  "params": [
    {
      "did": "REDACTED",
      "siid": 3,
      "piid": 1
    },
    {
      "did": "REDACTED",
      "siid": 2,
      "piid": 1
    },
    {
      "did": "REDACTED",
      "siid": 2,
      "piid": 3
    },
    {
      "did": "REDACTED",
      "siid": 2,
      "piid": 3
    }
  ]
}

so that's where I am right now. hope this is helpful, and if there's anything else I can provide, please let me know!

merdok commented 3 years ago

Ok, so we at least right now know that the device requires MiCloud, that is something. I will mark the device as MiCloud required.

I also see that i wrongly mapped the status property 😓 It should be siid: 2 piid: 2, that is why you see that the third and fourth are the same... I need to fix that, you can adjust that manually by yourself in the device file you want to test it out.

What i do not understand is why the actions does not work (pause, start, stop). They are mapped according to the xiaomi spec so i am not really sure what the issue is there... I do not even know what the error code means since i did not find any error code mapping... Maybe you could somehow sniff the traffic between the device and the MiCloud when using the xiaomi home app to check how the request look like which are being made there?

I wonder what the client error, feature is offline means when you try to start the oven...

EDIT: This miot docs which you posted seems to be some really good info, unfortunately it is only in Chinese, do you know if this is also available in English?

ahelpingchip commented 3 years ago

I edited my local copy and it works! I've made a pull request here together with the MiCloud requirement: https://github.com/merdok/homebridge-miot/pull/50

What i do not understand is why the actions does not work (pause, start, stop). They are mapped according to the xiaomi spec so i am not really sure what the issue is there...

Same. 🤷 I noticed one similar issue here in the Home Assistant project for MIOT integration (in Chinese): https://github.com/al-one/hass-xiaomi-miot/issues/49 — unfortunately they didn't come to a resolution. Maybe this device is just problematic?

One interesting thing I observed is I mapped all the available actions (pause, start_cook, cancel_cooking) to buttons, and pause actually works to stop the microwave, but I have to click it twice. I cannot start it again, however, whether with pause or start_cook.

Maybe you could somehow sniff the traffic between the device and the MiCloud when using the xiaomi home app to check how the request look like which are being made there?

I was thinking of doing just this! Unfortunately I am quite busy with work and life at the moment so I'm not sure if I can commit to investigating this within a prompt timeline. You can hold off on my pull request above if you would prefer a more "complete" fix/patch.

EDIT: This miot docs which you posted seems to be some really good info, unfortunately it is only in Chinese, do you know if this is also available in English?

I do not, sorry. That said I am just relying on Google Translate and it seems good enough? Hahaha 😅

merdok commented 3 years ago

Thanks for the pull request. I will already merge it so at least we have those minor issues fixed.

So i understand that now the status and heat level are correctly retrieved from the device? Also the switch in the home app should not properly update the status (on or off), is that the case?

So we now have to find out why the actions does not work... As i understand correctly you also use the xiaomi home app and you are able to start and stop the oven from there, right? That would mean that it should be possible to fire those actions, just maybe there is something missing for that specific device and i see the only way would be if you sniff the traffic with wireshark or some other tool so that we can see what request is the xiaomi home app sending.

One interesting thing I observed is I mapped all the available actions (pause, start_cook, cancel_cooking) to buttons, and pause actually works to stop the microwave, but I have to click it twice. I cannot start it again, however, whether with pause or start_cook.

Oh, you mean using home with my plugin? What does the request and response look like in that case?

Ah, i thought that you can read the Chinese 😅 I also now used the translator and could read most of it, it seems that there a different api endpoint is described but i think that in the end it should not matter...

ahelpingchip commented 3 years ago

So i understand that now the status and heat level are correctly retrieved from the device? Also the switch in the home app should not properly update the status (on or off), is that the case?

Yes, so basically — if I start/stop the microwave, the button in the Home app changes to reflect the state. But the other way doesn't work: clicking the button does not affect the microwave.

Oh, you mean using home with my plugin? What does the request and response look like in that case?

[6/29/2021, 3:58:13 PM] [miot] [Microwave] MiCloud request https://api.io.mi.com/app/miotspec/prop/get - {"_nonce":"Xrz/REDACTED+","data":"{\"params\":[{\"did\":\"REDACTED\",\"siid\":3,\"piid\":1},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":1},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":2},{\"did\":\"REDACTED\",\"siid\":2,\"piid\":3}]}","signature":"REDACTED+uF9/pgAmKqEy3w="}
[6/29/2021, 3:58:13 PM] [miot] [Microwave] Send action! RAW: {"did":"REDACTED","siid":2,"aiid":2,"in":[]}
[6/29/2021, 3:58:13 PM] [miot] [Microwave] MiCloud request https://api.io.mi.com/app/miotspec/action - {"_nonce":"REDACTED+","data":"{\"params\":{\"did\":\"REDACTED\",\"siid\":2,\"aiid\":2,\"in\":[]}}","signature":"iEidRSuq0/REDACTED+84="}
[6/29/2021, 3:58:15 PM] [miot] [Microwave] Send action! RAW: {"did":"REDACTED","siid":2,"aiid":1,"in":[]}
[6/29/2021, 3:58:15 PM] [miot] [Microwave] MiCloud request https://api.io.mi.com/app/miotspec/action - {"_nonce":"REDACTED+","data":"{\"params\":{\"did\":\"REDACTED\",\"siid\":2,\"aiid\":1,\"in\":[]}}","signature":"REDACTED/svo3VP8Dq98="}
[6/29/2021, 3:58:15 PM] [miot] [Microwave] Error while executing action start_cook with params ! Error: Invalid response. Response: {"did":"REDACTED","miid":0,"siid":2,"aiid":2,"code":-704040999,"exe_time":0,"net_cost":0,"ot_cost":0,"otlocalts":0,"oa_cost":0,"_oa_rpc_cost":0}
[6/29/2021, 3:58:16 PM] [miot] [Microwave] Error while executing action start_cook with params ! Error: Invalid response. Response: {"did":"REDACTED","miid":0,"siid":2,"aiid":2,"code":-704040999,"exe_time":0,"net_cost":0,"ot_cost":0,"otlocalts":0,"oa_cost":0,"_oa_rpc_cost":0}
[6/29/2021, 3:58:16 PM] [miot] [Tall Fan] Splitting properties into chunks. Number of chunks: 1. Chunk size: 14
[6/29/2021, 3:58:17 PM] [miot] [Microwave] Successfully executed action pause with params ! Result: {"did":"REDACTED","miid":0,"siid":2,"aiid":1,"code":0,"exe_time":0,"net_cost":0,"ot_cost":0,"otlocalts":0,"oa_cost":0,"_oa_rpc_cost":0}

Turns out I only need to tap "pause" once — it just takes a little while for the microwave to reflect. (Probably because of cloud latency.)

As i understand correctly you also use the xiaomi home app and you are able to start and stop the oven from there, right? That would mean that it should be possible to fire those actions, just maybe there is something missing for that specific device and i see the only way would be if you sniff the traffic with wireshark or some other tool so that we can see what request is the xiaomi home app sending.

So I proxied the requests from my phone using Charles with SSL introspection.

It makes a number of requests which are form-encoded so i'll post screenshots for readability.

Most of them are obfuscated/encrypted(?)

image

Some aren't, but those aren't the interesting or relevant ones.

image

Now for the relevant part:

It seems like most of the control and status messages for the devices pass thru this endpoint:

https://api.io.mi.com/app/home/rpc/<Device ID>

with a payload that looks like this:

image

where id counts up from 1 with every request since the start of the app session.

the response looks like this:

{
    "code": 0,
    "exe_time": 90,
    "id": 2,
    "net_cost": 303,
    "ot_cost": 3316,
    "otlocalts": 1624954170191675,
    "result": [12, 0, "00000001", 0, 0, 0, 0, 5, 0, "00", "00", "000a", "000000000000000000000000000000000000000000000000000000000000000000000000", 0, 0, 0]
}

Child Lock

here's me changing the child lock status. (nonce + signature omitted)

request:

{"id":5,"method":"set_lock","accessKey":"REDACTED","params":["050103"]

response:

{
    "code": 0,
    "exe_time": 220,
    "id": 5,
    "net_cost": 488,
    "ot_cost": 5582,
    "otlocalts": 1624954182718985,
    "result": [1]
}

request:

{"id":8,"method":"set_unlock","accessKey":"REDACTED","params":["050103"]}

response:

{
    "code": 0,
    "exe_time": 220,
    "id": 8,
    "otlocalts": 1624954196242238,
    "result": [1]
}

Start / stop

Here's me starting the stopping the microwave. Interestingly, the response says failed even though the microwave started and stopped successfully. (and the app transitioned to/from the "active cooking"/timer UI as well)

request:

{"id":23,"method":"set_start","accessKey":"REDACTED","params":["0501000000000181000e3c00000a0000000000000400000000000000000007d0006400640064d2b414140000d2b414100000d2b4140a0000d2b414060000d2b414040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000059ab"]}

the large body for params probably includes my desired power level and timer duration. i'm not sure how this is encoded.

response:

{
    "code": -5000,
    "error": {
        "code": -5000,
        "message": "start_failed"
    },
    "exe_time": 150,
    "id": 23,
    "message": "start_failed",
    "net_cost": 320,
    "ot_cost": 2729,
    "otlocalts": 1624954282411601
}

_-- in between were various messages with the get_prop method, presumably updating the time and other status?_

request:

{"id":26,"method":"cancel_cooking","accessKey":"REDACTED","params":["050103"]}

response:

{
    "code": -5000,
    "error": {
        "code": -5000,
        "message": "cancel_failed"
    },
    "exe_time": 270,
    "id": 26,
    "message": "cancel_failed",
    "net_cost": 290,
    "ot_cost": 3113,
    "otlocalts": 1624954295146608
}

I'm not sure I'll have so much time to investigate this further at this point, but I'll try to drop more findings here as I encounter them.

merdok commented 3 years ago

Nice 👍 That is some good information and findings. So what I can tell from this is that the xiaomi app itself uses the miio commands endpoint "home/rpc/did", which confirms that it is indeed an device which uses the older miio protocol.

The bad news about this is that this does not help us much, since my plugin is made to use the miot protocol... for that reason we need to stick to the miot protocol. As far as I understand everything for the oven works right now with the plugin, except start and cancel cooking, right?

What is also interesting that the xiaomi App start/cancel requests fail but the oven still starts/stops. Maybe the mapping in the xiaomi specs are wrong? Could you maybe try to change the action if for start cooking, like increasing a few times and see how the cloud responds?