diff --git a/Cargo.toml b/Cargo.toml index 6957b67..f9dacc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ irp = { version = "0.3.2", path = "irp" } assert_cmd = "2.0" serde_json = "1.0" liblircd = { path = "liblircd" } +rand = "0.8" [workspace] members = [ diff --git a/liblircd/src/ir-encode.c b/liblircd/src/ir-encode.c index 3d27991..a088a7e 100644 --- a/liblircd/src/ir-encode.c +++ b/liblircd/src/ir-encode.c @@ -379,9 +379,9 @@ static const struct { } protocols[] = { [RC_PROTO_UNKNOWN] = { "unknown" }, [RC_PROTO_OTHER] = { "other" }, - [RC_PROTO_RC5] = { "rc5", 0x1f7f, 24, 36000, rc5_encode }, + [RC_PROTO_RC5] = { "rc5", 0x1f7f, 25, 36000, rc5_encode }, [RC_PROTO_RC5X_20] = { "rc5x_20", 0x1f7f3f, 40, 36000, rc5_encode }, - [RC_PROTO_RC5_SZ] = { "rc5_sz", 0x2fff, 26, 36000, rc5_encode }, + [RC_PROTO_RC5_SZ] = { "rc5_sz", 0x2fff, 27, 36000, rc5_encode }, [RC_PROTO_SONY12] = { "sony12", 0x1f007f, 25, 40000, sony_encode }, [RC_PROTO_SONY15] = { "sony15", 0xff007f, 31, 40000, sony_encode }, [RC_PROTO_SONY20] = { "sony20", 0x1fff7f, 41, 40000, sony_encode }, @@ -390,7 +390,7 @@ static const struct { [RC_PROTO_NECX] = { "necx", 0xffffff, 67, 38000, nec_encode }, [RC_PROTO_NEC32] = { "nec32", 0xffffffff, 67, 38000, nec_encode }, [RC_PROTO_SANYO] = { "sanyo", 0x1fffff, 87, 38000, sanyo_encode }, - [RC_PROTO_RC6_0] = { "rc6_0", 0xffff, 24, 36000, rc6_encode }, + [RC_PROTO_RC6_0] = { "rc6_0", 0xffff, 41, 36000, rc6_encode }, [RC_PROTO_RC6_6A_20] = { "rc6_6a_20", 0xfffff, 52, 36000, rc6_encode }, [RC_PROTO_RC6_6A_24] = { "rc6_6a_24", 0xffffff, 60, 36000, rc6_encode }, [RC_PROTO_RC6_6A_32] = { "rc6_6a_32", 0xffffffff, 76, 36000, rc6_encode }, diff --git a/liblircd/src/lib.rs b/liblircd/src/lib.rs index f8f7678..e21384b 100644 --- a/liblircd/src/lib.rs +++ b/liblircd/src/lib.rs @@ -356,6 +356,7 @@ impl<'a> Code<'a> { } } +#[derive(Clone, Copy, PartialEq, Debug)] #[repr(u32)] pub enum rc_proto { RC_PROTO_UNKNOWN = 0, @@ -391,12 +392,12 @@ pub enum rc_proto { // These functions are defined in ir-encode.[ch], which comes from v4l-utils' ir-ctl #[allow(unused)] extern "C" { - fn protocol_match(name: *const c_char, proto: rc_proto); - fn protocol_carrier(proto: rc_proto) -> u32; - fn protocol_max_size(proto: rc_proto) -> u32; - fn protocol_scancode_valid(proto: rc_proto, scancode: *mut u32) -> bool; - fn protocol_scancode_mask(proto: rc_proto) -> u32; - fn protocol_encoder_available(proto: rc_proto) -> bool; - fn protocol_encode(proto: rc_proto, scancode: u32, buf: *mut u8) -> u32; - fn protocol_name(proto: rc_proto) -> *const c_char; + pub fn protocol_match(name: *const c_char, proto: rc_proto); + pub fn protocol_carrier(proto: rc_proto) -> u32; + pub fn protocol_max_size(proto: rc_proto) -> u32; + pub fn protocol_scancode_valid(proto: rc_proto, scancode: *mut u32) -> bool; + pub fn protocol_scancode_mask(proto: rc_proto) -> u32; + pub fn protocol_encoder_available(proto: rc_proto) -> bool; + pub fn protocol_encode(proto: rc_proto, scancode: u32, buf: *mut u32) -> u32; + pub fn protocol_name(proto: rc_proto) -> *const c_char; } diff --git a/src/bin/commands/transmit.rs b/src/bin/commands/transmit.rs index e20719d..bc263e3 100644 --- a/src/bin/commands/transmit.rs +++ b/src/bin/commands/transmit.rs @@ -1,6 +1,6 @@ #[cfg(target_os = "linux")] use super::config::{open_lirc, Purpose}; -use cir::lircd_conf; +use cir::{linux_protocol::LinuxProtocol, lircd_conf}; use irp::{Irp, Message, Pronto, Vartable}; use log::{error, info, warn}; use std::{ffi::OsStr, fs, path::Path, str::FromStr}; @@ -412,45 +412,22 @@ fn encode_scancode(protocol: &str, code: &str) -> Result { return Err(format!("invalid scancode {code}")); }; - let (irp, mask) = match protocol.to_ascii_lowercase().as_str() { - // TODO needs decoder name and protocol number - "rc5" => ("{36k,msb,889}<1,-1|-1,1>(1,~CODE:1:6,T:1,CODE:5:8,CODE:6,^114m) [CODE:0..0x1FFF,T:0..1=0]", 0x1f7f), - "rc5x_20" => ("{36k,msb,889}<1,-1|-1,1>(1,~CODE:1:14,T:1,CODE:5:16,-4,CODE:8:6,CODE:6,^114m) [CODE:0..0x1fffff,T:0..1=0]", 0x1f7f3f), - "rc5_sz" => ("{36k,msb,889}<1,-1|-1,1>(1,~CODE:1:13,T:1,T:1,CODE:12,^114m) [CODE:0..0x2fff,T:0..1=0]", 0x2fff), - "sony12" => ("{40k,600}<1,-1|2,-1>(4,-1,CODE:7,CODE:5:16,^45m)* [CODE:0..0x1fffff]",0x1f007f), - "sony15" => ("{40k,600}<1,-1|2,-1>(4,-1,CODE:7,CODE:8:16,^45m)* [CODE:0..0xffffff]",0xff007f), - "sony20" => ("{40k,600}<1,-1|2,-1>(4,-1,CODE:7,CODE:5:16,CODE:8:8,^45m)* [CODE:0..0x1fffff]", 0x1fff7f), - "jvc" => ("{37.9k,527,33%}<1,-1|1,-3>(16,-8,CODE:16,1,^59.08m,(CODE:16,1,^46.42m)*) [CODE=0..0xffff]", 0xffff), - // TODO: necx and nec32 must not match nec - "nec" => ("{38.4k,564}<1,-1|1,-3>(16,-8,CODE:8:8,CODE:8,~CODE:8:8,~CODE:8,1,^108m,(16,-4,1,^108m)*) [CODE:0..0xffff]", 0xffff), - "sanyo" => ("{38k,562.5}<1,-1|1,-3>(16,-8,CODE:13:8,~CODE:13:8,CODE:8,~CODE:8,1,-42,(16,-8,1,-150)*) [CODE:0..0x1fffff]", 0x1fffff), - "rc6_0" => ("{36k,444,msb}<-1,1|1,-1>((6,-2,1:1,0:3,<-2,2|2,-2>(T:1),CODE:16,^107m)*,T=1-T) [CODE:0..0xffff,T@:0..1=0]", 0xffff), - "rc6_6a_20" => ("{36k,444,msb}<-1,1|1,-1>((6,-2,1:1,6:3,<-2,2|2,-2>(T:1),CODE:20,-100m)*,T=1-T)[CODE:0..0xfffff,T@:0..1=0]", 0xf_ffff), - "rc6_6a_24" => ("{36k,444,msb}<-1,1|1,-1>((6,-2,1:1,6:3,<-2,2|2,-2>(T:1),CODE:24,^105m)*,T=1-T)[CODE:0..0xffffff,T@:0..1=0]", 0xff_ff_ff), - "rc6_6a_32" => ("{36k,444,msb}<-1,1|1,-1>((6,-2,1:1,6:3,<-2,2|2,-2>(T:1),CODE:32,MCE=(CODE>>16)==0x800f||(CODE>>16)==0x8034||(CODE>>16)==0x8046,^105m)*,T=1-T){MCE=0}[CODE:0..0xffffffff,T@:0..1=0]", 0xffff_ffff), - "rc6_mce" => ("{36k,444,msb}<-1,1|1,-1>((6,-2,1:1,6:3,-2,2,CODE:16:16,T:1,CODE:15,MCE=(CODE>>16)==0x800f||(CODE>>16)==0x8034||(CODE>>16)==0x8046,^105m)*,T=1-T){MCE=1}[CODE:0..0xffffffff,T@:0..1=0]", 0xffff_ffff), - "sharp" => ("{38k,264}<1,-3|1,-7>(CODE:5:8,CODE:8,1:2,1,-165,CODE:5:8,~CODE:8,2:2,1,-165)*[CODE:0..0x1fff]", 0x1fff), - "rc-mm-12" => ("{36k,msb}<166.7,-277.8|166.7,-444.4|166.7,-611.1|166.7,-777.8>(416.7,-277.8,CODE:12,166.7,^27.778ms) [CODE:0..0xfff]", 0xfff), - "rc-mm-24" => ("{36k,msb}<166.7,-277.8|166.7,-444.4|166.7,-611.1|166.7,-777.8>(416.7,-277.8,CODE:24,166.7,^27.778ms) [CODE:0..0xffffff]", 0xfff_fff), - // TODO: toggle bit - "rc-mm-32" => ("{36k,msb}<166.7,-277.8|166.7,-444.4|166.7,-611.1|166.7,-777.8>(416.7,-277.8,CODE:32,166.7,^27.778ms) [CODE:0..0xffffffff]", 0xffff_ffff), - // TODO: xbox-dvd trailing space? - "xbox-dvd" => ("{38k,msb}<550,-900|550,-1900>(4000,-3900,~CODE:12,CODE:12,550,Xm) [CODE:0..0xfff]", 0xfff), - // xmp, mcir2 - "imon" => ("{416,38k,msb}<-1|1>(1,>1,P=CHK&1)|0:2,(CHK=CHK>>1,P=1)>(CODE:31),^106m){P=1,CHK=0x7efec2} [CODE:0..0x7fffffff]", 0x7fffffff), - _ => { - return Err(format!("protocol {protocol} is not known")); - } + let Some(linux) = LinuxProtocol::find_like(protocol) else { + return Err(format!("protocol {protocol} is not known")); }; - let masked = scancode & mask; + if linux.irp.is_none() { + return Err(format!("protocol {protocol} is cannot be encoded")); + } + + let masked = scancode & linux.scancode_mask as u64; if masked != scancode { warn!("error: scancode {scancode:#x} masked to {masked:#x}"); scancode = masked; } - let irp = Irp::parse(irp).unwrap(); + let irp = Irp::parse(linux.irp.unwrap()).unwrap(); let mut vars = Vartable::new(); diff --git a/src/lib.rs b/src/lib.rs index f2d3895..7ef6cc2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod keymap; +pub mod linux_protocol; #[cfg(target_os = "linux")] pub mod lirc; pub mod lircd_conf; diff --git a/src/linux_protocol.rs b/src/linux_protocol.rs new file mode 100644 index 0000000..ff627c3 --- /dev/null +++ b/src/linux_protocol.rs @@ -0,0 +1,399 @@ +pub struct LinuxProtocol { + pub name: &'static str, + pub decoder: &'static str, + pub irp: Option<&'static str>, + pub scancode_mask: u32, + pub protocol_no: u32, +} + +impl LinuxProtocol { + pub fn find(name: &str) -> Option<&'static LinuxProtocol> { + LINUX_PROTOCOLS.iter().find(|e| e.name == name) + } + + /// Match protocol name with regard for spaces or dashes or underscores. + /// Behaviour should match protocol_match() in ir-ctl + pub fn find_like(name: &str) -> Option<&'static LinuxProtocol> { + let str_like = |name: &str| -> String { + name.chars() + .filter_map(|ch| { + if matches!(ch, ' ' | '-' | '_') || !ch.is_ascii() { + None + } else { + Some(ch.to_ascii_lowercase()) + } + }) + .collect::() + }; + + let name = str_like(name); + + LINUX_PROTOCOLS.iter().find(|e| str_like(e.name) == name) + } +} + +const LINUX_PROTOCOLS: &[LinuxProtocol] = &[ + LinuxProtocol { + name: "rc5", + decoder: "rc5", + irp: Some( + "{36k,msb,889}<1,-1|-1,1>(1,~CODE:1:6,T:1,CODE:5:8,CODE:6,^114m) [CODE:0..0x1FFF,T:0..1=0]", + ), + scancode_mask: 0x1f7f, + protocol_no: 2, + }, + LinuxProtocol { + name: "rc5x_20", + decoder: "rc5", + irp: Some("{36k,msb,889}<1,-1|-1,1>(1,~CODE:1:14,T:1,CODE:5:16,-4,CODE:6:8,CODE:6,^114m) [CODE:0..0x1fffff,T:0..1=0]"), + scancode_mask: 0x1f7f3f, + protocol_no: 3, + }, + LinuxProtocol { + name: "rc5_sz", + decoder: "rc5", + irp: Some("{36k,msb,889}<1,-1|-1,1>(1,CODE:1:13,T:1,CODE:12,^114m) [CODE:0..0x2fff,T:0..1=0]"), + scancode_mask: 0x2fff, + protocol_no: 4, + }, + LinuxProtocol { + name: "jvc", + decoder: "jvc", + irp: Some("{37.9k,527,33%}<1,-1|1,-3>(16,-8,CODE:8:8,CODE:8,1,^59.08m,(CODE:8:8,CODE:8,1,^46.42m)*) [CODE:0..0xffff]"), + scancode_mask: 0xffff, + protocol_no: 5, + }, + LinuxProtocol { + name: "sony12", + decoder: "sony", + irp: Some("{40k,600}<1,-1|2,-1>(4,-1,CODE:7,CODE:5:16,^45m) [CODE:0..0x1fffff]"), + scancode_mask: 0x1f007f, + protocol_no: 6, + }, + LinuxProtocol { + name: "sony15", + decoder: "sony", + irp: Some("{40k,600}<1,-1|2,-1>(4,-1,CODE:7,CODE:8:16,^45m) [CODE:0..0xffffff]"), + scancode_mask: 0xff007f, + protocol_no: 7, + }, + LinuxProtocol { + name: "sony20", + decoder: "sony", + irp: Some("{40k,600}<1,-1|2,-1>(4,-1,CODE:7,CODE:5:16,CODE:8:8,^45m) [CODE:0..0x1fffff]"), + scancode_mask: 0x1fff7f, + protocol_no: 8, + }, + LinuxProtocol { + name: "nec", + decoder: "nec", + irp: Some("{38.4k,564}<1,-1|1,-3>(16,-8,CODE:8:8,~CODE:8:8,CODE:8,~CODE:8,1,^108m,(16,-4,1,^108m)*) [CODE:0..0xffff]"), + scancode_mask: 0xffff, + protocol_no: 9, + }, + LinuxProtocol { + name: "necx", + decoder: "nec", + irp: Some("{38.4k,564}<1,-1|1,-3>(16,-8,CODE:8:16,CODE:8:8,CODE:8,~CODE:8,1,^108m,(16,-4,1,^108m)*) [CODE:0..0xffffff]"), + scancode_mask: 0xffffff, + protocol_no: 10, + }, + LinuxProtocol { + name: "nec32", + decoder: "nec", + irp: Some("{38.4k,564}<1,-1|1,-3>(16,-8,CODE:8:16,CODE:8:24,CODE:8,CODE:8:8,1,^108m,(16,-4,1,^108m)*) [CODE:0..0xffffffff]"), + scancode_mask: 0xffff_ffff, + protocol_no: 11, + }, + LinuxProtocol { + name: "sanyo", + decoder: "sanyo", + irp: Some("{38k,562.5}<1,-1|1,-3>(16,-8,CODE:13:8,~CODE:13:8,CODE:8,~CODE:8,1,-42,(16,-8,1,-150)*) [CODE:0..0x1fffff]"), + scancode_mask: 0x1fffff, + protocol_no: 12, + }, + LinuxProtocol { + name: "mcir2-kbd", + decoder: "mce_kbd", + irp: None, + scancode_mask: u32::MAX, + protocol_no: 13, + }, + LinuxProtocol { + name: "mcir2-mse", + decoder: "mce_kbd", + irp: None, + scancode_mask: u32::MAX, + protocol_no: 14, + }, + LinuxProtocol { + name: "rc6_0", + decoder: "rc6", + irp: Some("{36k,444,msb}<-1,1|1,-1>(6,-2,1:1,0:3,<-2,2|2,-2>(T:1),CODE:16,^107m) [CODE:0..0xffff,T@:0..1=0]"), + scancode_mask: 0xffff, + protocol_no: 15, + }, + LinuxProtocol { + name: "rc6_6a_20", + decoder: "rc6", + irp: Some("{36k,444,msb}<-1,1|1,-1>(6,-2,1:1,6:3,<-2,2|2,-2>(T:1),CODE:20,-100m) [CODE:0..0xfffff,T@:0..1=0]"), + scancode_mask: 0xf_ffff, + protocol_no: 16, + }, + LinuxProtocol { + name: "rc6_6a_24", + decoder: "rc6", + irp: Some("{36k,444,msb}<-1,1|1,-1>(6,-2,1:1,6:3,<-2,2|2,-2>(T:1),CODE:24,^105m) [CODE:0..0xffffff,T@:0..1=0]"), + scancode_mask: 0xff_ff_ff, + protocol_no: 17, + }, + LinuxProtocol { + name: "rc6_6a_32", + decoder: "rc6", + irp: Some("{36k,444,msb}<-1,1|1,-1>(6,-2,1:1,6:3,<-2,2|2,-2>(T:1),CODE:32,MCE=(CODE>>16)==0x800f||(CODE>>16)==0x8034||(CODE>>16)==0x8046,^105m){MCE=0}[CODE:0..0xffffffff,T@:0..1=0]"), + scancode_mask: 0xffff_ffff, + protocol_no: 18, + }, + LinuxProtocol { + name: "rc6_mce", + decoder: "rc6", + irp: Some("{36k,444,msb}<-1,1|1,-1>(6,-2,1:1,6:3,-2,2,CODE:16:16,T:1,CODE:15,MCE=(CODE>>16)==0x800f||(CODE>>16)==0x8034||(CODE>>16)==0x8046,^105m){MCE=1}[CODE:0..0xffffffff,T@:0..1=0]"), + scancode_mask: 0xffff_7fff, + protocol_no: 19, + }, + LinuxProtocol { + name: "sharp", + decoder: "sharp", + irp: Some("{38k,264}<1,-3|1,-7>(CODE:5:8,CODE:8,1:2,1,-165,CODE:5:8,~CODE:8,2:2,1,-165) [CODE:0..0x1fff]"), + scancode_mask: 0x1fff, + protocol_no: 20, + }, + LinuxProtocol { + name: "xmp", + decoder: "xmp", + // TODO: irp + irp: None, + scancode_mask: u32::MAX, + protocol_no: 21, + }, + LinuxProtocol { + name: "cec", + decoder: "cec", + irp: None, + scancode_mask: u32::MAX, + protocol_no: 22, + }, + LinuxProtocol { + name: "imon", + decoder: "imon", + // TODO: {416,38k,msb}<-1|1>(1,>1,P=CHK&1)|0:2,(CHK=CHK>>1,P=1)>(CODE:31),^106m){P=1,CHK=0x7efec2} [CODE:0..0x7fffffff], + irp: None, + scancode_mask: u32::MAX, + protocol_no: 23, + }, + LinuxProtocol { + name: "rc-mm-12", + decoder: "rc-mm", + irp: Some("{36k,msb}<166.7,-277.8|166.7,-444.4|166.7,-611.1|166.7,-777.8>(416.7,-277.8,CODE:12,166.7,^27.778m) [CODE:0..0xfff]"), + scancode_mask: 0xfff, + protocol_no: 24, + }, + LinuxProtocol { + name: "rc-mm-24", + decoder: "rc-mm", + irp: Some("{36k,msb}<166.7,-277.8|166.7,-444.4|166.7,-611.1|166.7,-777.8>(416.7,-277.8,CODE:24,166.7,^27.778m) [CODE:0..0xffffff]"), + scancode_mask: 0xfff_fff, + protocol_no: 25, + }, + LinuxProtocol { + name: "rc-mm-32", + decoder: "rc-mm", + // toggle? + irp: Some("{36k,msb}<166.7,-277.8|166.7,-444.4|166.7,-611.1|166.7,-777.8>(416.7,-277.8,CODE:32,166.7,^27.778m) [CODE:0..0xffffffff]"), + scancode_mask: 0xffff_ffff, + protocol_no: 26, + }, + LinuxProtocol { + name: "xbox-dvd", + decoder: "xbox-dvd", + // trailing space is a guess, should be verified on real hardware + irp: Some("{38k,msb}<550,-900|550,-1900>(4000,-3900,~CODE:12,CODE:12,550,^100m) [CODE:0..0xfff]"), + scancode_mask: 0xfff, + protocol_no: 27, + } +]; + +#[cfg(test)] +mod test { + use super::LinuxProtocol; + use irp::{Irp, Vartable}; + use rand::RngCore; + use std::ffi::CStr; + + #[test] + fn compare_encode_to_irctl() { + for proto in [ + liblircd::rc_proto::RC_PROTO_RC5, + liblircd::rc_proto::RC_PROTO_RC5X_20, + liblircd::rc_proto::RC_PROTO_RC5_SZ, + liblircd::rc_proto::RC_PROTO_JVC, + liblircd::rc_proto::RC_PROTO_SONY12, + liblircd::rc_proto::RC_PROTO_SONY15, + liblircd::rc_proto::RC_PROTO_SONY20, + liblircd::rc_proto::RC_PROTO_NEC, + liblircd::rc_proto::RC_PROTO_NECX, + liblircd::rc_proto::RC_PROTO_NEC32, + liblircd::rc_proto::RC_PROTO_SANYO, + liblircd::rc_proto::RC_PROTO_RC6_0, + liblircd::rc_proto::RC_PROTO_RC6_6A_20, + liblircd::rc_proto::RC_PROTO_RC6_6A_24, + liblircd::rc_proto::RC_PROTO_RC6_6A_32, + liblircd::rc_proto::RC_PROTO_RC6_MCE, + liblircd::rc_proto::RC_PROTO_SHARP, + liblircd::rc_proto::RC_PROTO_RCMM12, + liblircd::rc_proto::RC_PROTO_RCMM24, + liblircd::rc_proto::RC_PROTO_RCMM32, + liblircd::rc_proto::RC_PROTO_XBOX_DVD, + ] { + let name = unsafe { CStr::from_ptr(liblircd::protocol_name(proto)) } + .to_str() + .unwrap(); + let linux = LinuxProtocol::find(name).unwrap(); + + assert_eq!(linux.scancode_mask, unsafe { + liblircd::protocol_scancode_mask(proto) + }); + assert_eq!(linux.protocol_no, proto as u32); + let mut rng = rand::thread_rng(); + + if unsafe { liblircd::protocol_encoder_available(proto) } { + let irp = Irp::parse(linux.irp.unwrap()).unwrap(); + + if proto == liblircd::rc_proto::RC_PROTO_NEC + || proto == liblircd::rc_proto::RC_PROTO_NECX + || proto == liblircd::rc_proto::RC_PROTO_NEC32 + { + assert_eq!(irp.carrier(), 38400); + } else if proto == liblircd::rc_proto::RC_PROTO_JVC { + assert_eq!(irp.carrier(), 37900); + } else { + assert_eq!(irp.carrier(), unsafe { + liblircd::protocol_carrier(proto) as i64 + }); + } + + let max_size = unsafe { liblircd::protocol_max_size(proto) } as usize; + + let mut irctl = vec![0u32; max_size]; + + for _ in 0..1000 { + let scancode = rng.next_u32() & linux.scancode_mask; + + println!("proto: {proto:?} scancode:{scancode:#x}"); + + irctl.resize(max_size as usize, 0); + + let len = + unsafe { liblircd::protocol_encode(proto, scancode, irctl.as_mut_ptr()) }; + + irctl.resize(len as usize, 0); + + let mut vars = Vartable::new(); + + vars.set("CODE".into(), scancode as i64); + + let mut our = irp.encode_raw(vars, 0).unwrap(); + our.remove_trailing_gap(); + + if [ + liblircd::rc_proto::RC_PROTO_JVC, + liblircd::rc_proto::RC_PROTO_NEC, + liblircd::rc_proto::RC_PROTO_NECX, + liblircd::rc_proto::RC_PROTO_NEC32, + liblircd::rc_proto::RC_PROTO_SANYO, + liblircd::rc_proto::RC_PROTO_SHARP, + liblircd::rc_proto::RC_PROTO_RC6_0, + liblircd::rc_proto::RC_PROTO_RC6_6A_20, + liblircd::rc_proto::RC_PROTO_RC6_6A_24, + liblircd::rc_proto::RC_PROTO_RC6_6A_32, + liblircd::rc_proto::RC_PROTO_RC6_MCE, + ] + .contains(&proto) + { + assert!(compare_with_rounding(&irctl, &our.raw)); + } else { + assert_eq!(irctl, our.raw); + } + } + } else if let Some(irp) = linux.irp { + let irp = Irp::parse(irp).unwrap(); + + for _ in 0..1000 { + let scancode = rng.next_u32() & linux.scancode_mask; + + let mut vars = Vartable::new(); + + vars.set("CODE".into(), scancode as i64); + + let our = irp.encode_raw(vars, 0).unwrap(); + + assert!(!our.raw.is_empty()); + } + } + } + } + + fn compare_with_rounding(l: &[u32], r: &[u32]) -> bool { + if l == r { + return true; + } + + if l.len() != r.len() { + println!( + "comparing:\n{:?} with\n{:?}\n have different lengths {} and {}", + l, + r, + l.len(), + r.len() + ); + + return false; + } + + for i in 0..l.len() { + // sharp gap in the middle + if l[i] == 40000 && r[i] == 43560 { + continue; + } + + let diff = l[i].abs_diff(r[i]); + + // is the difference more than 168 + if diff > 168 { + println!( + "comparing:\nleft:{:?} with\nright:{:?}\nfailed at position {} out of {} diff {diff}", + l, + r, + i, + l.len() + ); + + return false; + } + } + + true + } + + #[test] + fn find_like() { + let p = LinuxProtocol::find_like("rc6-mce").unwrap(); + assert_eq!(p.name, "rc6_mce"); + + let p = LinuxProtocol::find_like("rcmm12").unwrap(); + assert_eq!(p.name, "rc-mm-12"); + + let p = LinuxProtocol::find_like("sony-12").unwrap(); + assert_eq!(p.name, "sony12"); + } +}