omnitiles/protocol/
parser.rs

1// SPDX-License-Identifier: MIT
2// © 2025–2026 Christopher Liu
3
4//! Message parser for the OmniTiles command protocol.
5//!
6//! This module provides functionality to parse incoming command messages
7//! and convert them into actionable commands for the OmniTiles system.
8
9use crate::protocol::messages::*;
10
11enum State {
12    WaitStart,
13    WaitId,
14    WaitPayload { id: u8 },
15    WaitChecksum { id: u8, payload: Option<u8> },
16}
17
18pub struct Parser {
19    state: State,
20    checksum: u8,
21}
22
23impl Parser {
24    pub fn new() -> Self {
25        Self {
26            state: State::WaitStart,
27            checksum: 0,
28        }
29    }
30
31    /// Process a single incoming byte. Returns `Some(Command)` if a complete packet is received.
32    pub fn push(&mut self, byte: u8) -> Option<Command> {
33        match self.state {
34            State::WaitStart => {
35                if byte == START_BYTE {
36                    self.state = State::WaitId;
37                    self.checksum = 0;
38                }
39            }
40            State::WaitId => {
41                self.checksum = self.checksum.wrapping_add(byte);
42
43                match byte {
44                    // Messages with payload
45                    MSG_M1_EXTEND | MSG_M1_RETRACT | MSG_M1_SET_POSITION | MSG_M2_EXTEND
46                    | MSG_M2_RETRACT | MSG_M2_SET_POSITION => {
47                        self.state = State::WaitPayload { id: byte };
48                    }
49                    // Messages with no payload
50                    MSG_M1_BRAKE | MSG_M2_BRAKE | MSG_PING => {
51                        self.state = State::WaitChecksum {
52                            id: byte,
53                            payload: None,
54                        };
55                    }
56                    _ => {
57                        // Unknown message ID, reset state
58                        self.state = State::WaitStart;
59                    }
60                }
61            }
62            State::WaitPayload { id } => {
63                self.checksum = self.checksum.wrapping_add(byte);
64                self.state = State::WaitChecksum {
65                    id,
66                    payload: Some(byte),
67                };
68            }
69            State::WaitChecksum { id, payload } => {
70                // Verify checksum
71                let valid = byte == self.checksum;
72                self.state = State::WaitStart; // Reset for next message
73
74                if valid {
75                    return match (id, payload) {
76                        (MSG_M1_EXTEND, Some(p)) => Some(Command::M1Extend(p)),
77                        (MSG_M1_RETRACT, Some(p)) => Some(Command::M1Retract(p)),
78                        (MSG_M1_BRAKE, None) => Some(Command::M1Brake),
79                        (MSG_M1_SET_POSITION, Some(p)) => Some(Command::M1SetPosition(p)),
80                        (MSG_M2_EXTEND, Some(p)) => Some(Command::M2Extend(p)),
81                        (MSG_M2_RETRACT, Some(p)) => Some(Command::M2Retract(p)),
82                        (MSG_M2_BRAKE, None) => Some(Command::M2Brake),
83                        (MSG_M2_SET_POSITION, Some(p)) => Some(Command::M2SetPosition(p)),
84                        (MSG_PING, None) => Some(Command::Ping),
85                        _ => None,
86                    };
87                }
88            }
89        }
90        None
91    }
92}