1use crate::command::Command;
4use core::convert::From;
5use core::fmt::{self, Display, Formatter};
6
7pub fn checksum(buf: &[u8]) -> u8 {
9 buf.iter()
10 .fold(0x00, |acc: u8, &x: &u8| acc.overflowing_sub(x).0)
11}
12
13const START_BYTE: u8 = 0xff;
14const COMMAND_MAGIC_BYTE: u8 = 0x01;
15
16#[derive(Clone, Debug, Eq, Hash, PartialEq)]
18pub struct Frame([u8; 9]);
19
20impl From<Command> for Frame {
21 fn from(command: Command) -> Self {
22 let mut buf = [START_BYTE, COMMAND_MAGIC_BYTE, 0, 0, 0, 0, 0, 0, 0];
23 buf[2..8].copy_from_slice(&command.serialize());
24 buf[8] = checksum(&buf[1..8]);
25 Self(buf)
26 }
27}
28
29impl AsRef<[u8]> for Frame {
30 fn as_ref(&self) -> &[u8] {
31 &self.0
32 }
33}
34
35impl Frame {
36 pub fn new(data: [u8; 9]) -> Self {
38 Self(data)
39 }
40
41 pub fn into_inner(self) -> [u8; 9] {
43 self.0
44 }
45
46 fn start_byte(&self) -> u8 {
47 self.0[0]
48 }
49
50 pub fn has_valid_start_byte(&self) -> bool {
52 self.start_byte() == START_BYTE
53 }
54
55 pub fn is_response(&self) -> bool {
57 self.0[1] != COMMAND_MAGIC_BYTE
58 }
59
60 pub fn op_code(&self) -> u8 {
62 if self.is_response() {
63 self.0[1]
64 } else {
65 self.0[2]
66 }
67 }
68
69 fn checksum(&self) -> u8 {
70 self.0[8]
71 }
72
73 pub fn data(&self) -> &[u8] {
75 if self.is_response() {
76 &self.0[2..8]
77 } else {
78 &self.0[3..8]
79 }
80 }
81
82 pub fn has_valid_checksum(&self) -> bool {
84 checksum(&self.0[1..8]) == self.checksum()
85 }
86
87 pub fn validate(&self) -> Result<(), ValidateFrameError> {
92 if !self.has_valid_start_byte() {
93 Err(ValidateFrameError::InvalidStartByte(self.start_byte()))
94 } else if !self.has_valid_checksum() {
95 Err(ValidateFrameError::InvalidChecksum {
96 expected: checksum(&self.0[1..8]),
97 actual: self.checksum(),
98 })
99 } else {
100 Ok(())
101 }
102 }
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum ValidateFrameError {
107 InvalidStartByte(u8),
109 InvalidChecksum { expected: u8, actual: u8 },
111}
112
113impl Display for ValidateFrameError {
114 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
115 use ValidateFrameError::*;
116 match self {
117 InvalidStartByte(got) => {
118 write!(f, "expected start byte 0xff, but got 0x{got:x}")
119 }
120 InvalidChecksum { expected, actual } => {
121 write!(f, "invalid checksum (got {actual} instead of {expected})",)
122 }
123 }
124 }
125}
126
127#[cfg(std)]
128impl std::error::Error for Error {}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use crate::command::Command;
134
135 #[test]
136 fn test_frame_read_co2_command() {
137 let frame = Frame::new([0xff, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79]);
138 assert!(frame.has_valid_start_byte());
139 assert!(!frame.is_response());
140 assert_eq!(frame.op_code(), 0x86);
141 assert_eq!(frame.data(), [0x00; 5]);
142 assert!(frame.has_valid_checksum());
143 assert!(frame.validate().is_ok());
144 }
145
146 #[test]
147 fn test_frame_read_co2_response() {
148 let frame = Frame::new([0xff, 0x86, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x77]);
149 assert!(frame.has_valid_start_byte());
150 assert!(frame.is_response());
151 assert_eq!(frame.op_code(), 0x86);
152 assert_eq!(frame.data(), [0x01, 0x02, 0x00, 0x00, 0x00, 0x00]);
153 assert!(frame.has_valid_checksum());
154 assert!(frame.validate().is_ok());
155 }
156
157 #[test]
158 fn test_frame_invalid_start_byte() {
159 let frame = Frame::new([0x00; 9]);
160 assert!(!frame.has_valid_start_byte());
161 assert_eq!(
162 frame.validate(),
163 Err(ValidateFrameError::InvalidStartByte(0x00))
164 );
165 }
166
167 #[test]
168 fn test_frame_invalid_checksum() {
169 let frame = Frame::new([0xff; 9]);
170 assert!(!frame.has_valid_checksum());
171 assert_eq!(
172 frame.validate(),
173 Err(ValidateFrameError::InvalidChecksum {
174 expected: 0x07,
175 actual: 0xff,
176 })
177 );
178 }
179
180 #[test]
181 fn test_frame_from_command() {
182 let frame: Frame = Command::SetSelfCalibrate(true).into();
183 assert!(frame.has_valid_start_byte());
184 assert!(!frame.is_response());
185 assert_eq!(frame.op_code(), Command::SetSelfCalibrate(true).op_code());
186 assert_eq!(
187 frame.data(),
188 &Command::SetSelfCalibrate(true).serialize()[1..]
189 );
190 assert!(frame.has_valid_checksum());
191 assert!(frame.validate().is_ok());
192 }
193
194 #[test]
195 fn test_checksum() {
196 assert_eq!(checksum(&[0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00]), 0x79);
197 }
198}