AceTime  1.7.1
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 
40 class SystemClockLoop : public SystemClock {
41  public:
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 
81  void loop() {
82  keepAlive();
83  if (getReferenceClock() == nullptr) return;
84 
85  uint32_t nowMillis = clockMillis();
86 
87  // Finite state machine based on mRequestStatus
88  switch (mRequestStatus) {
89  case kStatusReady:
91  mRequestStartMillis = nowMillis;
92  mRequestStatus = kStatusSent;
93  setPrevSyncAttemptMillis(nowMillis);
94  setNextSyncAttemptMillis(nowMillis
95  + mCurrentSyncPeriodSeconds * (uint32_t) 1000);
96  break;
97 
98  case kStatusSent: {
99  uint32_t elapsedMillis = nowMillis - mRequestStartMillis;
100  if (mTimingStats) mTimingStats->update((uint16_t) elapsedMillis);
101 
103  acetime_t nowSeconds = getReferenceClock()->readResponse();
104 
105  if (nowSeconds == kInvalidSeconds) {
106  // If response came back but was invalid, reschedule.
107  mRequestStatus = kStatusWaitForRetry;
109  } else {
110  // Request succeeded.
111  syncNow(nowSeconds);
112  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
113  mRequestStatus = kStatusOk;
115  }
116  } else {
117  // If timed out, reschedule.
118  if (elapsedMillis >= mRequestTimeoutMillis) {
119  mRequestStatus = kStatusWaitForRetry;
121  }
122  }
123  break;
124  }
125 
126  // The previous request succeeded, so wait until the next sync attempt.
127  case kStatusOk: {
128  uint32_t elapsedMillis = nowMillis - mRequestStartMillis;
129  if (elapsedMillis >= mCurrentSyncPeriodSeconds * (uint32_t) 1000) {
130  mRequestStatus = kStatusReady;
131  }
132  break;
133  }
134 
135  // Previous request failed, so update timing parameters so that
136  // subsequent loop() retries with an exponential backoff, until a
137  // maximum of mSyncPeriodSeconds is reached.
138  case kStatusWaitForRetry: {
139  uint32_t elapsedMillis = nowMillis - mRequestStartMillis;
140  // Adjust mCurrentSyncPeriodSeconds using exponential backoff.
141  if (elapsedMillis >= mCurrentSyncPeriodSeconds * (uint32_t) 1000) {
142  if (mCurrentSyncPeriodSeconds >= mSyncPeriodSeconds / 2) {
143  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
144  } else {
145  mCurrentSyncPeriodSeconds *= 2;
146  }
147  mRequestStatus = kStatusReady;
148  }
149  break;
150  }
151  }
152  }
153 
154  protected:
157 
158  // disable copy constructor and assignment operator
159  SystemClockLoop(const SystemClockLoop&) = delete;
160  SystemClockLoop& operator=(const SystemClockLoop&) = delete;
161 
162  private:
163  friend class ::SystemClockLoopTest_loop;
164 
166  static const uint8_t kStatusReady = 0;
167 
169  static const uint8_t kStatusSent = 1;
170 
172  static const uint8_t kStatusOk = 2;
173 
175  static const uint8_t kStatusWaitForRetry = 3;
176 
177  uint16_t const mSyncPeriodSeconds = 3600;
178  uint16_t const mRequestTimeoutMillis = 1000;
179  ace_common::TimingStats* const mTimingStats = nullptr;
180 
181  uint32_t mRequestStartMillis;
182  uint16_t mCurrentSyncPeriodSeconds = 5;
183  uint8_t mRequestStatus = kStatusReady;
184 };
185 
186 }
187 }
188 
189 #endif
ace_time::clock::SystemClock::setSyncStatusCode
void setSyncStatusCode(uint8_t code)
Set the status code of most recent sync attempt.
Definition: SystemClock.h:322
ace_time::clock::SystemClock::kSyncStatusOk
static const uint8_t kSyncStatusOk
Sync was successful.
Definition: SystemClock.h:55
ace_time::clock::SystemClock::kSyncStatusTimedOut
static const uint8_t kSyncStatusTimedOut
Sync request timed out.
Definition: SystemClock.h:61
ace_time::clock::SystemClockLoop::loop
void loop()
Make a request to the referenceClock every syncPeriodSeconds seconds.
Definition: SystemClockLoop.h:81
ace_time::clock::Clock::sendRequest
virtual void sendRequest() const
Send a time request asynchronously.
Definition: Clock.h:49
ace_time::clock::SystemClock::kSyncStatusError
static const uint8_t kSyncStatusError
Sync request failed.
Definition: SystemClock.h:58
ace_time::clock::SystemClock::syncNow
void syncNow(acetime_t epochSeconds)
Set the current mEpochSeconds to the given epochSeconds.
Definition: SystemClock.h:294
ace_time::clock::SystemClockLoop::SystemClockLoop
SystemClockLoop()
Empty constructor used for testing.
Definition: SystemClockLoop.h:156
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:255
ace_time::clock::SystemClock::setNextSyncAttemptMillis
void setNextSyncAttemptMillis(uint32_t ms)
Set the millis to next sync attempt.
Definition: SystemClock.h:312
ace_time::clock::SystemClock::setPrevSyncAttemptMillis
void setPrevSyncAttemptMillis(uint32_t ms)
Set the millis of prev sync attempt.
Definition: SystemClock.h:317
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::SystemClock::getReferenceClock
Clock * getReferenceClock() const
Get referenceClock.
Definition: SystemClock.h:241
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::SystemClock::clockMillis
virtual unsigned long clockMillis() const
Return the Arduino millis().
Definition: SystemClock.h:247
ace_time::clock::Clock::isResponseReady
virtual bool isResponseReady() const
Return true if a response is ready.
Definition: Clock.h:52
ace_time::clock::Clock::readResponse
virtual acetime_t readResponse() const
Returns number of seconds since AceTime epoch (2000-01-01).
Definition: Clock.h:59
ace_time::clock::SystemClockLoop
A subclass of SystemClock that sync with its mReferenceClock using the non-blocking Clock API of the ...
Definition: SystemClockLoop.h:40
ace_time::clock::SystemClock
A Clock that uses the Arduino millis() function to advance the time returned to the user.
Definition: SystemClock.h:52