diff --git a/irp/README.md b/irp/README.md index 432f189f..2bb05a20 100644 --- a/irp/README.md +++ b/irp/README.md @@ -194,7 +194,7 @@ fn main() { {36k,msb,889}<1,-1|-1,1>((1,~F:1:6,T:1,D:5,F:6,^114m)*,T=1-T) [D:0..31,F:0..127,T@:0..1=0]"#) .expect("parse should succeed"); - let dfa = irp.compile().expect("build dfa should succeed"); + let dfa = irp.compile(100, 3).expect("build dfa should succeed"); // Create a decoder with 100 microsecond tolerance, 30% relative tolerance, // and 20000 microseconds maximum gap. let mut decoder = Decoder::new(100, 30, 20000); diff --git a/irp/src/build_dfa.rs b/irp/src/build_dfa.rs index 29e16874..c84d8e8d 100644 --- a/irp/src/build_dfa.rs +++ b/irp/src/build_dfa.rs @@ -1,5 +1,5 @@ use super::{ - build_nfa::{Action, Edge, Vertex, NFA}, + build_nfa::{Action, Edge, Length, Vertex, NFA}, expression::clone_filter, Expression, Irp, }; @@ -24,18 +24,20 @@ struct Path { struct DfaEdge { from: usize, flash: bool, - length: Rc, + length: Length, } impl NFA { /// Build the DFA from the NFA - pub fn build_dfa(&self) -> DFA { + pub fn build_dfa(&self, aeps: u32, eps: u32) -> DFA { let mut builder = Builder { verts: Vec::new(), nfa_to_dfa: HashMap::new(), edges: HashMap::new(), nfa: self, visited: Vec::new(), + aeps, + eps, }; builder.build(); @@ -55,10 +57,10 @@ impl DFA { impl Irp { /// Generate an DFA decoder state machine for this IRP - pub fn compile(&self) -> Result { + pub fn compile(&self, aeps: u32, eps: u32) -> Result { let nfa = self.build_nfa()?; - Ok(nfa.build_dfa()) + Ok(nfa.build_dfa(aeps, eps)) } } @@ -68,6 +70,8 @@ struct Builder<'a> { edges: HashMap, verts: Vec, visited: Vec, + aeps: u32, + eps: u32, } impl<'a> Builder<'a> { @@ -109,7 +113,7 @@ impl<'a> Builder<'a> { fn add_input_path_to_dfa(&mut self, flash: bool, path: &[Path]) { let from = self.nfa_to_dfa[&path[0].from]; let nfa_to = path[path.len() - 1].to; - let length = self.path_length(path); + let length = self.length_to_range(self.path_length(path)); let dfa_edge = DfaEdge { from, @@ -117,7 +121,22 @@ impl<'a> Builder<'a> { length: length.clone(), }; - let actions = self.path_actions(path, flash, length); + let mut actions = self.path_actions(path); + + actions.insert( + 0, + if flash { + Action::Flash { + length, + complete: true, + } + } else { + Action::Gap { + length, + complete: true, + } + }, + ); if let Some(to) = self.edges.get(&dfa_edge) { if self.verts[from] @@ -156,6 +175,11 @@ impl<'a> Builder<'a> { { match action { Action::Gap { length, .. } | Action::Flash { length, .. } => { + let length = match length { + Length::Expression(expr) => expr, + Length::Range(..) => unreachable!(), + }; + let length = replace_vars(length, &vars); if let Some(prev) = len { @@ -184,21 +208,9 @@ impl<'a> Builder<'a> { len.unwrap() } - fn path_actions(&self, path: &[Path], flash: bool, length: Rc) -> Vec { + fn path_actions(&self, path: &[Path]) -> Vec { let mut res: Vec = Vec::new(); - res.push(if flash { - Action::Flash { - length, - complete: true, - } - } else { - Action::Gap { - length, - complete: true, - } - }); - for elem in path { res.extend(self.nfa.verts[elem.to].entry.iter().cloned()); res.extend( @@ -294,6 +306,22 @@ impl<'a> Builder<'a> { node } + + fn length_to_range(&self, length: Rc) -> Length { + if let Expression::Number(length) = length.as_ref() { + let length = *length as u32; + let min = std::cmp::min( + length.saturating_sub(self.aeps), + (length * (100 - self.eps)) / 100, + ); + + let max = std::cmp::min(length + self.aeps, (length * (100 + self.eps)) / 100); + + Length::Range(min, max) + } else { + Length::Expression(length) + } + } } fn replace_vars(expr: &Rc, vars: &HashMap<&str, Rc>) -> Rc { diff --git a/irp/src/build_nfa.rs b/irp/src/build_nfa.rs index 2719912c..f157c656 100644 --- a/irp/src/build_nfa.rs +++ b/irp/src/build_nfa.rs @@ -4,6 +4,7 @@ use super::{ use log::trace; use std::{ collections::HashMap, + fmt, ops::{Add, BitAnd, BitOr, BitXor, Neg, Not, Rem, Shl, Shr, Sub}, rc::Rc, }; @@ -18,14 +19,20 @@ pub(crate) struct Edge { pub actions: Vec, } -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Debug, Hash, Eq, Clone)] +pub enum Length { + Expression(Rc), + Range(u32, u32), +} + +#[derive(PartialEq, Debug, Hash, Eq, Clone)] pub(crate) enum Action { Flash { - length: Rc, + length: Length, complete: bool, }, Gap { - length: Rc, + length: Length, complete: bool, }, Set { @@ -101,12 +108,12 @@ impl NFA { let length = Rc::new(Expression::Number((*raw).into())); let actions = vec![if flash { Action::Flash { - length, + length: Length::Expression(length), complete: true, } } else { Action::Gap { - length, + length: Length::Expression(length), complete: true, } }]; @@ -1144,7 +1151,7 @@ impl<'a> Builder<'a> { self.add_edge(Edge { dest: node, actions: vec![Action::Flash { - length: Rc::new(Expression::Number(len)), + length: Length::Expression(Rc::new(Expression::Number(len))), complete: last, }], }); @@ -1163,7 +1170,7 @@ impl<'a> Builder<'a> { self.add_edge(Edge { dest: node, actions: vec![Action::Gap { - length: Rc::new(Expression::Number(len)), + length: Length::Expression(Rc::new(Expression::Number(len))), complete: last, }], }); @@ -1193,7 +1200,7 @@ impl<'a> Builder<'a> { self.add_edge(Edge { dest: node, actions: vec![Action::Flash { - length: expr, + length: Length::Expression(expr), complete: last, }], }); @@ -1232,7 +1239,7 @@ impl<'a> Builder<'a> { self.add_edge(Edge { dest: node, actions: vec![Action::Gap { - length: expr, + length: Length::Expression(expr), complete: last, }], }); @@ -1263,7 +1270,9 @@ impl<'a> Builder<'a> { self.add_edge(Edge { dest: node, actions: vec![Action::Gap { - length: Rc::new(Expression::Identifier("$extent".into())), + length: Length::Expression(Rc::new(Expression::Identifier( + "$extent".into(), + ))), complete: last, }], }); @@ -1570,3 +1579,12 @@ impl<'a> Builder<'a> { new.unwrap_or_else(|| expr.clone()) } } + +impl fmt::Display for Length { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Length::Expression(e) => write!(f, "{e}"), + Length::Range(min, max) => write!(f, "{min}..{max}"), + } + } +} diff --git a/irp/src/decoder.rs b/irp/src/decoder.rs index 8ba45bfb..4e9b7fb0 100644 --- a/irp/src/decoder.rs +++ b/irp/src/decoder.rs @@ -1,6 +1,6 @@ use super::{ build_dfa::DFA, - build_nfa::{Action, NFA}, + build_nfa::{Action, Length, NFA}, InfraredData, Vartable, }; use crate::{build_nfa::Vertex, Event, Message}; @@ -148,6 +148,37 @@ impl<'a> Decoder<'a> { } } + pub(crate) fn consume_flash_range( + &self, + ir: &mut Option, + min: i64, + max: i64, + complete: bool, + ) -> bool { + match ir { + Some(InfraredData::Flash(received)) => { + let received = *received as i64; + if received >= min && received <= max { + trace!("matched flash {} (range {}..{})", received, min, max); + *ir = None; + true + } else if !complete && received > min { + trace!( + "matched flash {} (range {}..{}) (incomplete consume)", + received, + min, + max + ); + *ir = Some(InfraredData::Flash((received - min) as u32)); + true + } else { + false + } + } + _ => false, + } + } + pub(crate) fn consume_gap( &self, ir: &mut Option, @@ -180,6 +211,47 @@ impl<'a> Decoder<'a> { } } + pub(crate) fn consume_gap_range( + &self, + ir: &mut Option, + min: i64, + max: i64, + complete: bool, + ) -> bool { + match ir { + Some(InfraredData::Gap(received)) => { + let received = *received as i64; + + if max > self.max_gap as i64 && received >= self.max_gap as i64 { + trace!( + "large gap matched gap {} (range {}..{})", + received, + min, + max + ); + *ir = None; + true + } else if received >= min && received <= max { + trace!("matched gap {} (range {}..{})", received, min, max); + *ir = None; + true + } else if !complete && received > min { + trace!( + "matched gap {} (range {}..{}) (incomplete consume)", + received, + min, + max, + ); + *ir = Some(InfraredData::Gap((received - min) as u32)); + true + } else { + false + } + } + _ => false, + } + } + pub fn nfa_input(&mut self, ir: InfraredData, nfa: &NFA, callback: F) where F: FnMut(Event, HashMap), @@ -287,7 +359,7 @@ impl<'a> Decoder<'a> { for a in actions { match a { Action::Flash { - length: expected, + length: Length::Expression(expected), complete, } => { let expected = expected.eval(vartab).unwrap(); @@ -300,8 +372,25 @@ impl<'a> Decoder<'a> { return ActionResult::Fail; } + Action::Flash { + length: Length::Range(min, max), + complete, + } => { + if ir.is_none() { + return ActionResult::Retry(vartable); + } else if self.consume_flash_range( + &mut ir, + (*min).into(), + (*max).into(), + *complete, + ) { + continue; + } + + return ActionResult::Fail; + } Action::Gap { - length: expected, + length: Length::Expression(expected), complete, } => { let expected = expected.eval(vartab).unwrap(); @@ -314,6 +403,23 @@ impl<'a> Decoder<'a> { return ActionResult::Fail; } + Action::Gap { + length: Length::Range(min, max), + complete, + } => { + if ir.is_none() { + return ActionResult::Retry(vartable); + } else if self.consume_gap_range( + &mut ir, + (*min).into(), + (*max).into(), + *complete, + ) { + continue; + } + + return ActionResult::Fail; + } Action::Set { var, expr } => { let val = expr.eval(&vartable).unwrap(); trace!("set {} = {} = {}", var, expr, val); diff --git a/irp/src/lib.rs b/irp/src/lib.rs index 5b04e12a..582577f5 100644 --- a/irp/src/lib.rs +++ b/irp/src/lib.rs @@ -201,7 +201,7 @@ pub enum InfraredData { } /// Decoded key event -#[derive(PartialEq, Debug, Clone, Copy)] +#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)] pub enum Event { Down, Repeat, diff --git a/irp/tests/tests.rs b/irp/tests/tests.rs index 5c22082e..4cee11d1 100644 --- a/irp/tests/tests.rs +++ b/irp/tests/tests.rs @@ -450,6 +450,8 @@ fn decode_all() { } }; + let dfa = nfa.build_dfa(10, 3); + let max_gap = if protocol.name == "Epson" { 100000 } else { @@ -463,7 +465,7 @@ fn decode_all() { for n in first..10 { let repeats = if n < 3 { n } else { rng.gen_range(n..n + 20) }; - decoder.nfa_input(InfraredData::Reset, &nfa, |_, _| {}); + decoder.dfa_input(InfraredData::Reset, &dfa, |_, _| {}); let mut vars = Vartable::new(); let mut params = HashMap::new(); diff --git a/src/bin/commands/decode.rs b/src/bin/commands/decode.rs index a949a285..74cbc60d 100644 --- a/src/bin/commands/decode.rs +++ b/src/bin/commands/decode.rs @@ -42,7 +42,7 @@ fn decode_irp(matches: &clap::ArgMatches) { } }; - let dfa = nfa.build_dfa(); + let dfa = nfa.build_dfa(abs_tolerance, rel_tolerance); match matches.value_of("GRAPHVIZ") { Some("nfa") => { @@ -255,8 +255,8 @@ fn decode_irp(matches: &clap::ArgMatches) { } fn decode_lircd(matches: &clap::ArgMatches) { - let graphviz_step = matches.value_of("GRAPHVIZ") == Some("nfa-step"); - let graphviz = matches.value_of("GRAPHVIZ") == Some("nfa"); + let graphviz_step = matches.value_of("GRAPHVIZ") == Some("dfa-step"); + let graphviz = matches.value_of("GRAPHVIZ") == Some("dfa"); let mut abs_tolerance = str::parse(matches.value_of("AEPS").unwrap()).expect("number expected"); let rel_tolerance = str::parse(matches.value_of("EPS").unwrap()).expect("number expected"); @@ -356,10 +356,13 @@ fn decode_lircd(matches: &clap::ArgMatches) { let decoder = remote.decoder(Some(abs_tolerance), Some(rel_tolerance), max_gap); if graphviz { - let filename = format!("{}_nfa.dot", remote.name); - info!("saving nfa as {}", filename); + let mut filename = format!("{}_dfa.dot", remote.name); + // characters not allowed on Windows/Mac/Linux: https://stackoverflow.com/a/35352640 + filename + .retain(|c| !matches!(c, ':' | '/' | '\\' | '*' | '?' | '"' | '<' | '>' | '|')); + info!("saving dfa as {}", filename); - decoder.nfa.dotgraphviz(&filename); + decoder.dfa.dotgraphviz(&filename); } decoder @@ -374,11 +377,14 @@ fn decode_lircd(matches: &clap::ArgMatches) { }); if graphviz_step { - let filename = format!("{}_nfa_step_{:04}.dot", decoder.remote.name, index); + let mut filename = format!("{}_dfa_step_{:04}.dot", decoder.remote.name, index); + filename.retain(|c| { + !matches!(c, ':' | '/' | '\\' | '*' | '?' | '"' | '<' | '>' | '|') + }); - info!("saving nfa at step {} as {}", index, filename); + info!("saving dfa at step {} as {}", index, filename); - decoder.nfa.dotgraphviz(&filename); + decoder.dfa.dotgraphviz(&filename); } } } diff --git a/src/lircd_conf/decode.rs b/src/lircd_conf/decode.rs index f2552604..baddae94 100644 --- a/src/lircd_conf/decode.rs +++ b/src/lircd_conf/decode.rs @@ -1,10 +1,10 @@ use super::Remote; -use irp::{Decoder, InfraredData, Irp, NFA}; +use irp::{Decoder, InfraredData, Irp, DFA, NFA}; use log::debug; pub struct LircDecoder<'a> { pub remote: &'a Remote, - pub nfa: NFA, + pub dfa: DFA, pub decoder: Decoder<'a>, } @@ -35,15 +35,16 @@ impl Remote { nfa }; - let decoder = Decoder::new( - abs_tolerance.unwrap_or(self.aeps as u32), - rel_tolerance.unwrap_or(self.eps as u32), - max_gap, - ); + let aeps = abs_tolerance.unwrap_or(self.aeps as u32); + let eps = rel_tolerance.unwrap_or(self.eps as u32); + + let dfa = nfa.build_dfa(aeps, eps); + + let decoder = Decoder::new(aeps, eps, max_gap); LircDecoder { remote: self, - nfa, + dfa, decoder, } } @@ -54,7 +55,7 @@ impl<'a> LircDecoder<'a> { where F: FnMut(&'a str, u64), { - self.decoder.nfa_input(ir, &self.nfa, |_, vars| { + self.decoder.dfa_input(ir, &self.dfa, |_, vars| { if let Some(decoded) = vars.get("CODE") { if self.remote.raw_codes.is_empty() { let (mask, toggle_bit_mask) = if self.remote.toggle_bit_mask.count_ones() == 1 { diff --git a/tests/decode_tests.rs b/tests/decode_tests.rs index 8403485c..97a3e7e2 100644 --- a/tests/decode_tests.rs +++ b/tests/decode_tests.rs @@ -21,7 +21,6 @@ fn toggle_bit_mask() { assert_eq!( stdout, r#"decoded: remote:DLink_DSM-10 code:KEY_1 -decoded: remote:DLink_DSM-10 code:KEY_1 "# ); @@ -49,7 +48,6 @@ fn ignore_mask() { assert_eq!( stdout, r#"decoded: remote:Apple_A1156 code:KEY_PLAY -decoded: remote:Apple_A1156 code:KEY_PLAY "# ); @@ -72,7 +70,6 @@ decoded: remote:Apple_A1156 code:KEY_PLAY assert_eq!( stdout, r#"decoded: remote:Apple_A1156 code:KEY_PLAY -decoded: remote:Apple_A1156 code:KEY_PLAY "# ); } diff --git a/tests/lircd_conf_tests.rs b/tests/lircd_conf_tests.rs index 9029b623..7fdf7fa5 100644 --- a/tests/lircd_conf_tests.rs +++ b/tests/lircd_conf_tests.rs @@ -53,7 +53,7 @@ fn lircd_encode_decode(path: &Path) { let our_remote = our_conf.next().unwrap(); - let mut decoder = our_remote.decoder(Some(10), Some(1), 200000); + let mut decoder = our_remote.decoder(Some(0), Some(0), 200000); if lircd_remote.is_raw() { for (our_code, lircd_code) in our_remote.raw_codes.iter().zip(lircd_remote.codes_iter()) @@ -98,7 +98,23 @@ fn lircd_encode_decode(path: &Path) { }); } - assert!(decoded.contains(&our_code.name.as_str())); + if !decoded.contains(&our_code.name.as_str()) { + if decoded.len() == 1 { + if let Some(x) = our_remote.raw_codes.iter().find(|e| e.name == decoded[0]) + { + let mut raw = message.raw.clone(); + raw.pop(); + assert_eq!(x.rawir, raw); + continue; + } + } + + println!("{}", message.print_rawir()); + panic!( + "DECODE MISMATCH got: {decoded:?} expected: {}", + our_code.name + ); + } } }