Skip to content

Instantly share code, notes, and snippets.

@svyatogor
Last active May 6, 2026 21:08
Show Gist options
  • Select an option

  • Save svyatogor/7839d00303998a9fa37eb48494dd680f to your computer and use it in GitHub Desktop.

Select an option

Save svyatogor/7839d00303998a9fa37eb48494dd680f to your computer and use it in GitHub Desktop.
Convert SmartIR Broadlink commands to Tuya
@LotharWoman
Copy link
Copy Markdown

LotharWoman commented Apr 26, 2025

Hello folks,
Unfortunately I have no idea about the matter. However, I would like to operate air conditioning in the "Followme" mode with an external temperature sensor. The remote control sends the determined value of the room temperature to the air conditioning every 2-3min. Unfortunately, it can only be placed unfavorably and the batteries empty very quickly.
I have already managed to intercept some codes required with "Learn Command" in home assistant. Unfortunately, they are only graded in 1 ° C steps and the air conditioning is not very finely regulated.
I hope for your help. Is someone able to decode the transmitted temperature value from attached codes and to create new complete codes in 0.2 ° C steps? You would help me and others a lot.

Thanks for your help.

    "FollowMe 19°C": "dRHoEDICOgZSAtQBUgIaBlICGgZSAhoGUgLUAVICGgYzAvQBUgL0AVEC/AVRAvUBUQLVAVEC9QFRAhsGMgL0AVICGgYyAjoGMwIaBlIC9AFRAhoGMwL0AVEC9AFSAvsFMgI6BjMCEwJSAtQBUgIaBjIC9AFSAhoGMwI5BjMC9AEyAhMCMgL0ATMCOgYyAjoGMwI5BjMCGwZRAhsGMgI6BjICEwIzAhoGUgL0ATIC9AEyAhQCMgL0ATIC9AFSAvQBMwI5BjMCQxQ1ERcRMgI4BjMC8wEyAjkGMgI4BjICOQYyAhMCEwJYBhMCEwISAjMCEgI5BjICEwITAhMCMgITAhMCWAYTAhICEwJYBhMCWAYTAlgGEwITAhMCWAYTAjIC9AEyAhMCWAYSAlgGEwISAhMCMgITAjkGEwIyAhMCWAYTAlcGEwITAhMCMgL0ATICEwJZBhMCWAYTAlcG9AFYBhMCdwbzAXcG9AEyAhMCWAb0AVEC9AEyAvQBUQL0ATIC9AEyAhICMgL0AXcG9AEwdQ==",
    "FollowMe 20°C": "ixHoEDECWQYTAhMCMgI4BhMCWQYUAlcGEwITAjMCOAYyAhQCEgITAhQCWAYSAhMCMwITAhMCEwITAlsGEAITAjUCOAYxAjoGEgJYBhQCEgIyAjoGMgISAhUCWAYSAhMCFAISAjICEwIUAhICMgI6BhICMwISAjoGMQITAhQCWAYWAlgGEgISAhICWQYTAhMCMwI4BhMCWQYSAloGEgJYBhMCEwITAlkGEwIUAjECOQYUAjECEwIUAhICMgIUAhICEwJZBhMCQBRUEfYQNAJXBhICFQISAlgGFAJXBhMCWAYUAhICEwJYBhUCMQIVAhICEgJaBhICEgIVAhICNALxATMCOwYvAvUBUwIYBjICOQYzAjsGMAL0AXAC+wUyAhQCUQIZBjMC9AFRAtUBcALWAVEC1AFSAhkGMgIUAlEC/QVPAvQBUQIaBjICOQY1AvIBUQIbBjEC9AFTAhgGMgI6BjICOQY0AjgGMQL2ATECOAY0AvMBMQI6BjICEgI0AvMBMgIUAjIC8wE0AjcGMwIwdQ==",
    "FollowMe 21°C": "eBECETMCOQYVAjICFAI5BjQCOAYTAlsGEgITAjQCOAY1AhECEwIVAhICWAYVAhICFQIxAhMCFAITAloGEgITAjQCOQYVAlcGEwJaBhMCFAITAlsGEwISAjICOgYUAjICEwI7BjICFQISAhMCEwJaBhMCFAIyAjsGEgIzAhQCOAYzAhQCFQISAhICWQYTAjUCEQI5BjQCOQYVAlcGEwJaBhMCFAITAlkGFAITAjQCOAYTAjMCEwIUAhUCEgITAjMCEgJcBhICRRQ2ERYRNQI4BjICFAITAlsG8gFcBjACOgYSAjQCEgI6BjMCFQIRAhMCFQJYBhMCEwIzAhUCEQITAhQCWAYUAjICFgI4BhMCWQYTAloGEgITAjQCOQYUAjICFAI5BjMCFQISAlgGFAITAvYBMAIzAjoGEwIVAjECOQY0AvMBNAI4BjUC8wEyAhQCMgIcBlEC8wFTAhoGUQL9BVICGAZSAhsGUgL0AVIC/AUyAhYCMAI5BjQC8wFSAtUBUQL3ATAC9AE0AjgGMgIwdQ==",
    "FollowMe 22°C": "dBEIETICWwYSAhMCFAJZBhUCWQYSAjoGMwITAhUCWAYTAhYCEgITAjICOgYTAjUCEgITAhMCFAIyAjwGEgIyAhMCOwYxAjsGEwJZBhMCFAIyAjwGEgIyAhQCOgYyAjoGEwI1AhECFAITAhUCMQI8BhECMwIUAjkGNAISAhMCFAITAlkGFAITAjICOwYSAjQC8wFbBjICPAYSAlgGEwJZBhUCEgITAloGEgIVAjICPAYQAjMCEwIUAhMCEwI1AhECEwJaBhMCRBQ3ERsRMgI8BhICMgIUAjsGNQI4BhYCWAYWAhICMgI7BhQCMwITAhUC9AF5BvUBMgIWAhICNAITAhYCVwYWAhICEwJbBhMCOwYzAjsGFAI0AhICPAYyAhMCFQJbBhICPAYSAhcCMgLzATICFAIzAhsGNAITAjMCHQZRAvUBUgLXAVECHAZRAtUBUgIeBlAC9AFUAvsFMgI8BjQCOgY0AjoGUwLUAVQCGgZUAtMBUgIcBjYC8gFSAvQBUwLVAVIC1gFRAhwGMwIwdQ==",
    "FollowMe 23°C": "iRHoEDUCOAZRAtcBTwIbBlMCGQZRAvsFcQLVAVICGgZSAtcBUQLTAXMC+gVUAvIBUQLWAVIC0wFxAvsFUgL1AVECHAZQAvsFcQL6BVMC9AFRAv0FUQLzAVMCGQZVAvkFcQL8BVAC9gFRAtQBUwIaBjQC8wFVAhkGNQLyAVIC9QFRAtUBVALzAVIC/AU1AjgGNgI4BjUCOQYyAhsGNAI7BjIC9QFxAvwFMwIVAlEC1wFPAtUBcwLUAVMC1AFUAvMBMgIcBjMCRhQ6ERgRNAI8BjEC9AEzAjoGMwI7BjMCOgYzAvQBNQI5BjQC8wEzAvQBMwI7BjICFQIyAvQBMwL1ATICOwYzAhMCNQIZBjUCOQY0AjkGNQLzATICOwY1AhICEwJaBhMCWgYUAjsGMgIUAhMCFQITAloGEwIWAjECPAYSAjMCEwIUAhUCEgITAjMCFAJaBhMCPAYTAlsGEwJbBhMCWQYVAjkGFQIxAhYCWAb2ATICEgIVAhMCNQISAhMCFAIyAvYBMgITAloGEwIwdQ==",
    "FollowMe 24°C": "lhHpEDACOQY0AhMCUQL6BVQCGAYzAjkGMwL2AU8CGgY0AhICUgLVAVECGgY1AvMBUAL2AVEC0wFUAhgGMwL0AVECHAYyAjgGMwI5BjUC8gEyAjoGMgI4BjQC8wEyAhUCMQLzATUCEAIzAvUBMgI7BjEC8wE1AhICMQIbBlECHQYwAjoGMQL2AVECGQY0AjgGMwI5BjICOwYzAjcGMgIaBlIC9gEwAjkGNQLyATICFQIxAvMBNALzATICFAIyAvYBMQI5BjQCQBQ2ETURFQJYBhICEwIUAlgGFgJXBhICOgYyAhMCEwJYBhQCEwIUAjICEwI5BhMCNQIRAhMCEwI0AhICWAb2ATACEwJaBhICWwYRAjkGFQIxAhMCWQYTAjsGEgIyAhQCEgITAjQCEgITAhQCMQL0AVgGFAIyAhUCEgITAlkGEwJYBhUCVwYTAhQCEwJYBhYCVQYTAlkGEwJZBvYBVwYSAnkG8wEyAvUBdwb1ATICEwIzAvMBMgL0AVIC9gEwAvQBVALyAVgGFQIwdQ==",
    "FollowMe 25°C": "lBHoEDMCOQZUAtMBUQIbBlECHQZSAhgGUQLVAVMCGQZSAtYBUAL0AVQC+AVxAtYBUQLUAVIC9AFRAv0FbwLUAVQCGAZSAhoGUwL6BXIC1AFRAhsGUgL8BXAC1gFRAtQBUwIZBlEC1gFxAtQBUwIZBlEC1AFSAtUBcwL5BTMCOgYyAhMCVALTAVECGwZRAhkGNAIZBlICGQYyAjoGMwI6BjIC9QFQAh0GMAITAlMC0wFSAtUBUgL0AVEC1AFUAvIBVAL5BTICQxRVEfYQVAIYBjMCFAIxAhkGUgIbBjICPAYvAhQCNAIZBjICFgIwAvQBNAI4BjIC9QExAhMCMgL0ATICOgYzAvUBMgI4BjQCOQY0AjkGNQLyATMCOwYyAlkGFAITAhQCFQIyAjkGFgISAjICFAITAlkGFQISAhMCFAIzAjsGEgJbBhMCNAISAhMCFAJZBhQCOgYTAlsGEgJaBhMCWQYUAloGFAITAhUCWAYWAhACFAIzAhMCFgISAhMCFAIyAhMCFAITAlsGEgIwdQ==",
    "FollowMe 26°C": "khHnEDICOgZRAtUBUgIaBlICGgZSAhoGUQLVAVICGgZRAtUBcQLUAVICGgZSAtQBUgLVAXEC1AFSAhoGUgLUAVICGwZRAhoGUgL7BXAC1QFSAhoGUQIaBlIC1QFRAhoGUgLVAVEC9AFSAtQBUgIaBlIC1AFSAvQBUgL7BXEC1AFSAhsGUQLVAVECGgZSAhoGUgIaBlIC+wVxAvsFUQIbBlEC1QFxAvsFUQL0AVIC1QFSAtQBcQLVAVEC1QFSAvMBUgIaBjMCIxR0EdgQUQIbBjICEwJSAhoGMgIbBlECGgYzAhMCUgL6BTMCEwJSAtUBUQIaBjMC9AFRAvQBUgLVAVICGwYzAvQBMwI6BjMCOgYyAjsGMgL0ATMCOgYzAjoGMwL0ATICOwYyAvQBMwIUAjMC8wEzAjoGMwL0ATMCFAIyAhsGUgL0ATMCOwYzAvQBMgI7BjMCOgYzAhsGMwI6BjMCOgYzAjsGMgL0ATMCOgYzAvQBMwITAjMCEwIUAhMCEwIzAhQCEwITAloGFAIwdQ==",
    "FollowMe 27°C": "hRHlEDICOQZRAtUBUQIZBlICGQZRAhkGUgLUAVECGgZRAvQBUQLVAVECGQYyAvQBUQL0AVEC1AFSAhkGMgL0AXAC+gUyAjgGMwI4BjIC9AFwAvsFMgI4BjICEwJRAvoFUgIZBjICEwJRAtUBUQIZBjIC9AFwAtUBUQIZBjIC9AEyAhMCMgLzATICOQYxAjkGMgI5BjICGQZRAhkGMgI4BjICEwIzAhkGUgL0ATIC9AEzAhMCMwL0ATICEwIzAvQBMgI5BjICQhQ1ERYRMwI5BjIC9AEzAjkGMwI5BjICOgYTAjMCEwI5BjMCEwITAhMCEwJZBhMCEwIzAhMCEwITAhMCWQYTAjMCEwI5BhMCWQYTAlkGEwITAhMCWQYTAlgGEwIUAhMCWQYTAlgGFAIyAvQBMgITAlgGEwIUAhMCMgITAjoGEwIzAhMCEwITAjICEwI6BhMCWQYTAncG9AF4BvQBeAb0AVgGEwIyAvQBeAb0ATMC8wEzAhMCMgL0ATMC9AFRAvQBMgL0AXgG8wEwdQ=="

@cord-otten
Copy link
Copy Markdown

cord-otten commented May 8, 2025

I seem to have an issue when trying to convert 1344 or 1161 (both relate to the "YB1FA" remote):

Traceback (most recent call last):
  File "C:\temp\script.py", line 175, in <module>
     output_file.write(process_commands(sys.argv[1]))

  File "C:\temp\script.py", line 167, in process_commands
    data['commands'] = process_commands_recursively(data.get('commands', {}))

  File "C:\temp\script.py", line 161, in process_commands_recursively
    processed_commands[key] = process_commands_recursively(value)

  File "C:\temp\script.py", line 161, in process_commands_recursively
    processed_commands[key] = process_commands_recursively(value)

  File "C:\temp\script.py", line 161, in process_commands_recursively
    processed_commands[key] = process_commands_recursively(value)

  File "C:\temp\script.py", line 159, in process_commands_recursively
    processed_commands[key] = encode_ir(value)

  File "C:\temp\script.py", line 16, in encode_ir
    signal = filter(get_raw_from_broadlink(base64.b64decode(command).hex()))

  File "C:\temp\script.py", line 137, in get_raw_from_broadlink
    length = int(string[6:8] + string[4:6], 16)  # Length of payload in little endian
ValueError: invalid literal for int() with base 16: ''

As far as I can see, they are for Broadlink, as opposed to the issue from @nazmibojan

Already thanks for any support and a huge thank you for the work!

@Jody1818
Copy link
Copy Markdown

@cord-otten
any luck with that issue? trying the same file for same remote and getting errors as well

@cord-otten
Copy link
Copy Markdown

Not yet. I tried some online converters without luck. I suspect the codes are not "standard" broadlink format.

@Jody1818
Copy link
Copy Markdown

I used the code and it does not add "ir_code_to_send":
should I do it menually? am I doing something wrong?
is it not a must?

@sneakysandals
Copy link
Copy Markdown

I got the script to run but have the same issue as @Jody1818 where none of the lines in the output have "ir_code_to_send"

Also all the output IR codes are wildly different from the original .json codes.

@Wobak
Copy link
Copy Markdown

Wobak commented Aug 27, 2025

I used the code and it does not add "ir_code_to_send": should I do it menually? am I doing something wrong? is it not a must?

I got the script to run but have the same issue as @Jody1818 where none of the lines in the output have "ir_code_to_send"

Also all the output IR codes are wildly different from the original .json codes.

The codes are different because they're not encoded in the same format, so it's completely normal.
The ir_code_to_send is not added because you should add it to your climate platform like this (in the configuration.yaml):

climate:
  - platform: smartir
    name: "My AC"
    device_code: 8888
    controller_data: "zigbee2mqtt/UFO-R11 Test/set/ir_code_to_send"

In that case the payload needed just needs to be the IR code.

@matthuisman
Copy link
Copy Markdown

For issue with invalid int, its because some temps dont have a command in the source file:
eg:
https://raw.githubusercontent.com/smartHomeHub/SmartIR/refs/heads/master/codes/climate/1281.json

        "static": {
          "16": "",
          "17": "JgAGAWk0DA0NDQ4mDQ4NJg4MDQ0NDg0mDiYNDg0MDQ0OJg4nDA4NDQ0MDg0NDQwODQ0NDA4NDQ0NDQ0NDQwOJw0NDQ0NDgwNDQ0ODA0NDScNDQ4MDQ0NDg0mDiYNJw0oDCgNJw0mDicMDg0NDScNDQwODQ0NDA4NDQ0NDQ0NDScNJw0NDQ4MDQ0NDgwNDQ0ODSYODQwNDQ4NDA0NDg0MDQ0ODQwNDg0mDicMDg0NDQwODQ0NDA4NDQ0MDg0NDQ0NDQ0NDA4NDQ0NDQ0NDQ4MDQ0NDgwNDQ0ODA0NDQ4MDQ0NDg0MDQ0ODQwNDQ4NDA0NDiYODQ0MDSgNDA4nDCgNDA4NDQ0MKA0ADQU=",

these should simply be skipped.
I suggest the script is updated to do that.
eg.
if isinstance(value, str):
becomes
if isinstance(value, str) and value:

@vincenzoca69
Copy link
Copy Markdown

I tried the conversion but with the tuya sensors the conversion files don't work. Does anyone have any ideas?

@tomer2526
Copy link
Copy Markdown

tomer2526 commented Mar 16, 2026

I have made a website that performs the conversion directly in your browser!
Here is a link: Broadlink → Raw MQTT Converter
You can even convert the other way around now
Here is a link to the site's Git: https://github.com/tomer2526/Convert-Broadlink-Codes-to-Row-MQTT-Format

@sergiobaiao
Copy link
Copy Markdown

sergiobaiao commented May 6, 2026

if you want to implement this also, it converts from Globalcache's format (sendir...) to Raw MQTT:

`

<title>GC to Z2M IR Converter</title> <style> body { font-family: sans-serif; max-width: 800px; margin: 20px auto; padding: 0 20px; line-height: 1.6; background: #f4f4f9; } textarea { width: 100%; height: 150px; margin-bottom: 10px; padding: 10px; border: 1px solid #ccc; border-radius: 4px; } button { background: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #0056b3; } .result-item { background: white; padding: 15px; margin-top: 10px; border-radius: 4px; border-left: 5px solid #007bff; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } code { background: #eee; padding: 2px 5px; word-break: break-all; } h2 { color: #333; } </style>

Global Caché to Zigbee2MQTT Raw

Paste one or more commands (one per line):

<textarea id="input" placeholder="sendir,1:1,1,38000,1,1,341,170,21,63..."></textarea> Convert All
<div id="results"></div>

<script>
    function convert() {
        const input = document.getElementById('input').value;
        const lines = input.split('\n').filter(line => line.trim() !== "");
        const resultsDiv = document.getElementById('results');
        resultsDiv.innerHTML = "";

        lines.forEach((line, index) => {
            const parts = line.split(',');
            if (parts.length < 7) return;

            const freq = parseInt(parts[3]);
            const rawValues = parts.slice(6).map(Number);
            
            // Convert GC pulses to Microseconds
            const durations = rawValues.map(val => Math.round((val / freq) * 1000000));

            // Encode to Base64 (using 16-bit Little Endian as per Tuya Z2M standards)
            const uint16Array = new Uint16Array(durations);
            const base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(uint16Array.buffer)));

            const div = document.createElement('div');
            div.className = 'result-item';
            div.innerHTML = `<strong>Command ${index + 1}:</strong><br><code>${base64}</code>`;
            resultsDiv.appendChild(div);
        });
    }
</script>
`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment