Skip to content

Instantly share code, notes, and snippets.

@JarrettR
Created May 20, 2024 20:10
Show Gist options
  • Save JarrettR/7c5bdf2491cffbd5753cb875f2f05b5c to your computer and use it in GitHub Desktop.
Save JarrettR/7c5bdf2491cffbd5753cb875f2f05b5c to your computer and use it in GitHub Desktop.
Convert LIRC config files to the ESP32 RMT peripheral boilerplate code.
import re
import argparse
class LIRCConfigParser:
def __init__(self, file_path):
self.file_path = file_path
self.config = {}
self.header = None
self.one = None
self.zero = None
self.ptrail = None
self.gap = None
self._parse_file()
def _parse_file(self):
with open(self.file_path, 'r') as file:
content = file.read()
self._parse_timing_parameters(content)
remote_blocks = re.findall(r'begin remote(.*?)end remote', content, re.DOTALL)
for block in remote_blocks:
remote_name = re.search(r'name\s+(\S+)', block)
if remote_name:
remote_name = remote_name.group(1)
self.config[remote_name] = self._parse_remote_block(block)
def _parse_timing_parameters(self, content):
header = re.search(r'header\s+(\d+)\s+(\d+)', content)
if header:
self.header = (int(header.group(1)), int(header.group(2)))
one = re.search(r'one\s+(\d+)\s+(\d+)', content)
if one:
self.one = (int(one.group(1)), int(one.group(2)))
zero = re.search(r'zero\s+(\d+)\s+(\d+)', content)
if zero:
self.zero = (int(zero.group(1)), int(zero.group(2)))
ptrail = re.search(r'ptrail\s+(\d+)', content)
if ptrail:
self.ptrail = int(ptrail.group(1))
gap = re.search(r'gap\s+(\d+)', content)
if gap:
self.gap = int(gap.group(1))
def _parse_remote_block(self, block):
remote_config = {}
lines = block.splitlines()
key_section = False
for line in lines:
line = line.strip()
if line.startswith('begin codes'):
key_section = True
remote_config['codes'] = {}
elif line.startswith('end codes'):
key_section = False
elif key_section:
parts = line.split()
if len(parts) == 2:
key, value = parts
remote_config['codes'][key] = value
else:
if ' ' in line:
key, value = line.split(None, 1)
remote_config[key] = value
return remote_config
def get_config(self):
return self.config
def get_remote_names(self):
return list(self.config.keys())
def get_remote(self, remote_name):
return self.config.get(remote_name, None)
c_start = '''
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/rmt.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_log.h"
#define RMT_IO (GPIO_NUM_18)
#define ON_OFF_CONTROL (GPIO_NUM_19)
#define PIN_INDICATOR (GPIO_NUM_5)
static const char* TAG = "ir_transmitter";
#define RMT_END {{{ 0, 1, 0, 0 }}}
'''
c_end = '''static void send_on()
{
ESP_ERROR_CHECK(rmt_write_items(RMT_CHANNEL_0, on_key, sizeof(on_key) / sizeof(on_key[0]), true));
vTaskDelay(44/portTICK_RATE_MS);
ESP_ERROR_CHECK(rmt_write_items(RMT_CHANNEL_0, tail_key, sizeof(tail_key) / sizeof(tail_key[0]), true));
}
static void send_off()
{
//Off needs to be pressed twice
ESP_ERROR_CHECK(rmt_write_items(RMT_CHANNEL_0, off_key, sizeof(off_key) / sizeof(off_key[0]), true));
vTaskDelay(2000/portTICK_RATE_MS);
ESP_ERROR_CHECK(rmt_write_items(RMT_CHANNEL_0, off_key, sizeof(off_key) / sizeof(off_key[0]), true));
}
void app_main(void)
{
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(RMT_IO, RMT_CHANNEL_0);
// set count to 1us
config.clk_div = 80;
config.tx_config.carrier_en = true;
config.tx_config.carrier_freq_hz = 38000;
config.tx_config.carrier_duty_percent = 35;
ESP_ERROR_CHECK(rmt_config(&config));
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
gpio_set_direction(ON_OFF_CONTROL, GPIO_MODE_INPUT);
gpio_set_pull_mode(ON_OFF_CONTROL, GPIO_PULLUP_ONLY);
gpio_set_direction(PIN_INDICATOR, GPIO_MODE_OUTPUT);
while (1) {
gpio_set_level(PIN_INDICATOR, 1);
if (gpio_get_level(ON_OFF_CONTROL) == 1) {
send_on();
} else {
send_off();
}
vTaskDelay(1);
gpio_set_level(PIN_INDICATOR, 0);
vTaskDelay(40/portTICK_RATE_MS);
}
}
'''
def generate_c_code(config, output_path, timings):
with open(output_path, 'w') as file:
file.write(c_start)
if timings.header and timings.one and timings.zero and timings.ptrail and timings.gap:
file.write(f'#define PACKET_HEADER {{{{ {timings.header[0]}, 1, {timings.header[1]}, 0 }}}}\n')
file.write(f'#define ONE_BIT {{{{ {timings.one[0]}, 1, {timings.one[1]}, 0 }}}}\n')
file.write(f'#define ZERO_BIT {{{{ {timings.zero[0]}, 1, {timings.zero[1]}, 0 }}}}\n')
file.write(f'#define PACKET_TAIL {{{{ {timings.ptrail}, 1, {timings.gap}, 0 }}}}\n\n')
file.write(f'#define PACKET_ZERO_NIBBLE ZERO_BIT,ZERO_BIT,ZERO_BIT,ZERO_BIT\n\n')
for remote_name, remote_config in config.items():
file.write(f"// Remote: {remote_name}\n")
if 'codes' in remote_config:
for key, value in remote_config['codes'].items():
file.write(f"//0x{value}\n")
file.write(f"static const rmt_item32_t {key.lower()}_key[] = {{\n")
file.write(f" PACKET_HEADER,\n")
for nibble in [value[i:i+4] for i in range(2, len(value), 4)]:
file.write(f" //{nibble}\n")
for bit in bin(int(nibble, 16))[2:].zfill(4):
if bit == '1':
file.write(f" ONE_BIT,\n")
else:
file.write(f" ZERO_BIT,\n")
file.write(f" PACKET_TAIL,\n")
file.write(f" RMT_END\n")
file.write(f"}};\n\n")
file.write(c_end)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Parse LIRC config files and generate a minimal C file.')
parser.add_argument('file_path', nargs='?', default='lircd.conf', help='Path to the LIRC config file')
parser.add_argument('--output', '-o', default='main.c', help='Output C file')
args = parser.parse_args()
lirc_parser = LIRCConfigParser(args.file_path)
config = lirc_parser.get_config()
generate_c_code(config, args.output, lirc_parser)
print(f"Generated C code has been written to {args.output}")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment