More supported arduinos

Modified, now also works on ESP8266, Arduino Nano and Teensy 3.2. More documentation, stil unfinished.
pull/1/head
Rop 7 years ago
parent 2a3e0549d1
commit fe5c113ec9

@ -242,8 +242,6 @@ berlin.setLocation("Europe/Berlin");
# ezTime: complete documentation
`#include <ezTime.h>` includes the library, creates `ezTime` object and `UTC` instance of `Timezone` class, as well as `defaultTZ`, which is a reference to UTC unless you set it to another timzone by calling `someTZ.setDefault()`.
## Index
* ezTime object
@ -290,95 +288,134 @@ berlin.setLocation("Europe/Berlin");
* year
## Work in progress...
## Documentation
### Starting it all
It all starts when you include the library with `#include <ezTime.h>`. From that point forward there is an object instance called `ezTime` with methods to control the behaviour of ezTime, as well as a timezone object called `UTC`. But nothing happens until you ask the library what time it is. This can be done by using any of the methods that tell time, like the `.dateTime` method of the timezone object. For instance, your code could do
`Serial.println(UTC.dateTime());` to print a complete textual representation of date and time to the serial port. The library would find out that time had not been synchronized yet, and it would send off an NTP request to one of the NTP servers that `pool.ntp.org` resolves to. If your Arduino has just woken up, it is probably not connected to the WiFi network just yet, and so the call to `.dateTime` would return a String with the date and time just after midnight on the 1st of January 1970: the zero-point for the unix-style time counter used by ezTime.
`bool setPosix(String posix)`
## Setting and synchronising time
`String getPosix()`
The NTP request from the scenario above failed because the network wasn't up yet, so the clock would still not be synchronized. Subsequent requests will retry the NTP query, but only if they happen at least 3 seconds later. (These 3 seconds are settable with the `NTP_RETRY` define from `ezTime.h`.)
`void setDefault()`
### ezTime.timeStatus
`bool isDST_local(time_t t = TIME_NOW)`
`timeStatus_t ezTime.timeStatus();`
`bool isDST_UTC(time_t t = TIME_NOW)`
Returns what state the clock is in. `ezTime.timeStatus()` will return either `timeNotSet`, `timeNeedsSync` or `timeSet`.
`bool isDST()`
* `timeNotSet` means no NTP update or other setting of the clock (with the `.settime` method) has taken place
* `timeSet` means the clock should have the current time
* `timeNeedsSync` means a scheduled NTP request has been due for more than an hour. (The time an update needs to be due before `timeNeedsSync` is set is configured by the `NTP_STALE_AFTER` define in the `ezTime.h` file.)
`String getTimezoneName(time_t t = TIME_NOW)`
### ezTime.waitForSync
`int32_t getOffset(time_t t = TIME_NOW)`
`bool ezTime.waitForSync(uint16_t timeout = 0);`
`time_t now(bool update_last_read = true)`
If your code uses timezones other than UTC, it might want to wait to initialise them until there is a valid time to see if the cached timezone definitions are still current. And if you are displaying a calendar or clock, it might look silly if it first says midnight on January 1st 1970 before showing the real time. `ezTime.waitForSync` will wait for the network to connect, and then for the time to be synchronized before returning `true`. If you specify a timeout (in seconds), it will return after that many seconds even if the clock is not in sync yet, returning `false`.
`void setTime(time_t t)`
### ezTime.setServer and ezTime.setInterval
`void setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t mnth, uint16_t yr)`
`void ezTime.setServer(String ntp_server = NTP_SERVER);`
`String dateTime(String format = DEFAULT_TIMEFORMAT)`
`void ezTime.setInterval(uint16_t seconds = 0);`
`String dateTime(time_t t, String format = DEFAULT_TIMEFORMAT)`
By default, ezTime is set to poll `pool.ntp.org` every 10 minutes. These defaults should work for most people, but you can change them by specifying a new server with `ezTime.setServer` or a new interval (in seconds) with ezTime.setInterval. If you call setInterval with an interval of 0 seconds or call it as `ezTime.setInterval()`, no more NTP queries will be made.
`uint8_t hour(time_t t = TIME_NOW)`
### ezTime.updateNow
`uint8_t minute(time_t t = TIME_NOW)`
`void ezTime.updateNow();`
`uint8_t second(time_t t = TIME_NOW)`
Schedules the next update to happen immediately, and then tries to query the NTP server. If that fails, it will keep retrying every 3 seconds.
`uint16_t ms(time_t t = TIME_NOW)`
### ezTime.queryNTP
`uint8_t day(time_t t = TIME_NOW)`
`bool ezTime.queryNTP(String server, time_t &t, unsigned long &measured_at);`
`uint8_t weekday(time_t t = TIME_NOW)`
This will send a single query to the NTP server your specify. It will put, in the `t` and `measured_at` variables passed by reference, the UTC unix-time and the `millis()` counter at the time the exact second happened. It does this by subtracting from `millis()` the fractional seconds received in the answer, as well as half the time it took to get an answer. This means it assumes the network delay was symmetrical, meaning it took just as long for the request to get to the server as for the answer to get back.
`uint8_t month(time_t t = TIME_NOW)`
If the time server answers, `ezTime.queryNTP` returns `true`. If `false` is returned, `ezTime.error()` will return either `NO_NETWORK` (if the WiFi is not connected) or `TIMEOUT` if a response took more than 1500 milliseconds (defined by `NTP_TIMEOUT` in `ezTime.h`).
`uint16_t year(time_t t = TIME_NOW)`
Note that this function is used internally by ezTime, but does not by itself set the time ezTime keeps. You will likely never need to call this from your code.
`bool secondChanged()`
## Errors and debug information
`bool minuteChanged()`
`void ezTime.debug(ezDebugLevel_t level);`
`ezError_t ezTime.error();`
`void breakTime(time_t time, tmElements_t &tm)`
`String ezTime.errorString(ezError_t err);`
`void clearCache();`
## Timezones
`time_t compileTime(String compile_date = __DATE__, String compile_time = __TIME__);`
`#include <ezTime.h>` includes the library, creates `ezTime` object and `UTC` instance of `Timezone` class, as well as `defaultTZ`, which is a reference to UTC unless you set it to another timzone by calling `someTZ.setDefault()`.
`void debug(ezDebugLevel_t level);`
`bool someTZ.setPosix(String posix)`
`ezError_t error();`
`String someTZ.getPosix()`
`String errorString(ezError_t err);`
`void someTZ.setDefault()`
`String getBetween(String &haystack, String before_needle, String after_needle = "");`
`bool someTZ.isDST_local(time_t t = TIME_NOW)`
`time_t makeTime(tmElements_t &tm);`
`bool someTZ.isDST_UTC(time_t t = TIME_NOW)`
`time_t makeTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t day, uint8_t month, int16_t year);`
`bool someTZ.isDST()`
`time_t makeUmpteenthTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t umpteenth, uint8_t wday, uint8_t month, int16_t year);`
`String someTZ.getTimezoneName(time_t t = TIME_NOW)`
`time_t now();`
`int32_t someTZ.getOffset(time_t t = TIME_NOW)`
`bool queryNTP(String server, time_t &t, unsigned long &measured_at);`
`bool ezTime.timezoneAPI(String location, String &olsen, String &posix);`
`void setInterval(uint16_t seconds = 0);`
## Getting/setting date and time
`void setServer(String ntp_server = NTP_SERVER);`
`time_t someTZ.now(bool update_last_read = true)`
`timeStatus_t timeStatus();`
`void someTZ.setTime(time_t t)`
`bool timezoneAPI(String location, String &olsen, String &posix);`
`void someTZ.setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t mnth, uint16_t yr)`
`void updateNow();`
```
String someTZ.dateTime(String format = DEFAULT_TIMEFORMAT);
String someTZ.dateTime(time_t t, String format = DEFAULT_TIMEFORMAT);
```
`String urlEncode(String str);`
```
uint8_t someTZ.hour(time_t t = TIME_NOW);
uint8_t someTZ.minute(time_t t = TIME_NOW);
uint8_t someTZ.second(time_t t = TIME_NOW);
uint16_t someTZ.ms(time_t t = TIME_NOW);
uint8_t someTZ.day(time_t t = TIME_NOW);
uint8_t someTZ.weekday(time_t t = TIME_NOW);
uint8_t someTZ.month(time_t t = TIME_NOW);
uint16_t someTZ.year(time_t t = TIME_NOW);
```
`bool waitForSync(uint16_t timeout = 0);`
```
bool someTZ.secondChanged();
bool someTZ.minuteChanged();
```
`String zeropad(uint32_t number, uint8_t length);`
## Compatibility with Arduino `Time` library
`time_t ezTime.makeTime(tmElements_t &tm);`
`time_t ezTime.makeTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t day, uint8_t month, int16_t year);`
`void ezTime.breakTime(time_t time, tmElements_t &tm)`
## Various functions
`time_t ezTime.makeUmpteenthTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t umpteenth, uint8_t wday, uint8_t month, int16_t year);`
`time_t ezTime.compileTime(String compile_date = __DATE__, String compile_time = __TIME__);`
`String ezTime.getBetween(String &haystack, String before_needle, String after_needle = "");`
`String urlEncode(String str);`
`String zeropad(uint32_t number, uint8_t length);`
`void ezTime.clearCache();`

@ -12,7 +12,6 @@
#include <ezTime.h>
#include <WiFi.h>
#define LOCALTZ_POSIX "PST+8PDT,M3.2.0/2,M11.1.0/2" // US Pacific time

@ -1,16 +1,29 @@
#include <ezTime.h>
#include <Arduino.h>
#include <ezTime.h>
#ifdef EZTIME_NETWORK_ENABLE
#ifdef ESP32
// Caching of namezone data is only available on ESP32
// Caching of namezone data is only available on EZTIME_NVS_ENABLE
#ifdef EZTIME_NVS_ENABLE
#include <Preferences.h> // For timezone lookup cache
#endif // EZTIME_NVS_ENABLE
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#else
#include <WiFi.h>
#endif
#endif // ESP32
#endif // EZTIME_NETWORK_ENABLE
const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
EZtime::EZtime() {
ezError_t _last_error = NO_ERROR;
ezDebugLevel_t _debug_level = NONE;
@ -20,7 +33,8 @@ EZtime::EZtime() {
_ntp_server = NTP_SERVER;
_ntp_local_port = NTP_LOCAL_PORT;
_update_due = 0;
_update_interval = UPDATE_INTERVAL;
_update_interval = NTP_INTERVAL;
_update_backoff = 0;
#endif // ifdef EZTIME_NETWORK_ENABLE
}
@ -57,7 +71,7 @@ ezError_t EZtime::error() {
void EZtime::error(ezError_t err) {
_last_error = err;
debugln(ERROR, "ERROR: " + errorString(err));
if (_last_error) debugln(ERROR, "ERROR: " + errorString(err));
}
void EZtime::debug(ezDebugLevel_t level) {
@ -79,6 +93,37 @@ void EZtime::debugln(ezDebugLevel_t level, String str) {
////////////////////////
String EZtime::monthString(uint8_t month) {
switch(month) {
case 1: return "January";
case 2: return "February";
case 3: return "March";
case 4: return "April";
case 5: return "May";
case 6: return "June";
case 7: return "July";
case 8: return "August";
case 9: return "September";
case 10: return "October";
case 11: return "November";
case 12: return "December";
}
return "";
}
String EZtime::dayString(uint8_t day) {
switch(day) {
case 1: return "Sunday";
case 2: return "Monday";
case 3: return "Tuesday";
case 4: return "Wednesday";
case 5: return "Thursday";
case 6: return "Friday";
case 7: return "Saturday";
}
return "";
}
timeStatus_t EZtime::timeStatus() { return _time_status; }
@ -86,8 +131,8 @@ time_t EZtime::now() {
unsigned long m = millis();
time_t t;
#ifdef EZTIME_NETWORK_ENABLE
if (_update_interval && (m >= _update_due) ) {
if (m - _update_due > 3600000) _time_status = timeNeedsSync; // If unable to sync for an hour, timeStatus = timeNeedsSync
if (_update_interval && (m >= _update_due + (_update_backoff * 1000)) ) {
if (m - _update_due > NTP_STALE_AFTER * 1000) _time_status = timeNeedsSync; // If unable to sync for an hour, timeStatus = timeNeedsSync
unsigned long measured_at;
if (queryNTP(_ntp_server, t, measured_at)) {
time_t old_time = _last_sync_time + ((m - _last_sync_millis) / 1000); //
@ -98,8 +143,9 @@ time_t EZtime::now() {
_last_sync_millis = measured_at;
uint16_t new_ms = (m - measured_at) % 1000;
int32_t correction = (t - old_time) * 1000 + new_ms - old_ms;
_update_due = m + _update_interval * 1000;
_last_read_ms = new_ms;
_update_due = m + _update_interval * 1000;
_update_backoff = 0;
debug(INFO, "Received time: " + UTC.dateTime(_last_sync_time, "l, d-M-y H:i:s.v T"));
if (_time_status != timeNotSet) {
debugln(INFO, " (internal clock was " + ( correction == 0 ? "spot on)" : String(abs(correction)) + " ms " + ( correction > 0 ? "slow)" : "fast)" ) ) );
@ -109,6 +155,8 @@ time_t EZtime::now() {
// _fudge = 0;
}
_time_status = timeSet;
} else {
_update_backoff += NTP_RETRY;
}
}
#endif // EZTIME_NETWORK_ENABLE
@ -203,26 +251,26 @@ time_t EZtime::makeTime(tmElements_t &tm){
uint32_t seconds;
// seconds from 1970 till 1 jan 00:00:00 of the given year
seconds= tm.Year * (3600 * 24 * 365);
seconds= tm.Year * SECS_PER_DAY * 365UL;
for (i = 0; i < tm.Year; i++) {
if (LEAP_YEAR(i)) {
seconds += 3600 * 24; // add extra days for leap years
seconds += SECS_PER_DAY; // add extra days for leap years
}
}
// add days for this year, months start from 1
for (i = 1; i < tm.Month; i++) {
if ( (i == 2) && LEAP_YEAR(tm.Year)) {
seconds += SECS_PER_DAY * 29;
seconds += SECS_PER_DAY * 29UL;
} else {
seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0
seconds += SECS_PER_DAY * (uint32_t)monthDays[i-1]; //monthDay array starts from 0
}
}
seconds+= (tm.Day-1) * SECS_PER_DAY;
seconds+= tm.Hour * 3600;
seconds+= tm.Minute * 60;
seconds+= tm.Hour * 3600UL;
seconds+= tm.Minute * 60UL;
seconds+= tm.Second;
return (time_t)seconds;
@ -516,6 +564,7 @@ String EZtime::getBetween(String &haystack, String before_needle, String after_n
}
time_t EZtime::compileTime(String compile_date /* = __DATE__ */, String compile_time /* = __TIME__ */) {
uint8_t hrs = compile_time.substring(0,2).toInt();
uint8_t min = compile_time.substring(3,5).toInt();
uint8_t sec = compile_time.substring(6).toInt();
@ -523,7 +572,7 @@ time_t EZtime::compileTime(String compile_date /* = __DATE__ */, String compile_
int16_t year = compile_date.substring(7).toInt();
String iterate_month;
for (uint8_t month = 1; month < 13; month++) {
iterate_month = english_months[month - 1];
iterate_month = monthString(month);
if ( iterate_month.substring(0,3) == compile_date.substring(0,3) ) {
return makeTime(hrs, min, sec, day, month, year);
}
@ -594,6 +643,7 @@ bool EZtime::waitForSync(uint16_t timeout /* = 0 */) {
debug(INFO, "Waiting for WiFi ... ");
while (!WiFi.isConnected()) {
if ( timeout && (millis() - start) / 1000 > timeout ) { error(TIMEOUT); return false;};
delay(25);
}
debugln(INFO, "connected");
}
@ -610,14 +660,14 @@ bool EZtime::waitForSync(uint16_t timeout /* = 0 */) {
}
#ifdef ESP32
#ifdef EZTIME_NVS_ENABLE
void EZtime::clearCache() {
Preferences preferences;
preferences.begin("ezTime", false); // read-write
preferences.clear();
preferences.end();
}
#endif // ESP32
#endif // EZTIME_NVS_ENABLE
bool EZtime::timezoneAPI(String location, String &olsen, String &posix) {
@ -690,8 +740,8 @@ String Timezone::getPosix() { return _posix;}
bool Timezone::setLocation(String location, bool force_lookup /* = false */) {
ezTime.debugln(INFO, "Timezone lookup for: " + location);
if (_locked_to_UTC) { ezTime.error(LOCKED_TO_UTC); return false; }
#ifdef ESP32
// Caching only on ESP32
#ifdef EZTIME_NVS_ENABLE
// Caching only on EZTIME_NVS_ENABLE
Preferences preferences;
@ -755,7 +805,7 @@ bool Timezone::setLocation(String location, bool force_lookup /* = false */) {
setPosix(posix);
_olsen = olsen; // Has to happen after setPosix because that sets _olsen to "";
#ifdef ESP32
#ifdef EZTIME_NVS_ENABLE
ezTime.debugln(INFO, "Storing to cache(" + String(cache_number) + "): " + location + ":" + olsen + ":" + String(year()) + ":" + posix);
char key[] = "lookupcache-xx";
key[12] = '0' + (cache_number / 10);
@ -772,8 +822,8 @@ bool Timezone::setLocation(String location, bool force_lookup /* = false */) {
_olsen = hit_olsen; // Has to happen after setPosix because that sets _olsen to "";
return true;
}
#endif // EZTIME_NVS_ENABLE
}
#endif // ESP32
ezTime.error(ezTime.error()); // This throws the last error (as generated by timezoneAPI) again.
return false;
@ -860,7 +910,6 @@ time_t Timezone::now(bool update_last_read /* = true */) {
time_t t;
t = ezTime.now();
if (_tzdata.dst_start_in_utc == _tzdata.dst_end_in_utc) {
t -= _tzdata.std_offset;
} else {
@ -870,9 +919,7 @@ time_t Timezone::now(bool update_last_read /* = true */) {
t -= (t >= _tzdata.dst_end_in_utc && t < _tzdata.dst_start_in_utc) ? _tzdata.dst_offset : _tzdata.std_offset;
}
}
if (update_last_read) _last_read_t = t;
return t;
}
@ -949,14 +996,14 @@ String Timezone::dateTime(time_t t, String format /* = DEFAULT_TIMEFORMAT */) {
out += ezTime.zeropad(tm.Day, 2);
break;
case 'D': // A textual representation of a day, three letters
tmpstr = english_days[tm.Wday - 1];
tmpstr = ezTime.dayString(tm.Wday);
out += tmpstr.substring(0,3);
break;
case 'j': // Day of the month without leading zeros
out += String(tm.Day);
break;
case 'l': // (lowercase L) A full textual representation of the day of the week
out += english_days[tm.Wday - 1];
out += ezTime.dayString(tm.Wday);
break;
case 'N': // ISO-8601 numeric representation of the day of the week. ( 1 = Monday, 7 = Sunday )
tmpint8 = tm.Wday - 1;
@ -983,13 +1030,13 @@ String Timezone::dateTime(time_t t, String format /* = DEFAULT_TIMEFORMAT */) {
out += String(tm.Wday);
break;
case 'F': // A full textual representation of a month, such as January or March
out += english_months[tm.Month - 1];
out += ezTime.monthString(tm.Month);
break;
case 'm': // Numeric representation of a month, with leading zeros
out += ezTime.zeropad(tm.Month, 2);
break;
case 'M': // A short textual representation of a month, three letters
tmpstr = english_months[tm.Month - 1];
tmpstr = ezTime.monthString(tm.Month);
out += tmpstr.substring(0,3);
break;
case 'n': // Numeric representation of a month, without leading zeros
@ -1146,10 +1193,10 @@ uint8_t hourFormat12(time_t t = TIME_NOW) { return (defaultTZ.hour(t) % 12); }
bool isAM(time_t t = TIME_NOW) { return (defaultTZ.hour(t) < 12) ? true : false; }
bool isPM(time_t t = TIME_NOW) { return (defaultTZ.hour(t) >= 12) ? true : false; }
String monthStr(const uint8_t month) { return english_months[month - 1]; }
String monthShortStr(const uint8_t month) { String tmp = english_months[month - 1]; return tmp.substring(0,3); }
String dayStr(const uint8_t day) { return english_days[day - 1]; }
String dayShortStr(const uint8_t day) { String tmp = english_days[day - 1]; return tmp.substring(0,3); }
String monthStr(const uint8_t month) { return ezTime.monthString(month); }
String monthShortStr(const uint8_t month) { String tmp = ezTime.monthString(month); return tmp.substring(0,3); }
String dayStr(const uint8_t day) { return ezTime.dayString(day); }
String dayShortStr(const uint8_t day) { String tmp = ezTime.dayString(day); return tmp.substring(0,3); }
void setTime(time_t t) { defaultTZ.setTime(t); }
void setTime(const uint8_t hr, const uint8_t min, const uint8_t sec, const uint8_t day, const uint8_t month, const uint16_t yr) { defaultTZ.setTime(hr, min, sec, day, month, yr); }

@ -6,12 +6,26 @@
// Compiles in NTP updating and timezoneapi.io fetching
#define EZTIME_NETWORK_ENABLE
// On espressif turn on NVS storage (with "Preferences")
#if defined (ESP32) // This syntax so we can add " || defined (SOMEOTHER"
#define EZTIME_NVS_ENABLE
#endif
#include <Arduino.h>
#ifdef EZTIME_NETWORK_ENABLE
#include <WiFi.h>
#endif // EZTIME_NETWORK_ENABLE
#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc
typedef unsigned long time_t;
#endif
#include <inttypes.h>
#ifndef __AVR__
#include <sys/types.h> // for __time_t_defined, but avr libc lacks sys/types.h
#endif
#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc
typedef unsigned long time_t;
#endif
////////// Error handing
@ -67,11 +81,14 @@ typedef struct {
#define TIME_NOW 0xFFFFFFFF
#define LAST_READ 0xFFFFFFFE
#define NTP_PACKET_SIZE 48
#define NTP_LOCAL_PORT 2342
#define NTP_SERVER "pool.ntp.org"
#define NTP_TIMEOUT 1500 // milliseconds
#define UPDATE_INTERVAL 600 // default update interval in seconds
#define NTP_INTERVAL 600 // default update interval in seconds
#define NTP_RETRY 3 // Retry after this many seconds on failed NTP
#define NTP_STALE_AFTER 3600 // If update due for this many seconds, set timeStatus to timeNeedsSync
// Various date-time formats
#define ATOM "Y-m-d\\TH:i:sP"
@ -88,10 +105,6 @@ typedef struct {
#define W3C "Y-m-d\\TH:i:sP"
#define DEFAULT_TIMEFORMAT RFC850
static const char * english_months[] PROGMEM = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
static const char * english_days[] PROGMEM = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" , "Saturday" };
static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0
class EZtime {
friend class Timezone;
@ -119,6 +132,8 @@ class EZtime {
time_t makeTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t day, uint8_t month, int16_t year);
time_t makeUmpteenthTime(uint8_t hour, uint8_t minute, uint8_t second, uint8_t umpteenth, uint8_t wday, uint8_t month, int16_t year);
time_t compileTime(String compile_date = __DATE__, String compile_time = __TIME__);
String monthString(uint8_t month);
String dayString(uint8_t day);
private:
tzData_t parsePosix(String posix, int16_t year);
@ -145,6 +160,7 @@ class EZtime {
private:
uint16_t _update_interval; // in seconds
time_t _update_due;
uint16_t _update_backoff; // seconds to add to _update_due to retry NTP
uint16_t _ntp_local_port, _ntp_interval, _ntp_max_drift;
String _ntp_server;

Loading…
Cancel
Save