Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
BroadcastTimeClient.hxx
Go to the documentation of this file.
1
35#ifndef _OPENLCB_BROADCASTTIMECLIENT_HXX_
36#define _OPENLCB_BROADCASTTIMECLIENT_HXX_
37
40
41namespace openlcb
42{
43
46{
47public:
55 bool configure_agent = false)
57 , configureAgent_(configure_agent)
58 , immediateUpdate_(false)
59 , immediatePending_(false)
60 , sleeping_(false)
61 , waiting_(false)
62 , rolloverPending_(false)
65 , serverDetected_(false)
66 {
67 EventRegistry::instance()->register_handler(
69 }
70
73 {
74 EventRegistry::instance()->unregister_handler(this);
75 }
76
79 bool is_server_detected() override
80 {
81 return serverDetected_;
82 }
83
86 bool is_server_self() override
87 {
88 return false;
89 }
90
96 EventReport *event,
97 BarrierNotifiable *done) override
98 {
99 AutoNotify an(done);
100 if (event->dst_node && event->dst_node != node_)
101 {
102 // not for us
103 return;
104 }
105
106 if (event->dst_node == node_)
107 {
108 // This is directed at us. We were likely re-initialized, so we
109 // need to query for a new clock server and possibly sync.
110 query();
111 }
112
113 if (is_terminated())
114 {
116 }
117
118 event->event_write_helper<1>()->WriteAsync(
119 node_, Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(),
121 done->new_child());
122 if (configureAgent_)
123 {
124 // we can configure our complementary time server
125 event->event_write_helper<2>()->WriteAsync(
127 WriteHelper::global(),
129 EncodeRange(entry.event |
131 0x1 << 15)),
132 done->new_child());
133 }
134 else
135 {
136 // we cannot configure our complementary time server
137 event->event_write_helper<2>()->WriteAsync(
139 WriteHelper::global(),
142 done->new_child());
143 }
144 }
145
151 EventReport *event,
152 BarrierNotifiable *done) override
153 {
154 AutoNotify an(done);
155 if (event->event < (eventBase_ | 0x5000))
156 {
157 event->event_write_helper<1>()->WriteAsync(
159 WriteHelper::global(), eventid_to_buffer(event->event),
160 done->new_child());
161 }
162 }
163
169 EventReport *event,
170 BarrierNotifiable *done) override
171 {
172 AutoNotify an(done);
173 if ((configureAgent_ &&
175 event->event == (entry.event |
177 {
178 // if we cannot configure our complementary time server,
179 // we only produce one event.
180 event->event_write_helper<1>()->WriteAsync(
182 WriteHelper::global(),
185 done->new_child());
186 }
187 }
188
194 EventReport *event,
195 BarrierNotifiable *done) override
196 {
197 done->notify();
198
199 if (event->state == EventState::VALID)
200 {
201 // Look for a Report Date Event ID.
202 if ((event->event & 0x000000000000F000ULL) == 0x2000ULL)
203 {
204 // We can only get here if there is a time server detected.
205 serverDetected_ = true;
206 }
207
208 // We only care about valid event state.
209 // Producer identified only happens when a clock synchronization
210 // is taking place. This voids previous date rollover events.
211 rolloverPending_ = false;
212 handle_updates(event, false);
213 }
214
215 }
216
222 EventReport *event,
223 BarrierNotifiable *done) override
224 {
225 done->notify();
226
227 handle_updates(event, true);
228 }
229
230private:
234 void handle_updates(EventReport *event, bool report);
235
239 void start_stop_logic(bool started)
240 {
241 bool notify = false;
242 time_t val = 0;
243 {
244 AtomicHolder h(this);
245 if (started_ != started)
246 {
247 if (!started)
248 {
249 seconds_ = time();
250 }
252 if (!started)
253 {
255 }
256 started_ = started;
257 notify = true;
258 val = seconds_;
259 }
260 // release AtomicHolder
261 }
262 if (notify)
263 {
264 service_callbacks(val, val);
265 }
266 }
267
271 {
272 rolloverPending_ = false;
273 rolloverPendingDate_ = false;
274 rolloverPendingYear_ = false;
275
276 waiting();
278 }
279
285 {
287 {
289 }
290 else
291 {
292 // because the set time sometimes comes in bursts, we will wait
293 // a little while for them to finish coming in. This avoids
294 // extra redundant processing.
295 sleeping();
296 return sleep_and_call(&timer_, MSEC_TO_NSEC(100),
298 }
299 }
300
307 {
308 if (timer_.is_triggered())
309 {
311 }
312 else
313 {
314 sleeping_ = false;
316 }
317 }
318
323 {
325 {
326 // initiate timeout, in case the time server doesn't complete the
327 // sequence successfully.
328 sleeping();
331 }
332
335
336 {
337 bool notify = false;
338 time_t old_seconds = 0;
339 time_t new_seconds = 0;
340 {
341 AtomicHolder h(this);
342 old_seconds = time();
343 if (rate_ != rateRequested_ ||
344 (immediateUpdate_ && immediatePending_))
345 {
346 // rate changed or an immediate update was pending.
347 notify = true;
348 }
349
350 // we are about to commit an updated time, reset the flags
351 immediateUpdate_ = false;
352 immediatePending_ = false;
353
355 seconds_ = ::mktime(&tm_);
357 new_seconds = seconds_;
359 {
360 // roll forward/back the 3 second delay for the year/date
361 // report
362 seconds_ += rate_ >= 0 ? 3 : -3;
363 rolloverPending_ = false;
364 }
365
366 if (notify == false)
367 {
368 if (std::abs(compare_realtime(seconds_, old_seconds)) > 3)
369 {
370 // We hadd a drift of more than 3 real seconds. 3 real
371 // seconds is chosen because it is within the magnitude
372 // of normal network jitter.
373 notify = true;
374 }
375 }
376 // release AtomicHolder
377 }
378 if (notify)
379 {
380 service_callbacks(old_seconds, new_seconds);
381 }
382 }
383
384 waiting();
386 }
387
392 {
393 if (timer_.is_triggered())
394 {
396 }
397 else
398 {
399 sleeping_ = false;
400 // A date rollover sequence failed. Query the clock in order to
401 // resync. In the meantime, continue using our internal time.
403 }
404 }
405
407 void waiting()
408 {
409 waiting_ = true;
410 sleeping_ = false;
411 }
412
414 void sleeping()
415 {
416 waiting_ = false;
417 sleeping_ = true;
418 }
419
421 void wakeup()
422 {
423 if (waiting_)
424 {
425 notify();
426 waiting_ = false;
427 }
428 if (sleeping_)
429 {
430 timer_.trigger();
431 sleeping_ = false;
432 }
433 }
434
435 uint16_t configureAgent_ : 1;
436 uint16_t immediateUpdate_ : 1;
437 uint16_t immediatePending_ : 1;
438 uint16_t sleeping_ : 1;
439 uint16_t waiting_ : 1;
440 uint16_t rolloverPending_ : 1;
441 uint16_t rolloverPendingDate_ : 1;
442 uint16_t rolloverPendingYear_ : 1;
443 uint16_t serverDetected_ : 1;
444
445
447};
448
449} // namespace openlcb
450
451#endif // _OPENLCB_BROADCASTTIMECLIENT_HXX_
452
#define STATE(_fn)
Turns a function name into an argument to be supplied to functions expecting a state.
Definition StateFlow.hxx:61
See OSMutexLock in os/OS.hxx.
Definition Atomic.hxx:153
This class sends a notification in its destructor.
A BarrierNotifiable allows to create a number of child Notifiable and wait for all of them to finish.
void notify() override
Implementation of the barrier semantics.
BarrierNotifiable * new_child()
Call this for each child task.
static long long get_monotonic()
Get the monotonic time since the system started.
Definition OS.hxx:560
static EventRegistry * instance()
Definition Singleton.hxx:77
Return type for a state flow callback.
bool is_terminated()
void notify() override
Wakeup call arrived.
Definition StateFlow.cxx:97
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 wait_and_call(Callback c)
Wait for resource to become available before proceeding to next state.
Action sleep_and_call(::Timer *timer, long long timeout_nsec, Callback c)
Suspends execution of this control flow for a specified time.
struct tm * gmtime_r(struct tm *result)
Get the time as a standard struct tm.
Definition TimeBase.hxx:108
uint16_t started_
true if clock is started
Definition TimeBase.hxx:293
time_t seconds_
Clock time at the last time update.
Definition TimeBase.hxx:290
long long timestamp_
OS time at the last time update.
Definition TimeBase.hxx:288
time_t compare_realtime(time_t t1, time_t t2)
Get the difference in time scaled to real time.
Definition TimeBase.hxx:74
time_t time()
Get the time as a value of seconds relative to the system epoch.
Definition TimeBase.hxx:87
int16_t rate_
effective clock rate
Definition TimeBase.hxx:291
bool is_triggered()
Definition Timer.hxx:273
void trigger()
This will wakeup the timer prematurely, immediately.
Definition Timer.hxx:237
Implementation of a Broadcast Time Protocol client.
void handle_identify_consumer(const EventRegistryEntry &entry, EventReport *event, BarrierNotifiable *done) override
Handle requested identification message.
void start_stop_logic(bool started)
Perform a bit of logic that is required whenever the clock's running state is changed.
void handle_identify_global(const EventRegistryEntry &entry, EventReport *event, BarrierNotifiable *done) override
Handle requested identification message.
void handle_updates(EventReport *event, bool report)
Handle an incoming time update.
void handle_identify_producer(const EventRegistryEntry &entry, EventReport *event, BarrierNotifiable *done) override
Handle requested identification message.
Action rollover_pending()
Wait on the completion of a date rollover sequence.
uint16_t waiting_
true if stateflow is waiting
uint16_t rolloverPendingDate_
a day rollover is about to occur
void sleeping()
Setup the state flags to reflect that we are in a sleep_and_call().
bool is_server_self() override
Test if this is a server.
void wakeup()
wakeup the state machine.
uint16_t serverDetected_
has a time server been detected
Action client_update()
Notification arrived that we should update our state.
uint16_t rolloverPending_
a day rollover is about to occur
void handle_event_report(const EventRegistryEntry &entry, EventReport *event, BarrierNotifiable *done) override
Handle an incoming event report.
BroadcastTimeClient(Node *node, NodeID clock_id, bool configure_agent=false)
Constructor.
bool is_server_detected() override
Has a time server been detected?
Action initialize()
Initialize client by sending a time query.
Action client_update_maybe()
Test if we are ready to update the client, or if we may want to wait for more messages (in the form o...
uint16_t rolloverPendingYear_
a day rollover is about to occur
Action client_update_commit()
commit the client update.
void handle_producer_identified(const EventRegistryEntry &entry, EventReport *event, BarrierNotifiable *done) override
Handle an incoming producer identified.
uint16_t sleeping_
future immediate upate expected
uint16_t configureAgent_
instance can be used to configure clock
uint16_t immediateUpdate_
true if the update should be immediate
void waiting()
Setup the state flags to reflect that we are in a wait_and_call().
Implementation of Broadcast Time Protocol.
StateFlowTimer timer_
timer helper
int16_t rateRequested_
pending clock rate
void service_callbacks(time_t old, time_t current)
Service all of the attached update subscribers.
Node * node()
Accessor method to get the Node reference.
NodeID clock_id()
Accessor method to get the (48-bit) Clock ID.
struct tm tm_
the time we are last set to as a struct tm
void query()
Query the current time.
EventId eventBase_
48-bit unique identifier for the clock instance
Node * node_
OpenLCB node to export the consumer on.
Structure used in registering event handlers.
EventId event
Stores the event ID or beginning of range for which to register the given handler.
Base class for NMRAnet nodes conforming to the asynchronous interface.
Definition Node.hxx:52
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Removes default copy-constructor and assignment added by C++.
Definition macros.h:171
uint64_t EncodeRange(uint64_t begin, unsigned size)
Creates a single encoded event range from the beginning of the range and the number fo events to cove...
uint64_t NodeID
48-bit NMRAnet Node ID type
Payload eventid_to_buffer(uint64_t eventid)
Converts an Event ID to a Payload suitable to be sent as an event report.
Definition If.cxx:72
#define MSEC_TO_NSEC(_msec)
Convert a millisecond value to a nanosecond value.
Definition os.h:268
#define SEC_TO_NSEC(_sec)
Convert a second value to a nanosecond value.
Definition os.h:286
@ EVENT_SET_SUFFIX_MASK
suffix max for setting a property
@ QUERY_EVENT_SUFFIX
query event suffix value
@ MTI_PRODUCER_IDENTIFIED_RANGE
producer broadcast about a range of producers
@ MTI_CONSUMER_IDENTIFIED_UNKNOWN
consumer broadcast, validity unknown
@ MTI_PRODUCER_IDENTIFIED_UNKNOWN
producer broadcast, validity unknown
@ MTI_CONSUMER_IDENTIFIED_RANGE
consumer broadcast about a range of consumers
Shared notification structure that is assembled for each incoming event-related message,...
EventState state
For producer/consumer identified messages, specifies the state of the producer/consumer as the sender...
EventId event
The event ID from the incoming message.
Node * dst_node
nullptr for global messages; points to the specific virtual node for addressed events identify messag...