AceTimeClock  1.3.0
Clock classes for Arduino 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 
89  acetime_t getNow() const override {
90  if (!mIsInit) return kInvalidSeconds;
91 
92  // Update mEpochSeconds by the number of seconds elapsed according to the
93  // millis(). This method is expected to be called multiple times a second,
94  // so the while() loop below will normally execute 0 times, until the
95  // millis() clock goes past the mPrevKeepAliveMillis by 1 second.
96  //
97  // There are 2 reasons why this method will be called multiple times a
98  // second:
99  //
100  // 1) A physical clock with an external display will want to refresh
101  // its display 5-10 times a second, so that it can capture the transition
102  // from one second to the next without too much jitter. So it will call
103  // this method multiple times a second to check if one second has passed.
104  //
105  // 2) If the SystemClockCoroutine or SystemClockLoop classes is used,
106  // then the keepAlive() method will be called perhaps 100's times per
107  // second, as fast as the iteration speed of the global loop() function.
108  while ((uint16_t) ((uint16_t) clockMillis() - mPrevKeepAliveMillis)
109  >= 1000) {
110  mPrevKeepAliveMillis += 1000;
111  mEpochSeconds += 1;
112  }
113 
114  return mEpochSeconds;
115  }
116 
123  void setNow(acetime_t epochSeconds) override {
124  syncNow(epochSeconds);
125 
126  // Also set the reference clock if possible.
127  if (mReferenceClock != nullptr) {
128  mReferenceClock->setNow(epochSeconds);
129  }
130  }
131 
145  void forceSync() {
146  if (mReferenceClock) {
147  acetime_t nowSeconds = mReferenceClock->getNow();
148  syncNow(nowSeconds);
149  }
150  }
151 
156  acetime_t getLastSyncTime() const {
157  return mLastSyncTime;
158  }
159 
161  uint8_t getSyncStatusCode() const { return mSyncStatusCode; }
162 
176  int32_t getSecondsSinceSyncAttempt() const {
177  return (int32_t) (clockMillis() - mPrevSyncAttemptMillis) / 1000;
178  }
179 
186  int32_t getSecondsToSyncAttempt() const {
187  return (int32_t) (mNextSyncAttemptMillis - clockMillis()) / 1000;
188  }
189 
200  int16_t getClockSkew() const {
201  return mClockSkew;
202  }
203 
205  bool isInit() const { return mIsInit; }
206 
207  protected:
208  friend class ::SystemClockLoopTest;
209  friend class ::SystemClockCoroutineTest;
210  friend class ::SystemClockLoopTest_loop;
211  friend class ::SystemClockLoopTest_syncNow;
212  friend class ::SystemClockLoopTest_setup;
213  friend class ::SystemClockLoopTest_backupNow;
214  friend class ::SystemClockLoopTest_getNow;
215 
216  // disable copy constructor and assignment operator
217  SystemClockTemplate(const SystemClockTemplate&) = delete;
218  SystemClockTemplate& operator=(const SystemClockTemplate&) = delete;
219 
238  Clock* referenceClock /* nullable */,
239  Clock* backupClock /* nullable */
240  ) :
241  mReferenceClock(referenceClock),
242  mBackupClock(backupClock) {}
243 
248  explicit SystemClockTemplate() {}
249 
252  Clock* referenceClock /* nullable */,
253  Clock* backupClock /* nullable */
254  ) {
255  mReferenceClock = referenceClock;
256  mBackupClock = backupClock;
257 
258  mEpochSeconds = kInvalidSeconds;
259  mPrevSyncAttemptMillis = 0;
260  mNextSyncAttemptMillis = 0;
261  mPrevKeepAliveMillis = 0;
262  mIsInit = false;
263  mSyncStatusCode = kSyncStatusUnknown;
264  }
265 
267  Clock* getReferenceClock() const { return mReferenceClock; }
268 
273  unsigned long clockMillis() const { return T_CI::millis(); }
274 
281  void keepAlive() {
282  getNow();
283  }
284 
291  void backupNow(acetime_t nowSeconds) {
292  if (mBackupClock != nullptr) {
293  mBackupClock->setNow(nowSeconds);
294  }
295  }
296 
320  void syncNow(acetime_t epochSeconds) {
321  if (epochSeconds == kInvalidSeconds) return;
322 
323  mLastSyncTime = epochSeconds;
324  acetime_t skew = mEpochSeconds - epochSeconds;
325  mClockSkew = skew;
326  if (skew == 0) return;
327 
328  mEpochSeconds = epochSeconds;
329  mPrevKeepAliveMillis = clockMillis();
330  mIsInit = true;
331 
332  if (mBackupClock != mReferenceClock) {
333  backupNow(epochSeconds);
334  }
335  }
336 
338  void setNextSyncAttemptMillis(uint32_t ms) {
339  mNextSyncAttemptMillis = ms;
340  }
341 
343  void setPrevSyncAttemptMillis(uint32_t ms) {
344  mPrevSyncAttemptMillis = ms;
345  }
346 
348  void setSyncStatusCode(uint8_t code) {
349  mSyncStatusCode = code;
350  }
351 
352  private:
353  Clock* mReferenceClock;
354  Clock* mBackupClock;
355 
356  mutable acetime_t mEpochSeconds = kInvalidSeconds;
357  acetime_t mLastSyncTime = kInvalidSeconds; // time when last synced
358  uint32_t mPrevSyncAttemptMillis = 0;
359  uint32_t mNextSyncAttemptMillis = 0;
360  mutable uint16_t mPrevKeepAliveMillis = 0; // lower 16-bits of clockMillis()
361  int16_t mClockSkew = 0; // diff between reference and this clock
362  bool mIsInit = false; // true if setNow() or syncNow() was successful
363  uint8_t mSyncStatusCode = kSyncStatusUnknown;
364 };
365 
367 using SystemClock = SystemClockTemplate<hw::ClockInterface>;
368 
369 }
370 }
371 
372 #endif
Abstract base class for objects that provide and store time.
Definition: Clock.h:19
virtual acetime_t getNow() const =0
Return the number of seconds since the AceTime epoch (2000-01-01T00:00:00Z).
virtual void setNow(acetime_t)
Set the time to the indicated seconds.
Definition: Clock.h:66
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 Clock that uses the Arduino millis() function to advance the time returned to the user.
Definition: SystemClock.h:60
int32_t getSecondsToSyncAttempt() const
Return the number of seconds until the next syncNow() attempt.
Definition: SystemClock.h:186
uint8_t getSyncStatusCode() const
Get sync status code.
Definition: SystemClock.h:161
static const uint8_t kSyncStatusError
Sync request failed.
Definition: SystemClock.h:66
void setup()
Attempt to retrieve the time from the backupClock if it exists.
Definition: SystemClock.h:75
void forceSync()
Manually force a sync with the referenceClock if it exists.
Definition: SystemClock.h:145
SystemClockTemplate()
Empty constructor primarily for tests.
Definition: SystemClock.h:248
void initSystemClock(Clock *referenceClock, Clock *backupClock)
Same as constructor but allows delayed initialization, e.g.
Definition: SystemClock.h:251
void backupNow(acetime_t nowSeconds)
Write the nowSeconds to the backupClock (which can be an RTC that has non-volatile memory).
Definition: SystemClock.h:291
void setSyncStatusCode(uint8_t code)
Set the status code of most recent sync attempt.
Definition: SystemClock.h:348
void setNow(acetime_t epochSeconds) override
Set the time to the indicated seconds.
Definition: SystemClock.h:123
void setPrevSyncAttemptMillis(uint32_t ms)
Set the millis of prev sync attempt.
Definition: SystemClock.h:343
SystemClockTemplate(Clock *referenceClock, Clock *backupClock)
Constructor.
Definition: SystemClock.h:237
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
acetime_t getNow() const override
Return the number of seconds since the AceTime epoch (2000-01-01T00:00:00Z).
Definition: SystemClock.h:89
bool isInit() const
Return true if initialized by setNow() or syncNow().
Definition: SystemClock.h:205
int32_t getSecondsSinceSyncAttempt() const
Return the number of seconds since the previous sync attempt, successful or not.
Definition: SystemClock.h:176
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
int16_t getClockSkew() const
Difference between this clock compared to reference at last sync.
Definition: SystemClock.h:200
static const uint8_t kSyncStatusUnknown
Sync was never done.
Definition: SystemClock.h:72
unsigned long clockMillis() const
Return the Arduino millis().
Definition: SystemClock.h:273
acetime_t getLastSyncTime() const
Return the time (seconds since Epoch) of the last successful syncNow() call.
Definition: SystemClock.h:156
static const uint8_t kSyncStatusOk
Sync was successful.
Definition: SystemClock.h:63