1mod app_control;
2mod monitor;
3mod profile;
4mod recipe;
5
6use super::{hardware_enums::*, machine_enum::*};
7pub use app_control::*;
8pub use monitor::*;
9pub use profile::*;
10pub use recipe::*;
11
12pub trait PartialEncode {
14 fn partial_encode(&self, out: &mut Vec<u8>);
15
16 fn encode(&self) -> Vec<u8> {
17 let mut v = vec![];
18 self.partial_encode(&mut v);
19 v
20 }
21}
22
23impl PartialEncode for u8 {
24 fn partial_encode(&self, out: &mut Vec<u8>) {
25 out.push(*self);
26 }
27}
28
29impl PartialEncode for u16 {
30 fn partial_encode(&self, out: &mut Vec<u8>) {
31 out.push((*self >> 8) as u8);
32 out.push(*self as u8);
33 }
34}
35
36impl<T: PartialEncode> PartialEncode for Vec<T> {
37 fn partial_encode(&self, out: &mut Vec<u8>) {
38 for t in self.iter() {
39 t.partial_encode(out);
40 }
41 }
42}
43
44impl<T: MachineEnumerable<T>> PartialEncode for &MachineEnum<T> {
45 fn partial_encode(&self, out: &mut Vec<u8>) {
46 out.push((**self).into())
47 }
48}
49
50pub trait PartialDecode<T> {
52 fn partial_decode(input: &mut &[u8]) -> Option<T>;
54
55 fn decode(mut input: &[u8]) -> (Option<T>, &[u8]) {
57 let ret = Self::partial_decode(&mut input);
58 (ret, input)
59 }
60}
61
62impl<T: PartialDecode<T>> PartialDecode<Vec<T>> for Vec<T> {
63 fn partial_decode(input: &mut &[u8]) -> Option<Self> {
64 let mut v = vec![];
65 while !input.is_empty() {
66 v.push(<T>::partial_decode(input)?);
67 }
68 Some(v)
69 }
70}
71
72impl<T: MachineEnumerable<T>> PartialDecode<MachineEnum<T>> for MachineEnum<T> {
73 fn partial_decode(input: &mut &[u8]) -> Option<Self> {
74 let (head, tail) = input.split_first()?;
75 *input = tail;
76 Some(MachineEnum::decode(*head))
77 }
78}
79
80impl PartialDecode<u8> for u8 {
81 fn partial_decode(input: &mut &[u8]) -> Option<u8> {
82 let (head, tail) = input.split_first()?;
83 *input = tail;
84 Some(*head)
85 }
86}
87
88impl PartialDecode<u16> for u16 {
89 fn partial_decode(input: &mut &[u8]) -> Option<u16> {
90 let a = <u8>::partial_decode(input)? as u16;
91 let b = <u8>::partial_decode(input)? as u16;
92 Some((a << 8) | b)
93 }
94}
95
96impl PartialDecode<u32> for u32 {
97 fn partial_decode(input: &mut &[u8]) -> Option<u32> {
98 let a = <u16>::partial_decode(input)? as u32;
99 let b = <u16>::partial_decode(input)? as u32;
100 Some((a << 16) | b)
101 }
102}
103
104macro_rules! packet_definition {
105 (
106 $(
107 $name:ident
108 ( $( $req_name:tt $req_type:ty ),* $(,)? )
109 =>
110 ( $( $resp_name:tt $resp_type:ty ),* $(,)? )
111 ),* $(,)? ) => {
112
113 #[allow(dead_code)]
115 #[derive(Clone, Debug, Eq, PartialEq)]
116 pub enum Request {
117 $(
118 $name( $($req_type),* ),
119 )*
120 }
121
122 impl PartialEncode for Request {
123 fn partial_encode(&self, mut out: &mut Vec<u8>) {
124 match self {
125 $(
126 Self::$name(
127 $(
128 $req_name
129 ),*
130 ) => {
131 out.push(EcamRequestId::$name as u8);
132 if self.is_response_required() {
133 out.push(0xf0);
134 } else {
135 out.push(0x0f);
136 }
137 $($req_name.partial_encode(&mut out); )*
138 }
139 )*
140 }
141 }
142 }
143
144 impl Request {
145 pub fn ecam_request_id(&self) -> EcamRequestId {
146 match self {
147 $( Self::$name(..) => { EcamRequestId::$name } )*
148 }
149 }
150 }
151
152 #[allow(dead_code)]
154 #[derive(Clone, Debug, Eq, PartialEq)]
155 pub enum Response {
156 $(
157 $name ( $($resp_type),* ),
158 )*
159 }
160
161 impl Response {
162 pub fn ecam_request_id(&self) -> EcamRequestId {
163 match self {
164 $( Self::$name(..) => { EcamRequestId::$name } )*
165 }
166 }
167 }
168
169 impl PartialDecode<Response> for Response {
170 fn partial_decode(input: &mut &[u8]) -> Option<Self> {
171 if input.len() < 2 {
172 return None;
173 }
174 let id = EcamRequestId::try_from(input[0]);
175 if let Ok(id) = id {
176 let _ = input[1];
177 *input = &input[2..];
178 match id {
179 $(
180 EcamRequestId::$name => {
181 $(
182 let $resp_name = <$resp_type>::partial_decode(input)?;
183 )*
184 return Some(Self::$name(
185 $( $resp_name ),*
186 ));
187 }
188 )*
189 }
190 }
191 None
192 }
193 }
194 };
195}
196
197packet_definition!(
198 SetBtMode() => (),
199 MonitorV0() => (),
200 MonitorV1() => (),
201 MonitorV2() => (response MonitorV2Response),
202 BeverageDispensingMode(
203 recipe MachineEnum<EcamBeverageId>,
204 trigger MachineEnum<EcamOperationTrigger>,
205 ingredients Vec<RecipeInfo<u16>>,
206 mode MachineEnum<EcamBeverageTasteType>) => (unknown0 u8, unknown1 u8),
207 AppControl(request AppControl) => (),
208 ParameterRead(parameter u16, len u8) => (),
209 ParameterWrite() => (),
210 ParameterReadExt(parameter u16, len u8) => (parameter u16, data Vec<u8>),
211 StatisticsRead(parameter u16, len u8) => (data Vec<Statistic>),
212 Checksum() => (),
213 ProfileNameRead(start u8, end u8) => (names Vec<WideStringWithIcon>),
214 ProfileNameWrite() => (),
215 RecipeQuantityRead(profile u8, recipe MachineEnum<EcamBeverageId>)
216 => (profile u8, recipe MachineEnum<EcamBeverageId>, ingredients Vec<RecipeInfo<u16>>),
217 RecipePriorityRead() => (priorities Vec<u8>),
218 ProfileSelection() => (),
219 RecipeNameRead(start u8, end u8) => (names Vec<WideStringWithIcon>),
220 RecipeNameWrite() => (),
221 SetFavoriteBeverages(profile u8, recipies Vec<u8>) => (),
222 RecipeMinMaxSync(recipe MachineEnum<EcamBeverageId>) => (recipe MachineEnum<EcamBeverageId>, bounds Vec<RecipeMinMaxInfo>),
223 PinSet() => (),
224 BeanSystemSelect() => (),
225 BeanSystemRead() => (),
226 BeanSystemWrite() => (),
227 PinRead() => (),
228 SetTime() => (),
229);
230
231impl Request {
232 fn is_response_required(&self) -> bool {
233 !matches!(
234 self,
235 Request::AppControl(..)
236 | Request::MonitorV0()
237 | Request::MonitorV1()
238 | Request::MonitorV2()
239 | Request::StatisticsRead(..)
240 )
241 }
242}
243
244#[derive(Copy, Clone, Debug, Eq, PartialEq)]
246pub struct Statistic {
247 pub stat: u16,
248 pub value: u32,
249}
250
251impl PartialDecode<Statistic> for Statistic {
252 fn partial_decode(input: &mut &[u8]) -> Option<Self> {
253 let stat = <u16>::partial_decode(input)?;
254 let value = <u32>::partial_decode(input)?;
255 Some(Statistic { stat, value })
256 }
257}
258
259#[cfg(test)]
260mod test {
261 use super::*;
262 use crate::protocol::*;
263 use rstest::*;
264
265 #[rstest]
266 #[case(&crate::protocol::test::RESPONSE_BREW_RECEIVED)]
267 #[case(&crate::protocol::test::RESPONSE_STATUS_CAPPUCCINO_MILK)]
268 #[case(&crate::protocol::test::RESPONSE_STATUS_READY_AFTER_CAPPUCCINO)]
269 #[case(&crate::protocol::test::RESPONSE_STATUS_CLEANING_AFTER_CAPPUCCINO)]
270 #[case(&crate::protocol::test::RESPONSE_STATUS_STANDBY_NO_ALARMS)]
271 #[case(&crate::protocol::test::RESPONSE_STATUS_STANDBY_NO_WATER_TANK)]
272 #[case(&crate::protocol::test::RESPONSE_STATUS_STANDBY_WATER_SPOUT)]
273 #[case(&crate::protocol::test::RESPONSE_STATUS_STANDBY_NO_COFFEE_CONTAINER)]
274 fn real_packets_decode_as_expected(#[case] bytes: &[u8]) {
275 let (packet, remainder) = Response::decode(unwrap_packet(bytes));
276 let packet = packet.expect("Expected to decode something");
277 assert_eq!(remainder, &[]);
278 println!("{:?}", packet);
280 }
281
282 #[test]
283 fn test_decode_monitor_packet() {
284 let buf = [117_u8, 15, 1, 5, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0];
285 let input = &mut buf.as_slice();
286 assert_eq!(
287 <Response>::partial_decode(input).expect("Failed to decode"),
288 Response::MonitorV2(MonitorV2Response {
289 state: EcamMachineState::ReadyOrDispensing.into(),
290 accessory: EcamAccessory::Water.into(),
291 progress: 0,
292 percentage: 0,
293 switches: SwitchSet::of(&[
294 EcamMachineSwitch::WaterSpout,
295 EcamMachineSwitch::MotorDown
296 ]),
297 alarms: SwitchSet::empty(),
298 ..Default::default()
299 })
300 );
301 }
302
303 #[test]
304 fn test_decode_monitor_packet_alarm() {
305 let buf = [117_u8, 15, 1, 69, 0, 1, 0, 7, 0, 0, 0, 0, 0, 0, 0];
306 let input = &mut buf.as_slice();
307 assert_eq!(
308 <Response>::partial_decode(input).expect("Failed to decode"),
309 Response::MonitorV2(MonitorV2Response {
310 state: EcamMachineState::ReadyOrDispensing.into(),
311 accessory: EcamAccessory::Water.into(),
312 progress: 0,
313 percentage: 0,
314 switches: SwitchSet::of(&[
315 EcamMachineSwitch::WaterSpout,
316 EcamMachineSwitch::MotorDown,
317 EcamMachineSwitch::WaterLevelLow,
318 ]),
319 alarms: SwitchSet::of(&[EcamMachineAlarm::EmptyWaterTank]),
320 ..Default::default()
321 })
322 );
323 }
324
325 #[test]
326 fn test_decode_profile_packet() {
327 let buf = [
328 164_u8, 240, 0, 77, 0, 97, 0, 116, 0, 116, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,
329 77, 0, 105, 0, 97, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 80, 0, 82, 0, 79, 0,
330 70, 0, 73, 0, 76, 0, 69, 0, 32, 0, 51, 0, 0, 3,
331 ];
332 let input = &mut buf.as_slice();
333 assert_eq!(
334 <Response>::partial_decode(input).expect("Failed to decode"),
335 Response::ProfileNameRead(vec![
336 WideStringWithIcon::new("Matt", 3),
337 WideStringWithIcon::new("Mia", 8),
338 WideStringWithIcon::new("PROFILE 3", 3)
339 ])
340 )
341 }
342
343 #[test]
344 fn test_brew_coffee() {
345 let recipe = vec![
346 RecipeInfo::new(EcamIngredients::Coffee, 103),
347 RecipeInfo::new(EcamIngredients::Taste, 2),
348 RecipeInfo::new(EcamIngredients::Temp, 0),
349 ];
350 assert_eq!(
351 Request::BeverageDispensingMode(
352 EcamBeverageId::RegularCoffee.into(),
353 EcamOperationTrigger::Start.into(),
354 recipe,
355 EcamBeverageTasteType::PrepareInversion.into()
356 )
357 .encode(),
358 vec![
359 0x83, 0xf0, 0x02, 0x01, 0x01, 0x00, 0x67, 0x02, 0x02, 0x00, 0x00, 0x06
360 ]
361 );
362 }
363}