irp/
message.rs

1use super::Message;
2use num::Integer;
3use std::fmt::Write;
4
5impl Message {
6    /// Create an empty packet
7    pub fn new() -> Self {
8        Message::default()
9    }
10
11    /// Create from raw vector of u32
12    pub fn from_raw_slice(raw: &[u32]) -> Self {
13        Message {
14            raw: raw.to_vec(),
15            duty_cycle: None,
16            carrier: None,
17        }
18    }
19
20    /// Concatenate two messages. Self must have a trailing gap.
21    pub fn extend(&mut self, other: &Message) {
22        assert!(self.raw.is_empty() || self.has_trailing_gap());
23
24        if self.carrier.is_none() {
25            self.carrier = other.carrier;
26        }
27
28        if self.duty_cycle.is_none() {
29            self.duty_cycle = other.duty_cycle;
30        }
31
32        self.raw.extend_from_slice(&other.raw);
33    }
34
35    /// Do we have a trailing gap
36    pub fn has_trailing_gap(&self) -> bool {
37        let len = self.raw.len();
38
39        len > 0 && (len % 2) == 0
40    }
41
42    /// Remove any trailing gap
43    pub fn remove_trailing_gap(&mut self) {
44        if self.has_trailing_gap() {
45            self.raw.pop();
46        }
47    }
48
49    /// Print the flash and gap information as an raw ir string
50    pub fn print_rawir(&self) -> String {
51        let mut s = String::new();
52
53        self.raw.iter().enumerate().for_each(|(i, v)| {
54            write!(
55                s,
56                "{}{}{}",
57                if i == 0 { "" } else { " " },
58                if i.is_even() { "+" } else { "-" },
59                v
60            )
61            .unwrap()
62        });
63
64        s
65    }
66
67    /// Parse a raw IR string of the form `+9000 -45000 +2250`
68    pub fn parse(s: &str) -> Result<Self, String> {
69        let mut raw = Vec::new();
70        let mut flash = true;
71
72        for e in s.split(|c: char| c.is_whitespace() || c == ',') {
73            if e.is_empty() {
74                continue;
75            }
76
77            let mut chars = e.chars().peekable();
78
79            match chars.peek() {
80                Some('+') => {
81                    if !flash {
82                        return Err("unexpected ‘+’ encountered".to_string());
83                    }
84                    chars.next();
85                }
86                Some('-') => {
87                    if flash {
88                        return Err("unexpected ‘-’ encountered".to_string());
89                    }
90                    chars.next();
91                }
92                Some(ch) if !ch.is_numeric() => {
93                    return Err(format!("unexpected ‘{ch}’ encountered"));
94                }
95                _ => (),
96            }
97
98            let v = chars.collect::<String>();
99
100            let v = v.parse().map_err(|_| format!("invalid number ‘{v}’"))?;
101
102            if v == 0 {
103                return Err("nonsensical 0 length".to_string());
104            }
105
106            raw.push(v);
107
108            flash = !flash;
109        }
110
111        if raw.is_empty() {
112            return Err("missing length".to_string());
113        }
114
115        Ok(Message {
116            raw,
117            carrier: None,
118            duty_cycle: None,
119        })
120    }
121
122    /// Parse pulse/space text. This format is produces by lirc's mode2 tool.
123    /// Some lirc drivers sometimes produce consecutive pulses or spaces, rather
124    /// than alternating. These are automatically folded into one.
125    ///
126    /// The return value is the line number and the error string.
127    pub fn parse_mode2(s: &str) -> Result<Message, (usize, String)> {
128        let mut res = Vec::new();
129        let mut carrier = None;
130        let mut line_no = 0;
131
132        for line in s.lines() {
133            line_no += 1;
134
135            let mut words = line.split_whitespace();
136
137            let is_pulse = match words.next() {
138                Some("pulse") => true,
139                Some("space") => false,
140                Some("timeout") => false,
141                Some("carrier") => {
142                    match words.next() {
143                        Some(w) => match w.parse() {
144                            Ok(c) => {
145                                if carrier.is_some() && carrier != Some(c) {
146                                    return Err((
147                                        line_no,
148                                        String::from("carrier specified more than once"),
149                                    ));
150                                }
151
152                                if c < 0 {
153                                    return Err((
154                                        line_no,
155                                        format!("negative carrier {c} does not make sense"),
156                                    ));
157                                }
158
159                                carrier = Some(c);
160                            }
161                            Err(_) => {
162                                return Err((
163                                    line_no,
164                                    format!("carrier argument ‘{w}’ is not a number"),
165                                ));
166                            }
167                        },
168                        None => return Err((line_no, String::from("missing carrier value"))),
169                    }
170
171                    if let Some(w) = words.next() {
172                        if !w.starts_with('#') && !w.starts_with("//") {
173                            return Err((line_no, format!("unexpected ‘{w}’")));
174                        }
175                    }
176
177                    continue;
178                }
179                Some(w) => {
180                    if !w.starts_with('#') && !w.starts_with("//") {
181                        return Err((line_no, format!("unexpected ‘{w}’")));
182                    }
183                    continue;
184                }
185                None => {
186                    continue;
187                }
188            };
189
190            let value = match words.next() {
191                Some(w) => match w.parse() {
192                    Ok(0) => {
193                        return Err((line_no, "nonsensical 0 duration".to_string()));
194                    }
195                    Ok(n) => {
196                        if n > 0xff_ff_ff {
197                            return Err((line_no, format!("duration ‘{w}’ too long")));
198                        }
199                        n
200                    }
201                    Err(_) => {
202                        return Err((line_no, format!("invalid duration ‘{w}’")));
203                    }
204                },
205                None => {
206                    return Err((line_no, "missing duration".to_string()));
207                }
208            };
209
210            if let Some(trailing) = words.next() {
211                return Err((line_no, format!("unexpected ‘{trailing}’")));
212            }
213
214            if is_pulse {
215                if res.len() % 2 == 1 {
216                    // two consecutive pulses should be folded
217                    *res.last_mut().unwrap() += value;
218                } else {
219                    res.push(value);
220                }
221            } else if res.len() % 2 == 0 {
222                // two consecutive spaces should be folded, but leading spaces are ignored
223                if let Some(last) = res.last_mut() {
224                    *last += value;
225                }
226            } else {
227                res.push(value);
228            }
229        }
230
231        if res.is_empty() {
232            if line_no == 0 {
233                line_no = 1;
234            }
235            return Err((line_no, "missing pulse".to_string()));
236        }
237
238        Ok(Message {
239            duty_cycle: None,
240            carrier,
241            raw: res,
242        })
243    }
244}
245
246#[test]
247fn parse_mode2() {
248    assert_eq!(
249        Message::parse_mode2("").err(),
250        Some((1, "missing pulse".to_string()))
251    );
252    assert_eq!(
253        Message::parse_mode2("pulse 0").err(),
254        Some((1, "nonsensical 0 duration".to_string()))
255    );
256    assert_eq!(
257        Message::parse_mode2("pulse").err(),
258        Some((1, "missing duration".to_string()))
259    );
260    assert_eq!(
261        Message::parse_mode2("pulse abc").err(),
262        Some((1, "invalid duration ‘abc’".to_string()))
263    );
264    assert_eq!(
265        Message::parse_mode2("pulse 1\npulse 2").unwrap().raw,
266        vec!(3u32)
267    );
268    assert_eq!(
269        Message::parse_mode2("space 1\r\nspace 2\npulse 1\npulse 2")
270            .unwrap()
271            .raw,
272        vec!(3u32)
273    );
274    assert_eq!(
275        Message::parse_mode2("pulse 100\npulse 21\nspace 10\nspace 50")
276            .unwrap()
277            .raw,
278        vec!(121u32, 60u32)
279    );
280    assert_eq!(
281        Message::parse_mode2("polse 100\nspace 10\nspace 50").err(),
282        Some((1, "unexpected ‘polse’".to_string()))
283    );
284    assert_eq!(
285        Message::parse_mode2("pulse 100\nspace 10\npulse 50")
286            .unwrap()
287            .raw,
288        vec!(100u32, 10u32, 50u32)
289    );
290    assert_eq!(
291        Message::parse_mode2("pulse 100\nspace 10\npulse 50\nspace 34134134").err(),
292        Some((4, "duration ‘34134134’ too long".to_string()))
293    );
294    assert_eq!(
295        Message::parse_mode2("pulse 100\nspace 10\npulse 50 foobar\nspace 34134134").err(),
296        Some((3, "unexpected ‘foobar’".to_string()))
297    );
298    assert_eq!(
299        Message::parse_mode2("pulse 100\nspace 10\ncarrier foobar\nspace 34134134").err(),
300        Some((3, "carrier argument ‘foobar’ is not a number".to_string()))
301    );
302    assert_eq!(
303        Message::parse_mode2("pulse 100\nspace 10\ncarrier\nspace 34134134").err(),
304        Some((3, "missing carrier value".to_string()))
305    );
306    assert_eq!(
307        Message::parse_mode2("pulse 100\nspace 10\ncarrier 500 x\nspace 34134134").err(),
308        Some((3, "unexpected ‘x’".to_string()))
309    );
310    assert_eq!(
311        Message::parse_mode2("pulse 100\nspace 10\npulse 50\ncarrier 500 // hiya\ntimeout 100000")
312            .unwrap(),
313        Message {
314            carrier: Some(500),
315            duty_cycle: None,
316            raw: vec!(100u32, 10u32, 50u32, 100000u32)
317        }
318    );
319}
320
321#[test]
322fn parse_test() {
323    assert_eq!(
324        Message::parse("+100 +100"),
325        Err("unexpected ‘+’ encountered".to_string())
326    );
327
328    assert_eq!(
329        Message::parse("+100 -100 -1"),
330        Err("unexpected ‘-’ encountered".to_string())
331    );
332
333    assert_eq!(
334        Message::parse("+100 -100"),
335        Ok(Message {
336            raw: vec!(100, 100),
337            duty_cycle: None,
338            carrier: None
339        })
340    );
341
342    assert_eq!(Message::parse(""), Err("missing length".to_string()));
343
344    assert_eq!(Message::parse("+a"), Err("invalid number ‘a’".to_string()));
345
346    assert_eq!(
347        Message::parse("+0"),
348        Err("nonsensical 0 length".to_string())
349    );
350
351    assert_eq!(
352        Message::parse("100  \n100\r +1"),
353        Ok(Message {
354            raw: vec!(100u32, 100u32, 1u32),
355            duty_cycle: None,
356            carrier: None
357        })
358    );
359    assert_eq!(
360        Message::parse("100,100,+1,-20000"),
361        Ok(Message {
362            raw: vec!(100u32, 100u32, 1u32, 20000u32),
363            duty_cycle: None,
364            carrier: None
365        })
366    );
367}
368
369#[test]
370fn print_test() {
371    let m = Message {
372        raw: vec![100, 50, 75],
373        carrier: None,
374        duty_cycle: None,
375    };
376
377    assert_eq!(m.print_rawir(), "+100 -50 +75");
378}