rytilahti / python-miio

Python library & console tool for controlling Xiaomi smart appliances
https://python-miio.readthedocs.io
GNU General Public License v3.0
3.63k stars 549 forks source link

Support for pronto codes on chuangmi.remote.h102a03 #495

Open nicole-ashley opened 5 years ago

nicole-ashley commented 5 years ago

I have a model of the Xiaomi IR remote that reports as chuangmi.remote.h102a03. It works fine when capturing and replaying those captured raw commands, but any commands converted from Pronto do nothing - no flashing light, no response from the target device, but - quite puzzlingly - an ok response from the device's API.

Here is the device on Ali Express.

Here are some examples:

>>> ir.info()
DEBUG:miio.device:10.1.1.23:54321 >>: {'id': 3, 'method': 'miIO.info', 'params': []}
DEBUG:miio.device:10.1.1.23:54321 >>: {'id': 104, 'method': 'miIO.info', 'params': []}
DEBUG:miio.device:10.1.1.23:54321 (ts: 1970-01-30 15:52:36, id: 104) << {'result':
  {'life': 2562756, 'cfg_time': 0, 'token': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 
   'mac': 'xx:xx:xx:xx:xx:xx', 'fw_ver': '1.4.3_10', 'hw_ver': 'MC200', 
   'model': 'chuangmi.remote.h102a03', 'wifi_fw_ver': 'SD878x-14.76.36.p79-702.1.0-WM', 
   'ap': {'rssi': -63, 'ssid': 'xxxxxxxxx', 'bssid': 'xx:xx:xx:xx:xx:xx'}, 
   'netif': {'localIp': '10.1.1.23', 'mask': '255.255.255.0', 'gw': '10.1.1.1', 
             'gw_mac': 'xx:xx:xx:xx:xx:xx' },
  'mmfree': 56428, 'ot': 'otu', 'otu_stat': [325, 333, 3647, 125, 3134, 10], 
  'ott_stat': [19, 4, 3, 612]}, 'id': 104}
chuangmi.remote.h102a03 v1.4.3_10 (xx:xx:xx:xx:xx:xx) @ 10.1.1.23 - token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# Works (previously recorded command):

>>> ir.play_raw('tllsNyt1am1Wp1Bo1ZodBoNDtVCoNBoNRoNrAFKtWcAQgAPAA8ADwAPBTKi0IFAgViooB53eg2cAsqiAWQCaUGg02g0kAsgIyAfOogZpPQ==')
DEBUG:miio.device:10.1.1.23:54321 >>: {'id': 2, 'method': 'miIO.ir_play', 
  'params': {'freq': 38400, 'code': 'tllsNyt1am1Wp1Bo1ZodBoNDtVCoNBoNRoNrAFKtWcAQgAPAA8ADwAPBTKi0IFAgViooB53eg2cAsqiAWQCaUGg02g0kAsgIyAfOogZpPQ=='}}
DEBUG:miio.device:10.1.1.23:54321 (ts: 1970-01-30 15:49:27, id: 2) << {'result': ['ok'], 'id': 2}
['ok']

# Doesn't work (Pronto code that should do the same):

>>> ir.play_pronto('0000 0067 0000 000D 0060 0018 0018 0018 0030 0018 0030 0018 0030 0018 0018 0018 0030 0018 0018 0018 0030 0018 0018 0018 0018 0018 0018 0018 0018 03F6')
DEBUG:miio.device:10.1.1.23:54321 >>: {'id': 419, 'method': 'miIO.ir_play', 
  'params': {'freq': 40244, 'code': 'Z6UzAFQCAACoBAAAUQkAAGxiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAEBAQABAAEAAAAwAgABAQEAAQABAAAAMA=='}}
DEBUG:miio.device:10.1.1.23:54321 (ts: 1970-01-30 16:28:16, id: 419) << {'result': ['ok'], 'id': 419}
['ok']

>>> ir.play_raw('6UzAFQCAACoBAAAUQkAAGxiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAEBAQABAAEAAAAwAgABAQEAAQABAAAAMA==', 40244)
DEBUG:miio.device:10.1.1.23:54321 >>: {'id': 420, 'method': 'miIO.ir_play', 
  'params': {'freq': 40244, 'code': '6UzAFQCAACoBAAAUQkAAGxiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAEBAQABAAEAAAAwAgABAQEAAQABAAAAMA=='}}
DEBUG:miio.device:10.1.1.23:54321 (ts: 1970-01-30 16:28:39, id: 420) << {'result': ['ok'], 'id': 420}
['ok']

# From specs (also does nothing):

>>> ir.play_pronto('0000 006C 0022 0002 015B 00AD 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0016 0041 0016 0041 0016 0041 0016 0041 0016 0041 0016 0041 0016 0041 0016 0016 0016 0016 0016 0041 0016 0016 0016 0041 0016 0016 0016 0016 0016 0016 0016 0016 0016 0041 0016 0016 0016 0041 0016 0016 0016 0041 0016 0041 0016 0041 0016 0041 0016 0622 015B 0057 0016 0E6C')
DEBUG:miio.device:10.1.1.23:54321 >>: {'id': 524, 'method': 'miIO.ir_play', 
  'params': {'freq': 38381, 'code': 'Z6VHAD0CAACdBgAA2ggAAJsRAABQIwAAyZ8AAMF3AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAQEBAQEBAQAAAQABAAAAAAEAAQABAQEBBQJGA='}}
DEBUG:miio.device:10.1.1.23:54321 (ts: 1970-01-30 16:42:56, id: 524) << {'result': ['ok'], 'id': 524}
['ok']

>>> ir.play_raw('Z6VHAD0CAACdBgAA2ggAAJsRAABQIwAAyZ8AAMF3AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAQEBAQEBAQAAAQABAAAAAAEAAQABAQEBBQJGA=', 38381)
DEBUG:miio.device:10.1.1.23:54321 >>: {'id': 526, 'method': 'miIO.ir_play', 
  'params': {'freq': 38381, 'code': 'Z6VHAD0CAACdBgAA2ggAAJsRAABQIwAAyZ8AAMF3AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAQEBAQEBAQAAAQABAAAAAAEAAQABAQEBBQJGA='}}
DEBUG:miio.device:10.1.1.23:54321 (ts: 1970-01-30 16:43:58, id: 526) << {'result': ['ok'], 'id': 526}
['ok']

Unfortunately I don't have a clue where to start, or I'd tinker with the code myself. I'm happy to work with you if you need to test things on my device.

mpsOxygen commented 3 years ago

Do you know of any tool that extracts the elf file from the .bin image? I'm seeing 7 segments and will probably end up dumping all of them, but I was just wondering if there is a ready made tool that does this already.

yawor commented 3 years ago

@mpsOxygen isn't it other way around? The firmware bin image should be a pure executable (just machine code + program data). ELF format defines a container for linkable execs (more or less) which is much more than what you have in the bin file. Microcontrollers usually don't use ELF, just pure machine code, as the entry point to the application is predetermined depending on the µC used.

mpsOxygen commented 3 years ago

He explains it better than I can: https://www.youtube.com/watch?v=w4_3vwN_2dI

So the initial app is just an elf then that gets converted to esp32 binary format. They also wrote a tool, you can find it here: https://github.com/tenable/esp32_image_parser

Haven't used it yet.

yawor commented 3 years ago

OK, it's more like a conversion between formats than extraction. The main issue is the lack of support for raw image format in the IDA Xtensa module. In addition to their tool there's also a custom IDA loader which is actually able to load raw image directly, without the manual conversion to ELF (it's mentioned briefly in the video at ~5:24): https://github.com/jrozner/esp-image-ida

mpsOxygen commented 3 years ago

I am using Ghidra and tried with this: https://github.com/tslater2006/esp32_flash_loader But it did not work. The image parser does not work either. I am starting to run out of ideas...

yawor commented 3 years ago

Do you also have this module? https://github.com/yath/ghidra-xtensa It's needed for Ghidra to understand ESP32 machine code.

mpsOxygen commented 3 years ago

@yawor Yes I have the xtensa CPU extension added.

@rezmus Did you pull the firmware of the device or downloaded it from the cloud? I think this bin only contains a sort of diff to be updated and not the full firmware.

yawor commented 3 years ago

Can I also get the file?

rezmus commented 3 years ago

it's just ota, i don't have this model.

yawor commented 3 years ago

Thanks for the file. This a content of an application partition (doesn't matter if it's a factory, OTA1 or OTA2, the application format itself is the same). It means it's a complete executable running on the device, so it should be possible to disassemble it, but it's going to require some work. I'll look into it.

yawor commented 3 years ago

I've been able to convert the app image into ELF format after fixing the image2elf function a little (I've just had to add a single line) and calling it from another python file instead of using their command line parser and logic. The Ghidra loads the file and is able to analyse it. The problem is that during the build process, when ESP-IDF generates bin image from compiled elf files, the symbol table is dropped, so function positions, variables, labels etc are lost. All you get is a lot of Xtensa assembler and few functions which Ghidra tried to decompile back into C.

mpsOxygen commented 3 years ago

Looks like the yath Xtensa Processor for Ghidra is a bit lacking. Using this one I've had way better results: https://github.com/Ebiroll/ghidra-xtensa

Also use this svd loader plugin with the esp32.svd file in order to get all the peripherals: https://github.com/leveldown-security/SVD-Loader-Ghidra

Also using the C header files from esp-idf with Parse C source helps with some of the types.

Unfortunately there are so many functions and no names that it is really hard to pick out the ones that handle IR. :(

yawor commented 3 years ago

A lot of the code is from stdlib and ESP-IDF. Quite large part is the FreeRTOS code, which implement multi-threading. But recognising which parts of the binary contain what may be quite difficult task for sure.