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.
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_runCoroutine;
18 
19 namespace ace_time {
20 namespace clock {
21 
45 class SystemClockCoroutine :
46  public SystemClock,
47  public ace_routine::Coroutine {
48 
49  public:
66  explicit SystemClockCoroutine(
67  Clock* referenceClock /* nullable */,
68  Clock* backupClock /* nullable */,
69  uint16_t syncPeriodSeconds = 3600,
70  uint16_t initialSyncPeriodSeconds = 5,
71  uint16_t requestTimeoutMillis = 1000,
72  ace_common::TimingStats* timingStats = nullptr):
73  SystemClock(referenceClock, backupClock),
74  mSyncPeriodSeconds(syncPeriodSeconds),
75  mRequestTimeoutMillis(requestTimeoutMillis),
76  mTimingStats(timingStats),
77  mCurrentSyncPeriodSeconds(initialSyncPeriodSeconds) {}
78 
95  int runCoroutine() override {
96  keepAlive();
97  if (getReferenceClock() == nullptr) return 0;
98 
99  uint32_t nowMillis = clockMillis();
100 
101  COROUTINE_LOOP() {
102  // Send request
103  getReferenceClock()->sendRequest();
104  mRequestStartMillis = coroutineMillis();
105  mRequestStatus = kStatusSent;
106  setPrevSyncAttemptMillis(nowMillis);
107  setNextSyncAttemptMillis(
108  nowMillis + mCurrentSyncPeriodSeconds * (uint32_t) 1000);
109 
110  // Wait for request until mRequestTimeoutMillis.
111  while (true) {
112  if (getReferenceClock()->isResponseReady()) {
113  mRequestStatus = kStatusOk;
114  break;
115  }
116 
117  {
118  // Local variable waitMillis must be scoped with {} so that the
119  // goto in COROUTINE_LOOP() skips past it. This seems to be
120  // a problem only in clang++; g++ seems to be fine without it.
121  uint16_t waitMillis =
122  (uint16_t) coroutineMillis() - mRequestStartMillis;
123  if (waitMillis >= mRequestTimeoutMillis) {
124  mRequestStatus = kStatusTimedOut;
125  setSyncStatusCode(kSyncStatusTimedOut);
126  break;
127  }
128  }
129 
130  COROUTINE_YIELD();
131  }
132 
133  // Process the response
134  if (mRequestStatus == kStatusOk) {
135  acetime_t nowSeconds = getReferenceClock()->readResponse();
136  if (mTimingStats != nullptr) {
137  uint16_t elapsedMillis =
138  (uint16_t) coroutineMillis() - mRequestStartMillis;
139  mTimingStats->update(elapsedMillis);
140  }
141 
142  if (nowSeconds == kInvalidSeconds) {
143  setSyncStatusCode(kSyncStatusError);
144  // Clobber the mRequestStatus to trigger the exponential backoff
145  mRequestStatus = kStatusUnknown;
146  } else {
147  syncNow(nowSeconds);
148  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
149  setSyncStatusCode(kSyncStatusOk);
150  }
151  }
152 
153  // Wait for mCurrentSyncPeriodSeconds
154  setNextSyncAttemptMillis(
155  nowMillis + mCurrentSyncPeriodSeconds * (uint32_t) 1000);
156  COROUTINE_DELAY_SECONDS(mCurrentSyncPeriodSeconds);
157 
158  // Determine the retry delay time based on success or failure. If
159  // failure, retry with exponential backoff, until the delay becomes
160  // mSyncPeriodSeconds.
161  if (mRequestStatus != kStatusOk) {
162  if (mCurrentSyncPeriodSeconds >= mSyncPeriodSeconds / 2) {
163  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
164  } else {
165  mCurrentSyncPeriodSeconds *= 2;
166  }
167  }
168  }
169  }
170 
172  uint8_t getRequestStatus() const { return mRequestStatus; }
173 
174  protected:
176  SystemClockCoroutine() {}
177 
178  private:
179  friend class ::SystemClockCoroutineTest_runCoroutine;
180 
182  static const uint8_t kStatusUnknown = 0;
183 
185  static const uint8_t kStatusSent = 1;
186 
188  static const uint8_t kStatusOk = 2;
189 
191  static const uint8_t kStatusTimedOut = 3;
192 
193  // disable copy constructor and assignment operator
194  SystemClockCoroutine(const SystemClockCoroutine&) = delete;
195  SystemClockCoroutine& operator=(const SystemClockCoroutine&) = delete;
196 
197  uint16_t const mSyncPeriodSeconds = 3600;
198  uint16_t const mRequestTimeoutMillis = 1000;
199  ace_common::TimingStats* const mTimingStats = nullptr;
200 
201  uint16_t mRequestStartMillis; // lower 16-bit of millis()
202  uint16_t mCurrentSyncPeriodSeconds = 5;
203  uint16_t mWaitCount;
204  uint8_t mRequestStatus = kStatusUnknown;
205 };
206 
207 }
208 }
209 
210 #endif
211 
212 #endif