Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Whynter codes to be more reliable #144

Merged
merged 1 commit into from
Oct 11, 2024
Merged

Update Whynter codes to be more reliable #144

merged 1 commit into from
Oct 11, 2024

Conversation

thewade
Copy link

@thewade thewade commented Oct 9, 2024

I am the original contributor of this file, however the learned codes never seem to be reliable.

So I adapted https://github.com/esphome/esphome/blob/dev/esphome/components/whynter/whynter.cpp to generate to codes in raw form and used https://github.com/elupus/irgen to convert to Broadlink.

My adapted Python code:
https://gist.github.com/thewade/5f0d87a5e3e69455410015f4105593c0

I should also be able to generate codes for Whynter devices that support heating if someone wants it.

- codes are now generated algorithimically to be millisecond accurate
@litinoveweedle
Copy link
Owner

Hello, than you for your PR. everything seems to be in order, so I will merge it. If you like to provide support also for other Wynter devices, you are welcome.

Regarding your code. Do you think you would be able to help me with code, which would be able to convert between different code types (at least between some) on demand? I would integrate this into Controller class. ;-)

@litinoveweedle litinoveweedle merged commit 5bc255b into litinoveweedle:master Oct 11, 2024
5 checks passed
@thewade
Copy link
Author

thewade commented Oct 15, 2024

@litinoveweedle I see the helper class already has some converter functions.

Right now the helpers are used to convert ENC_PRONTO to ENC_BASE64 for Broadlink controller by using lirc as an intermediary

        if self._encoding == ENC_PRONTO:
            try:
                _command = _command.replace(" ", "")
                _command = bytearray.fromhex(_command)
                _command = Helper.pronto2lirc(_command)
                _command = Helper.lirc2broadlink(_command)
                _command = b64encode(_command).decode("utf-8")

For universal support I would add https://pypi.org/project/irgen/ (https://github.com/elupus/irgen) as a dependency to your project. It has all the converters you need:

  • gen_raw_from_pronto
  • gen_pronto_from_raw
  • gen_raw_from_broadlink
  • gen_broadlink_from_raw

You could then use raw as a universal intermediary between all controllers.

In the future I plan on moving from a Broadlink controller to ESPHome, as I like that it can also receive commands from the actual remote. When I do that I could test using my Broadlink codes from this PR with the ESP32. I don't know when I would be doing the changeover, and I am likely to put my AC away for the season soon. If nobody else tackles this, I might take a look in the spring.

@litinoveweedle
Copy link
Owner

Thank you for pointing to this project, it looks rather promising. I am just not sure, if raw encoding data are actually interchangeable between different controller types. I also don't have currently device to test it (I ma only having Broadlink as well).

@litinoveweedle
Copy link
Owner

FYI - nice article which help me to understand topic a bit: https://tasmota.github.io/docs/IRSend-RAW-Encoding/#converting-broadlink-ir-codes-to-tasmota

@thewade
Copy link
Author

thewade commented Oct 15, 2024

You can see the supported formats are defined in controller_const.py:

BROADLINK_COMMANDS_ENCODING = [ENC_BASE64, ENC_HEX, ENC_PRONTO]
XIAOMI_COMMANDS_ENCODING = [ENC_PRONTO, ENC_RAW]
MQTT_COMMANDS_ENCODING = [ENC_RAW]
LOOKIN_COMMANDS_ENCODING = [ENC_PRONTO, ENC_RAW]
ESPHOME_COMMANDS_ENCODING = [ENC_RAW]
ZHA_COMMANDS_ENCODING = [ENC_RAW]
UFOR11_COMMANDS_ENCODING = [ENC_RAW]

Seems everything except Broadlink supports raw, but I did not review all the implementations.

If you want to see how the functions I mentioned work the source code is here:
https://github.com/elupus/irgen/blob/master/src/irgen/__init__.py

You could also integrate code directly as it is MIT licensed.

@litinoveweedle
Copy link
Owner

litinoveweedle commented Oct 15, 2024

I know, I created that struct myself. :-) what I lack is understanding of the IR formats. I also checked the irgen and it seems better for long term maintenance to just use it as dependency.
I will prepare branch with this conversion support and let see.

@thewade
Copy link
Author

thewade commented Oct 16, 2024

Great! Let me know if you need any help in testing. I already have an ESPHome IR transmitter that I can use.

@litinoveweedle litinoveweedle mentioned this pull request Oct 16, 2024
@litinoveweedle
Copy link
Owner

I am just checking different code files and I am afraid I was correct what RAW encoding in the device files really means.

fan/1175.json

  "supportedController": "MQTT",
  "commandsEncoding": "Raw",
......
      "1": "{\"Protocol\":\"NEC\",\"Bits\":32,\"Data\":\"0xCFD12E\",\"DataLSB\":\"0xF38B74\",\"Repeat\":0}",
      "2": "{\"Protocol\":\"NEC\",\"Bits\":32,\"Data\":\"0xCF09F6\",\"DataLSB\":\"0xF3906F\",\"Repeat\":0}",
      "3": "{\"Protocol\":\"NEC\",\"Bits\":32,\"Data\":\"0xCF51AE\",\"DataLSB\":\"0xF38A75\",\"Repeat\":0}",
      "4": "{\"Protocol\":\"NEC\",\"Bits\":32,\"Data\":\"0xCFC936\",\"DataLSB\":\"0xF3936C\",\"Repeat\":0}",
      "5": "{\"Protocol\":\"NEC\",\"Bits\":32,\"Data\":\"0xCF11EE\",\"DataLSB\":\"0xF38877\",\"Repeat\":0}",
      "boost": "{\"Protocol\":\"NEC\",\"Bits\":32,\"Data\":\"0xCFF10E\",\"DataLSB\":\"0xF38F70\",\"Repeat\":0}"

fan/7040.json

  "supportedController": "ESPHome",
  "commandsEncoding": "Raw",
.......
    "off": "[1238, -438, 1238, -439, 340, -1272, 1239, -438, 1239, -438, 1239, -439, 340, -1274, 340, -1273, 340, -1272, 1239, -438, 1240, -439, 340, -7436, 1238, -438, 1239, -439, 340, -1273, 1239, -438, 1240, -438, 1240, -439, 340, -1275, 340, -1273, 340, -1273, 1240, -437, 1240, -439, 340, -7263, 1239, -437, 1240, -439, 340, -1274, 1240, -438, 1240, -438, 1240, -440, 340, -1275, 340, -1274, 340, -1274, 1239, -438, 1240, -440, 340, -7242, 1239, -438, 1240, -439, 340, -1274, 1240, -438, 1240, -439, 1240, -440, 340, -1275, 340, -1275, 340, -1273, 1240, -438, 1240, -440, 340, -7266, 1238, -439, 1239, -440, 339, -1275, 1239, -440, 1239, -439, 1240, -440, 340, -1276, 339, -1276, 339, -1274, 1239, -439, 1240, -440, 339, -7246, 1238, -440, 1239, -440, 339, -1276, 1239, -439, 1239, -440, 1240, -441, 339, -1276, 339, -1277, 339, -1274, 1239, -440, 1239, -441, 339, -7574, 1238, -440, 1239, -441, 338, -1276, 1239, -440, 1239, -441, 1239, -441, 339, -1277, 339, -1277, 338, -1276, 1238, -440, 1239, -441, 339, -7452, 1238, -440, 1239, -441, 339, -1276, 1238, -441, 1239, -440, 1240, -441, 338, -1278, 338, -1277, 339, -1275, 1239, -441, 1238, -442, 338]",
    "default": {
      "low": "[1239, -438, 1239, -439, 339, -1273, 1239, -438, 1239, -439, 340, -1274, 340, -1272, 1240, -438, 340, -1274, 340, -1273, 340, -1273, 340, -7280, 1239, -438, 1240, -438, 340, -1273, 1240, -438, 1240, -439, 340, -1275, 340, -1273, 1239, -439, 340, -1275, 340, -1274, 340, -1273, 341, -7348, 1239, -438, 1240, -438, 341, -1273, 1240, -438, 1240, -439, 341, -1274, 341, -1273, 1240, -439, 340, -1274, 341, -1274, 340, -1273, 341, -7349, 1239, -438, 1240, -439, 340, -1274, 1240, -438, 1240, -439, 340, -1275, 341, -1273, 1240, -439, 340, -1275, 340, -1274, 340, -1274, 340, -7351, 1239, -439, 1239, -440, 340, -1274, 1240, -439, 1240, -440, 339, -1276, 340, -1274, 1240, -440, 339, -1276, 339, -1275, 340, -1275, 340, -7330, 1238, -439, 1239, -440, 340, -1274, 1240, -439, 1240, -440, 339, -1276, 340, -1274, 1240, -440, 339, -1276, 339, -1276, 340, -1274, 340, -7658, 1239, -439, 1239, -441, 339, -1275, 1239, -439, 1240, -441, 339, -1276, 340, -1274, 1239, -441, 339, -1277, 339, -1276, 339, -1275, 340, -7294, 1238, -439, 1239, -441, 339, -1276, 1239, -440, 1239, -441, 339, -1277, 339, -1275, 1239, -441, 339, -1276, 339, -1276, 339, -1276, 339]",
      "medium": "[1239, -438, 1239, -439, 339, -1273, 1239, -438, 1240, -438, 340, -1274, 341, -1273, 340, -1272, 1239, -439, 340, -1274, 340, -1273, 341, -7324, 1238, -438, 1240, -438, 340, -1273, 1240, -438, 1240, -439, 340, -1274, 341, -1273, 340, -1273, 1240, -439, 340, -1274, 340, -1274, 340, -7349, 1239, -438, 1240, -438, 340, -1274, 1240, -438, 1240, -439, 340, -1275, 341, -1273, 341, -1272, 1240, -440, 339, -1275, 340, -1274, 340, -7351, 1240, -437, 1240, -439, 340, -1274, 1240, -439, 1240, -439, 340, -1276, 340, -1274, 340, -1274, 1239, -440, 340, -1275, 340, -1274, 341, -7352, 1239, -438, 1240, -440, 340, -1275, 1239, -439, 1240, -440, 339, -1276, 340, -1275, 340, -1274, 1239, -440, 340, -1275, 340, -1275, 340, -7341, 1238, -439, 1240, -440, 340, -1274, 1240, -439, 1240, -440, 340, -1276, 340, -1275, 340, -1274, 1239, -441, 339, -1276, 340, -1275, 340]",
      "high": "[1239, -438, 1240, -439, 339, -1274, 1239, -438, 1240, -439, 340, -1274, 1240, -439, 340, -1274, 340, -1274, 341, -1273, 340, -1274, 340, -7328, 1239, -438, 1240, -439, 340, -1274, 1240, -438, 1240, -439, 340, -1274, 1241, -439, 340, -1275, 340, -1274, 341, -1274, 340, -1274, 341, -7351, 1240, -438, 1240, -439, 340, -1274, 1241, -438, 1241, -439, 340, -1274, 1241, -439, 340, -1276, 340, -1275, 340, -1274, 341, -1274, 340, -7353, 1240, -438, 1240, -440, 340, -1274, 1241, -438, 1241, -440, 339, -1275, 1241, -439, 340, -1276, 340, -1275, 341, -1275, 340, -1275, 340, -7354, 1240, -439, 1239, -440, 340, -1275, 1240, -439, 1240, -442, 339, -1274, 1240, -440, 340, -1276, 340, -1275, 340, -1275, 340, -1275, 340]"

What RAW seems to seems to mean at least in the first example is default encoding for my remote, instead of the real IR RAW encoding (as in the the second example). As you can clearly see, that the first example is NEC encoding... But if the given MQTT controller can handle a properly process such command than it doesn't matter. The problem will only rise if we will try to convert it from assumed RAW into for example PRONTO or BROADLINK.

@litinoveweedle
Copy link
Owner

One more example:

climate/4380.json

  "supportedController": "Xiaomi",
  "commandsEncoding": "Raw",
.......
    "off": "Z6XHACMCAABkBgAAKhEAAH8UAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiEAAQEAAAEAAAEAAAEBAAEAAQEBAQABAQEAAAAAAQAAAQEBAAAAAAAAAAABAQEBAQMCIQABAQAAAQAAAQAAAQEAAQABAQEBAAEBAQAAAAABAAABAQEAAAAAAAAAAAEBAQEBAA",

This loo more like ENC_HEX, but is again marked as ENC_RAW, while Xiaomi controller is supposed to handle only ENC_PRONTO, and ENC_RAW.

@litinoveweedle
Copy link
Owner

litinoveweedle commented Oct 16, 2024

The same for ZHA:

climate/1705.json

  "supportedController": "ZHA",
  "commandsEncoding": "Raw",
.......
"B0gjmxEqAqkGgAPgBwHAF+ADB+ADAUAX4A8BQBvAA+AHAQJbAirgGAFAO0ABwAfgJwHgLzfgBwFAq0AD4BsBQCdAA+AHAUCLQBdAB+ALAUAX4AMDQAELqQYqAioCKgKpBioC",

Again look more like ENC_BASE64, but is marked as ENC_RAW .

@litinoveweedle
Copy link
Owner

@thewade Any thoughts?

@thewade
Copy link
Author

thewade commented Oct 19, 2024

The ESPHome ones are raw as I know it. Which is the positive number being the number of milliseconds the IR led is on, and the negative number is the time it is off. This was the format I used in my script before converting to Broadlink.

MQTT is only used in 1175.json. For that example, you have is NEC format in 1175.json, but given the way the JSON is structured I assume it might support other encodings. Zigbee2MQTT is just an interface to devices so maybe the codes are not universal. You can encode decode NEC with the library I shared if you want to handle this one case, although it might not be worth the effort. I would change the datatype from RAW to something like MqttDataStructure.

For Xiaomi those codes appear to be Base64, decoding them we see what looks like raw:

>>> code = "mczm82lkxm82nEsmkxADKazAAfJjMpxNgEPAQ0AfwIIAp8AfwKfAh8AfwB/AH8AfwB/B58BjwB/AH8EDwGPAH8CjwIPAH8DjxePEt8AjwCPAISYQAA=="
>>> list(base64.b64decode(code))
[153, 204, 230, 243, 105, 100, 198, 111, 54, 156, 75, 38, 147, 16, 3, 41, 172, 192, 1, 242, 99, 50, 156, 77, 128, 67, 192, 67, 64, 31, 192, 130, 0, 167, 192, 31, 192, 167, 192, 135, 192, 31, 192, 31, 192, 31, 192, 31, 192, 31, 193, 231, 192, 99, 192, 31, 192, 31, 193, 3, 192, 99, 192, 31, 192, 163, 192, 131, 192, 31, 192, 227, 197, 227, 196, 183, 192, 35, 192, 35, 192, 33, 38, 16, 0]

Credit to https://pro-domo.ddns.net/blog/hack-application-android-xiaomi-mi-remote-partie-1.html for this. I didn’t read the whole thing but you can try using Google Translate to read it yourself.

I think in the case the integer just alternates between milliseconds on an off. More testing would be need to confirm if any of this is true. In this case I would call the format XiaomiBase64Raw cause it is unique.

ZHA is Zigbee to Home Assistant just another interface to the devices and I am again unsure if the codes are universal. Given it is only used in one file I wouldn’t focus to must on it.

I would also suggest that you look into building an ESPHome IR transmitter/receiver. You can make one for under $20 and would help in your testing and maintenance of this project. The receiver can also be used to decode lots of different devices, this can help you very confirm if your translations are correct on devices you don't own. More info here:
https://esphome.io/components/remote_receiver.html

Hope this help getting you started.

@gpaesano
Copy link

Hi, maybe I can help you with IR codes.
Currently I use all the broadlink config files with a $8, bk7231n re-flashed with ESPHome (having as well Temperature and Humidity readings + you can record new codes in case). The model on ALiexpress is called S08pro.
There is also a smaller version for just $5 but has no T&H, called S18.
Please see my pull-request on how to use.
I agree it would be nice to have on-the-fly translation in order not be binded to a specific device.
ESPHome is working with the format described by @thewade . You should have one number positive and the next one negative, in some files of other devices there are all posive numbers but they should be read as + - starting from +. To complete the description the number is the pulse duration in ms, usually the frequency is 38Khz, having a period T of 26,3 us. Raw codes do not have frequency or other info.
PRONTO codes are like:
0000 006D 0046 0000 0158 00AB 0018 0040 0016 0018 0017 0016 0017 0016 0019 003F ....
they cointains also frequency and other info at the beginning and duration is measured in number of T.
Hex codes are somehow a simplified version of the PRONTO.
Hex can be "easily" be converted to base64.
Actually the small c++ for ESPHome lambda function is able to switch from RAW to HEX and from HEX to Base64 , but in case it could use PRONTO as well (by the way PRONTO are native in ESPHome as well).
I can convert codes using an excel file for testing, I can share it but is not very human readable right now.

Going to SmartIR, today a code file for Broadlink starts with:
{
"manufacturer": "Midea",
"supportedModels": ["KFR-35G"],
"commandsEncoding": "Base64",
"supportedController": "Broadlink",
"temperatureUnit": "C",
"minTemperature": 17,
...

To use it with ESPHome I had to change:
"supportedController": "ESPHome",

then I had to add
ESPHOME_COMMANDS_ENCODING = [ENC_RAW, ENC_BASE64]
in the python code and a small mod in the call of the service.

In my idea the code file for a device should not be binded to a specific controller so either:

"supportedControllers": ["Broadlink","ESPHome",.....]

or just skip this setting and set the controller somewhere else.

Not a fun of Python but let me know if I can assist.

@ysard
Copy link

ysard commented Nov 2, 2024

@thewade
Hi, I am the author of the article you quoted in your post.

I'd just like to clarify that Xiaomi's base64 code does indeed contain
IR pulses but several steps are required to obtain them.
The Xiaomi database data I use is compressed and encrypted with an AES key in ECB mode.

The example of code you picked exposes that discovery because the entropy of what we obtain
by just decoding the base64 blob is not what we expect from the data.

Here's the One-Liner presented in this chapter:

import base64
import gzip
from Crypto.Cipher import AES

PATTERN_SECRET_KEY = "fd7e915003168929c1a9b0ec32a60788"
encoded_pattern = <my_base64_xiaomi_code>

json.loads(gzip.decompress(AES.new(PATTERN_SECRET_KEY.encode(), AES.MODE_ECB).decrypt(base64.b64decode(encoded_pattern)).rstrip()))

I don't know where you sourced your IR code example but It doesn't seem to follow the rules I discovered.
The values you decompress from base64 encoding cannot be directly IR codes (no repetitions, etc.).
Perhaps a new way of encrypting data?

Feel free to post your findings in a issue on https://github.com/ysard/mi_remote_database/ :p (maybe this project will interest you btw).

@thewade
Copy link
Author

thewade commented Dec 4, 2024

@litinoveweedle do you want to enable discussions so people can collaborate on things like this?

@litinoveweedle
Copy link
Owner

Hello @thewade and @ysard sorry for the delay in response, I had to manage some of my other tasks first. Now when those task are behind me I would really love to finish our discussion about IR code translations and implement it. I enabled discussion, so we can either continue here or there - up to you. I am looking forward hearing from you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants