Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
TractionModem.hxx
Go to the documentation of this file.
1
35#ifndef _TRACTIONMODEM_TRACTIONMODEM_HXX_
36#define _TRACTIONMODEM_TRACTIONMODEM_HXX_
37
40
41#include "traction_modem/TractionModemDefs.hxx"
42#if !defined(GTEST)
43#include "hardware.hxx"
44#endif
45
50
52{
53
55{
56 bool valid() const
57 {
58 return Defs::is_valid(payload);
59 }
60
61 uint16_t command() const
62 {
63 return Defs::get_uint16(payload, Defs::OFS_CMD);
64 }
65
66 uint16_t length() const
67 {
68 return Defs::get_uint16(payload, Defs::OFS_LEN);
69 }
70
73 uint16_t response_status()
74 {
75 return Defs::get_uint16(payload, Defs::OFS_DATA);
76 }
77
78 string payload;
79};
80
81using PacketFlowInterface = FlowInterface<Buffer<TxMessage>>;
82using TxFlowBase = StateFlow<Buffer<TxMessage>, QList<2>>;
83
85class TxFlow : public TxFlowBase
86{
87public:
92 {
93 LOG(INFO, "[ModemTx] constructor");
94 }
95
98 void start(int fd)
99 {
100 LOG(INFO, "[ModemTx] fd");
101 fd_ = fd;
102 }
103
106 Action entry() override
107 {
108 if (fd_ < 0)
109 {
110 LOG(INFO, "[ModemTx] no uart");
111 return release_and_exit();
112 }
113 LOG(INFO, "[ModemTx] msg len %d",
114 (int)message()->data()->payload.size());
115 return write_repeated(&helper_, fd_, message()->data()->payload.data(),
116 message()->data()->payload.size(), STATE(write_complete));
117 }
118
122 {
123 unsigned len = message()->data()->payload.size();
124 unsigned num_sent = len - helper_.remaining_;
125 const uint8_t *d = (const uint8_t *)message()->data()->payload.data();
126 LOG(INFO, "[ModemTx] sent E%d len %u done %u %08x %04x...",
127 helper_.hasError_, len, num_sent, *(unsigned *)(d + 0),
128 (unsigned)be32toh(*(uint16_t *)(d + 4)));
130 return release_and_exit();
131 }
132
133private:
137 int fd_ = -1;
138};
139
142class RxFlow : public StateFlowBase
143{
144public:
147
152 , receiver_(nullptr)
153 { }
154
157 void start(int fd)
158 {
159 fd_ = fd;
161 }
162
166 {
167 receiver_ = rcv;
168 }
169
173 {
176 return CHARACTER_NSEC;
177 }
178
179#if defined(GTEST)
180 bool exit_ = false;
181#endif
182
183private:
187 {
188 // Looking for the start of a new message. The clear is needed to
189 // "reset" the string from the std::move() operation that might have
190 // occurred on the prior message, leaving it in an unknown state.
191 payload_.clear();
192 // Note: We are relying on the fact that the default allocator is used,
193 // which is the heap allocator, and that the heap allocator will
194 // allocate memory on (at least) the native machine size boundary.
195 // This is important in order to be able to cast payload_.data()
196 // to a Defs::Message type for easier decoding. Otherwise if the
197 // architecture supports unaligned accesses, then we are fine for
198 // all allocators.
200 recvCnt_ = 0;
202 }
203
207 {
208#if defined(GTEST)
209 if (exit_)
210 {
211 return exit();
212 }
213#endif
214 // Every message is at least a minimum size. There is really no point
215 // to waste any cycles processing until at least the minimum count is
216 // received.
218 {
219 // We already have enough data to start processing.
221 }
222
224
225 // This is pre-emptive. We will have received this count by the time we
226 // enter the next state because the read blocks until we have all the
227 // base data.
228 size_t offset = recvCnt_;
230
231 return read_repeated(&helper_, fd_, &payload_[offset],
233 }
234
239 {
240 const Defs::Message *m = (const Defs::Message*)payload_.data();
241
242 // Look for the preamble.
243 if (m->header_.preamble_ == htobe32(Defs::PREAMBLE))
244 {
245 // Valid preamble found where it is supposed to be. Assume for now
246 // that we also have a valid header that follows.
247 LOG(INFO, "[ModemRx] recv cmd: 0x%04x, len: %u",
248 be16toh(m->header_.command_), be16toh(m->header_.length_));
250 }
251
252 // Else valid preamble and/or header not found where it is supposed to
253 // be.
254 LOG(INFO,
255 "[ModemRx] Did not find an expected valid preamble and/or header.");
257 }
258
264 {
265 const Defs::Message *m = (const Defs::Message*)payload_.data();
266 size_t len = be16toh(m->header_.length_);
267
268 if (len > MAX_DATA_LEN)
269 {
270 // Violated the maximum length data allowed by the protocol. We are
271 // probably out of sync.
272 LOG(INFO, "[ModemRx] Maximum data length violation.");
274 }
275
276 size_t total_len = MIN_MESSAGE_SIZE + len;
277 if (recvCnt_ >= total_len)
278 {
279 // We already have enough data for the complete message.
281 }
282
283 payload_.resize(total_len);
284 size_t needed_len = total_len - recvCnt_;
285
287 2 * get_character_nsec() * needed_len, fd_, &payload_[recvCnt_],
288 needed_len, STATE(maybe_message_complete));
289 }
290
296 {
297 const Defs::Message *m = (const Defs::Message*)payload_.data();
298 size_t len = be16toh(m->header_.length_);
299
301 {
302 // Timeout, we may be out ot sync. Check for a preamble in the data
303 // we did receive.
304 recvCnt_ += len - helper_.remaining_;
305 LOG(INFO, "[ModemRx] Timeout waiting for expected receive data, "
306 "remaining: %u", helper_.remaining_);
308 }
309
310 Defs::CRC crc_calc;
311 Defs::CRC crc_recv = Defs::get_crc(payload_, len);
313 payload_.data() + sizeof(uint32_t),
314 sizeof(m->header_.command_) + sizeof(m->header_.length_) + len,
315 &crc_calc.crc[0]);
316 if (crc_calc != crc_recv)
317 {
318 LOG(INFO, "[ModemRx] CRC Error, received: 0x%04X 0x%04X 0x%04X, "
319 "calculated: 0x%04X 0x%04X 0x%04X",
320 crc_recv.all_, crc_recv.even_, crc_recv.odd_,
321 crc_calc.all_, crc_calc.even_, crc_calc.odd_);
323 }
324
325 size_t total_len = MIN_MESSAGE_SIZE + len;
326 std::string payload_tmp;
327 if (payload_.size() > total_len)
328 {
329 // We have the start of the next message, which we need to save off.
330 payload_tmp =
331 payload_.substr(total_len, payload_.size() - total_len);
332 payload_.resize(total_len);
333 }
334 // A this point, we have a valid message.
335 LOG(INFO, "[ModemRx] %s", string_to_hex(payload_).c_str());
336
337 if (receiver_)
338 {
339 auto *b = receiver_->alloc();
340 b->data()->payload = std::move(payload_);
341 receiver_->send(b);
342 }
343
344 if (payload_tmp.size())
345 {
346 // Move the start of the next message into payload.
347 LOG(INFO, "[ModemRx] Remaining payload size: %zu",
348 payload_tmp.size());
349 payload_ = std::move(payload_tmp);
350 recvCnt_ = payload_.size();
352 }
354 }
355
361 {
362 // Message parsing out of sync.
363
368
369 for (unsigned idx = 1; idx < recvCnt_; ++idx)
370 {
371 if (payload_[idx] == Defs::PREAMBLE_FIRST)
372 {
373 if ((idx + 4) <= recvCnt_)
374 {
375 uint32_t p;
376 memcpy(&p, payload_.data() + idx, 4);
377 if (p != htobe32(Defs::PREAMBLE))
378 {
379 continue;
380 }
381 }
382 // A memmove is more efficient than payload_.erase(0, idx)
383 // because it will not shrink the size of the payload_, and it
384 // maintains proper alignment by reusing the original heap
385 // allocation.
386 memmove(&payload_[0], &payload_[idx], recvCnt_ - idx);
387 recvCnt_ -= idx;
388 LOG(INFO, "[ModemRx] Sync on preamble first, recvCnt_: %zu",
389 recvCnt_);
391 }
392 }
393 // Now: we're out of sync and never found a viable first byte. Drop
394 // all data.
396 }
397
399 static constexpr long long CHARACTER_NSEC = 10 * SEC_TO_NSEC(1) / 250000;
400
402 static constexpr unsigned MIN_MESSAGE_SIZE = Defs::LEN_BASE;
403
405 static constexpr unsigned MAX_DATA_LEN = Defs::MAX_LEN;
406
410 string payload_;
413 size_t recvCnt_;
416
418 int fd_ = -1;
419};
420
422{
423public:
425 : pendingRead_(false)
426 , doneRead_(false)
427 , pendingWrite_(false)
428 , doneWrite_(false)
429 , txFlow_(tx)
430 { }
431
432 address_t max_address() override
433 {
434 return 1023;
435 }
436
438 bool read_only() override
439 {
440 return false;
441 }
442
443 size_t write(address_t destination, const uint8_t *data, size_t len,
444 errorcode_t *error, Notifiable *again) override
445 {
446 if (doneWrite_)
447 {
448 doneWrite_ = false;
449 *error = errorCode_;
450 return actualLen_;
451 }
452
453 actualLen_ = len;
454 pendingWrite_ = true;
455 doneWrite_ = false;
456 done_ = again;
457 *error = ERROR_AGAIN;
458
459 auto *b = txFlow_->alloc();
460 b->data()->payload =
461 Defs::get_memw_payload(proxySpace_, destination, data, len);
462 txFlow_->send(b);
463 return 0;
464 }
465
466 size_t read(address_t source, uint8_t *dst, size_t len, errorcode_t *error,
467 Notifiable *again) override
468 {
469 if (doneRead_)
470 {
471 doneRead_ = false;
472 *error = errorCode_;
473 return actualLen_;
474 }
475
476 readBuf_ = dst;
477 actualLen_ = len;
478 pendingRead_ = true;
479 doneRead_ = false;
480 done_ = again;
481 *error = ERROR_AGAIN;
482
483 auto *b = txFlow_->alloc();
484 b->data()->payload = Defs::get_memr_payload(proxySpace_, source, len);
485 txFlow_->send(b);
486 return 0;
487 }
488
491 void send(Buffer<TxMessage>* buf, unsigned prio) override {
492 auto rb = get_buffer_deleter(buf);
493 auto& txm = *buf->data();
494 if (!txm.valid()) {
495 return;
496 }
497 switch (txm.command())
498 {
499 case Defs::RESP_MEM_R:
500 {
501 if (pendingRead_)
502 {
503 handle_read_response(txm);
504 }
505 break;
506 }
507 case Defs::RESP_MEM_W:
508 {
509 if (pendingWrite_)
510 {
511 handle_write_response(txm);
512 }
513 break;
514 }
515 }
516 }
517
518 void handle_read_response(TxMessage &txm)
519 {
520 doneRead_ = true;
521 pendingRead_ = false;
523 unsigned data_bytes = txm.length() - 2;
524 if (data_bytes > actualLen_)
525 {
526 // We should not have received more bytes back than we requested,
527 // but we still clip.
528 data_bytes = actualLen_;
529 }
530 memcpy(readBuf_, txm.payload.data() + Defs::OFS_DATA + 2, data_bytes);
531 actualLen_ = data_bytes;
532 done_->notify();
533 }
534
535 void handle_write_response(TxMessage& txm) {
536 pendingWrite_ = false;
537 doneWrite_ = true;
538 errorCode_ = txm.response_status();
539 if (errorCode_)
540 {
541 actualLen_ = Defs::get_uint16(txm.payload, Defs::OFS_DATA + 2);
542 }
543 done_->notify();
544 }
545
548
550 bool pendingRead_ : 1;
552 bool doneRead_ : 1;
556 bool doneWrite_ : 1;
557
559 uint16_t errorCode_ = 0;
560
562 uint8_t* readBuf_ = nullptr;
565 unsigned actualLen_ = 0;
566
568 Notifiable* done_ = nullptr;
569
572};
573
575{
576public:
577 ModemTrain(Service *service)
578 : txFlow_(service)
579 , rxFlow_(service)
580 , isActive_(false)
581 {
582 rxFlow_.set_listener(&cvSpace_);
583 }
584
585 void start(int uart_fd)
586 {
587 txFlow_.start(uart_fd);
588 rxFlow_.start(uart_fd);
589 }
590
593 void set_is_active(bool is_active)
594 {
595 if (isActive_ != is_active)
596 {
597 isActive_ = is_active;
598 send_packet(Defs::get_wireless_present_payload(isActive_));
599 }
600 }
601
602 openlcb::MemorySpace *get_cv_space()
603 {
604 return &cvSpace_;
605 }
606
607 // ====== Train interface =======
608
609 void set_speed(openlcb::SpeedType speed) override
610 {
611 set_is_active(true);
612 inEStop_ = false;
613 lastSpeed_ = speed;
614 send_packet(Defs::get_speed_set_payload(speed));
615 }
616
619 {
620 return lastSpeed_;
621 }
622
624 void set_emergencystop() override
625 {
626 inEStop_ = true;
627 lastSpeed_.set_mph(0); // keeps direction
628 send_packet(Defs::get_estop_payload());
629 }
630
631 bool get_emergencystop() override
632 {
633 return inEStop_;
634 }
635
642 void set_fn(uint32_t address, uint16_t value) override
643 {
644 set_is_active(true);
645 send_packet(Defs::get_fn_set_payload(address, value));
650 switch (address)
651 {
652 default:
653 break;
654#if !defined(GTEST)
655 case 0:
656 if (lastSpeed_.direction() == openlcb::Velocity::REVERSE)
657 {
658 LOGIC_F0R_Pin::set(value ? 1 : 0);
659 }
660 break;
661 case 1:
662 LOGIC_F1_Pin::set(value ? 1 : 0);
663 break;
664 case 2:
665 LOGIC_F2_Pin::set(value ? 1 : 0);
666 break;
667 case 3:
668 LOGIC_F3_Pin::set(value ? 1 : 0);
669 break;
670 case 4:
671 LOGIC_F4_Pin::set(value ? 1 : 0);
672 break;
673 // F5 and F6 also overwritten with input1 and input2 values when
674 // button 7 or 8 are pressed
675 case 5:
676 LOGIC_F5_Pin::set(value ? 1 : 0);
677 break;
678 case 6:
679 LOGIC_F6_Pin::set(value ? 1 : 0);
680 break;
681#endif // !defined(GTEST)
682// Commented out since these pins cannot be defined as both inputs
683// and outputs.
684#if 0
685 case 7:
686 LOGIC_F7_Pin::set(value ? 1 : 0);
687 break;
688 case 8:
689 LOGIC_F8_Pin::set(value ? 1 : 0);
690 break;
691#endif
692#if !defined(GTEST)
693 case 7:
694 {
695 bool input1 = INPUT1_Pin::get();
696 LOGIC_F5_Pin::set(INPUT2_Pin::get());
697 LOG(INFO, "INPUT1: %u", input1);
698 break;
699 }
700 case 8:
701 {
702 bool input2 = INPUT2_Pin::get();
703 LOGIC_F6_Pin::set(INPUT2_Pin::get());
704 LOG(INFO, "INPUT2: %u", input2);
705 break;
706 }
707#endif // !defined(GTEST)
708 }
709 }
710
712 uint16_t get_fn(uint32_t address) override
713 {
715 return 0;
716 }
717
718 uint32_t legacy_address() override
719 {
721 return 883;
722 }
723
726 {
727 return dcc::TrainAddressType::DCC_LONG_ADDRESS;
728 }
729
730private:
731 inline void send_packet(Defs::Payload p)
732 {
733 auto *b = txFlow_.alloc();
734 b->data()->payload = std::move(p);
735 txFlow_.send(b);
736 }
737
738 bool isRunning_ = false;
740 int fd_;
741
742 openlcb::SpeedType lastSpeed_ = 0.0;
744 bool inEStop_ = false;
745
746 TxFlow txFlow_;
747 RxFlow rxFlow_;
748 CvSpace cvSpace_{&txFlow_};
749 bool isActive_;
750};
751
752} // namespace traction_modem
753
754#endif // _TRACTIONMODEM_TRACTIONMODEM_HXX_
BufferPtr< T > get_buffer_deleter(Buffer< T > *b)
Helper function to create a BufferPtr of an appropriate type without having to explicitly specify the...
Definition Buffer.hxx:272
static void crc3_crc16_ccitt(const void *data, size_t length_bytes, uint16_t checksum[3])
Computes the triple-CRC value over a chunk of data.
Definition Crc.hxx:325
#define STATE(_fn)
Turns a function name into an argument to be supplied to functions expecting a state.
Definition StateFlow.hxx:61
Base class for all QMember types that hold data in an expandable format.
Definition Buffer.hxx:195
T * data()
get a pointer to the start of the data.
Definition Buffer.hxx:215
Abstract class for message recipients.
virtual void send(MessageType *message, unsigned priority=UINT_MAX)=0
Entry point to the flow.
MessageType * alloc()
Synchronously allocates a message buffer from the pool of this flow.
An object that can schedule itself on an executor to run.
virtual void notify()=0
Generic callback.
A list of queues.
Definition Queue.hxx:466
Collection of related state machines that pend on incoming messages.
Return type for a state flow callback.
Base class for state machines.
Action read_repeated(StateFlowSelectHelper *helper, int fd, void *buf, size_t size, Callback c, unsigned priority=Selectable::MAX_PRIO)
Blocks until size bytes are read and then invokes the next state.
Action read_repeated_with_timeout(StateFlowTimedSelectHelper *helper, long long timeout_nsec, int fd, void *buf, size_t size, Callback c, unsigned priority=Selectable::MAX_PRIO)
Blocks until size bytes are read, or a timeout expires.
Service * service()
Return a pointer to the service I am bound to.
Action exit()
Terminate current StateFlow activity.
void start_flow(Callback c)
Resets the flow to the specified state and starts it.
Action call_immediately(Callback c)
Imediately call the next state upon return.
Action write_repeated(StateFlowSelectHelper *helper, int fd, const void *buf, size_t size, Callback c, unsigned priority=Selectable::MAX_PRIO)
Writes some data into a file descriptor, repeating the operation as necessary until all bytes are wri...
Action release_and_exit()
Terminates the processing of the current message.
State flow with a given typed input queue.
void send(MessageType *msg, unsigned priority=UINT_MAX) OVERRIDE
Sends a message to the state flow for processing.
Abstract base class for the address spaces exported via the Memory Config Protocol.
static const errorcode_t ERROR_AGAIN
This error code signals that the operation was only partially completed, the again notify was used an...
Abstract base class for train implementations.
This class provides a mechanism for working with velocity in different forms.
Definition Velocity.hxx:73
@ REVERSE
reverse direction
Definition Velocity.hxx:80
void set_mph(float mph)
Sets the speed value from a given mph value.
Definition Velocity.hxx:213
bool direction() const
Return the direction independent of speed.
Definition Velocity.hxx:164
size_t write(address_t destination, const uint8_t *data, size_t len, errorcode_t *error, Notifiable *again) override
uint8_t * readBuf_
Where to put the bytes read.
unsigned actualLen_
How many bytes to put there.
address_t max_address() override
Notifiable * done_
Notifiable to mark when the pending read/write completes.
static constexpr uint8_t proxySpace_
This is the memory space we will be using on the decoder.
bool pendingRead_
true if we are waiting for a read response
PacketFlowInterface * txFlow_
We send outgoing packets to the decoder using this interface.
size_t read(address_t source, uint8_t *dst, size_t len, errorcode_t *error, Notifiable *again) override
bool doneRead_
true if we the read response arrived
void send(Buffer< TxMessage > *buf, unsigned prio) override
Handles messages coming back from the decoder via the traction modem protocol.
uint16_t errorCode_
Returned error code from the backend.
bool pendingWrite_
true if we are waiting for a write response
bool doneWrite_
true if we the write response arrived
void set_speed(openlcb::SpeedType speed) override
Sets the speed of the locomotive.
bool inEStop_
True if the last set was estop, false if it was a speed.
void set_emergencystop() override
Sets the train to emergency stop.
openlcb::SpeedType get_speed() override
Returns the last set speed of the locomotive.
uint16_t get_fn(uint32_t address) override
dcc::TrainAddressType legacy_address_type() override
void set_fn(uint32_t address, uint16_t value) override
Sets the value of a function.
uint32_t legacy_address() override
bool get_emergencystop() override
Get the current E-Stop state.
void set_is_active(bool is_active)
Set the active state of the wireless control.
int fd_
UART fd to send traffic to the device.
Object responsible for reading in a stream of bytes over the modem interface and forming the stream o...
static constexpr unsigned MIN_MESSAGE_SIZE
Minimum size of a message.
Receiver * receiver_
Incoming messages get routed to this object.
Action maybe_message_complete()
We might have a complete message if we have received enough data.
void start(int fd)
Start the flow using the given interface.
Action reset()
Resets the message reception state machine.
static constexpr unsigned MAX_DATA_LEN
Maximum size of the data portion of a message.
Action base_data_received()
Received at least as much data as the minimum message size.
long long get_character_nsec()
Get the wire time for a single character.
Action wait_for_base_data()
Wait until we have at least as much data as a minimum size message.
static constexpr long long CHARACTER_NSEC
Used to place a bounds on a timeout of a message.
Action resync()
Something went wrong in decoding the data stream.
string payload_
We assemble the message here.
StateFlowTimedSelectHelper helper_
Helper for reading in a select flow.
void set_listener(Receiver *rcv)
Register a listener to send incoming messages to.
RxFlow(Service *service)
Constructor.
size_t recvCnt_
Number of bytes that have been received into payload_, which may be less than payload_....
Action header_complete()
We think we have a complete header because we just validated a preamble.
Object responsible for writing messages to the modem interface.
void start(int fd)
Bind an interface to the flow to start transmitting to.
Action write_complete()
Finish up the write and exit.
Action entry() override
Entry point to the state flow for incoming TxMessages to transmit.
StateFlowSelectHelper helper_
Helper for performing the writes.
TxFlow(Service *service)
Constructor.
TrainAddressType
Which address type this legacy train node uses.
Definition dcc/Defs.hxx:46
#define LOG(level, message...)
Conditionally write a message to the logging output.
Definition logging.h:99
static const int INFO
Loglevel that is printed by default, reporting some status information.
Definition logging.h:57
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
#define SEC_TO_NSEC(_sec)
Convert a second value to a nanosecond value.
Definition os.h:286
Use this class to read from an fd using select() in a state flow.
unsigned remaining_
Number of bytes still outstanding to read.
unsigned hasError_
1 if there was an error reading of writing.
Use this class to read from an fd with select and timeout.
@ SPACE_DCC_CV
proxy space for DCC functions
uint16_t even_
CRC of even bytes.
uint16_t odd_
CRC of odd bytes.
uint16_t all_
CRC of all bytes.
uint16_t command_
message command
uint32_t preamble_
packet preamble
The definition of a message.
uint8_t data[0]
start of the message data
static const CRC get_crc(const Payload &p, uint16_t length)
Extract the CRC value(s) from a given payload.
static bool is_valid(const Payload &p)
Verifies that a given payload is a valid packet.
static uint16_t get_uint16(const Payload &p, unsigned ofs)
Extract uint16_t value from a given offset in a wire formatted payload.
static constexpr unsigned MAX_LEN
Maximum allowed len value.
@ RESP_MEM_R
memory read response
@ RESP_MEM_W
memory write response
static constexpr unsigned OFS_LEN
Offset of the length in the packet.
static Payload get_speed_set_payload(openlcb::Velocity v)
Computes payload to set speed and direction.
static Payload get_memr_payload(uint8_t space, uint32_t address, uint8_t count)
Computes payload to read some data.
static constexpr uint32_t PREAMBLE
Every command starts with these bytes.
static constexpr unsigned OFS_CMD
Offset of the command in the packet.
static Payload get_wireless_present_payload(bool is_present)
Computes payload for the wireless present message.
static constexpr unsigned LEN_BASE
Length of a zero-payload packet.
static Payload get_fn_set_payload(unsigned fn, uint16_t value)
Computes payload to set a function.
static constexpr unsigned OFS_DATA
Offset of the first data byte in the packet.
static Payload get_estop_payload()
Computes payload to set estop.
static constexpr char PREAMBLE_FIRST
First byte of the preamble.
static Payload get_memw_payload(uint8_t space, uint32_t address, const std::string &data)
Computes payload to write some data.
uint16_t response_status()
Return the first two bytes of a response payload as an uint16_t.