AceTimeClock
1.3.0
Clock classes for Arduino that can synchronize from an NTP server or an RTC chip
|
A Clock that uses the Arduino millis() function to advance the time returned to the user. More...
#include <SystemClock.h>
Public Member Functions | |
void | setup () |
Attempt to retrieve the time from the backupClock if it exists. | |
acetime_t | getNow () const override |
Return the number of seconds since the AceTime epoch (2000-01-01T00:00:00Z). More... | |
void | setNow (acetime_t epochSeconds) override |
Set the time to the indicated seconds. More... | |
void | forceSync () |
Manually force a sync with the referenceClock if it exists. More... | |
acetime_t | getLastSyncTime () const |
Return the time (seconds since Epoch) of the last successful syncNow() call. More... | |
uint8_t | getSyncStatusCode () const |
Get sync status code. | |
int32_t | getSecondsSinceSyncAttempt () const |
Return the number of seconds since the previous sync attempt, successful or not. More... | |
int32_t | getSecondsToSyncAttempt () const |
Return the number of seconds until the next syncNow() attempt. More... | |
int16_t | getClockSkew () const |
Difference between this clock compared to reference at last sync. More... | |
bool | isInit () const |
Return true if initialized by setNow() or syncNow(). | |
![]() | |
Clock ()=default | |
Default constructor. | |
~Clock ()=default | |
We deliberately avoid using a virtual destructor. More... | |
virtual void | sendRequest () const |
Send a time request asynchronously. | |
virtual bool | isResponseReady () const |
Return true if a response is ready. | |
virtual acetime_t | readResponse () const |
Returns number of seconds since AceTime epoch (2000-01-01). More... | |
Static Public Attributes | |
static const uint8_t | kSyncStatusOk = 0 |
Sync was successful. | |
static const uint8_t | kSyncStatusError = 1 |
Sync request failed. | |
static const uint8_t | kSyncStatusTimedOut = 2 |
Sync request timed out. | |
static const uint8_t | kSyncStatusUnknown = 128 |
Sync was never done. | |
![]() | |
static const acetime_t | kInvalidSeconds = LocalTime::kInvalidSeconds |
Error value returned by getNow() and other methods when this object is not yet initialized. | |
Protected Member Functions | |
SystemClockTemplate (const SystemClockTemplate &)=delete | |
SystemClockTemplate & | operator= (const SystemClockTemplate &)=delete |
SystemClockTemplate (Clock *referenceClock, Clock *backupClock) | |
Constructor. More... | |
SystemClockTemplate () | |
Empty constructor primarily for tests. More... | |
void | initSystemClock (Clock *referenceClock, Clock *backupClock) |
Same as constructor but allows delayed initialization, e.g. More... | |
Clock * | getReferenceClock () const |
Get referenceClock. | |
unsigned long | clockMillis () const |
Return the Arduino millis(). More... | |
void | keepAlive () |
Call this (or getNow() every 65.535 seconds or faster to keep the internal counter in sync with millis(). More... | |
void | backupNow (acetime_t nowSeconds) |
Write the nowSeconds to the backupClock (which can be an RTC that has non-volatile memory). More... | |
void | syncNow (acetime_t epochSeconds) |
Set the current mEpochSeconds to the given epochSeconds. More... | |
void | setNextSyncAttemptMillis (uint32_t ms) |
Set the millis to next sync attempt. | |
void | setPrevSyncAttemptMillis (uint32_t ms) |
Set the millis of prev sync attempt. | |
void | setSyncStatusCode (uint8_t code) |
Set the status code of most recent sync attempt. | |
A Clock that uses the Arduino millis() function to advance the time returned to the user.
It has 2 major features:
1) The built-in millis() is not accurate, so this class allows a periodic sync using the (presumably) more accurate referenceClock. 2) The current time can be periodically backed up into the backupClock which is expected to be an RTC chip that continues to keep time during power loss. Upon (re)start, SystemClock::setup() reads back the time from the backupClock if it exists.
There are 2 maintenance tasks which this class must perform peridicallly:
1) The value of the previous system time millis() is stored internally as a uint16_t. That has 2 advantages: 1) it saves memory, 2) the upper bound of the execution time of getNow() limited to 65 iterations. The disadvantage is the that internal counter will rollover within 65.535 milliseconds. To prevent that, keepAlive() must be called more frequently than every 65.536 seconds. 2) The current time can be synchronized to the referenceClock peridically. Some reference clocks can take hundreds or thousands of milliseconds to return, so it's important that the non-block methods of Clock are used to synchronize to the reference clock.
Two subclasses of SystemClock expose 2 different ways of performing these maintenance tasks.
1) Call SystemClockCoroutine::runCoroutine using the framework of the AceRoutine library in the global loop() function. 2) Call the SystemClockLoop::loop() method from the global loop() function.
T_CI | class name of the ClockInterface, normally ace_time::ClockInterface |
Definition at line 60 of file SystemClock.h.
|
inlineexplicitprotected |
Constructor.
referenceClock | The authoritative source of the time. If this is null, this object relies just on clockMillis() to keep time, and the user is expected to set the proper time using setNow(). |
backupClock | An RTC chip which continues to keep time even when power is lost. If this clock exists, then the time is retrieved from the backupClock during setup() and used to set the referenceClock which is assumed to have lost its info. If the reference clock continues to keep time during power loss, then the backupClock does not need to be given. One should never need to give the same clock instance as both the referenceClock and the backupClock, but the code tries to detect this case and attempts to do the right thing. This parameter can be null. |
Definition at line 237 of file SystemClock.h.
|
inlineexplicitprotected |
Empty constructor primarily for tests.
The init() must be called before using the object.
Definition at line 248 of file SystemClock.h.
|
inlineprotected |
Write the nowSeconds to the backupClock (which can be an RTC that has non-volatile memory).
If the referenceClock already preserves its date and time during power loss, then we don't need a backupClock and this method does not need to be called.
Definition at line 291 of file SystemClock.h.
|
inlineprotected |
Return the Arduino millis().
Override for unit testing. Named 'clockMillis()' to avoid conflict with Coroutine::millis().
Definition at line 273 of file SystemClock.h.
|
inline |
Manually force a sync with the referenceClock if it exists.
Intended to be mostly for diagnostic or debugging.
This calls the synchronous Clock::getNow() method on the reference clock, which can block the program from continuing if the reference clock takes a long time.
Normally syncing with the reference clock happens through the SystemClockCoroutine::runCoroutine() or SystemClockLoop::loop(), both of which use the non-blocking calls (Clock::sendRequest(), Clock::isResponseReady(), Clock::readResponse()) on the reference clock.
Definition at line 145 of file SystemClock.h.
|
inline |
Difference between this clock compared to reference at last sync.
A negative value means that the SystemClock was slower than the referenceClock, and a positive value means that the SystemClock was faster than the referenceClock.
The clock skew is expected to be very small, a few seconds, so we use int16_t
type to save memory. The maximum clock skew that can be stored is 32767 seconds, or just over 9 hours.
Definition at line 200 of file SystemClock.h.
|
inline |
Return the time (seconds since Epoch) of the last successful syncNow() call.
Returns kInvalidSeconds if never synced.
Definition at line 156 of file SystemClock.h.
|
inlineoverridevirtual |
Return the number of seconds since the AceTime epoch (2000-01-01T00:00:00Z).
Returns kInvalidSeconds if an error has occured.
This is a blocking call. Some clocks (e.g. NTP client) this may take many seconds. On those clocks, use the asynchronous methods (sendRequest(), isResponseReady(), and readResponse()) instead.
Implements ace_time::clock::Clock.
Definition at line 89 of file SystemClock.h.
|
inline |
Return the number of seconds since the previous sync attempt, successful or not.
This should always return a positive integer, unless the last sync attempt happened so long ago that the int16_t
wrapped around, which is an undefined behavior in C (and probably C++ too).
It can be converted into a human readable form using the TimePeriod class. In some UI, it might be make sense to display this as a negative number.
The return value is undefined if getSyncStatusCode() is kSyncStatusUnknown.
Definition at line 176 of file SystemClock.h.
|
inline |
Return the number of seconds until the next syncNow() attempt.
The return value is undefined if getSyncStatusCode() is kSyncStatusUnknown.
Definition at line 186 of file SystemClock.h.
|
inlineprotected |
Same as constructor but allows delayed initialization, e.g.
in tests.
Definition at line 251 of file SystemClock.h.
|
inlineprotected |
Call this (or getNow() every 65.535 seconds or faster to keep the internal counter in sync with millis().
This will normally happen through the SystemClockCoroutine::runCoroutine() or SystemClockLoop::loop() methods.
Definition at line 281 of file SystemClock.h.
|
inlineoverridevirtual |
Set the time to the indicated seconds.
Calling with a value of kInvalidSeconds indicates an error condition, so the method should do nothing. Some clocks do not support this feature, for example, NTP or GPS clocks and this method will be a no-op.
Reimplemented from ace_time::clock::Clock.
Definition at line 123 of file SystemClock.h.
|
inlineprotected |
Set the current mEpochSeconds to the given epochSeconds.
This method is intended to be used by the SystemClockCoroutine or SystemClockLoop classes to update the current mEpochSeconds using the epochSeconds retrieved from the referenceClock, using the asynchronous methods of the referenceClock to avoid blocking.
This method exists because the implementation details of synchronizing the referenceClock to the systemClock is decoupled from this parent class and moved into the subclasses (currently one of SystemClockCoroutine and SystemClockLoop). This method is the hook that allows the subclasses to perform the synchronization.
This method is the same as setNow() (in fact, setNow() just calls this method), except that we don't set the referenceClock, since that was the original source of the epochSeconds. If we saved it back to its source, we would probably see drifting of the referenceClock due to the 1-second granularity of many RTC clocks.
TODO: Implement a more graceful syncNow() algorithm which shifts only a few milliseconds per iteration, and which guarantees that the clock never goes backwards in time.
Definition at line 320 of file SystemClock.h.