AceTime  1.7.4
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.
SystemClock.h
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_SYSTEM_CLOCK_H
7 #define ACE_TIME_SYSTEM_CLOCK_H
8 
9 #include <stdint.h>
10 #include "Clock.h"
11 #include "../hw/ClockInterface.h"
12 
13 class SystemClockCoroutineTest;
14 class SystemClockLoopTest;
15 class SystemClockLoopTest_loop;
16 class SystemClockLoopTest_setup;
17 class SystemClockLoopTest_backupNow;
18 class SystemClockLoopTest_syncNow;
19 class SystemClockLoopTest_getNow;
20 
21 namespace ace_time {
22 namespace clock {
23 
59 template <typename T_CI>
60 class SystemClockTemplate: public Clock {
61  public:
63  static const uint8_t kSyncStatusOk = 0;
64 
66  static const uint8_t kSyncStatusError = 1;
67 
69  static const uint8_t kSyncStatusTimedOut = 2;
70 
72  static const uint8_t kSyncStatusUnknown = 128;
73 
75  void setup() {
76  if (mBackupClock != nullptr) {
77  setNow(mBackupClock->getNow());
78  }
79  }
80 
81  acetime_t getNow() const override {
82  if (!mIsInit) return kInvalidSeconds;
83 
84  // Update mEpochSeconds by the number of seconds elapsed according to the
85  // millis(). This method is expected to be called multiple times a second,
86  // so the while() loop below will normally execute 0 times, until the
87  // millis() clock goes past the mPrevKeepAliveMillis by 1 second.
88  //
89  // There are 2 reasons why this method will be called multiple times a
90  // second:
91  //
92  // 1) A physical clock with an external display will want to refresh
93  // its display 5-10 times a second, so that it can capture the transition
94  // from one second to the next without too much jitter. So it will call
95  // this method multiple times a second to check if one second has passed.
96  //
97  // 2) If the SystemClockCoroutine or SystemClockLoop classes is used,
98  // then the keepAlive() method will be called perhaps 100's times per
99  // second, as fast as the iteration speed of the global loop() function.
100  while ((uint16_t) ((uint16_t) clockMillis() - mPrevKeepAliveMillis)
101  >= 1000) {
102  mPrevKeepAliveMillis += 1000;
103  mEpochSeconds += 1;
104  }
105 
106  return mEpochSeconds;
107  }
108 
109  void setNow(acetime_t epochSeconds) override {
110  syncNow(epochSeconds);
111 
112  // Also set the reference clock if possible.
113  if (mReferenceClock != nullptr) {
114  mReferenceClock->setNow(epochSeconds);
115  }
116  }
117 
131  void forceSync() {
132  if (mReferenceClock) {
133  acetime_t nowSeconds = mReferenceClock->getNow();
134  syncNow(nowSeconds);
135  }
136  }
137 
142  acetime_t getLastSyncTime() const {
143  return mLastSyncTime;
144  }
145 
147  uint8_t getSyncStatusCode() const { return mSyncStatusCode; }
148 
162  int32_t getSecondsSinceSyncAttempt() const {
163  return (int32_t) (clockMillis() - mPrevSyncAttemptMillis) / 1000;
164  }
165 
172  int32_t getSecondsToSyncAttempt() const {
173  return (int32_t) (mNextSyncAttemptMillis - clockMillis()) / 1000;
174  }
175 
186  int16_t getClockSkew() const {
187  return mClockSkew;
188  }
189 
191  bool isInit() const { return mIsInit; }
192 
193  protected:
194  friend class ::SystemClockLoopTest;
195  friend class ::SystemClockCoroutineTest;
196  friend class ::SystemClockLoopTest_loop;
197  friend class ::SystemClockLoopTest_syncNow;
198  friend class ::SystemClockLoopTest_setup;
199  friend class ::SystemClockLoopTest_backupNow;
200  friend class ::SystemClockLoopTest_getNow;
201 
202  // disable copy constructor and assignment operator
203  SystemClockTemplate(const SystemClockTemplate&) = delete;
204  SystemClockTemplate& operator=(const SystemClockTemplate&) = delete;
205 
224  Clock* referenceClock /* nullable */,
225  Clock* backupClock /* nullable */
226  ) :
227  mReferenceClock(referenceClock),
228  mBackupClock(backupClock) {}
229 
234  explicit SystemClockTemplate() {}
235 
238  Clock* referenceClock /* nullable */,
239  Clock* backupClock /* nullable */
240  ) {
241  mReferenceClock = referenceClock;
242  mBackupClock = backupClock;
243 
244  mEpochSeconds = kInvalidSeconds;
245  mPrevSyncAttemptMillis = 0;
246  mNextSyncAttemptMillis = 0;
247  mPrevKeepAliveMillis = 0;
248  mIsInit = false;
249  mSyncStatusCode = kSyncStatusUnknown;
250  }
251 
253  Clock* getReferenceClock() const { return mReferenceClock; }
254 
259  unsigned long clockMillis() const { return T_CI::millis(); }
260 
267  void keepAlive() {
268  getNow();
269  }
270 
277  void backupNow(acetime_t nowSeconds) {
278  if (mBackupClock != nullptr) {
279  mBackupClock->setNow(nowSeconds);
280  }
281  }
282 
306  void syncNow(acetime_t epochSeconds) {
307  if (epochSeconds == kInvalidSeconds) return;
308 
309  mLastSyncTime = epochSeconds;
310  acetime_t skew = mEpochSeconds - epochSeconds;
311  mClockSkew = skew;
312  if (skew == 0) return;
313 
314  mEpochSeconds = epochSeconds;
315  mPrevKeepAliveMillis = clockMillis();
316  mIsInit = true;
317 
318  if (mBackupClock != mReferenceClock) {
319  backupNow(epochSeconds);
320  }
321  }
322 
324  void setNextSyncAttemptMillis(uint32_t ms) {
325  mNextSyncAttemptMillis = ms;
326  }
327 
329  void setPrevSyncAttemptMillis(uint32_t ms) {
330  mPrevSyncAttemptMillis = ms;
331  }
332 
334  void setSyncStatusCode(uint8_t code) {
335  mSyncStatusCode = code;
336  }
337 
338  private:
339  Clock* mReferenceClock;
340  Clock* mBackupClock;
341 
342  mutable acetime_t mEpochSeconds = kInvalidSeconds;
343  acetime_t mLastSyncTime = kInvalidSeconds; // time when last synced
344  uint32_t mPrevSyncAttemptMillis = 0;
345  uint32_t mNextSyncAttemptMillis = 0;
346  mutable uint16_t mPrevKeepAliveMillis = 0; // lower 16-bits of clockMillis()
347  int16_t mClockSkew = 0; // diff between reference and this clock
348  bool mIsInit = false; // true if setNow() or syncNow() was successful
349  uint8_t mSyncStatusCode = kSyncStatusUnknown;
350 };
351 
353 using SystemClock = SystemClockTemplate<hw::ClockInterface>;
354 
355 }
356 }
357 
358 #endif
ace_time::clock::SystemClockTemplate::getReferenceClock
Clock * getReferenceClock() const
Get referenceClock.
Definition: SystemClock.h:253
ace_time::clock::SystemClockTemplate::SystemClockTemplate
SystemClockTemplate()
Empty constructor primarily for tests.
Definition: SystemClock.h:234
ace_time::clock::SystemClockTemplate::getClockSkew
int16_t getClockSkew() const
Difference between this clock compared to reference at last sync.
Definition: SystemClock.h:186
ace_time::clock::SystemClockTemplate::getSyncStatusCode
uint8_t getSyncStatusCode() const
Get sync status code.
Definition: SystemClock.h:147
ace_time::clock::SystemClockTemplate::setup
void setup()
Attempt to retrieve the time from the backupClock if it exists.
Definition: SystemClock.h:75
ace_time::clock::SystemClockTemplate::kSyncStatusTimedOut
static const uint8_t kSyncStatusTimedOut
Sync request timed out.
Definition: SystemClock.h:69
ace_time::clock::Clock::getNow
virtual acetime_t getNow() const =0
Return the number of seconds since the AceTime epoch (2000-01-01T00:00:00Z).
ace_time::clock::SystemClockTemplate::getSecondsToSyncAttempt
int32_t getSecondsToSyncAttempt() const
Return the number of seconds until the next syncNow() attempt.
Definition: SystemClock.h:172
ace_time::clock::SystemClockTemplate::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:267
ace_time::clock::SystemClockTemplate::setNow
void setNow(acetime_t epochSeconds) override
Set the time to the indicated seconds.
Definition: SystemClock.h:109
ace_time::clock::SystemClockTemplate::SystemClockTemplate
SystemClockTemplate(Clock *referenceClock, Clock *backupClock)
Constructor.
Definition: SystemClock.h:223
ace_time::clock::SystemClockTemplate::setPrevSyncAttemptMillis
void setPrevSyncAttemptMillis(uint32_t ms)
Set the millis of prev sync attempt.
Definition: SystemClock.h:329
ace_time::clock::Clock::setNow
virtual void setNow(acetime_t)
Set the time to the indicated seconds.
Definition: Clock.h:67
ace_time::clock::SystemClockTemplate::backupNow
void backupNow(acetime_t nowSeconds)
Write the nowSeconds to the backupClock (which can be an RTC that has non-volatile memory).
Definition: SystemClock.h:277
ace_time::clock::SystemClockTemplate::syncNow
void syncNow(acetime_t epochSeconds)
Set the current mEpochSeconds to the given epochSeconds.
Definition: SystemClock.h:306
ace_time::clock::SystemClockTemplate::kSyncStatusOk
static const uint8_t kSyncStatusOk
Sync was successful.
Definition: SystemClock.h:63
ace_time::clock::SystemClockTemplate::getLastSyncTime
acetime_t getLastSyncTime() const
Return the time (seconds since Epoch) of the last successful syncNow() call.
Definition: SystemClock.h:142
ace_time::clock::SystemClockTemplate::kSyncStatusError
static const uint8_t kSyncStatusError
Sync request failed.
Definition: SystemClock.h:66
ace_time::clock::SystemClockTemplate::forceSync
void forceSync()
Manually force a sync with the referenceClock if it exists.
Definition: SystemClock.h:131
ace_time::clock::SystemClockTemplate::clockMillis
unsigned long clockMillis() const
Return the Arduino millis().
Definition: SystemClock.h:259
ace_time::clock::Clock
Abstract base class for objects that provide and store time.
Definition: Clock.h:20
ace_time::clock::Clock::kInvalidSeconds
static const acetime_t kInvalidSeconds
Error value returned by getNow() and other methods when this object is not yet initialized.
Definition: Clock.h:26
ace_time::clock::SystemClockTemplate
A Clock that uses the Arduino millis() function to advance the time returned to the user.
Definition: SystemClock.h:60
ace_time::clock::SystemClockTemplate::getSecondsSinceSyncAttempt
int32_t getSecondsSinceSyncAttempt() const
Return the number of seconds since the previous sync attempt, successful or not.
Definition: SystemClock.h:162
ace_time::clock::SystemClockTemplate::isInit
bool isInit() const
Return true if initialized by setNow() or syncNow().
Definition: SystemClock.h:191
ace_time::clock::SystemClockTemplate::initSystemClock
void initSystemClock(Clock *referenceClock, Clock *backupClock)
Same as constructor but allows delayed initialization, e.g.
Definition: SystemClock.h:237
ace_time::clock::SystemClockTemplate::setNextSyncAttemptMillis
void setNextSyncAttemptMillis(uint32_t ms)
Set the millis to next sync attempt.
Definition: SystemClock.h:324
ace_time::clock::SystemClockTemplate::kSyncStatusUnknown
static const uint8_t kSyncStatusUnknown
Sync was never done.
Definition: SystemClock.h:72
ace_time::clock::SystemClockTemplate::getNow
acetime_t getNow() const override
Return the number of seconds since the AceTime epoch (2000-01-01T00:00:00Z).
Definition: SystemClock.h:81
ace_time::clock::SystemClockTemplate::setSyncStatusCode
void setSyncStatusCode(uint8_t code)
Set the status code of most recent sync attempt.
Definition: SystemClock.h:334