python-miio
python-miio copied to clipboard
A guide how to communicate with MIOT devices -> Plugin?
I was able to get the data from an unsupported device (HeatCold Heating Floor Controller - cubee.airrtc.th123e) and to set the required parameters from a Terminal on Mac. Can someone help with converting this into a working plugin for Home Assistant and then HomeKit?
Here is my step-by-step guide for all newcomers who want to play with their devices:
-
Install python-miio on Mac. Enter this in Terminal app on Mac:
sudo pip3 install python-miio
-
Get model name of your device ("cubee.airrtc.th123e" in my case), IP address (like "192.168.1.152") and Token (like "b110204d86732b019d3d6axxxxb9ad3a"). The easiest way for me was to use this integration for Home Assistant - https://github.com/AlexxIT/XiaomiGateway3
-
Check that your device is accessible by entering the following in a Terminal app on Mac:
miiocli -d device --ip 192.168.1.152 --token b110204d86732b019d3d6axxxxb9ad3a info
Note 1: You need to put your device's IP and Token here. Note 2: "-d" is for Debug and can be excluded for a shorter output. In my case I got something like this: "Model: cubee.airrtc.th123e Hardware version: esp32 Firmware version: 2.1.7" Device is responding! Nice! Going to the next step. -
Find your device in the list of MIOT devices: http://miot-spec.org/miot-spec-v2/instances?status=all I searched for "cubee.airrtc.th123e" and found the following line:
{"status":"released","model":"cubee.airrtc.th123e","version":1,"type":**"urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e:1"**},
-
Copy the "urn" part after this url: https://miot-spec.org/miot-spec-v2/instance?type= in my case: https://miot-spec.org/miot-spec-v2/instance?type=urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e:1 You will see an unformatted JSON text of the device's specs
-
Put this JSON text to any JSON Formatter to make it more human readable. For example here: http://json.parser.online.fr/
-
You will see a hierarchy with a list of Services (later used as "siid"), each of which has a list of Properties ("piid"):
-
You need to find some property in the list which you want to get from the device and which is readable (in my case siid 1 didn't return the value, so you may want to check other services like "siid 2" and other properties) I wanted to receive the current "Target Temperature". This is "siid":2 and "piid":5:
-
Try to get a response from your device by entering this in Terminal:
miiocli -d device --ip 192.168.1.152 --token b110204d86732b019d3d6axxxxb9ad3a raw_command get_properties "[{'did': 'MYDID', 'siid': 2, 'piid': 5 }]"
Note: replace IP, token, siid and piid with your values I got a response the last line of which was: "[{'did': 'MYDID', 'siid': 2, 'piid': 5, 'code': 0, 'value': 29}]" This was an actual Target Temperature set on my device: 29 degrees celsius! It works! -
Now let's try to change some properties. Make sure it has a "write" access (look at the the field "access" for a particular piid in the JSON file mentioned above). I want to set 28 degrees and here is the command for that:
miiocli -d device --ip 192.168.1.152 --token b110204d86732b019d3d6axxxxb9ad3a raw_command set_properties "[{'did': 'MYDID', 'siid': 2, 'piid': 5, 'value' : 28 }]"
The new temperature was set on my device and was also updated in MiHome app! -
Check that the value / setting was set on an actual device and redo step 9 to confirm that the new value is successfully returned by the device.
That's all! Now you can retrieve and set values from a Terminal. This was my part. I would be glad if someone describes how to further make a Home Assistant plugin based on this knowledge :)
Originally posted by @SpeedFire0 in https://github.com/rytilahti/python-miio/issues/543#issuecomment-755767331
What is 'did': 'MYDID'
though?
What is
'did': 'MYDID'
though?
I'm not sure, to be honest. In my case the word "MYDID" did the job (so I haven't replaced it with any other word or number).
The 'did' (device id, most likely) is something that gets returned back by the device for responses, but you can put any value to it. The current miot implementations are using that as a key to a user-friendly property name.
Thanks for the guide, the current documentation on that front is definitely lacking. There is a tool under devtools
directory, which allows downloading the relevant json file based on the URN (that could be improved to allow downloading by the model):
$ python miottemplate.py download urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e:1
Saving data to urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e:1.json
And printing out the information:
$ python miottemplate.py print urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e:1.json
Device 'urn:miot-spec-v2:device:thermostat:0000A031:cubee-th123e:1': Thermostat with 3 services
* Service siid 1: (Device Information): 4 props, 0 actions
## Properties ##
siid 1: piid: 1 (Device Manufacturer): (string, unit: None) (acc: ['read'])
siid 1: piid: 2 (Device Model): (string, unit: None) (acc: ['read'])
siid 1: piid: 3 (Device Serial Number): (string, unit: None) (acc: ['read'])
siid 1: piid: 4 (Current Firmware Version): (string, unit: None) (acc: ['read'])
* Service siid 2: (Thermostat): 6 props, 0 actions
## Properties ##
siid 2: piid: 1 (Switch Status): (bool, unit: None) (acc: ['read', 'write', 'notify'])
siid 2: piid: 2 (Status): (uint8, unit: None) (acc: ['read', 'notify'])
{'value': 0, 'description': '未加热状态'}
{'value': 1, 'description': '加热中'}
siid 2: piid: 3 (Device Fault): (uint8, unit: None) (acc: ['read', 'notify'])
{'value': 1, 'description': '传感器错误'}
{'value': 0, 'description': '无错误'}
{'value': 2, 'description': '高温保护'}
{'value': 3, 'description': '低温保护'}
siid 2: piid: 4 (Mode): (uint8, unit: none) (acc: ['read', 'write', 'notify'])
{'value': 0, 'description': 'Manual'}
{'value': 1, 'description': 'Home'}
{'value': 2, 'description': 'Away'}
{'value': 3, 'description': 'Smart'}
{'value': 4, 'description': 'Sleep'}
siid 2: piid: 5 (Target Temperature): (float, unit: celsius) (acc: ['read', 'write', 'notify'])
Range: [0, 90, 1]
siid 2: piid: 7 (Temperature): (float, unit: celsius) (acc: ['read', 'notify'])
Range: [-30, 100, 1]
* Service siid 4: (): 7 props, 0 actions
## Properties ##
siid 4: piid: 1 (): (bool, unit: None) (acc: ['read', 'write', 'notify'])
siid 4: piid: 2 (): (uint8, unit: None) (acc: ['read', 'notify', 'write'])
{'value': 0, 'description': '内置传感器'}
{'value': 1, 'description': '外置传感器'}
{'value': 2, 'description': '内外置传感器'}
siid 4: piid: 3 (): (uint8, unit: None) (acc: ['read', 'notify', 'write'])
Range: [1, 9, 1]
siid 4: piid: 4 (): (int8, unit: None) (acc: ['read', 'notify', 'write'])
Range: [-9, 9, 1]
siid 4: piid: 5 (): (int8, unit: None) (acc: ['read', 'notify'])
Range: [0, 100, 1]
siid 4: piid: 6 (): (uint8, unit: celsius) (acc: ['read', 'notify'])
Range: [35, 90, 1]
siid 4: piid: 7 (): (uint8, unit: celsius) (acc: ['read', 'notify'])
Range: [0, 30, 1]
I just modified the miottemplate to download the mapping file, if not available, to allow using download
with only the model as input. python miottemplate.py download <some model>
followed by python miottemplate.py print <json file>
for the downloaded json file should do the trick and be much simpler than it used to be.
If you don't mind, please check out the linked PR and try miiocli miotdevice get_property_by
and set_property_by
commands. I cannot test these as I have no test devices anymore.
get_property_by [siid] [piid]
and set_property_by [siid] [piid] [value]
with an optional [value_type]
(if the value needs to be other than a string).
@rytilahti get_property_by
works but I'm getting only 'hello'
string for everything under siid: 1
(Device Information):
$ miiocli miotdevice ... get_property_by 1 1
[{'did': '1-1', 'siid': 1, 'piid': 1, 'code': 0, 'value': 'hello'}]
set_property_by
sort of works but miiocli
sends wrong payload.
I tested with my pet waterer from #897.
When I turn indicator light on using set_property_by 4 1 'True' 'bool'
it turns on. It was off before I started.
If I turn it off using set_property_by 4 1 'False' 'bool'
it doesn't turn off.
When I run miiocli -d miotdevice ... set_property_by 4 1 'False' 'bool'
(while indicator light is on):
DEBUG:miio.miioprotocol:192.168.xxx.xxx:xxxxx >>: {'id': 1, 'method': 'set_properties', 'params': [{'did': 'set-4-1', 'siid': 4, 'piid': 1, 'value': True}]}
If I use set_property_by 4 1 'True' 'bool'
while indicator is on it doesn't turn it off.
Also there's number of "actions" in miot specs but I don't see how can I use them using miiocli
.
I have found and modified an example of a simple switch for Home Assistant. Here is the repo for an example in the guide: https://github.com/SpeedFire0/xiaomi_miot_heatcold Device is successfully switches on/off both in Home Assistant and HomeKit.
@rytilahti
get_property_by
works but I'm getting only'hello'
string for everything undersiid: 1
(Device Information):
Great! Yes, the siid1 (which is there for all miot devices, miot_info
command is still missing to output that) was also hard-coded on the test device I had at one point (https://github.com/rytilahti/python-miio/pull/672#issuecomment-615930765). That could be the sdk default and vendors haven't changed it for a reason or another.
If I turn it off using
set_property_by 4 1 'False' 'bool'
it doesn't turn off.
Try set_property_by 4 1 False bool
or set_property_by 4 1 0 bool
. I think it detects 'False'
as a string -> which is true when converted to bool.
I'll try add a way to call an action, thanks for noticing & testing!
@SpeedFire0 I think xiaomi_raw from @syssi is the proper way for quick, stop-gap solutions. That is, as soon as it gets miot support. You can follow this issue for that: https://github.com/syssi/xiaomi_raw/issues/6
The problem with custom components is that they deem to break when the official integration gets updated to use APIs that are not available for the python-miio versions pinned by custom integrations. In turn, the less custom components one has, the easier it is to do the manual adjustments to make it work again.
Try
set_property_by 4 1 False bool
orset_property_by 4 1 0 bool
. I think it detects'False'
as a string -> which is true when converted to bool.
Both didn't work
Ah, indeed. bool('0')
equates to True (as the input itself is a string). I'll try to find a solution for that, at worst bools will need to be inputed as 0/1 in the command line, and it'll be converted using bool(int(value))
.
@rytilahti there's method for that in distutils
I tried to get commands from a Xiaomi camera and it returned the following error:
Error: {'code': -10000, 'message': 'user undefined error'}
Probably this is some sort of protection from unauthorised access. Is it possible to define my user credentials (in MiHome app?) and send it to the device?
which camera? most cams don't use miot, only a few latest models.
which camera? most cams don't use miot, only a few latest models. Outdoor camera - "chuangmi.camera.ipc020" Indoor camera - "chuangmi.camera.ipc021"
these models talk miio only (unless something changed in latest fw). all devices have miot specs, but older devices never really deploy it in firmware and they still use human readable miio.
these models talk miio only (unless something changed in latest fw). all devices have miot specs, but older devices never really deploy it in firmware and they still use human readable miio.
I didn't find any way how to control them (even the simplest function like on/off). Is it possible?
This is an example of "info" response:
DEBUG:miio.miioprotocol:192.168.1.92:54321 >>: {'id': 1, 'method': 'miIO.info', 'params': []} DEBUG:miio.miioprotocol:192.168.1.92:54321 (ts: 1970-02-20 21:26:43, id: 1) << {'id': 1, 'result': {'life': 4397203, 'model': 'chuangmi.camera.ipc016', 'token': '396a67493638696a6552324f6356664e', 'ipflag': 1, 'miio_ver': '0.0.8', 'mac': '78:8B:2A:9F:EA:02', 'fw_ver': '16.4.0.9_0213', 'hw_ver': 'Linux', 'bootloader_ver': 'a97a650aea304f0d2b08da11cab16439', 'miio_client_ver': '4.0.10', 'VmPeak': 6820, 'VmRSS': 1588, 'MemFree': 1988, 'ap': {'ssid': 'PointerDacha', 'bssid': '52:FF:20:5E:75:C0', 'rssi': '-53', 'freq': 2457}, 'netif': {'localIp': '192.168.1.92', 'mask': '255.255.255.0', 'gw': '192.168.1.1'}, 'miio_times': [4397197, 7, 5486, 4391660]}, 'exe_time': 2} Model: chuangmi.camera.ipc016 Hardware version: Linux Firmware version: 16.4.0.9_0213
https://github.com/rytilahti/python-miio/blob/master/miio/chuangmi_camera.py
https://github.com/rytilahti/python-miio/blob/master/miio/chuangmi_camera.py
OK. But I feel myself as a complete idiot because I can't figure out how to use this in Home Assistant :( Maybe someone will help with a guide in the future.
@rytilahti there's method for that in distutils
It's weird to add an import for this single function, but I have now fixed this by simply checking of the value is either 'true' or '1'. Feel free to test it again, the newest commit also adds the ability to call actions using call_action_by
.
@rytilahti it works now. As I mentioned earlier, I use "Indicator light" property to test and set_property_by 4 1 false bool
now works fine for this case.
call_action_by 6 1
also works fo me (it is an action to reset time-to-clean).
Thanks for testing @gudvinr! I'll write some tests for the new functionality when I'll find some free time again, and then it can be merged.
@SpeedFire0 For the plugin you may try https://github.com/ha0y/xiaomi_miot_raw. The author of xiaomi_raw didn't implement it for so long time, so I did it myself.
@ha0y You could help to improve xiaomi_raw
. I'm happy about any contribution.
i got this: DEBUG:miio.miioprotocol:192.168.154.160:54321 (ts: 1970-01-01 01:40:03, id: 1) << {'id': 1, 'error': {'code': -9999, 'message': 'user ack timeout'}, 'exe_time': 4010} DEBUG:miio.click_common:Exception: {'code': -9999, 'message': 'user ack timeout'} ... File "c:\users\lenovo\appdata\local\programs\python\python37\lib\site-packages\miio\miioprotocol.py", line 214, in send self._handle_error(payload["error"]) File "c:\users\lenovo\appdata\local\programs\python\python37\lib\site-packages\miio\miioprotocol.py", line 274, in _handle_error raise DeviceError(error) miio.exceptions.DeviceError: {'code': -9999, 'message': 'user ack timeout'} Error: {'code': -9999, 'message': 'user ack timeout'} i dont know how to solve it,and i run the miioprotocal.py,the error is No module named 'main.exceptions'.
@rytilahti I am trying to use miottemplate.py
as you suggested, but it's complaining that it can't find the module requests
. Any idea how to fix this?
@AlexKalopsia pip install requests
will install the required lib.
hi, I'm reading this thread, I can't do what needs to be done so that the thermostat starts displaying data, please tell me
@rytilahti it works now. As I mentioned earlier, I use "Indicator light" property to test and
set_property_by 4 1 false bool
now works fine for this case.call_action_by 6 1
also works fo me (it is an action to reset time-to-clean).
Can i ask a question?what's the action you want to do about the command?i have a smart socket,the mode is cuco.plug.v3,i want to turn on and turn off,do you have try on this action on you plug?
I will lock this issue now as it's old and it contains a lot of outdated information, thanks to everyone who contributed and helped!
The instructions on how to use genericmiot
integration supporting devices based on the schema files can be found in the README: https://github.com/rytilahti/python-miio/#controlling-modern-miot-devices – Note: this is currently only available on git master branch until 0.6.0 is released!
To see the full list of available commands, use miiocli genericmiot --help
:
❯ miiocli genericmiot --help
Usage: miiocli genericmiot [OPTIONS] COMMAND [ARGS]...
Options:
--ip TEXT [required]
--token TEXT [required]
--model TEXT
--help Show this message and exit.
Commands:
actions Return device actions.
call Call action by name.
call_action_by Call an action.
call_action_from_mapping Call an action by a name in the mapping.
descriptors Return a collection containing all...
get_property_by Get a single property (siid/piid).
info Get (and cache) miIO protocol information...
raw_command Send a raw command to the device.
sensors Return read-only properties.
set Change setting value.
set_property_by Set a single property (siid/piid) to given...
settings Return settable properties.
status Return status based on the miot model.