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}