AceTime  1.6
Date and time classes for Arduino that support timezones from the TZ Database, and a system clock that can synchronize from an NTP server or an RTC chip.
SystemClockLoop.h
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_SYSTEM_CLOCK_LOOP_H
7 #define ACE_TIME_SYSTEM_CLOCK_LOOP_H
8 
9 #include <stdint.h>
10 #include <AceCommon.h> // TimingStats
11 #include "SystemClock.h"
12 
13 class SystemClockLoopTest_loop;
14 
15 namespace ace_time {
16 namespace clock {
17 
31  public:
33  static const uint8_t kStatusReady = 0;
34 
36  static const uint8_t kStatusSent = 1;
37 
39  static const uint8_t kStatusOk = 2;
40 
42  static const uint8_t kStatusWaitForRetry = 3;
43 
59  explicit SystemClockLoop(
60  Clock* referenceClock /* nullable */,
61  Clock* backupClock /* nullable */,
62  uint16_t syncPeriodSeconds = 3600,
63  uint16_t initialSyncPeriodSeconds = 5,
64  uint16_t requestTimeoutMillis = 1000,
65  ace_common::TimingStats* timingStats = nullptr):
66  SystemClock(referenceClock, backupClock),
67  mSyncPeriodSeconds(syncPeriodSeconds),
68  mRequestTimeoutMillis(requestTimeoutMillis),
69  mTimingStats(timingStats),
70  mCurrentSyncPeriodSeconds(initialSyncPeriodSeconds) {}
71 
76  void loop() {
77  keepAlive();
78  if (mReferenceClock == nullptr) return;
79 
80  unsigned long nowMillis = clockMillis();
81 
82  // Finite state machine based on mRequestStatus
83  switch (mRequestStatus) {
84  case kStatusReady:
85  mReferenceClock->sendRequest();
86  mRequestStartMillis = nowMillis;
87  mRequestStatus = kStatusSent;
88  break;
89  case kStatusSent:
90  if (mReferenceClock->isResponseReady()) {
91  acetime_t nowSeconds = mReferenceClock->readResponse();
92  if (mTimingStats != nullptr) {
93  uint16_t elapsedMillis = nowMillis - mRequestStartMillis;
94  mTimingStats->update(elapsedMillis);
95  }
96  if (nowSeconds == kInvalidSeconds) {
97  mRequestStatus = kStatusWaitForRetry;
98  } else {
99  syncNow(nowSeconds);
100  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
101  mLastSyncMillis = nowMillis;
102  mRequestStatus = kStatusOk;
103  }
104  } else {
105  unsigned long waitMillis = nowMillis - mRequestStartMillis;
106  if (waitMillis >= mRequestTimeoutMillis) {
107  mRequestStatus = kStatusWaitForRetry;
108  }
109  }
110  break;
111  case kStatusOk: {
112  unsigned long millisSinceLastSync = nowMillis - mLastSyncMillis;
113  if (millisSinceLastSync >= mCurrentSyncPeriodSeconds * 1000UL) {
114  mRequestStatus = kStatusReady;
115  }
116  break;
117  }
118  case kStatusWaitForRetry: {
119  // subsequent loop() retries with an exponential backoff, until a
120  // maximum of mSyncPeriodSeconds is reached.
121  unsigned long waitMillis = nowMillis - mRequestStartMillis;
122  if (waitMillis >= mCurrentSyncPeriodSeconds * 1000UL) {
123  if (mCurrentSyncPeriodSeconds >= mSyncPeriodSeconds / 2) {
124  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
125  } else {
126  mCurrentSyncPeriodSeconds *= 2;
127  }
128  mRequestStatus = kStatusReady;
129  }
130  break;
131  }
132  }
133  }
134 
135  private:
136  friend class ::SystemClockLoopTest_loop;
137 
138  // disable copy constructor and assignment operator
139  SystemClockLoop(const SystemClockLoop&) = delete;
140  SystemClockLoop& operator=(const SystemClockLoop&) = delete;
141 
142  uint16_t const mSyncPeriodSeconds;
143  uint16_t const mRequestTimeoutMillis;
144  ace_common::TimingStats* const mTimingStats;
145 
146  unsigned long mLastSyncMillis;
147  unsigned long mRequestStartMillis;
148  uint16_t mCurrentSyncPeriodSeconds;
149  uint8_t mRequestStatus = kStatusReady;
150 };
151 
152 }
153 }
154 
155 #endif
ace_time::clock::SystemClockLoop::loop
void loop()
Call this from the global loop() method.
Definition: SystemClockLoop.h:76
ace_time::clock::Clock::sendRequest
virtual void sendRequest() const
Send a time request asynchronously.
Definition: Clock.h:38
ace_time::clock::SystemClockLoop::kStatusReady
static const uint8_t kStatusReady
Ready to send request.
Definition: SystemClockLoop.h:33
ace_time::clock::SystemClock::syncNow
void syncNow(acetime_t epochSeconds)
Similar to setNow() except that backupNow() is called only if the backupClock is different from the r...
Definition: SystemClock.h:155
ace_time::clock::SystemClockLoop::kStatusSent
static const uint8_t kStatusSent
Request sent, waiting for response.
Definition: SystemClockLoop.h:36
ace_time::clock::SystemClockLoop::kStatusWaitForRetry
static const uint8_t kStatusWaitForRetry
Request received but is invalid, so retry with exponential backoff.
Definition: SystemClockLoop.h:42
ace_time::clock::SystemClock::keepAlive
void keepAlive()
Call this (or getNow() every 65.535 seconds or faster to keep the internal counter in sync with milli...
Definition: SystemClock.h:131
ace_time::clock::SystemClockLoop::SystemClockLoop
SystemClockLoop(Clock *referenceClock, Clock *backupClock, uint16_t syncPeriodSeconds=3600, uint16_t initialSyncPeriodSeconds=5, uint16_t requestTimeoutMillis=1000, ace_common::TimingStats *timingStats=nullptr)
Constructor.
Definition: SystemClockLoop.h:59
ace_time::clock::SystemClockLoop::kStatusOk
static const uint8_t kStatusOk
Request received and is valid.
Definition: SystemClockLoop.h:39
ace_time::clock::Clock
Base class for objects that provide and store time.
Definition: Clock.h:20
ace_time::clock::SystemClock::clockMillis
virtual unsigned long clockMillis() const
Return the Arduino millis().
Definition: SystemClock.h:125
ace_time::clock::Clock::isResponseReady
virtual bool isResponseReady() const
Return true if a response is ready.
Definition: Clock.h:41
ace_time::clock::Clock::readResponse
virtual acetime_t readResponse() const
Returns number of seconds since AceTime epoch (2000-01-01).
Definition: Clock.h:48
ace_time::clock::SystemClockLoop
A subclass of SystemClock that sync with its mReferenceClock using a blocking mReferenceClock->getNow...
Definition: SystemClockLoop.h:30
ace_time::clock::SystemClock
A Clock that uses the Arduino millis() function to advance the time returned to the user.
Definition: SystemClock.h:52