AceTimeClock  1.3.0
Clock classes for Arduino that can synchronize from an NTP server or an RTC chip
SystemClockCoroutine.h
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_SYSTEM_CLOCK_COROUTINE_H
7 #define ACE_TIME_SYSTEM_CLOCK_COROUTINE_H
8 
9 // activate only if <AceRoutine.h> is included before this header
10 #ifdef ACE_ROUTINE_VERSION
11 
12 #include <stdint.h>
13 #include <AceCommon.h> // TimingStats
14 #include <AceRoutine.h>
15 #include "SystemClock.h"
16 
17 class SystemClockCoroutineTest;
18 class SystemClockCoroutineTest_runCoroutine;
19 
20 namespace ace_time {
21 namespace clock {
22 
49 template <typename T_SCCI, typename T_CRCI>
50 class SystemClockCoroutineTemplate :
51  public SystemClockTemplate<T_SCCI>,
52  public ace_routine::CoroutineTemplate<T_CRCI, uint16_t> {
53 
54  public:
71  explicit SystemClockCoroutineTemplate(
72  Clock* referenceClock /* nullable */,
73  Clock* backupClock /* nullable */,
74  uint16_t syncPeriodSeconds = 3600,
75  uint16_t initialSyncPeriodSeconds = 5,
76  uint16_t requestTimeoutMillis = 1000,
77  ace_common::TimingStats* timingStats = nullptr):
78  SystemClockTemplate<T_SCCI>(referenceClock, backupClock),
79  ace_routine::CoroutineTemplate<T_CRCI, uint16_t>(),
80  mSyncPeriodSeconds(syncPeriodSeconds),
81  mRequestTimeoutMillis(requestTimeoutMillis),
82  mTimingStats(timingStats),
83  mCurrentSyncPeriodSeconds(initialSyncPeriodSeconds) {}
84 
101  int runCoroutine() override {
102  this->keepAlive();
103  if (this->getReferenceClock() == nullptr) return 0;
104 
105  uint32_t nowMillis = this->clockMillis();
106 
107  COROUTINE_LOOP() {
108  // Send request
109  this->getReferenceClock()->sendRequest();
110  mRequestStartMillis = this->coroutineMillis();
111  mRequestStatus = kStatusSent;
112  this->setPrevSyncAttemptMillis(nowMillis);
113  this->setNextSyncAttemptMillis(
114  nowMillis + mCurrentSyncPeriodSeconds * (uint32_t) 1000);
115 
116  // Wait for request until mRequestTimeoutMillis.
117  while (true) {
118  if (this->getReferenceClock()->isResponseReady()) {
119  mRequestStatus = kStatusOk;
120  break;
121  }
122 
123  {
124  // Local variable waitMillis must be scoped with {} so that the
125  // goto in COROUTINE_LOOP() skips past it. This seems to be
126  // a problem only in clang++; g++ seems to be fine without it.
127  uint16_t waitMillis =
128  (uint16_t) this->coroutineMillis() - mRequestStartMillis;
129  if (waitMillis >= mRequestTimeoutMillis) {
130  mRequestStatus = kStatusTimedOut;
131  this->setSyncStatusCode(this->kSyncStatusTimedOut);
132  break;
133  }
134  }
135 
136  COROUTINE_YIELD();
137  }
138 
139  // Process the response
140  if (mRequestStatus == kStatusOk) {
141  acetime_t nowSeconds = this->getReferenceClock()->readResponse();
142  if (mTimingStats != nullptr) {
143  uint16_t elapsedMillis =
144  (uint16_t) this->coroutineMillis() - mRequestStartMillis;
145  mTimingStats->update(elapsedMillis);
146  }
147 
148  if (nowSeconds == this->kInvalidSeconds) {
149  this->setSyncStatusCode(this->kSyncStatusError);
150  // Clobber the mRequestStatus to trigger the exponential backoff
151  mRequestStatus = kStatusUnknown;
152  } else {
153  this->syncNow(nowSeconds);
154  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
155  this->setSyncStatusCode(this->kSyncStatusOk);
156  }
157  }
158 
159  // Wait for mCurrentSyncPeriodSeconds
160  this->setNextSyncAttemptMillis(
161  nowMillis + mCurrentSyncPeriodSeconds * (uint32_t) 1000);
162  for (mWaitCount = 0;
163  mWaitCount < mCurrentSyncPeriodSeconds;
164  mWaitCount++
165  ) {
166  COROUTINE_DELAY(1000);
167  }
168 
169  // Determine the retry delay time based on success or failure. If
170  // failure, retry with exponential backoff, until the delay becomes
171  // mSyncPeriodSeconds.
172  if (mRequestStatus != kStatusOk) {
173  if (mCurrentSyncPeriodSeconds >= mSyncPeriodSeconds / 2) {
174  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
175  } else {
176  mCurrentSyncPeriodSeconds *= 2;
177  }
178  }
179  }
180  }
181 
183  uint8_t getRequestStatus() const { return mRequestStatus; }
184 
185  protected:
187  SystemClockCoroutineTemplate() {}
188 
189  private:
190  friend class ::SystemClockCoroutineTest;
191  friend class ::SystemClockCoroutineTest_runCoroutine;
192 
194  static const uint8_t kStatusUnknown = 0;
195 
197  static const uint8_t kStatusSent = 1;
198 
200  static const uint8_t kStatusOk = 2;
201 
203  static const uint8_t kStatusTimedOut = 3;
204 
205  // disable copy constructor and assignment operator
206  SystemClockCoroutineTemplate(const SystemClockCoroutineTemplate&) = delete;
207  SystemClockCoroutineTemplate& operator=(
208  const SystemClockCoroutineTemplate&) = delete;
209 
210  uint16_t const mSyncPeriodSeconds = 3600;
211  uint16_t const mRequestTimeoutMillis = 1000;
212  ace_common::TimingStats* const mTimingStats = nullptr;
213 
214  uint16_t mRequestStartMillis; // lower 16-bit of millis()
215  uint16_t mCurrentSyncPeriodSeconds = 5;
216  uint16_t mWaitCount;
217  uint8_t mRequestStatus = kStatusUnknown;
218 };
219 
258 using SystemClockCoroutine = SystemClockCoroutineTemplate<
259  hw::ClockInterface, ace_routine::ClockInterface
260 >;
261 
262 }
263 }
264 
265 #endif
266 
267 #endif