locodrive/protocol.rs
1use crate::args::*;
2use crate::error::MessageParseError;
3
4/// Represents the types of messages that are specified by the model railroads protocol.
5#[repr(u8)]
6#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
7pub enum Message {
8 /// Forces the model railroads to switch in Idle state. An emergency stop for all trains is broadcast.
9 /// Note: The model railroads may not response any more.
10 Idle,
11 /// Turns the global power on. Activates the railway.
12 GpOn,
13 /// Turns the global power off. Deactivates the railway.
14 GpOff,
15 /// This is the master `Busy` code. Receiving this indicates that
16 /// the master needs time to fulfill a send request.
17 Busy,
18
19 /// Requests a loco address to be put to a free slot by the master.
20 ///
21 /// Using this slot the train is controllable.
22 ///
23 /// # Success
24 ///
25 /// [`Message::SlRdData`] containing all slot and address information.
26 ///
27 /// # Fail
28 ///
29 /// [`Message::LongAck`] with [`Ack1Arg::failed()`]
30 /// Meaning no free slots are available.
31 LocoAdr(AddressArg),
32 /// Request state of switch with acknowledgment function
33 ///
34 /// # Success
35 ///
36 /// [`Message::LongAck`] with [`Ack1Arg::success()`]
37 ///
38 /// # Fail
39 ///
40 /// [`Message::LongAck`] with [`Ack1Arg::failed()`]
41 /// Meaning switch state not known / Switch not known.
42 SwAck(SwitchArg),
43 /// Request state of switch
44 ///
45 /// # Success
46 ///
47 /// [`Message::LongAck`] with [`Ack1Arg::success()`]
48 ///
49 /// # Fail
50 ///
51 /// [`Message::LongAck`] with [`Ack1Arg::failed()`]
52 /// Meaning switch state not known / Switch not known.
53 SwState(SwitchArg),
54 /// Request slot data or status block
55 ///
56 /// # Success
57 ///
58 /// [`Message::SlRdData`] containing all slot information.
59 RqSlData(SlotArg),
60 /// Moves all slot information from a `source` to a `destination` slot address.
61 ///
62 /// # Special operations
63 ///
64 /// ## `NULL`-Move
65 ///
66 /// A `NULL`-Move (Move with equal source and destination) can be used to mark the slot as [`State::InUse`].
67 ///
68 /// ## Dispatch put
69 ///
70 /// Moving a slot **to** the *slot 0* marks it as `DISPATCH`-Slot.
71 /// In this case the destination slot is not copied to,
72 /// but marked by the system as DISPATCH slot.
73 ///
74 /// ## Dispatch get
75 ///
76 /// Moving a slot **from** *slot 0* with no destination given (not needed) will
77 /// response with a [`Message::SlRdData`] if a as dispatch marked slot is saved in *slot 0*.
78 /// Otherwise a [`Message::LongAck`] with [`Ack1Arg::failed()`] indicates the
79 /// failure of the operation.
80 ///
81 /// # Success
82 ///
83 /// [`Message::SlRdData`] containing all slot information.
84 ///
85 /// # Fail
86 ///
87 /// [`Message::LongAck`] with [`Ack1Arg::failed()`]
88 /// Meaning slot data could not be moved.
89 MoveSlots(SlotArg, SlotArg),
90 /// Links the first given slot to the second one
91 ///
92 /// # Success
93 ///
94 /// [`Message::SlRdData`] containing slot information.
95 ///
96 /// # Fail
97 ///
98 /// [`Message::LongAck`] with [`Ack1Arg::failed()`]
99 /// Meaning slot data could not be linked.
100 LinkSlots(SlotArg, SlotArg),
101 /// Unlinks the first given slot from the second one
102 ///
103 /// # Success
104 ///
105 /// [`Message::SlRdData`] containing slot information.
106 ///
107 /// # Fail
108 ///
109 /// [`Message::LongAck`] with [`Ack1Arg::failed()`]
110 /// Meaning slot data could not be linked.
111 UnlinkSlots(SlotArg, SlotArg),
112 /// Sets function bits in a [`Consist::LogicalMid`] or
113 /// [`Consist::LogicalSubMember`] linked slot.
114 ConsistFunc(SlotArg, DirfArg),
115 /// Writes a slots stat1.
116 /// See [`Stat1Arg`] for information on what you can configure here.
117 SlotStat1(SlotArg, Stat1Arg),
118 /// This is a long acknowledgment message mostly used to indicate that some requested
119 /// operation has failed or succeed.
120 LongAck(LopcArg, Ack1Arg),
121 /// This holds general sensor input from an addressed sensor.
122 ///
123 /// On state change this message is automatically send from the sensor over the model railroads,
124 /// but if you want to receive all your sensor states you can configure a switch address that
125 /// forces the sensor module to send its state in the model railroads sensor device.
126 InputRep(InArg),
127 /// Switch sensor report
128 ///
129 /// Reports the switches type and meta information
130 SwRep(SnArg),
131 /// Requests a switch function. More precisely requests a switch to switch to a
132 /// specific direction and activation.
133 ///
134 /// # Fail
135 ///
136 /// [`Message::LongAck`] with [`Ack1Arg::failed()`]
137 /// Meaning the requested action could not be performed.
138 SwReq(SwitchArg),
139 /// Sets a slots sound function bits. (functions 5 to 8)
140 LocoSnd(SlotArg, SndArg),
141 /// Sets a slots direction and first four function bits.
142 LocoDirf(SlotArg, DirfArg),
143 /// Sets a slot speed.
144 LocoSpd(SlotArg, SpeedArg),
145
146 /// Used for power management and transponding
147 MultiSense(MultiSenseArg, AddressArg),
148 /// In systems from `Uhlenbrock` this message could be used to
149 /// access the slot functions 9 to 28.
150 UhliFun(SlotArg, FunctionArg),
151
152 /// Used to write special and more complex slot data.
153 ///
154 /// # Success
155 ///
156 /// [`Message::LongAck`] with [`Ack1Arg::success()`]
157 ///
158 /// # Fail
159 ///
160 /// [`Message::LongAck`] with [`Ack1Arg::failed()`]
161 WrSlData(WrSlDataStructure),
162 /// This is a slot data response holding all information on the slot.
163 SlRdData(
164 SlotArg,
165 Stat1Arg,
166 AddressArg,
167 SpeedArg,
168 DirfArg,
169 TrkArg,
170 Stat2Arg,
171 SndArg,
172 IdArg,
173 ),
174 /// Holds a SlRdData response of the programming slot 127
175 ///
176 /// The first arguments represent this message in the format of a SlRdData response,
177 /// but some data where extra interpreted in the below message arguments.
178 ProgrammingFinalResponse(
179 SlotArg,
180 Stat1Arg,
181 AddressArg,
182 SpeedArg,
183 DirfArg,
184 TrkArg,
185 Stat2Arg,
186 SndArg,
187 IdArg,
188 Pcmd,
189 PStat,
190 AddressArg,
191 CvDataArg,
192 ),
193 /// Indicates that the programming service mode is aborted.
194 ProgrammingAborted(ProgrammingAbortedArg),
195
196 /// Moves 8 bytes peer to peer from the source slot to the destination.
197 ///
198 /// Slot = 0: Slot is master
199 /// Slot = 0x70 - 0x7E: reserved
200 /// Slot = 0x7F: Throttle message xfer
201 ///
202 PeerXfer(SlotArg, DstArg, PxctData),
203
204 /// This message holds reports
205 /// (I am not really sure what this reports represent
206 /// and what they are used for.
207 /// If you understand them better,
208 /// you may help me to improve this documentation and
209 /// the implementation of reading and writing this messages.)
210 Rep(RepStructure),
211
212 /// Sends n-byte packet immediate
213 ///
214 /// # Response
215 ///
216 /// - [`Message::LongAck`] with [`Ack1Arg::success()`]: Not limited
217 /// - [`Message::LongAck`] with [`Ack1Arg::limited_success()`]:
218 /// limited with [`Ack1Arg::ack1()`] as limit
219 /// - [`Message::LongAck`] with [`Ack1Arg::failed()`]: Busy
220 ImmPacket(ImArg),
221}
222
223impl Message {
224 /// Parses a model railroads message from `buf`.
225 ///
226 /// # Errors
227 ///
228 /// This function returns an error if the message could not be parsed:
229 ///
230 /// - [`UnknownOpcode`]: If the message has an unknown opcode
231 /// - [`UnexpectedEnd`]: If the buf not holds the complete message
232 /// - [`InvalidChecksum`]: If the checksum is invalid
233 /// - [`InvalidFormat`]: If the message is in invalid format
234 ///
235 /// [`UnknownOpcode`]: MessageParseError::UnknownOpcode
236 /// [`UnexpectedEnd`]: MessageParseError::UnexpectedEnd
237 /// [`InvalidChecksum`]: MessageParseError::InvalidChecksum
238 /// [`InvalidFormat`]: MessageParseError::InvalidFormat
239 pub fn parse(buf: &[u8]) -> Result<Self, MessageParseError> {
240 let opc = buf[0];
241 // We calculate the length of the remaining message to read
242 let len = match opc & 0xE0 {
243 0x80 => 2,
244 0xA0 => 4,
245 0xC0 => 6,
246 0xE0 => buf[1] as usize,
247 _ => return Err(MessageParseError::UnknownOpcode(opc)),
248 };
249
250 // validate checksum
251 if !Self::validate(&buf[0..len]) {
252 return Err(MessageParseError::InvalidChecksum(opc));
253 }
254
255 // call appropriate parse function
256 match len {
257 2 => Self::parse2(opc),
258 4 => Self::parse4(opc, &buf[1..3]),
259 6 => Self::parse6(opc, &buf[1..5]),
260 var => Self::parse_var(opc, &buf[1..var - 1]),
261 }
262 }
263
264 /// Parse all messages of two bytes length. As the second byte is every time the checksum,
265 /// only the `opc` is needed for parsing.
266 ///
267 /// # Errors
268 ///
269 /// - [`UnknownOpcode`]: If the message has an unknown opcode
270 ///
271 /// [`UnknownOpcode`]: MessageParseError::UnknownOpcode
272 fn parse2(opc: u8) -> Result<Self, MessageParseError> {
273 match opc {
274 0x85 => Ok(Self::Idle),
275 0x83 => Ok(Self::GpOn),
276 0x82 => Ok(Self::GpOff),
277 0x81 => Ok(Self::Busy),
278 _ => Err(MessageParseError::UnknownOpcode(opc)),
279 }
280 }
281
282 /// Parse all messages of four bytes length.
283 /// Therefore the first byte specifying the message type is passed as `opc` and the
284 /// other two message bytes are passed as `args`.
285 ///
286 /// # Errors
287 ///
288 /// - [`UnknownOpcode`]: If the message has an unknown opcode
289 /// - [`UnexpectedEnd`]: If the buf not holds the complete message
290 ///
291 /// [`UnknownOpcode`]: MessageParseError::UnknownOpcode
292 /// [`UnexpectedEnd`]: MessageParseError::UnexpectedEnd
293 fn parse4(opc: u8, args: &[u8]) -> Result<Self, MessageParseError> {
294 if args.len() != 2 {
295 return Err(MessageParseError::UnexpectedEnd(opc));
296 }
297 match opc {
298 0xBF => Ok(Self::LocoAdr(AddressArg::parse(args[0], args[1]))),
299 0xBD => Ok(Self::SwAck(SwitchArg::parse(args[0], args[1]))),
300 0xBC => Ok(Self::SwState(SwitchArg::parse(args[0], args[1]))),
301 0xBB => Ok(Self::RqSlData(SlotArg::parse(args[0]))),
302 0xBA => Ok(Self::MoveSlots(
303 SlotArg::parse(args[0]),
304 SlotArg::parse(args[1]),
305 )),
306 0xB9 => Ok(Self::LinkSlots(
307 SlotArg::parse(args[0]),
308 SlotArg::parse(args[1]),
309 )),
310 0xB8 => Ok(Self::UnlinkSlots(
311 SlotArg::parse(args[0]),
312 SlotArg::parse(args[1]),
313 )),
314 0xB6 => Ok(Self::ConsistFunc(
315 SlotArg::parse(args[0]),
316 DirfArg::parse(args[1]),
317 )),
318 0xB5 => Ok(Self::SlotStat1(
319 SlotArg::parse(args[0]),
320 Stat1Arg::parse(args[1]),
321 )),
322 0xB4 => Ok(Self::LongAck(
323 LopcArg::parse(args[0]),
324 Ack1Arg::parse(args[1]),
325 )),
326 0xB2 => Ok(Self::InputRep(InArg::parse(args[0], args[1]))),
327 0xB1 => Ok(Self::SwRep(SnArg::parse(args[0], args[1]))),
328 0xB0 => Ok(Self::SwReq(SwitchArg::parse(args[0], args[1]))),
329 0xA2 => Ok(Self::LocoSnd(
330 SlotArg::parse(args[0]),
331 SndArg::parse(args[1]),
332 )),
333 0xA1 => Ok(Self::LocoDirf(
334 SlotArg::parse(args[0]),
335 DirfArg::parse(args[1]),
336 )),
337 0xA0 => Ok(Self::LocoSpd(
338 SlotArg::parse(args[0]),
339 SpeedArg::parse(args[1]),
340 )),
341 _ => Err(MessageParseError::UnknownOpcode(opc)),
342 }
343 }
344
345 /// Parse all messages of six bytes length.
346 /// Therefore the first byte specifying the message type is passed as `opc` and the
347 /// other four message bytes are passed as `args`.
348 ///
349 /// # Errors
350 ///
351 /// - [`UnknownOpcode`]: If the message has an unknown opcode
352 /// - [`UnexpectedEnd`]: If the buf not holds the complete message
353 /// - [`InvalidFormat`]: If the message is in invalid format
354 ///
355 /// [`UnknownOpcode`]: MessageParseError::UnknownOpcode
356 /// [`UnexpectedEnd`]: MessageParseError::UnexpectedEnd
357 /// [`InvalidFormat`]: MessageParseError::InvalidFormat
358 fn parse6(opc: u8, args: &[u8]) -> Result<Self, MessageParseError> {
359 if args.len() != 4 {
360 return Err(MessageParseError::UnexpectedEnd(opc));
361 }
362 match opc {
363 0xD0 => Ok(Self::MultiSense(
364 MultiSenseArg::parse(args[0], args[1]),
365 AddressArg::parse(args[2], args[3]),
366 )),
367 0xD4 => {
368 if 0x20 != args[0] {
369 return Err(MessageParseError::InvalidFormat(format!(
370 "Expected first arg of UhliFun to be 0x20 got {:02x}",
371 args[0]
372 )));
373 }
374 Ok(Self::UhliFun(
375 SlotArg::parse(args[1]),
376 FunctionArg::parse(args[2], args[3]),
377 ))
378 }
379 _ => Err(MessageParseError::UnknownOpcode(opc)),
380 }
381 }
382
383 /// Parse all messages of variable length.
384 /// Therefore the first byte specifying the message type is passed as `opc` and the
385 /// other message bytes are passed as `args`.
386 ///
387 /// # Errors
388 ///
389 /// - [`UnknownOpcode`]: If the message has an unknown opcode
390 /// - [`UnexpectedEnd`]: If the buf not holds the complete message
391 /// - [`InvalidFormat`]: If the message is in invalid format
392 ///
393 /// [`UnknownOpcode`]: MessageParseError::UnknownOpcode
394 /// [`UnexpectedEnd`]: MessageParseError::UnexpectedEnd
395 /// [`InvalidFormat`]: MessageParseError::InvalidFormat
396 fn parse_var(opc: u8, args: &[u8]) -> Result<Self, MessageParseError> {
397 if args.len() + 2 != args[0] as usize {
398 return Err(MessageParseError::UnexpectedEnd(opc));
399 }
400
401 match opc {
402 0xED => {
403 if args.len() != 9 {
404 return Err(MessageParseError::UnexpectedEnd(opc));
405 }
406
407 if args[1] != 0x7F {
408 return Err(MessageParseError::InvalidFormat(format!(
409 "The check byte of the received message whith opcode {:x} was invalid. \
410 Expected 0x7F got {:02x}",
411 opc, args[1]
412 )));
413 }
414
415 Ok(Self::ImmPacket(ImArg::parse(
416 args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8],
417 )))
418 }
419 0xEF => {
420 if args.len() != 12 {
421 return Err(MessageParseError::UnexpectedEnd(opc));
422 }
423
424 Ok(Self::WrSlData(WrSlDataStructure::parse(
425 args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8],
426 args[9], args[10], args[11],
427 )))
428 }
429 0xE7 => {
430 if args.len() != 12 {
431 return Err(MessageParseError::UnexpectedEnd(opc));
432 }
433
434 if args[1] == 0x7C {
435 Ok(Self::ProgrammingFinalResponse(
436 SlotArg::parse(args[1]),
437 Stat1Arg::parse(args[2]),
438 AddressArg::parse(args[8], args[3]),
439 SpeedArg::parse(args[4]),
440 DirfArg::parse(args[5]),
441 TrkArg::parse(args[6]),
442 Stat2Arg::parse(args[7]),
443 SndArg::parse(args[9]),
444 IdArg::parse(args[10], args[11]),
445 Pcmd::parse(args[2]),
446 PStat::parse(args[3]),
447 AddressArg::parse(args[4], args[5]),
448 CvDataArg::parse(args[7], args[8], args[9]),
449 ))
450 } else {
451 Ok(Self::SlRdData(
452 SlotArg::parse(args[1]),
453 Stat1Arg::parse(args[2]),
454 AddressArg::parse(args[8], args[3]),
455 SpeedArg::parse(args[4]),
456 DirfArg::parse(args[5]),
457 TrkArg::parse(args[6]),
458 Stat2Arg::parse(args[7]),
459 SndArg::parse(args[9]),
460 IdArg::parse(args[10], args[11]),
461 ))
462 }
463 }
464 0xE6 => {
465 if args.len() < 2 {
466 return Err(MessageParseError::UnexpectedEnd(opc));
467 }
468
469 Ok(Message::ProgrammingAborted(ProgrammingAbortedArg::parse(
470 args[0],
471 &args[1..],
472 )))
473 }
474 0xE4 => {
475 if args.len() < 2 {
476 return Err(MessageParseError::UnexpectedEnd(opc));
477 }
478
479 Ok(Self::Rep(RepStructure::parse(args[0], &args[1..])?))
480 }
481 0xE5 => {
482 if args.len() != 14 {
483 return Err(MessageParseError::UnexpectedEnd(opc));
484 }
485
486 Ok(Self::PeerXfer(
487 SlotArg::parse(args[1]),
488 DstArg::parse(args[2], args[3]),
489 PxctData::parse(
490 args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11],
491 args[12], args[13],
492 ),
493 ))
494 }
495 _ => Err(MessageParseError::UnknownOpcode(opc)),
496 }
497 }
498
499 /// Validates the `msg` by xor-ing all bytes and checking for the result to be 0xFF.
500 fn validate(msg: &[u8]) -> bool {
501 msg.iter().fold(0, |acc, &b| acc ^ b) == 0xFF
502 }
503
504 /// Parses the given [`Message`] to a [`Vec<u8>`] using the model railroads protocol.
505 pub fn to_message(self) -> Vec<u8> {
506 // Parses the message
507 let mut message = match self {
508 Message::Idle => vec![0x85_u8],
509 Message::GpOn => vec![0x83_u8],
510 Message::GpOff => vec![0x82_u8],
511 Message::Busy => vec![0x81_u8],
512 Message::LocoAdr(adr_arg) => vec![0xBF_u8, adr_arg.adr2(), adr_arg.adr1()],
513 Message::SwAck(switch_arg) => vec![0xBD_u8, switch_arg.sw1(), switch_arg.sw2()],
514 Message::SwState(switch_arg) => vec![0xBC_u8, switch_arg.sw1(), switch_arg.sw2()],
515 Message::RqSlData(slot_arg) => vec![0xBB_u8, slot_arg.slot(), 0x00_u8],
516 Message::MoveSlots(src, dst) => vec![0xBA_u8, src.slot(), dst.slot()],
517 Message::LinkSlots(sl1, sl2) => vec![0xB9_u8, sl1.slot(), sl2.slot()],
518 Message::UnlinkSlots(sl1, sl2) => vec![0xB8_u8, sl1.slot(), sl2.slot()],
519 Message::ConsistFunc(slot, dirf) => vec![0xB6_u8, slot.slot(), dirf.dirf()],
520 Message::SlotStat1(slot, stat1) => vec![0xB5_u8, slot.slot(), stat1.stat1()],
521 Message::LongAck(lopc, ack1) => vec![0xB4_u8, lopc.lopc(), ack1.ack1()],
522 Message::InputRep(input) => vec![0xB2_u8, input.in1(), input.in2()],
523 Message::SwRep(sn_arg) => vec![0xB1_u8, sn_arg.sn1(), sn_arg.sn2()],
524 Message::SwReq(sw) => vec![0xB0_u8, sw.sw1(), sw.sw2()],
525 Message::LocoSnd(slot, snd) => vec![0xA2_u8, slot.slot(), snd.snd()],
526 Message::LocoDirf(slot, dirf) => vec![0xA1_u8, slot.slot(), dirf.dirf()],
527 Message::LocoSpd(slot, spd) => vec![0xA0_u8, slot.slot(), spd.spd()],
528 Message::MultiSense(multi_sense, address) => vec![
529 0xD0_u8,
530 multi_sense.m_high(),
531 multi_sense.zas(),
532 address.adr2(),
533 address.adr1(),
534 ],
535 Message::UhliFun(slot, function) => vec![
536 0xD4_u8,
537 0x20_u8,
538 slot.slot(),
539 function.group(),
540 function.function(),
541 ],
542 Message::WrSlData(wr_slot_data_arg) => wr_slot_data_arg.to_message(),
543 Message::SlRdData(slot, stat1, adr, spd, dirf, trk, stat2, snd, id) => vec![
544 0xE7_u8,
545 0x0E_u8,
546 slot.slot(),
547 stat1.stat1(),
548 adr.adr1(),
549 spd.spd(),
550 dirf.dirf(),
551 trk.trk_arg(),
552 stat2.stat2(),
553 adr.adr2(),
554 snd.snd(),
555 id.id1(),
556 id.id2(),
557 ],
558 Message::ProgrammingFinalResponse(
559 slot,
560 stat1,
561 adr,
562 spd,
563 dirf,
564 trk,
565 stat2,
566 snd,
567 id,
568 pcmd,
569 stat,
570 opsa,
571 cv_data,
572 ) => vec![
573 0xE7_u8,
574 0x0E_u8,
575 slot.slot(),
576 stat1.stat1() | pcmd.pcmd(),
577 adr.adr1() | stat.stat(),
578 spd.spd() | opsa.adr2(),
579 dirf.dirf() | opsa.adr1(),
580 trk.trk_arg(),
581 stat2.stat2() | cv_data.cvh(),
582 adr.adr2() | cv_data.cvl(),
583 snd.snd() | cv_data.data7(),
584 id.id1(),
585 id.id2(),
586 ],
587 Message::ProgrammingAborted(args) => args.to_message(),
588 Message::ImmPacket(im) => vec![
589 0xED_u8,
590 0x0B_u8,
591 0x7F_u8,
592 im.reps(),
593 im.dhi(),
594 im.im1(),
595 im.im2(),
596 im.im3(),
597 im.im4(),
598 im.im5(),
599 ],
600 Message::Rep(rep) => match rep {
601 RepStructure::RFID7Report(report) => report.to_message(),
602 RepStructure::RFID5Report(report) => report.to_message(),
603 RepStructure::LissyIrReport(report) => report.to_message(),
604 RepStructure::WheelcntReport(report) => report.to_message(),
605 },
606 Message::PeerXfer(src, dst, pxct) => vec![
607 0xE5,
608 0x10,
609 src.slot(),
610 dst.dst_low(),
611 dst.dst_high(),
612 pxct.pxct1(),
613 pxct.d1(),
614 pxct.d2(),
615 pxct.d3(),
616 pxct.d4(),
617 pxct.pxct2(),
618 pxct.d5(),
619 pxct.d6(),
620 pxct.d7(),
621 pxct.d8(),
622 ],
623 };
624
625 // Appending checksum to the created message
626 message.push(Self::check_sum(&message));
627
628 message
629 }
630
631 /// Calculates the check sum for the given `msg`.
632 fn check_sum(msg: &[u8]) -> u8 {
633 0xFF - msg.iter().fold(0, |acc, &b| acc ^ b)
634 }
635
636 /// Checks whether the given operation code is a valid
637 /// known operation code that could be passed by this protocol implementation.
638 ///
639 /// # Parameters
640 ///
641 /// - `opc`: The operation code to check
642 ///
643 /// # Returns
644 ///
645 /// If the given operation code is known
646 pub fn known_opc(opc: u8) -> bool {
647 matches!(
648 opc,
649 0x85 | 0x83
650 | 0x82
651 | 0x81
652 | 0xBF
653 | 0xBD
654 | 0xBC
655 | 0xBB
656 | 0xBA
657 | 0xB9
658 | 0xB8
659 | 0xB6
660 | 0xB5
661 | 0xB4
662 | 0xB2
663 | 0xB1
664 | 0xB0
665 | 0xA2
666 | 0xA1
667 | 0xA0
668 | 0xD0
669 | 0xD4
670 | 0xEF
671 | 0xE7
672 | 0xE6
673 | 0xE5
674 | 0xE4
675 | 0xED
676 )
677 }
678
679 /// # Returns
680 ///
681 /// The op code for the specified message
682 pub fn opc(&self) -> u8 {
683 match *self {
684 Message::Idle => 0x85,
685 Message::GpOn => 0x83,
686 Message::GpOff => 0x82,
687 Message::Busy => 0x81,
688 Message::LocoAdr(..) => 0xBF,
689 Message::SwAck(..) => 0xBD,
690 Message::SwState(..) => 0xBC,
691 Message::RqSlData(..) => 0xBB,
692 Message::MoveSlots(..) => 0xBA,
693 Message::LinkSlots(..) => 0xB9,
694 Message::UnlinkSlots(..) => 0xB8,
695 Message::ConsistFunc(..) => 0xB6,
696 Message::SlotStat1(..) => 0xB5,
697 Message::LongAck(..) => 0xB4,
698 Message::InputRep(..) => 0xB2,
699 Message::SwRep(..) => 0xB1,
700 Message::SwReq(..) => 0xB0,
701 Message::LocoSnd(..) => 0xA2,
702 Message::LocoDirf(..) => 0xA1,
703 Message::LocoSpd(..) => 0xA0,
704 Message::MultiSense(..) => 0xD0,
705 Message::UhliFun(..) => 0xD4,
706 Message::WrSlData(..) => 0xEF,
707 Message::SlRdData(..) => 0xE7,
708 Message::ProgrammingFinalResponse(..) => 0xE7,
709 Message::ProgrammingAborted(..) => 0xE6,
710 Message::PeerXfer(..) => 0xE5,
711 Message::Rep(..) => 0xE4,
712 Message::ImmPacket(..) => 0xED,
713 }
714 }
715
716 /// Checks whether this message expects a long acknowledgment message to follow.
717 pub fn answer_follows(&self) -> bool {
718 0x01 & self.opc() == 0x01
719 }
720
721 /// Indicates if a request with the specified slot
722 /// data was awaited after that message.
723 pub fn await_slot_data(&self) -> bool {
724 matches!(
725 self,
726 Message::LocoAdr(..)
727 | Message::RqSlData(..)
728 | Message::MoveSlots(..)
729 | Message::LinkSlots(..)
730 | Message::UnlinkSlots(..)
731 )
732 }
733}