Created
March 18, 2025 07:36
-
-
Save klattimer/02db1921b8bf5c84f1d4110286a72588 to your computer and use it in GitHub Desktop.
Convert LIRC remote codes to broadlink b64/hex codes and generate home assistant configurations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # lirc2broadlink.py | |
| # | |
| # Converts LIRC conf files to Broadlink b64/hex for use in home assistant. | |
| # | |
| # Converted from lirc2broadlink.pl - https://gist.github.com/jeevesmkii/f29806a7a701b5a1a89f8c584d7aaabe | |
| # | |
| import sys | |
| import base64 | |
| import re | |
| from math import ceil | |
| ha_template = """ | |
| %s: | |
| sequence: | |
| - action: remote.send_command | |
| target: | |
| entity_id: remote.wi_fi_universal_remote | |
| data: | |
| device: %s | |
| command: b64:%s | |
| num_repeats: 5 | |
| """ | |
| def pulse_code(message, bits, params): | |
| pulse_code = [] | |
| while bits > 0: | |
| bits -= 1 | |
| pulse_code.extend(params['one'] if (message >> bits) & 1 else params['zero']) | |
| return pulse_code | |
| def raw_pulses_to_broadlink(pulses): | |
| broadlink = [] | |
| for pulse in pulses: | |
| pulse = round(pulse * 269 / 8192) | |
| if pulse < 256: | |
| broadlink.append(pulse) | |
| else: | |
| broadlink.extend([0x00, pulse >> 8, pulse & 0xff]) | |
| broadlink.extend([0x00, 0x0d, 0x05]) | |
| code_len = len(broadlink) | |
| broadlink = [0x26, 0x00, code_len & 0xff, code_len >> 8] + broadlink | |
| pad_bytes = (len(broadlink) + 4) % 16 | |
| broadlink.extend([0x00] * (16 - pad_bytes)) | |
| return broadlink | |
| def process_raw(raw_codes): | |
| codes = re.findall(r'name\s+([^\s]+)\s+((\d+\s+)+)', raw_codes, re.IGNORECASE) | |
| broadlink_codes = {} | |
| for code in codes: | |
| pulses = list(map(int, re.findall(r'\d+', code[1]))) | |
| broadlink_codes[code[0]] = base64.b64encode(bytes(raw_pulses_to_broadlink(pulses))).decode('utf-8') | |
| return broadlink_codes | |
| def process_codes(lirc): | |
| lirc_params = {} | |
| if re.search(r'^\s+repeat', lirc, re.IGNORECASE | re.MULTILINE): | |
| raise Exception("Sorry, I haven't implemented repeating codes :(") | |
| lirc_params['one'] = list(map(int, re.search(r'^\s+one\s+(\d+)\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE).groups())) | |
| lirc_params['zero'] = list(map(int, re.search(r'^\s+zero\s+(\d+)\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE).groups())) | |
| lirc_params['bits'] = int(re.search(r'^\s+bits\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE).group(1)) | |
| header, footer = [], [] | |
| if match := re.search(r'^\s+header\s+(\d+)\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| header.extend(map(int, match.groups())) | |
| if match := re.search(r'^\s+plead\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| header.append(int(match.group(1))) | |
| if match := re.search(r'^\s+pre_data\s+(0x[\da-f]+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| pre_data = int(match.group(1), 16) | |
| if match := re.search(r'^\s+pre_data_bits\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| header.extend(pulse_code(pre_data, int(match.group(1)), lirc_params)) | |
| if match := re.search(r'^\s+pre\s+(\d+)\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| header.extend(map(int, match.groups())) | |
| if match := re.search(r'^\s+post\s+(\d+)\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| footer.extend(map(int, match.groups())) | |
| if match := re.search(r'^\s+post_data\s+(0x[\da-f]+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| post_data = int(match.group(1), 16) | |
| if match := re.search(r'^\s+post_data_bits\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| footer.extend(pulse_code(post_data, int(match.group(1)), lirc_params)) | |
| if match := re.search(r'^\s+ptrail\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| footer.append(int(match.group(1))) | |
| if match := re.search(r'^\s+foot\s+(\d+)\s+(\d+)', lirc, re.IGNORECASE | re.MULTILINE): | |
| footer.extend(map(int, match.groups())) | |
| code_block = re.search(r'begin\s+codes\s+(.*)end\s+codes', lirc, re.IGNORECASE | re.DOTALL).group(1) | |
| broadlink_codes = {} | |
| codes = re.findall(r'^\s*([^\s]+)\s+(0x[\da-f]+).*$', code_block, re.IGNORECASE | re.MULTILINE) | |
| for code in codes: | |
| pulses = header + pulse_code(int(code[1], 16), lirc_params['bits'], lirc_params) + footer | |
| broadlink_codes[code[0]] = base64.b64encode(bytes(raw_pulses_to_broadlink(pulses))).decode('utf-8') | |
| return broadlink_codes | |
| if __name__ == "__main__": | |
| if len(sys.argv) < 2: | |
| print(f"Usage: {sys.argv[0]} <lirc.conf> <remote_name>") | |
| sys.exit(1) | |
| with open(sys.argv[1], 'r') as f: | |
| lirc = f.read() | |
| if len(sys.argv) == 3: | |
| remote_name = sys.argv[2] | |
| else: | |
| remote_name = re.search(r'begin\s+remote\s+name\s+([^\s]+)', lirc, re.IGNORECASE | re.MULTILINE).group(1) | |
| if match := re.search(r'begin\s+raw_codes\s+(.*)end\s+raw_codes', lirc, re.IGNORECASE | re.DOTALL): | |
| broadlink_codes = process_raw(match.group(1)) | |
| else: | |
| broadlink_codes = process_codes(lirc) | |
| print(f"Broadlink b64 codes for {remote_name}:") | |
| for key in sorted(broadlink_codes.keys()): | |
| hexval = base64.b64decode(broadlink_codes[key]).hex() | |
| print(f"{key}\n b64:{broadlink_codes[key]}\n hex:{hexval}") | |
| for key in sorted(broadlink_codes.keys()): | |
| k = re.sub(r'[^a-zA-Z0-9-_]', '', key) | |
| print(ha_template % (k, remote_name, broadlink_codes[key])) | |
| print() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment