Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
Logon.hxx
Go to the documentation of this file.
1
34#ifndef _DCC_LOGON_HXX_
35#define _DCC_LOGON_HXX_
36
37#include "dcc/LogonFeedback.hxx"
38#include "dcc/PacketSource.hxx"
39#include "dcc/TrackIf.hxx"
40#include "dcc/UpdateLoop.hxx"
42
43namespace dcc
44{
45
49{
50public:
53 unsigned num_locos();
54
57 bool is_valid_loco_id(unsigned loco_id);
58
63 uint8_t &loco_flags(unsigned loco_id);
64
68 uint64_t loco_did(unsigned loco_id);
69
74 unsigned create_or_lookup_loco(uint64_t decoder_id);
75
82 void run_address_policy(unsigned loco_id, uint16_t desired_address);
83
86 uint16_t assigned_address(unsigned loco_id);
87
90 void assign_complete(unsigned loco_id);
91
116}; // LogonHandlerModule
117
119template <class Module>
121{
122public:
130 Service *service, TrackIf *track, RailcomHubFlow *rcom_hub, Module *m)
132 , trackIf_(track)
133 , module_(m)
134 , fbParser_(this, rcom_hub)
137 , needShutdown_(0)
138 {
139 }
140
142 void startup_logon(uint16_t cid, uint8_t session_id)
143 {
144 cid_ = cid;
145 sessionId_ = session_id;
147 }
148
149#ifdef GTEST
150 void shutdown()
151 {
152 needShutdown_ = 1;
154 logonSelect_.ensure_triggered();
155 }
156#endif
157
158 // Callbacks from LogonFeedback
159
163 PacketType classify_packet(uintptr_t feedback_key) override
164 {
165 if (feedback_key >= 1 << 14)
166 {
167 LOG(INFO, "classify %x", (unsigned)feedback_key);
168 }
169 if (is_logon_enable_key(feedback_key))
170 {
171 return LOGON_ENABLE;
172 }
173 else if (is_select_shortinfo_key(feedback_key))
174 {
175 return SELECT_SHORTINFO;
176 }
177 else if (is_logon_assign_key(feedback_key))
178 {
179 return LOGON_ASSIGN;
180 }
181 return UNKNOWN;
182 }
183
190 uintptr_t feedback_key, bool error, uint64_t data) override
191 {
192 if (!is_select_shortinfo_key(feedback_key))
193 {
194 LOG(WARNING, "Unexpected select shortinfo key: %08x",
195 (unsigned)feedback_key);
196 return;
197 }
198 unsigned loco_id = feedback_key & LOCO_ID_MASK;
199 if (!module_->is_valid_loco_id(loco_id))
200 {
201 LOG(WARNING,
202 "Unexpected select shortinfo key: %08x - invalid loco id",
203 (unsigned)feedback_key);
204 return;
205 }
206 uint8_t &flags = module_->loco_flags(loco_id);
207 LOG(INFO, "Select shortinfo for loco ID %d, flags %02x error %d",
208 loco_id, flags, error);
209 flags &= ~LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO;
210 if (error)
211 {
213 {
214 flags &= ~LogonHandlerModule::FLAG_PENDING_RETRY;
216 return;
217 }
218 else
219 {
222 logonSelect_.wakeup();
223 return;
224 }
225 }
226 if (flags &
229 {
230 // Got multiple returns.
231 return;
232 }
233 module_->run_address_policy(loco_id, (data >> 32) & 0x3FFF);
235 logonSelect_.wakeup();
236 }
237
244 uintptr_t feedback_key, bool error, uint64_t data) override
245 {
246 if (!is_logon_assign_key(feedback_key))
247 {
248 LOG(WARNING, "Unexpected logon assign key: %08x",
249 (unsigned)feedback_key);
250 return;
251 }
252 unsigned loco_id = feedback_key & LOCO_ID_MASK;
253 if (!module_->is_valid_loco_id(loco_id))
254 {
255 LOG(WARNING, "Unexpected logon assign key: %08x - invalid loco id",
256 (unsigned)feedback_key);
257 return;
258 }
259 uint8_t &flags = module_->loco_flags(loco_id);
260 flags &= ~LogonHandlerModule::FLAG_PENDING_ASSIGN;
262 {
263 // duplicate responses.
264 return;
265 }
266 if (error)
267 {
269 {
270 flags &= ~LogonHandlerModule::FLAG_PENDING_RETRY;
272 return;
273 }
274 else
275 {
278 logonSelect_.wakeup();
279 return;
280 }
281 }
282 module_->assign_complete(loco_id);
283 flags &= ~LogonHandlerModule::FLAG_PENDING_TICK;
284 LOG(INFO, "Assign completed for loco %d address %d", loco_id,
285 module_->assigned_address(loco_id));
286 }
287
294 uintptr_t feedback_key, bool error, uint64_t data) override
295 {
297 if (data)
298 {
300 } else {
301 // No railcom feedback returned.
302 return;
303 }
304 if (LOGLEVEL >= INFO)
305 {
306 unsigned didh = (data >> 32) & 0xfff;
307 unsigned didl = data & 0xffffffffu;
308 LOG(INFO, "Decoder id %03x%08x error %d", didh, didl, error);
309 }
310 if (error)
311 {
313 return;
314 }
315 uint64_t did = data & DECODER_ID_MASK;
316 auto lid = module_->create_or_lookup_loco(did);
317 if (!module_->is_valid_loco_id(lid))
318 {
319 return;
320 }
321 auto &flags = module_->loco_flags(lid);
323 logonSelect_.wakeup();
324 }
325
326private:
332
335 {
336 logon_send_helper(Defs::LogonEnableParam::NOW, 0);
338 }
339
347 {
348 if (needShutdown_)
349 {
350 return exit();
351 }
353 timer_.start_absolute(next_time);
355 }
356
359 {
360 if (needShutdown_)
361 {
362 return exit();
363 }
364 if (timer_.is_triggered())
365 {
366 // found something via logon
368 {
372 }
373 // Not sure why we were woken up, let's start a sleep again.
375 }
376 else
377 {
378 // timer expired, send another logon.
380 }
381 }
382
389
392 {
393 logon_send_helper(Defs::LogonEnableParam::ALL, 3);
395 }
396
399 {
400 if (countLogonToSend_ >= 4)
401 {
404 }
405 else
406 {
411 }
412 }
413
419 void logon_send_helper(Defs::LogonEnableParam param, unsigned rept)
420 {
421 HASSERT(rept < 4u);
423 b->data()->set_dcc_logon_enable(param, cid_, sessionId_);
424 b->data()->feedback_key = LOGON_ENABLE_KEY;
425 b->data()->packet_header.rept_count = rept;
426 b->set_done(bn_.reset(this));
427
431
432 trackIf_->send(b);
433 }
434
435 class LogonSelect;
436 friend class LogonSelect;
437
440 class LogonSelect : public StateFlowBase, public ::Timer
441 {
442 public:
444 : StateFlowBase(parent->service())
445 , ::Timer(parent->service()->executor()->active_timers())
446 , parent_(parent)
447 {
448 start(MSEC_TO_NSEC(50));
449 }
450
452 void wakeup()
453 {
454 if (is_terminated())
455 {
456 cycleNextId_ = 0;
458 }
459 }
460
462 void tick()
463 {
464 bool need_wakeup = false;
465 for (unsigned id = 0; id < m()->num_locos() && id < MAX_LOCO_ID;
466 ++id)
467 {
468 uint8_t &fl = m()->loco_flags(cycleNextId_);
470 {
471 fl &= ~LogonHandlerModule::FLAG_PENDING_TICK;
472 }
474 {
475 fl &= ~LogonHandlerModule::FLAG_PENDING_RETRY;
477 }
479 {
481 // re-tried every now and then. We would probably need an
482 // extra counter for this though somewhere.
483 }
485 {
486 fl &= ~LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO;
489 need_wakeup = true;
490 }
492 {
493 fl &= ~LogonHandlerModule::FLAG_PENDING_ASSIGN;
496 need_wakeup = true;
497 }
498 }
499 if (need_wakeup)
500 {
501 wakeup();
502 }
503 }
504
505 private:
507 long long timeout() override
508 {
510 {
511 return 0;
512 }
513 tick();
514 return RESTART;
515 }
516
518 Module *m()
519 {
520 return parent_->module_;
521 }
522
525 {
526 return parent_->trackIf_;
527 }
528
532 {
534 {
535 return exit();
536 }
537 bool mid_cycle = (cycleNextId_ != 0);
538 for (;
539 cycleNextId_ < m()->num_locos() && cycleNextId_ <= MAX_LOCO_ID;
540 ++cycleNextId_)
541 {
542 uint8_t fl = m()->loco_flags(cycleNextId_);
544 {
545 return allocate_and_call(
547 }
549 {
550 return allocate_and_call(
552 }
553 }
554 cycleNextId_ = 0;
555 // Check if we need to run the search for the first half of the
556 // loco space too.
557 if (mid_cycle)
558 {
559 return again();
560 }
561 return exit();
562 }
563
567 {
569 uint64_t did = m()->loco_did(cycleNextId_);
570 b->data()->set_dcc_select_shortinfo(did);
571 b->data()->feedback_key =
573 b->set_done(bn_.reset((StateFlowBase *)this));
574 track()->send(b);
575 uint8_t &fl = m()->loco_flags(cycleNextId_);
576 fl &= ~LogonHandlerModule::FLAG_NEEDS_GET_SHORTINFO;
579 return wait_and_call(STATE(search));
580 }
581
585 {
587 uint64_t did = m()->loco_did(cycleNextId_);
588 b->data()->set_dcc_logon_assign(
589 did, m()->assigned_address(cycleNextId_));
590 b->data()->feedback_key =
592 b->set_done(bn_.reset((StateFlowBase *)this));
593 track()->send(b);
594 uint8_t &fl = m()->loco_flags(cycleNextId_);
595 fl &= ~LogonHandlerModule::FLAG_NEEDS_ASSIGN;
598 return wait_and_call(STATE(search));
599 }
600
603
606 unsigned cycleNextId_;
607
610 } logonSelect_ {this};
611
613 static constexpr uintptr_t LOGON_ENABLE_KEY =
614 uint32_t((Defs::ADDRESS_LOGON << 24) | (Defs::DCC_LOGON_ENABLE << 16));
615
619 static constexpr bool is_logon_enable_key(uintptr_t feedback_key)
620 {
621 return feedback_key == LOGON_ENABLE_KEY;
622 }
623
625 static constexpr uintptr_t SELECT_SHORTINFO_KEY =
626 uint32_t((Defs::ADDRESS_LOGON << 24) | (Defs::DCC_SELECT << 16) |
627 (Defs::CMD_READ_SHORT_INFO << 12));
628
632 static constexpr bool is_select_shortinfo_key(uintptr_t feedback_key)
633 {
634 return ((feedback_key & ~LOCO_ID_MASK) == SELECT_SHORTINFO_KEY);
635 }
636
638 static constexpr uintptr_t LOGON_ASSIGN_KEY =
639 uint32_t((Defs::ADDRESS_LOGON << 24) | (Defs::DCC_LOGON_ASSIGN << 16));
640
644 static constexpr bool is_logon_assign_key(uintptr_t feedback_key)
645 {
646 return ((feedback_key & ~LOCO_ID_MASK) == LOGON_ASSIGN_KEY);
647 }
648
650 static constexpr unsigned LOGON_PERIOD_MSEC = 295;
651
653 static constexpr unsigned MAX_LOCO_ID = 0xfff;
655 static constexpr uintptr_t LOCO_ID_MASK = MAX_LOCO_ID;
656
658 static constexpr uint64_t DECODER_ID_MASK = (1ull << 44) - 1;
659
662
664 Module *module_;
665
668
671
673
675 long long lastLogonTime_ {0};
676
678 uint16_t cid_;
680 uint8_t sessionId_;
681
688 uint8_t needShutdown_ : 1;
689
691 uint8_t countLogonToSend_ {0};
692
693}; // LogonHandler
694
695} // namespace dcc
696
697#endif // _DCC_LOGON_HXX_
#define STATE(_fn)
Turns a function name into an argument to be supplied to functions expecting a state.
Definition StateFlow.hxx:61
A BarrierNotifiable allows to create a number of child Notifiable and wait for all of them to finish.
BarrierNotifiable * reset(Notifiable *done)
Resets the barrier. Returns &*this. Asserts that is_done().
ActiveTimers * active_timers()
Definition Executor.hxx:144
virtual void send(MessageType *message, unsigned priority=UINT_MAX)=0
Entry point to the flow.
Templated implementation of the HubFlow.
Definition Hub.hxx:150
Collection of related state machines that pend on incoming messages.
ExecutorBase * executor()
Return type for a state flow callback.
Use this timer class to deliver the timeout notification to a stateflow.
Base class for state machines.
Service * service()
Return a pointer to the service I am bound to.
bool is_terminated()
Action allocate_and_call(FlowInterface< Buffer< T > > *target_flow, Callback c, Pool *pool=nullptr)
Allocates a buffer from a pool and proceed to the next state when allocation is successful.
StateFlowBase()
Default constructor.
Action exit()
Terminate current StateFlow activity.
void start_flow(Callback c)
Resets the flow to the specified state and starts it.
Buffer< T > * get_allocation_result(FlowInterface< Buffer< T > > *target_flow)
Takes the result of the asynchronous allocation.
Action again()
Call the current state again via call_immediately.
Action call_immediately(Callback c)
Imediately call the next state upon return.
Action wait_and_call(Callback c)
Wait for resource to become available before proceeding to next state.
A timer that can schedule itself to run on an executor at specified times in the future.
Definition Timer.hxx:134
bool is_triggered()
Definition Timer.hxx:273
@ RESTART
Restart the timer with existing period.
Definition Timer.hxx:162
void start(long long period=-1)
Starts a timer.
Definition Timer.hxx:185
void start_absolute(long long expiry_time_nsec)
Starts the timer with an absolute deadline.
Definition Timer.hxx:199
void ensure_triggered()
Triggers the timer if it is not expired yet.
Definition Timer.hxx:249
Abstract class to get callbacks for recognized feedback messages.
@ LOGON_ENABLE
Logon Enable packet.
@ SELECT_SHORTINFO
Select packet with Get Short Info command.
@ LOGON_ASSIGN
Logon Assign packet.
@ UNKNOWN
Non-254 packet or not known (not relevant) 254 packet type.
Parser for RailCom feedback that recognizes logon messages.
This class needs to be a base class for the template argument of the Logon Handler.
Definition Logon.hxx:49
void assign_complete(unsigned loco_id)
Invoked when the address assignment completes for a decoder.
Flags
Flags for the logon handler module.
Definition Logon.hxx:94
@ FLAG_PENDING_GET_SHORTINFO
We sent a get shortinfo command.
Definition Logon.hxx:98
@ FLAG_COMPLETE
1 if we completed the address assignment.
Definition Logon.hxx:106
@ FLAG_PENDING_RETRY
1 if we have asked for a re-try.
Definition Logon.hxx:110
@ FLAG_NEEDS_GET_SHORTINFO
This decoder needs a get shortinfo command.
Definition Logon.hxx:96
@ FLAG_ERROR_STATE
1 if we ended up in an error state for this loco.
Definition Logon.hxx:108
@ FLAG_PENDING_TICK
This is a 1-bit pre-scaler on a shared 50 msec timer that controls the delay of re-tries.
Definition Logon.hxx:114
@ FLAG_PENDING_ASSIGN
We sent an assign command.
Definition Logon.hxx:103
@ FLAG_NEEDS_ASSIGN
This decoder needs an assign command.
Definition Logon.hxx:101
uint16_t assigned_address(unsigned loco_id)
unsigned create_or_lookup_loco(uint64_t decoder_id)
Creates a new locomotive by decoder ID, or looks up an existing locomotive by decoder ID.
uint8_t & loco_flags(unsigned loco_id)
Finds the storage cell for a locomotive and returns the flag byte for it.
uint64_t loco_did(unsigned loco_id)
Retrieves the decoder unique ID.
void run_address_policy(unsigned loco_id, uint16_t desired_address)
Runs the locomotive address policy.
bool is_valid_loco_id(unsigned loco_id)
Flow that sends out addressed packets that are part of the logon sequences.
Definition Logon.hxx:441
Action send_assign()
Called with a buffer allocated.
Definition Logon.hxx:584
LogonHandler * parent_
Owning logon handler.
Definition Logon.hxx:602
Action send_get_shortinfo()
Called with a buffer allocated.
Definition Logon.hxx:566
void tick()
Called by a timer every 50 msec.
Definition Logon.hxx:462
BarrierNotifiable bn_
Helper for self notification.
Definition Logon.hxx:609
Action search()
Entry to the flow.
Definition Logon.hxx:531
long long timeout() override
Timer callback.
Definition Logon.hxx:507
unsigned cycleNextId_
Identifier of the storage that provides the next locomotive to look at.
Definition Logon.hxx:606
void wakeup()
Notifies the flow that there is work to do.
Definition Logon.hxx:452
Handles the automatic logon flow for DCC decoders.
Definition Logon.hxx:121
long long lastLogonTime_
Timestamp of the last logon packet we sent out.
Definition Logon.hxx:675
uint16_t cid_
Command station unique ID.
Definition Logon.hxx:678
Action send_logon_now()
Sends a Logon Enable(now) packet.
Definition Logon.hxx:334
PacketType classify_packet(uintptr_t feedback_key) override
Determines based on feedback key what the given DCC packet was.
Definition Logon.hxx:163
Module * module_
Storage module.
Definition Logon.hxx:664
uint8_t needShutdown_
Signals that we need to shut down the flow.
Definition Logon.hxx:688
uint8_t countLogonToSend_
Tracks how many logons to send out.
Definition Logon.hxx:691
void startup_logon(uint16_t cid, uint8_t session_id)
Initiates a logon sequence at startup.
Definition Logon.hxx:142
static constexpr unsigned LOGON_PERIOD_MSEC
How often to send logon enable packets.
Definition Logon.hxx:650
static constexpr uintptr_t LOGON_ENABLE_KEY
We send this as feedback key for logon enable packets.
Definition Logon.hxx:613
void process_decoder_id(uintptr_t feedback_key, bool error, uint64_t data) override
Handles a Decoder ID feedback message.
Definition Logon.hxx:293
static constexpr uintptr_t SELECT_SHORTINFO_KEY
We send this as feedback key for select/get short info packets.
Definition Logon.hxx:625
Action allocate_logon_many()
Called when we have seen a conflict on a logon enable packet.
Definition Logon.hxx:385
LogonHandler(Service *service, TrackIf *track, RailcomHubFlow *rcom_hub, Module *m)
Constructor.
Definition Logon.hxx:129
Action allocate_logon_now()
Allocates a buffer and sends a Logon Enable(now) packet.
Definition Logon.hxx:328
static constexpr bool is_logon_enable_key(uintptr_t feedback_key)
Checks if a feedback key is for logon enable.
Definition Logon.hxx:619
uint8_t hasLogonEnableConflict_
1 if we got an error (presumably a conflict) in the logon enable feedback.
Definition Logon.hxx:684
Action send_logon_many()
Send out the repeated logon request.
Definition Logon.hxx:391
static constexpr bool is_select_shortinfo_key(uintptr_t feedback_key)
Checks if a feedback key is for select shortinfo.
Definition Logon.hxx:632
uint8_t hasLogonEnableFeedback_
1 if we got any feedback packet from logon enable.
Definition Logon.hxx:686
static constexpr uint64_t DECODER_ID_MASK
Mask selecting bits that belong to the decoder ID.
Definition Logon.hxx:658
Action start_logon_wait()
Called when the logon now packet is released.
Definition Logon.hxx:346
static constexpr bool is_logon_assign_key(uintptr_t feedback_key)
Checks if a feedback key is for logon assign.
Definition Logon.hxx:644
static constexpr uintptr_t LOCO_ID_MASK
Mask selecting bits that belong to the locomotive ID.
Definition Logon.hxx:655
static constexpr uintptr_t LOGON_ASSIGN_KEY
We send this as feedback key for logon assign packets.
Definition Logon.hxx:638
StateFlowTimer timer_
Helper object for timing.
Definition Logon.hxx:667
void process_select_shortinfo(uintptr_t feedback_key, bool error, uint64_t data) override
Handles a Select ShortInfo feedback message.
Definition Logon.hxx:189
Action evaluate_logon()
Called when the logon timer expires or is cancelled due to feedback.
Definition Logon.hxx:358
LogonFeedbackParser fbParser_
Helper object for parsing railcom feedback from the railcom hub.
Definition Logon.hxx:670
uint8_t sessionId_
Session ID of the current session.
Definition Logon.hxx:680
static constexpr unsigned MAX_LOCO_ID
Maximum allowed locomotive ID.
Definition Logon.hxx:653
Action many_logon_wait()
Called after the many logon packets' buffer is freed.
Definition Logon.hxx:398
void logon_send_helper(Defs::LogonEnableParam param, unsigned rept)
Helper function to send out logon enable commands.
Definition Logon.hxx:419
TrackIf * trackIf_
If we need to send packets to the track, we can do it here directly.
Definition Logon.hxx:661
void process_logon_assign(uintptr_t feedback_key, bool error, uint64_t data) override
Handles a Logon Assign feedback message.
Definition Logon.hxx:243
LogonEnableParam
Parameters for the Logon Enable command.
Definition dcc/Defs.hxx:265
#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 HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
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