AceTimeClock  1.3.0
Clock classes for Arduino 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;
14 class SystemClockLoopTest_loop;
15 class SystemClockLoopTest_setup;
16 class SystemClockLoopTest_backupNow;
17 class SystemClockLoopTest_syncNow;
18 class SystemClockLoopTest_getNow;
19 
20 namespace ace_time {
21 namespace clock {
22 
47 template <typename T_SCCI>
49  public:
68  Clock* referenceClock /* nullable */,
69  Clock* backupClock /* nullable */,
70  uint16_t syncPeriodSeconds = 3600,
71  uint16_t initialSyncPeriodSeconds = 5,
72  uint16_t requestTimeoutMillis = 1000,
73  ace_common::TimingStats* timingStats = nullptr):
74  SystemClockTemplate<T_SCCI>(referenceClock, backupClock),
75  mSyncPeriodSeconds(syncPeriodSeconds),
76  mRequestTimeoutMillis(requestTimeoutMillis),
77  mTimingStats(timingStats),
78  mCurrentSyncPeriodSeconds(initialSyncPeriodSeconds) {}
79 
89  void loop() {
90  this->keepAlive();
91  if (this->getReferenceClock() == nullptr) return;
92 
93  uint32_t nowMillis = this->clockMillis();
94 
95  // Finite state machine based on mRequestStatus
96  switch (mRequestStatus) {
97  case kStatusReady:
98  this->getReferenceClock()->sendRequest();
99  mRequestStartMillis = nowMillis;
100  mRequestStatus = kStatusSent;
101  this->setPrevSyncAttemptMillis(nowMillis);
102  this->setNextSyncAttemptMillis(nowMillis
103  + mCurrentSyncPeriodSeconds * (uint32_t) 1000);
104  break;
105 
106  case kStatusSent: {
107  uint32_t elapsedMillis = nowMillis - mRequestStartMillis;
108  if (mTimingStats) mTimingStats->update((uint16_t) elapsedMillis);
109 
110  if (this->getReferenceClock()->isResponseReady()) {
111  acetime_t nowSeconds = this->getReferenceClock()->readResponse();
112 
113  if (nowSeconds == this->kInvalidSeconds) {
114  // If response came back but was invalid, reschedule.
115  mRequestStatus = kStatusWaitForRetry;
116  this->setSyncStatusCode(this->kSyncStatusError);
117  } else {
118  // Request succeeded.
119  this->syncNow(nowSeconds);
120  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
121  mRequestStatus = this->kStatusOk;
122  this->setSyncStatusCode(this->kSyncStatusOk);
123  }
124  } else {
125  // If timed out, reschedule.
126  if (elapsedMillis >= mRequestTimeoutMillis) {
127  mRequestStatus = this->kStatusWaitForRetry;
129  }
130  }
131  break;
132  }
133 
134  // The previous request succeeded, so wait until the next sync attempt.
135  case kStatusOk: {
136  uint32_t elapsedMillis = nowMillis - mRequestStartMillis;
137  if (elapsedMillis >= mCurrentSyncPeriodSeconds * (uint32_t) 1000) {
138  mRequestStatus = kStatusReady;
139  }
140  break;
141  }
142 
143  // Previous request failed, so update timing parameters so that
144  // subsequent loop() retries with an exponential backoff, until a
145  // maximum of mSyncPeriodSeconds is reached.
146  case kStatusWaitForRetry: {
147  uint32_t elapsedMillis = nowMillis - mRequestStartMillis;
148  // Adjust mCurrentSyncPeriodSeconds using exponential backoff.
149  if (elapsedMillis >= mCurrentSyncPeriodSeconds * (uint32_t) 1000) {
150  if (mCurrentSyncPeriodSeconds >= mSyncPeriodSeconds / 2) {
151  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
152  } else {
153  mCurrentSyncPeriodSeconds *= 2;
154  }
155  mRequestStatus = kStatusReady;
156  }
157  break;
158  }
159  }
160  }
161 
162  protected:
165 
166  // disable copy constructor and assignment operator
168  SystemClockLoopTemplate& operator=(const SystemClockLoopTemplate&) = delete;
169 
170  private:
171  friend class ::SystemClockLoopTest;
172  friend class ::SystemClockLoopTest_loop;
173  friend class ::SystemClockLoopTest_syncNow;
174  friend class ::SystemClockLoopTest_setup;
175  friend class ::SystemClockLoopTest_backupNow;
176  friend class ::SystemClockLoopTest_getNow;
177 
179  static const uint8_t kStatusReady = 0;
180 
182  static const uint8_t kStatusSent = 1;
183 
185  static const uint8_t kStatusOk = 2;
186 
188  static const uint8_t kStatusWaitForRetry = 3;
189 
190  uint16_t const mSyncPeriodSeconds = 3600;
191  uint16_t const mRequestTimeoutMillis = 1000;
192  ace_common::TimingStats* const mTimingStats = nullptr;
193 
194  uint32_t mRequestStartMillis;
195  uint16_t mCurrentSyncPeriodSeconds = 5;
196  uint8_t mRequestStatus = kStatusReady;
197 };
198 
205 using SystemClockLoop = SystemClockLoopTemplate<hw::ClockInterface>;
206 
207 
208 }
209 }
210 
211 #endif
Abstract base class for objects that provide and store time.
Definition: Clock.h:19
virtual void sendRequest() const
Send a time request asynchronously.
Definition: Clock.h:48
virtual acetime_t readResponse() const
Returns number of seconds since AceTime epoch (2000-01-01).
Definition: Clock.h:58
virtual bool isResponseReady() const
Return true if a response is ready.
Definition: Clock.h:51
static const acetime_t kInvalidSeconds
Error value returned by getNow() and other methods when this object is not yet initialized.
Definition: Clock.h:25
A subclass of SystemClock that sync with its mReferenceClock using the non-blocking Clock API of the ...
void loop()
Make a request to the referenceClock every syncPeriodSeconds seconds.
SystemClockLoopTemplate(Clock *referenceClock, Clock *backupClock, uint16_t syncPeriodSeconds=3600, uint16_t initialSyncPeriodSeconds=5, uint16_t requestTimeoutMillis=1000, ace_common::TimingStats *timingStats=nullptr)
Constructor.
SystemClockLoopTemplate()
Empty constructor used for testing.
A Clock that uses the Arduino millis() function to advance the time returned to the user.
Definition: SystemClock.h:60
static const uint8_t kSyncStatusError
Sync request failed.
Definition: SystemClock.h:66
void setSyncStatusCode(uint8_t code)
Set the status code of most recent sync attempt.
Definition: SystemClock.h:348
void setPrevSyncAttemptMillis(uint32_t ms)
Set the millis of prev sync attempt.
Definition: SystemClock.h:343
void keepAlive()
Call this (or getNow() every 65.535 seconds or faster to keep the internal counter in sync with milli...
Definition: SystemClock.h:281
void syncNow(acetime_t epochSeconds)
Set the current mEpochSeconds to the given epochSeconds.
Definition: SystemClock.h:320
Clock * getReferenceClock() const
Get referenceClock.
Definition: SystemClock.h:267
static const uint8_t kSyncStatusTimedOut
Sync request timed out.
Definition: SystemClock.h:69
void setNextSyncAttemptMillis(uint32_t ms)
Set the millis to next sync attempt.
Definition: SystemClock.h:338
unsigned long clockMillis() const
Return the Arduino millis().
Definition: SystemClock.h:273
static const uint8_t kSyncStatusOk
Sync was successful.
Definition: SystemClock.h:63