AceTime  1.3
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 
33 class SystemClockCoroutine: public SystemClock, public ace_routine::Coroutine {
34  public:
36  static const uint8_t kStatusUnknown = 0;
37 
39  static const uint8_t kStatusSent = 1;
40 
42  static const uint8_t kStatusOk = 2;
43 
45  static const uint8_t kStatusTimedOut = 3;
46 
63  explicit SystemClockCoroutine(
64  Clock* referenceClock /* nullable */,
65  Clock* backupClock /* nullable */,
66  uint16_t syncPeriodSeconds = 3600,
67  uint16_t initialSyncPeriodSeconds = 5,
68  uint16_t requestTimeoutMillis = 1000,
69  ace_common::TimingStats* timingStats = nullptr):
70  SystemClock(referenceClock, backupClock),
71  mSyncPeriodSeconds(syncPeriodSeconds),
72  mRequestTimeoutMillis(requestTimeoutMillis),
73  mTimingStats(timingStats),
74  mCurrentSyncPeriodSeconds(initialSyncPeriodSeconds) {}
75 
83  int runCoroutine() override {
84  keepAlive();
85  if (mReferenceClock == nullptr) return 0;
86 
87  COROUTINE_LOOP() {
88  // Send request
89  mReferenceClock->sendRequest();
90  mRequestStartMillis = coroutineMillis();
91  mRequestStatus = kStatusSent;
92 
93  // Wait for request
94  while (true) {
95  if (mReferenceClock->isResponseReady()) {
96  mRequestStatus = kStatusOk;
97  break;
98  }
99 
100  {
101  // Local variable waitMillis must be scoped with {} so that the
102  // goto in COROUTINE_LOOP() skips past it. This seems to be
103  // a problem only in clang++; g++ seems to be fine without it.
104  uint16_t waitMillis =
105  (uint16_t) coroutineMillis() - mRequestStartMillis;
106  if (waitMillis >= mRequestTimeoutMillis) {
107  mRequestStatus = kStatusTimedOut;
108  break;
109  }
110  }
111 
112  COROUTINE_YIELD();
113  }
114 
115  // Process the response
116  if (mRequestStatus == kStatusOk) {
117  acetime_t nowSeconds = mReferenceClock->readResponse();
118  if (mTimingStats != nullptr) {
119  uint16_t elapsedMillis =
120  (uint16_t) coroutineMillis() - mRequestStartMillis;
121  mTimingStats->update(elapsedMillis);
122  }
123  syncNow(nowSeconds);
124  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
125  }
126 
127  COROUTINE_DELAY_SECONDS(mCurrentSyncPeriodSeconds);
128 
129  // Determine the retry delay time based on success or failure. If
130  // failure, retry with exponential backoff, until the delay becomes
131  // mSyncPeriodSeconds.
132  if (mRequestStatus == kStatusTimedOut) {
133  if (mCurrentSyncPeriodSeconds >= mSyncPeriodSeconds / 2) {
134  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
135  } else {
136  mCurrentSyncPeriodSeconds *= 2;
137  }
138  }
139  }
140  }
141 
143  uint8_t getRequestStatus() const { return mRequestStatus; }
144 
145  private:
146  friend class ::SystemClockCoroutineTest_runCoroutine;
147 
148  // disable copy constructor and assignment operator
149  SystemClockCoroutine(const SystemClockCoroutine&) = delete;
150  SystemClockCoroutine& operator=(const SystemClockCoroutine&) = delete;
151 
152  uint16_t const mSyncPeriodSeconds;
153  uint16_t const mRequestTimeoutMillis;
154  ace_common::TimingStats* const mTimingStats;
155 
156  uint16_t mRequestStartMillis; // lower 16-bit of millis()
157  uint16_t mCurrentSyncPeriodSeconds;
158  uint8_t mRequestStatus = kStatusUnknown;
159 };
160 
161 }
162 }
163 
164 #endif
165 
166 #endif