From b284293de0ad0f1d6ee82b9f655853fcb54bf20e Mon Sep 17 00:00:00 2001 From: Sean Young Date: Sat, 27 Apr 2024 08:45:05 +0100 Subject: [PATCH] Add encoder for same bpf protocols that ir-ctl can encode Create IRPs for old v4l-utils bpf protocols. Signed-off-by: Sean Young --- libirctl/src/bpf_encoder.c | 22 ++- libirctl/src/lib.rs | 2 +- src/keymap.rs | 249 +++++++++++++++++++++++- testdata/rc_keymaps/RM-687C.toml | 35 ++++ testdata/rc_keymaps/TelePilot_100C.toml | 39 ++++ testdata/rc_keymaps/dish_network.toml | 62 ++++++ tests/bpf_encode_tests.rs | 52 +++++ 7 files changed, 452 insertions(+), 9 deletions(-) create mode 100644 testdata/rc_keymaps/RM-687C.toml create mode 100644 testdata/rc_keymaps/TelePilot_100C.toml create mode 100644 testdata/rc_keymaps/dish_network.toml create mode 100644 tests/bpf_encode_tests.rs diff --git a/libirctl/src/bpf_encoder.c b/libirctl/src/bpf_encoder.c index 1d075d94..d79dacd3 100644 --- a/libirctl/src/bpf_encoder.c +++ b/libirctl/src/bpf_encoder.c @@ -61,24 +61,24 @@ static void encode_pulse_length(struct keymap *map, uint32_t scancode, int *buf, if (keymap_param(map, "reverse", 0)) { for (i = 0; i < bits; i++) { if (scancode & (1 << i)) - buf[len++] = keymap_param(map, "bit_1_space", 1625); + buf[len++] = keymap_param(map, "bit_1_pulse", 1625); else - buf[len++] = keymap_param(map, "bit_0_space", 375); + buf[len++] = keymap_param(map, "bit_0_pulse", 375); - buf[len++] = keymap_param(map, "bit_pulse", 625); + buf[len++] = keymap_param(map, "bit_space", 625); } } else { for (i = bits - 1; i >= 0; i--) { if (scancode & (1 << i)) - buf[len++] = keymap_param(map, "bit_1_space", 1625); + buf[len++] = keymap_param(map, "bit_1_pulse", 1625); else - buf[len++] = keymap_param(map, "bit_0_space", 375); + buf[len++] = keymap_param(map, "bit_0_pulse", 375); - buf[len++] = keymap_param(map, "bit_pulse", 625); + buf[len++] = keymap_param(map, "bit_space", 625); } } - *length = len; + *length = len - 1; } static void manchester_advance_space(int *buf, int *len, unsigned length) @@ -101,6 +101,14 @@ static void encode_manchester(struct keymap *map, uint32_t scancode, int *buf, i { int len = 0, bits, i; + int header_pulse = keymap_param(map, "header_pulse", 0); + int header_space = keymap_param(map, "header_space", 0); + + if (header_pulse > 0) { + manchester_advance_pulse(buf, &len, header_pulse); + manchester_advance_space(buf, &len, header_space); + } + bits = keymap_param(map, "bits", 14); for (i = bits - 1; i >= 0; i--) { diff --git a/libirctl/src/lib.rs b/libirctl/src/lib.rs index d4d2cec3..e2e6f0fa 100644 --- a/libirctl/src/lib.rs +++ b/libirctl/src/lib.rs @@ -45,7 +45,7 @@ pub struct raw_entry { } extern "C" { - pub fn parse_keymap(fname: *const c_char, keymap: *const *const keymap, verbose: bool) -> i32; + pub fn parse_keymap(fname: *const c_char, keymap: *mut *mut keymap, verbose: bool) -> i32; pub fn free_keymap(keymap: *const keymap); pub fn keymap_param(keymap: *const keymap, name: *const c_char, fallback: i32) -> i32; } diff --git a/src/keymap.rs b/src/keymap.rs index adf2769d..796ad11c 100644 --- a/src/keymap.rs +++ b/src/keymap.rs @@ -1,6 +1,6 @@ //! Parse linux rc keymaps -use std::{collections::HashMap, ffi::OsStr, path::Path}; +use std::{collections::HashMap, ffi::OsStr, fmt::Write, path::Path}; use toml::{Table, Value}; #[derive(PartialEq, Eq, Debug, Default)] @@ -9,6 +9,7 @@ pub struct Protocol { pub protocol: String, pub variant: Option, pub irp: Option, + pub rc_protocol: Option, pub raw: Option>, pub scancodes: Option>, } @@ -120,6 +121,18 @@ fn parse_toml(contents: &str, filename: &Path) -> Result { variant = Some(entry.to_owned()); } + let mut rc_protocol = None; + if let Some(Value::Integer(n)) = entry.get("rc_protocol") { + if let Ok(n) = (*n).try_into() { + rc_protocol = Some(n); + } else { + return Err(format!( + "{}: rc_protocol {n} must be 16 bit value", + filename.display() + )); + } + } + let mut irp = None; let mut raw = None; let mut scancodes = None; @@ -184,6 +197,11 @@ fn parse_toml(contents: &str, filename: &Path) -> Result { if let Some(Value::String(entry)) = entry.get("irp") { irp = Some(entry.to_owned()); } + } else if entry.get("irp").is_some() { + return Err("set the protocol to irp when using irp".to_string()); + } else if let Some(rewrite) = bpf_protocol_irp(protocol, entry.as_table().unwrap()) { + log::info!("{}: protocol {protocol} has been rewritten to irp {rewrite}", filename.display()); + irp = Some(rewrite); } if let Some(Value::Table(codes)) = entry.get("scancodes") { @@ -206,6 +224,7 @@ fn parse_toml(contents: &str, filename: &Path) -> Result { protocol: protocol.to_owned(), variant, raw, + rc_protocol, scancodes, irp, }); @@ -214,6 +233,185 @@ fn parse_toml(contents: &str, filename: &Path) -> Result { Ok(Keymap { protocols: res }) } +fn bpf_protocol_irp(protocol: &str, entry: &Table) -> Option { + let param = |name: &str, default: i64| -> i64 { + if let Some(Value::Integer(n)) = entry.get(name) { + *n + } else { + default + } + }; + + match protocol { + "pulse_distance" => { + let mut irp = "{".to_owned(); + let bits = param("bits", 4); + + if param("reverse", 0) == 0 { + irp.push_str("msb,"); + } + + if entry.contains_key("carrier") { + write!(irp, "{}Hz,", param("carrier", 0)).unwrap(); + } + + if irp.ends_with(',') { + irp.pop(); + } + + write!( + irp, + "}}<{},-{}|{},-{}>({},-{},CODE:{},{},-40m", + param("bit_pulse", 625), + param("bit_0_space", 375), + param("bit_pulse", 625), + param("bit_1_space", 1625), + param("header_pulse", 2125), + param("header_space", 1875), + bits, + param("trailer_pulse", 625), + ) + .unwrap(); + + let header_optional = param("header_optional", 0); + + if header_optional > 0 { + write!( + irp, + ",(CODE:{},{},-40m)*", + bits, + param("trailer_pulse", 625), + ) + .unwrap(); + } else { + let repeat_pulse = param("repeat_pulse", 0); + if repeat_pulse > 0 { + write!( + irp, + ",({},-{},{},-40)*", + repeat_pulse, + param("repeat_space", 0), + param("trailer_pulse", 625) + ) + .unwrap(); + } + } + + write!(irp, ") [CODE:0..{}]", gen_mask(bits)).unwrap(); + + Some(irp) + } + "pulse_length" => { + let mut irp = "{".to_owned(); + let bits = param("bits", 4); + + if param("reverse", 0) == 0 { + irp.push_str("msb,"); + } + + if entry.contains_key("carrier") { + write!(irp, "{}Hz,", param("carrier", 0)).unwrap(); + } + + if irp.ends_with(',') { + irp.pop(); + } + + write!( + irp, + "}}<{},-{}|{},-{}>({},-{},CODE:{},-40m", + param("bit_0_pulse", 375), + param("bit_space", 625), + param("bit_1_pulse", 1625), + param("bit_space", 625), + param("header_pulse", 2125), + param("header_space", 1875), + bits, + ) + .unwrap(); + + let header_optional = param("header_optional", 0); + + if header_optional > 0 { + write!(irp, ",(CODE:{},-40m)*", bits).unwrap(); + } else { + let repeat_pulse = param("repeat_pulse", 0); + if repeat_pulse > 0 { + write!( + irp, + ",({},-{},{},-40)*", + repeat_pulse, + param("repeat_space", 0), + param("trailer_pulse", 625) + ) + .unwrap(); + } + } + + write!(irp, ") [CODE:0..{}]", gen_mask(bits)).unwrap(); + + Some(irp) + } + "manchester" => { + let mut irp = "{msb,".to_owned(); + let bits = param("bits", 14); + let toggle_bit = param("toggle_bit", 100); + + if entry.contains_key("carrier") { + write!(irp, "{}Hz,", param("carrier", 0)).unwrap(); + } + + if irp.ends_with(',') { + irp.pop(); + } + + write!( + irp, + "}}<-{},{}|{},-{}>(", + param("zero_space", 888), + param("zero_pulse", 888), + param("one_pulse", 888), + param("one_space", 888), + ) + .unwrap(); + + let header_pulse = param("header_pulse", 0); + let header_space = param("header_space", 0); + + if header_pulse > 0 && header_space > 0 { + write!(irp, "{},-{},", header_pulse, header_space).unwrap(); + } + + if toggle_bit >= bits { + write!(irp, "CODE:{},-40m", bits,).unwrap(); + } else { + let leading = bits - toggle_bit; + if leading > 1 { + write!(irp, "CODE:{}:{},", leading - 1, toggle_bit + 1).unwrap(); + } + write!(irp, "T:1,").unwrap(); + if toggle_bit > 0 { + write!(irp, "CODE:{},", toggle_bit).unwrap(); + } + irp.pop(); + } + + write!(irp, ",-40m) [CODE:0..{}]", gen_mask(bits)).unwrap(); + + Some(irp) + } + _ => None, + } +} + +fn gen_mask(v: i64) -> u64 { + if v < 64 { + (1u64 << v) - 1 + } else { + u64::MAX + } +} + #[test] fn parse_toml_test() { let s = r#" @@ -358,3 +556,52 @@ fn parse_text_test() { } } } + +#[test] +fn parse_bpf_toml_test() { + let s = r#" + [[protocols]] + name = "meh" + protocol = "manchester" + toggle_bit = 12 + [protocols.scancodes] + 0x1e3b = "KEY_SELECT" + 0x1e3d = "KEY_POWER2" + 0x1e1c = "KEY_TV" + "#; + + let k = parse(s, Path::new("x.toml")).unwrap(); + + assert_eq!(k.protocols[0].name, "meh"); + assert_eq!(k.protocols[0].protocol, "manchester"); + assert_eq!( + k.protocols[0].irp, + Some("{msb}<-888,888|888,-888>(CODE:1:13,T:1,CODE:12,-40m) [CODE:0..16383]".into()) + ); + + let s = r#" + [[protocols]] + name = "meh" + protocol = "manchester" + toggle_bit = 1 + carrier = 38000 + header_pulse = 300 + header_space = 350 + [protocols.scancodes] + 0x1e3b = "KEY_SELECT" + 0x1e3d = "KEY_POWER2" + 0x1e1c = "KEY_TV" + "#; + + let k = parse(s, Path::new("x.toml")).unwrap(); + + assert_eq!(k.protocols[0].name, "meh"); + assert_eq!(k.protocols[0].protocol, "manchester"); + assert_eq!( + k.protocols[0].irp, + Some( + "{msb,38000Hz}<-888,888|888,-888>(300,-350,CODE:12:2,T:1,CODE:1,-40m) [CODE:0..16383]" + .into() + ) + ); +} diff --git a/testdata/rc_keymaps/RM-687C.toml b/testdata/rc_keymaps/RM-687C.toml new file mode 100644 index 00000000..996f8597 --- /dev/null +++ b/testdata/rc_keymaps/RM-687C.toml @@ -0,0 +1,35 @@ +[[protocols]] +name = 'Sony_RM-687C' +protocol = 'pulse_length' +header_pulse = 2369 +header_space = 637 +bits = 12 +bit_space = 637 +bit_1_pulse = 1166 +bit_0_pulse = 565 +[protocols.scancodes] +0xa90 = 'KEY_STANDBY' +0x290 = 'KEY_MUTE' +0x5d0 = 'KEY_DISPLAY' +0x010 = 'KEY_1' +0x810 = 'KEY_2' +0x410 = 'KEY_3' +0xc10 = 'KEY_4' +0x210 = 'KEY_5' +0xa10 = 'KEY_6' +0x610 = 'KEY_7' +0xe10 = 'KEY_8' +0x110 = 'KEY_9' +0x310 = 'KEY_1-' +0x910 = 'KEY_0' +0xb10 = 'KEY_2-' +0x6d0 = 'KEY_SLEEP' +0x3f0 = 'KEY_SELECT' +0x2f0 = 'KEY_KPPLUS' +0x690 = 'KEY_NORMAL' +0xaf0 = 'KEY_KPMINUS' +0x490 = 'KEY_VOLUMEUP' +0x090 = 'KEY_CHANNELUP' +0xa50 = 'KEY_TV/VIDEO' +0xc90 = 'KEY_VOLUMEDOWN' +0x890 = 'KEY_CHANNELDOWN' diff --git a/testdata/rc_keymaps/TelePilot_100C.toml b/testdata/rc_keymaps/TelePilot_100C.toml new file mode 100644 index 00000000..e869479e --- /dev/null +++ b/testdata/rc_keymaps/TelePilot_100C.toml @@ -0,0 +1,39 @@ +[[protocols]] +name = 'GRUNDIG_TelePilot_100C' +protocol = 'manchester' +header_pulse = 512 +header_space = 2560 +bits = 8 +zero_pulse = 512 +zero_space = 512 +one_pulse = 512 +one_space = 512 +[protocols.scancodes] +0x5e = 'KEY_POWER' +0x34 = 'KEY_AUDIO' +0x22 = 'KEY_>|' +0x2e = 'KEY_|<' +0x3f = 'KEY_MUTE' +0x3a = 'KEY_1' +0x5a = 'KEY_2' +0x1a = 'KEY_3' +0x6a = 'KEY_4' +0x2a = 'KEY_5' +0x4a = 'KEY_6' +0x0a = 'KEY_7' +0x72 = 'KEY_8' +0x32 = 'KEY_9' +0x7a = 'KEY_0' +0x24 = 'KEY_MENU' +0x0e = 'KEY_DISCMENU' +0x54 = 'KEY_CHANNELUP' +0x14 = 'KEY_CHANNELDOWN' +0x36 = 'KEY_KPMINUS' +0x76 = 'KEY_KPPLUS' +0x7e = 'KEY_OK' +0x78 = 'KEY_TEXT' +0x6e = 'KEY_TV-G' +0x50 = 'KEY_RED' +0x40 = 'KEY_GREEN' +0x10 = 'KEY_YELLOW' +0x44 = 'KEY_BLUE' diff --git a/testdata/rc_keymaps/dish_network.toml b/testdata/rc_keymaps/dish_network.toml new file mode 100644 index 00000000..9614edae --- /dev/null +++ b/testdata/rc_keymaps/dish_network.toml @@ -0,0 +1,62 @@ +# See https://www.mythtv.org/wiki/DISHNetworkLIRCConfiguration +[[protocols]] +name = 'Dish Network' +protocol = 'pulse_distance' +trailer_pulse = 450 +header_optional = 1 +header_pulse = 525 +header_space = 6045 +bits = 16 +bit_pulse = 440 +bit_1_space = 1645 +bit_0_space = 2780 +[protocols.scancodes] +0x0400 = 'KEY_SAT' +0xa801 = 'KEY_TV' +0xac02 = 'KEY_DVD' +0xb003 = 'KEY_AUX' +0x0800 = 'KEY_POWER' +0x2c00 = 'KEY_MENU' +0x5c00 = 'KEY_SWITCHVIDEOMODE' # Input +0x3c10 = 'KEY_PAGEUP' +0x1c10 = 'KEY_PAGEDOWN' +0x5000 = 'KEY_EPG' +0x6800 = 'KEY_UP' +0x7000 = 'KEY_LEFT' +0x4000 = 'KEY_SELECT' +0x6000 = 'KEY_RIGHT' +0x7800 = 'KEY_DOWN' +0x6c00 = 'KEY_LAST' +0x0000 = 'KEY_INFO' +0xb400 = 'KEY_SEARCH' +0x5800 = 'KEY_TV' # View Live TV +0x4800 = 'KEY_CANCEL' +0x4c00 = 'KEY_RED' +0xd400 = 'KEY_GREEN' +0x8800 = 'KEY_YELLOW' +0x8c00 = 'KEY_BLUE' +0xd810 = 'KEY_PREVIOUS' +0xe410 = 'KEY_PVR' +0xdc10 = 'KEY_NEXT' +0xc410 = 'KEY_REWIND' +0x8000 = 'KEY_PAUSE' +0xc810 = 'KEY_FASTFORWARD' +0x8400 = 'KEY_STOP' +0x7c00 = 'KEY_RECORD' +0x0c10 = 'KEY_PLAY' +0x1000 = 'KEY_NUMERIC_1' +0x1400 = 'KEY_NUMERIC_2' +0x1800 = 'KEY_NUMERIC_3' +0x2000 = 'KEY_NUMERIC_4' +0x2400 = 'KEY_NUMERIC_5' +0x2800 = 'KEY_NUMERIC_6' +0x3000 = 'KEY_NUMERIC_7' +0x3400 = 'KEY_NUMERIC_8' +0x3800 = 'KEY_NUMERIC_9' +0x4400 = 'KEY_NUMERIC_0' +0x9400 = 'KEY_NUMERIC_STAR' +0x9800 = 'KEY_NUMERIC_POUND' +0xf410 = 'KEY_AB' +0xe810 = 'KEY_VIDEO' # PIP (Picture-in-picture) +0xec10 = 'KEY_SCREEN' # Position +0xd010 = 'KEY_MEDIA' # Dish diff --git a/tests/bpf_encode_tests.rs b/tests/bpf_encode_tests.rs new file mode 100644 index 00000000..3622bec8 --- /dev/null +++ b/tests/bpf_encode_tests.rs @@ -0,0 +1,52 @@ +use irp::{Irp, Vartable}; +use libirctl::{encode_bpf_protocol, free_keymap, keymap, parse_keymap}; +use std::{ffi::CString, fs::File, io::Read, path::PathBuf}; + +fn irctl_compare_encode(path: &str, scancode: u32) { + // first encode using old ir-ctl + let mut keymap: *mut keymap = std::ptr::null_mut(); + let mut buf = vec![0u32; 1024]; + + let cs = CString::new(path).unwrap(); + + unsafe { + assert_eq!(parse_keymap(cs.as_ptr(), &mut keymap, false), 0); + }; + + let mut length = 0; + + unsafe { encode_bpf_protocol(keymap, scancode, buf.as_mut_ptr(), &mut length) }; + + buf.truncate(length as usize); + + unsafe { free_keymap(keymap) }; + + let path = PathBuf::from(path); + + let mut f = File::open(&path).unwrap(); + + let mut contents = String::new(); + + f.read_to_string(&mut contents).unwrap(); + + let keymap = cir::keymap::parse(&contents, &path).unwrap(); + + let irp = keymap.protocols[0].irp.as_ref().unwrap(); + let irp = Irp::parse(irp).unwrap(); + + let mut vars = Vartable::new(); + vars.set("CODE".into(), scancode.into()); + + let mut message = irp.encode_raw(vars, 0).unwrap(); + + message.remove_trailing_gap(); + + assert_eq!(message.raw, buf); +} + +#[test] +fn encode() { + irctl_compare_encode("testdata/rc_keymaps/dish_network.toml", 0x8c00); + irctl_compare_encode("testdata/rc_keymaps/TelePilot_100C.toml", 0x7e); + irctl_compare_encode("testdata/rc_keymaps/RM-687C.toml", 0x3f0); +}