Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
TractionCvSpace.cxx
1
39
40// We try this many times to write a CV using railcom if we keep getting an
41// unknown railcom response. After that we try to verify with a read.
42static const int WRITE_RETRY_COUNT_ON_UNKNOWN = 10;
43// We try at most this many times to read a CV if we keep getting an unknown
44// railcom response. After that we report an error.
45static const int READ_RETRY_COUNT_ON_UNKNOWN = 10;
46
47// Maximum time we keep trying to read or write a given CV.
48static const int RAILCOM_POM_OP_TIMEOUT_MSEC = 2000;
49
50
51namespace openlcb
52{
53
54TractionCvSpace::TractionCvSpace(MemoryConfigHandler *parent,
55 dcc::TrackIf *track,
56 dcc::RailcomHubFlow *railcom_hub,
57 uint8_t space_id)
58 : StateFlowBase(parent->service())
59 , parent_(parent)
60 , track_(track)
61 , railcomHub_(railcom_hub)
62 , errorCode_(ERROR_NOOP)
63 , spaceId_(space_id)
64 , timer_(this)
65{
66 parent_->registry()->insert(nullptr, spaceId_, this);
67 // We purposefully do not start the state flow until a request comes in.
68}
69
70TractionCvSpace::~TractionCvSpace()
71{
72 parent_->registry()->erase(nullptr, spaceId_, this);
73 if (errorCode_ == ERROR_PENDING)
74 {
75 timer_.cancel();
76 }
77}
78
79bool TractionCvSpace::set_node(Node *node)
80{
81 if (!node)
82 {
83 return false;
84 }
85 NodeID id = node->node_id();
86 if ((id & 0xFFFF00000000ULL) != TractionDefs::NODE_ID_DCC)
87 {
88 currId_ = 0;
89 return false;
90 }
91 if ((id & 0xFFFF) == currId_)
92 {
93 return true;
94 }
95 currId_ = (id & 0xFFFF);
97 uint32_t addr;
98 if (!TractionDefs::legacy_address_from_train_node_id(id, &type, &addr)) {
99 currId_ = 0;
100 return false;
101 }
102 switch(type) {
103 case dcc::TrainAddressType::DCC_LONG_ADDRESS:
104 dccAddressNum_ = addr;
105 dccIsLong_ = true;
106 break;
107 case dcc::TrainAddressType::DCC_SHORT_ADDRESS:
108 dccAddressNum_ = addr;
109 dccIsLong_ = false;
110 break;
111 default:
112 currId_ = 0;
113 return false;
114 }
115 errorCode_ = ERROR_NOOP;
116 return true;
117}
118
119const unsigned TractionCvSpace::MAX_CV;
120
121size_t TractionCvSpace::read(const address_t source, uint8_t *dst, size_t len,
122 errorcode_t *error, Notifiable *again)
123{
124 if (source == OFFSET_CV_INDEX) {
125 lastIndexedNode_ = currId_;
126 uint8_t* lastcv = (uint8_t*)&lastIndexedCv_;
127 if (len > 0) dst[0] = lastcv[3];
128 if (len > 1) dst[1] = lastcv[2];
129 if (len > 2) dst[2] = lastcv[1];
130 if (len > 3) dst[3] = lastcv[0];
131 return std::min(len, size_t(4));
132 }
133 uint32_t cv = -1;
134 if (source == OFFSET_CV_VALUE || source == OFFSET_CV_VERIFY_RESULT)
135 {
136 if (currId_ != lastIndexedNode_)
137 {
138 *error = Defs::ERROR_PERMANENT;
139 return 0;
140 }
141 // Translate from user-visible CV to wire protocol CV.
142 cv = lastIndexedCv_ - 1;
143 // fall through to regular processing
144 }
145 if (source < OFFSET_CV_INDEX)
146 {
147 cv = source;
148 }
149 LOG(INFO, "cv read %" PRIu32, cv);
150 if (cv > MAX_CV)
151 {
152 *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS;
153 errorCode_ = ERROR_NOOP;
154 return 0;
155 }
156 if (cv == cvNumber_)
157 {
158 if (errorCode_ == ERROR_OK)
159 {
160 *dst = cvData_;
161 errorCode_ = ERROR_NOOP;
162 return 1;
163 }
164 else if (errorCode_ == _ERROR_TIMEOUT)
165 {
166 *error = Defs::ERROR_OPENLCB_TIMEOUT;
167 errorCode_ = ERROR_NOOP;
168 return 0;
169 }
170 else if (errorCode_ == _ERROR_BUSY)
171 {
172 *error = Defs::ERROR_OUT_OF_ORDER;
173 errorCode_ = ERROR_NOOP;
174 return 0;
175 }
176 }
177 done_ = again;
178 cvNumber_ = cv;
179 errorCode_ = ERROR_NOOP;
180 cvData_ = 0;
181 numTry_ = 0;
182 if ((source == OFFSET_CV_VALUE) || (source <= MAX_CV))
183 {
184 start_flow(STATE(try_read1));
185 }
186 else if (source == OFFSET_CV_VERIFY_RESULT)
187 {
188 start_flow(STATE(pgm_verify));
189 }
190 else
191 {
192 DIE("Have not started the flow but will return AGAIN.");
193 }
194 *error = ERROR_AGAIN;
195 deadline_ =
196 os_get_time_monotonic() + MSEC_TO_NSEC(RAILCOM_POM_OP_TIMEOUT_MSEC);
197 return 0;
198}
199
200StateFlowBase::Action TractionCvSpace::pgm_verify()
201{
202 return invoke_subflow_and_wait(
204 STATE(pgm_verify_wait_flush),
205 ProgrammingTrackRequest::ENTER_SERVICE_MODE);
206}
207
208StateFlowBase::Action TractionCvSpace::pgm_verify_wait_flush()
209{
210 auto b = get_buffer_deleter(
211 full_allocation_result(Singleton<ProgrammingTrackBackend>::instance()));
212 if (b->data()->resultCode != 0)
213 {
214 // Failed to enter service mode. Maybe we are in ESTOP.
215 errorCode_ = _ERROR_BUSY;
216 done_->notify();
217 return exit();
218 }
219 return sleep_and_call(&timer_, MSEC_TO_NSEC(10), STATE(pgm_verify_reset));
220}
221
222StateFlowBase::Action TractionCvSpace::pgm_verify_reset()
223{
224 return invoke_subflow_and_wait(
226 STATE(pgm_verify_packet), ProgrammingTrackRequest::SEND_RESET, 15);
227}
228
229StateFlowBase::Action TractionCvSpace::pgm_verify_packet()
230{
231 auto b = get_buffer_deleter(
232 full_allocation_result(Singleton<ProgrammingTrackBackend>::instance()));
233
234 dcc::Packet pkt;
235 pkt.set_dcc_svc_verify_byte(lastIndexedCv_, lastVerifyValue_);
236 pkt.set_dcc_svc_verify_bit(0, 7, 0);
237 /*pkt.start_dcc_packet();
238 pkt.payload[0] = 0b01110100 | ((lastIndexedCv_ >> 8) & 0b11);
239 pkt.payload[1] = (lastIndexedCv_ & 0xff);
240 pkt.payload[2] = lastVerifyValue_;
241 pkt.dlc = 3;
242 pkt.add_dcc_checksum();*/
243 return invoke_subflow_and_wait(
245 ProgrammingTrackRequest::SEND_PROGRAMMING_PACKET, pkt, 15);
246}
247
248StateFlowBase::Action TractionCvSpace::pgm_verify_done()
249{
250 auto b = get_buffer_deleter(
251 full_allocation_result(Singleton<ProgrammingTrackBackend>::instance()));
252 if (b->data()->hasAck_)
253 {
254 cvData_ = 1;
255 }
256 else
257 {
258 cvData_ = 0;
259 }
260 errorCode_ = ERROR_OK;
261 return invoke_subflow_and_wait(
263 STATE(pgm_verify_reset_done), ProgrammingTrackRequest::SEND_RESET, 15);
264}
265
266StateFlowBase::Action TractionCvSpace::pgm_verify_reset_done()
267{
268 auto b = get_buffer_deleter(
269 full_allocation_result(Singleton<ProgrammingTrackBackend>::instance()));
270 return invoke_subflow_and_wait(
272 ProgrammingTrackRequest::EXIT_SERVICE_MODE);
273}
274
275StateFlowBase::Action TractionCvSpace::pgm_verify_exit()
276{
277 auto b = get_buffer_deleter(
278 full_allocation_result(Singleton<ProgrammingTrackBackend>::instance()));
279 return async_done();
280}
281
282StateFlowBase::Action TractionCvSpace::try_read1()
283{
284 return allocate_and_call(track_, STATE(fill_read1_packet));
285}
286StateFlowBase::Action TractionCvSpace::try_write1()
287{
288 return allocate_and_call(track_, STATE(fill_write1_packet));
289}
290
291StateFlowBase::Action TractionCvSpace::fill_read1_packet()
292{
293 auto *b = get_allocation_result(track_);
294 b->data()->start_dcc_packet();
295 if (dccIsLong_)
296 {
297 b->data()->add_dcc_address(dcc::DccLongAddress(dccAddressNum_));
298 }
299 else
300 {
301 b->data()->add_dcc_address(dcc::DccShortAddress(dccAddressNum_));
302 }
303 b->data()->add_dcc_pom_read1(cvNumber_);
304 b->data()->feedback_key = reinterpret_cast<uintptr_t>(this);
305 // We proactively repeat read packets twice.
306 b->data()->packet_header.rept_count = 1;
307 errorCode_ = ERROR_PENDING;
308 railcomHub_->register_port(this);
309 track_->send(b);
310 return sleep_and_call(&timer_, MSEC_TO_NSEC(500), STATE(read_returned));
311}
312
313StateFlowBase::Action TractionCvSpace::read_returned()
314{
315 LOG(WARNING, "railcom read returned status %d value %d", errorCode_, cvData_);
316 switch (errorCode_) {
317 case ERROR_PENDING:
318 errorCode_ = _ERROR_TIMEOUT;
319 railcomHub_->unregister_port(this);
320 break;
321 case ERROR_OK:
322 break;
323 default:
324 case ERROR_UNKNOWN_RESPONSE:
325 if (numTry_ >= READ_RETRY_COUNT_ON_UNKNOWN) {
326 errorCode_ = _ERROR_TIMEOUT;
327 break;
328 }
329 numTry_++;
330 return call_immediately(STATE(try_read1));
331 case _ERROR_BUSY:
332 if (os_get_time_monotonic() > deadline_) {
333 errorCode_ = _ERROR_TIMEOUT;
334 break;
335 }
336 return call_immediately(STATE(try_read1));
337 }
338 return async_done();
339}
340
341size_t TractionCvSpace::write(address_t destination, const uint8_t *src,
342 size_t len, errorcode_t *error, Notifiable *again)
343{
344 if (destination == OFFSET_CV_INDEX) {
345 lastIndexedNode_ = currId_;
346 uint8_t* lastcv = (uint8_t*)&lastIndexedCv_;
347 if (len > 0) lastcv[3] = src[0];
348 if (len > 1) lastcv[2] = src[1];
349 if (len > 2) lastcv[1] = src[2];
350 if (len > 3) lastcv[0] = src[3];
351 return std::min(len, size_t(4));
352 }
353 if (destination == OFFSET_CV_VERIFY_VALUE)
354 {
355 lastVerifyValue_ = src[0];
356 return 1;
357 }
358 if (destination == OFFSET_CV_VALUE) {
359 if (currId_ != lastIndexedNode_) {
360 *error = Defs::ERROR_TEMPORARY;
361 return 0;
362 }
363 // Translate from user-visible CV to wire protocol CV.
364 destination = lastIndexedCv_ - 1;
365 // fall through to regular processing
366 }
367 LOG(INFO, "cv write %" PRIu32 " := %d", destination, *src);
368 if (destination > MAX_CV)
369 {
370 *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS;
371 errorCode_ = ERROR_NOOP;
372 return 0;
373 }
374 if (errorCode_ == ERROR_OK && destination == cvNumber_)
375 {
376 errorCode_ = ERROR_NOOP;
377 return 1;
378 }
379 if (errorCode_ == _ERROR_TIMEOUT && destination == cvNumber_)
380 {
381 *error = Defs::ERROR_TEMPORARY | 1;
382 errorCode_ = ERROR_NOOP;
383 return 0;
384 }
385 done_ = again;
386 cvNumber_ = destination;
387 cvData_ = *src;
388 numTry_ = 0;
389 errorCode_ = ERROR_NOOP;
390 start_flow(STATE(try_write1));
391 *error = ERROR_AGAIN;
392 deadline_ = os_get_time_monotonic() + MSEC_TO_NSEC(RAILCOM_POM_OP_TIMEOUT_MSEC);
393 return 0;
394}
395
396StateFlowBase::Action TractionCvSpace::fill_write1_packet()
397{
398 auto *b = get_allocation_result(track_);
399 b->data()->start_dcc_packet();
400 if (dccIsLong_)
401 {
402 b->data()->add_dcc_address(dcc::DccLongAddress(dccAddressNum_));
403 }
404 else
405 {
406 b->data()->add_dcc_address(dcc::DccShortAddress(dccAddressNum_));
407 }
408 b->data()->add_dcc_pom_write1(cvNumber_, cvData_);
409 b->data()->feedback_key = reinterpret_cast<uintptr_t>(this);
410 // POM write packets need to appear at least twice on non-back-to-back
411 // packets by the standard. We make 4 back to back packets and that
412 // fulfills the requirement.
413 b->data()->packet_header.rept_count = 3;
414 errorCode_ = ERROR_PENDING;
415 railcomHub_->register_port(this);
416 track_->send(b);
417 return sleep_and_call(&timer_, MSEC_TO_NSEC(500), STATE(write_returned));
418}
419
420StateFlowBase::Action TractionCvSpace::write_returned()
421{
422 LOG(WARNING, "railcom write returned status %d value %d", errorCode_, cvData_);
423 switch (errorCode_)
424 {
425 case ERROR_PENDING:
426 errorCode_ = _ERROR_TIMEOUT;
427 railcomHub_->unregister_port(this);
428 break;
429 case ERROR_OK:
430 break;
431 default:
432 case ERROR_UNKNOWN_RESPONSE:
433 if (numTry_ >= WRITE_RETRY_COUNT_ON_UNKNOWN)
434 {
435 // We switch from writing to reading if we had tried many enough
436 // times. Maybe the write was actually successful.
437 return call_immediately(STATE(try_read1));
438 }
439 numTry_++;
440 return call_immediately(STATE(try_write1));
441 case _ERROR_BUSY:
442 if (os_get_time_monotonic() > deadline_)
443 {
444 errorCode_ = _ERROR_TIMEOUT;
445 break;
446 }
448 return call_immediately(STATE(try_write1));
449 }
450 return async_done();
451}
452
453void TractionCvSpace::record_railcom_status(unsigned code)
454{
455 errorCode_ = code;
456 timer_.trigger();
457 railcomHub_->unregister_port(this);
458}
459
460void TractionCvSpace::send(Buffer<dcc::RailcomHubData> *b, unsigned priority)
461{
463 if (errorCode_ != ERROR_PENDING)
464 return;
465 const dcc::Feedback &f = *b->data();
466 if (f.feedbackKey != (reinterpret_cast<uintptr_t>(this)) || f.channel == 0xff)
467 {
468 // Skip railcom from other packets; also skip the railcom-based
469 // occupancy information packets.
470 return;
471 }
472 LOG(INFO, "CV railcom feedback ch=%d: %s", f.channel, railcom_debug(f).c_str());
473 if (!f.ch2Size)
474 {
475 return record_railcom_status(ERROR_NO_RAILCOM_CH2_DATA);
476 }
477 dcc::parse_railcom_data(f, &interpretedResponse_);
478 unsigned new_status = ERROR_PENDING;
479 for (const auto& e : interpretedResponse_) {
480 if (e.railcom_channel != 2) continue;
481 switch(e.type) {
482 case dcc::RailcomPacket::BUSY:
483 if (new_status == ERROR_PENDING) {
484 new_status = _ERROR_BUSY;
485 }
486 break;
487 case dcc::RailcomPacket::NACK:
488 if (new_status == ERROR_PENDING) {
489 // We ignore NACK responses because certain versions ofthe
490 // standard had NACK interpreted the same way as ACK as well as
491 // it was being used as a filler. There are locomotives out
492 // there who do this.
493 // new_status = ERROR_NACK;
494 }
495 break;
496 case dcc::RailcomPacket::ACK:
497 if (new_status == ERROR_PENDING) {
498 // Ack should not change the state machine of CV reads or
499 // writes. Both of those need to return explicitly with a
500 // MOB_POM.
501 }
502 break;
503 case dcc::RailcomPacket::GARBAGE:
504 if (new_status == ERROR_PENDING) {
505 // If we got garbage, we stay in pending state which will
506 // re-send the command again.
507 }
508 break;
509 case dcc::RailcomPacket::MOB_POM:
510 cvData_ = e.argument;
511 new_status = ERROR_OK;
512 break;
513 default:
514 break;
515 }
516 }
517 if (new_status == ERROR_PENDING)
518 {
519 // Do not record status if it is still pending.
520 return;
521 }
522 return record_railcom_status(new_status);
523}
524
525} // namespace openlcb
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
std::unique_ptr< Buffer< T >, BufferDelete< T > > AutoReleaseBuffer
This class will automatically unref a Buffer when going out of scope.
Definition Buffer.hxx:256
void parse_railcom_data(const dcc::Feedback &fb, std::vector< struct RailcomPacket > *output)
Interprets the data from a railcom feedback.
Definition RailCom.cxx:267
std::string railcom_debug(const Feedback &fb)
Formats a dcc::Feedback message into a debug string.
#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
Templated implementation of the HubFlow.
Definition Hub.hxx:150
An object that can schedule itself on an executor to run.
Singleton class.
Definition Singleton.hxx:65
Return type for a state flow callback.
Base class for state machines.
Base class for NMRAnet nodes conforming to the asynchronous interface.
Definition Node.hxx:52
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 WARNING
Loglevel that is always printed, reporting a warning or a retryable error.
Definition logging.h:55
static const int INFO
Loglevel that is printed by default, reporting some status information.
Definition logging.h:57
#define DIE(MSG)
Unconditionally terminates the current process with a message.
Definition macros.h:143
uint64_t NodeID
48-bit NMRAnet Node ID type
long long os_get_time_monotonic(void)
Get the monotonic time since the system started.
Definition os.c:571
#define MSEC_TO_NSEC(_msec)
Convert a millisecond value to a nanosecond value.
Definition os.h:268
uint8_t ch2Size
Number of bytes in channel two.
Definition railcom.h:49
uint8_t channel
Used by multi-channel railcom receiver drivers.
Definition railcom.h:54
uintptr_t feedbackKey
Opaque identifier that allows linking outgoing dcc::Packet sent to the DCC waveform generator to the ...
Definition railcom.h:58
Strongly typed wrapper representing a long DCC address.
Definition Address.hxx:66
Strongly typed wrapper representing a short DCC address.
Definition Address.hxx:49
Structure used for reading (railcom) feedback data from DCC / Railcom device drivers.
Definition RailCom.hxx:50
Represents a command to be sent to the track driver.
Definition Packet.hxx:52
void set_dcc_svc_verify_byte(unsigned cv_number, uint8_t value)
Sets the packet to a DCC service mode packet verifying the contents of an entire CV.
Definition Packet.cxx:274
void set_dcc_svc_verify_bit(unsigned cv_number, unsigned bit, bool expected)
Sets the packet to a DCC service mode packet verifying the contents of a single bit in a CV.
Definition Packet.cxx:286