AceTime  0.5
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.
SystemClockSyncCoroutine.h
1 /*
2  * MIT License
3  * Copyright (c) 2018 Brian T. Park
4  */
5 
6 #ifndef ACE_TIME_SYSTEM_CLOCK_SYNC_COROUTINE_H
7 #define ACE_TIME_SYSTEM_CLOCK_SYNC_COROUTINE_H
8 
9 #include <stdint.h>
10 #include "../common/TimingStats.h"
11 #include "SystemClock.h"
12 
13 class SystemClockSyncCoroutineTest;
14 
15 namespace ace_time {
16 namespace clock {
17 
29 class SystemClockSyncCoroutine: public ace_routine::Coroutine {
30  public:
43  explicit SystemClockSyncCoroutine(SystemClock& systemClock,
44  uint16_t syncPeriodSeconds = 3600,
45  uint16_t initialSyncPeriodSeconds = 5,
46  uint16_t requestTimeoutMillis = 1000,
47  common::TimingStats* timingStats = nullptr):
48  mSystemClock(systemClock),
49  mSyncPeriodSeconds(syncPeriodSeconds),
50  mRequestTimeoutMillis(requestTimeoutMillis),
51  mTimingStats(timingStats),
52  mCurrentSyncPeriodSeconds(initialSyncPeriodSeconds) {}
53 
61  int runCoroutine() override {
62  if (mSystemClock.mSyncTimeProvider == nullptr) return 0;
63 
64  COROUTINE_LOOP() {
65  // Send request
66  mSystemClock.mSyncTimeProvider->sendRequest();
67  mRequestStartTime = millis();
68 
69  // Wait for request
70  while (true) {
71  if (mSystemClock.mSyncTimeProvider->isResponseReady()) {
72  mRequestStatus = kStatusOk;
73  break;
74  }
75 
76  {
77  // Local variable waitTime must be scoped with {} so that the goto
78  // in COROUTINE_LOOP() skip past it in clang++. g++ seems to be
79  // fine without it.
80  uint16_t waitTime = millis() - mRequestStartTime;
81  if (waitTime >= mRequestTimeoutMillis) {
82  mRequestStatus = kStatusTimedOut;
83  break;
84  }
85  }
86 
87  COROUTINE_YIELD();
88  }
89 
90  // Process the response
91  if (mRequestStatus == kStatusOk) {
92  acetime_t nowSeconds =
93  mSystemClock.mSyncTimeProvider->readResponse();
94  uint16_t elapsedTime = millis() - mRequestStartTime;
95  if (mTimingStats != nullptr) {
96  mTimingStats->update(elapsedTime);
97  }
98  mSystemClock.sync(nowSeconds);
99  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
100  }
101 
102  COROUTINE_DELAY_SECONDS(mDelayLoopCounter, mCurrentSyncPeriodSeconds);
103 
104  // Determine the retry delay time based on success or failure. If
105  // failure, retry with exponential backoff, until the delay becomes
106  // mSyncPeriodSeconds.
107  if (mRequestStatus == kStatusTimedOut) {
108  if (mCurrentSyncPeriodSeconds >= mSyncPeriodSeconds / 2) {
109  mCurrentSyncPeriodSeconds = mSyncPeriodSeconds;
110  } else {
111  mCurrentSyncPeriodSeconds *= 2;
112  }
113  }
114  }
115  }
116 
117  private:
118  friend class ::SystemClockSyncCoroutineTest;
119 
120  // disable copy constructor and assignment operator
123  delete;
124 
125  static const uint8_t kStatusOk = 0;
126  static const uint8_t kStatusTimedOut = 1;
127 
128  SystemClock& mSystemClock;
129  uint16_t const mSyncPeriodSeconds;
130  uint16_t const mRequestTimeoutMillis;
131  common::TimingStats* const mTimingStats;
132 
133  uint16_t mRequestStartTime;
134  uint16_t mCurrentSyncPeriodSeconds;
135  uint8_t mRequestStatus;
136  uint16_t mDelayLoopCounter;
137 };
138 
139 }
140 }
141 
142 #endif
A coroutine that syncs the SystemClock with its mSyncTimeProvider.
A TimeKeeper that uses the Arduino millis() function to advance the time returned to the user...
Definition: SystemClock.h:50
virtual bool isResponseReady() const
Return true if a response is ready.
Definition: TimeProvider.h:37
void sync(acetime_t epochSeconds)
Similar to setNow() except that backupNow() is called only if the backupTimeKeeper is different from ...
Definition: SystemClock.h:109
Helper class to collect timing statistics such as min, max, average.
Definition: TimingStats.h:19
virtual void sendRequest() const
Send a time request asynchronously.
Definition: TimeProvider.h:34
virtual acetime_t readResponse() const
Returns number of seconds since AceTime epoch (2000-01-01).
Definition: TimeProvider.h:44
SystemClockSyncCoroutine(SystemClock &systemClock, uint16_t syncPeriodSeconds=3600, uint16_t initialSyncPeriodSeconds=5, uint16_t requestTimeoutMillis=1000, common::TimingStats *timingStats=nullptr)
Constructor.