Add UDP sync of system time

This commit is contained in:
cschwinne 2021-05-27 02:02:02 +02:00
parent 6c8bf090fe
commit c2892d7887
7 changed files with 95 additions and 32 deletions

View File

@ -14,7 +14,7 @@ class RTCUsermod : public Usermod {
void setup() { void setup() {
time_t rtcTime = RTC.get(); time_t rtcTime = RTC.get();
if (rtcTime) { if (rtcTime) {
toki.setTime(rtcTime,0); toki.setTime(rtcTime,TOKI_NO_MS_ACCURACY,TOKI_TS_RTC);
updateLocalTime(); updateLocalTime();
} else { } else {
if (!RTC.chipPresent()) disabled = true; //don't waste time if H/W error if (!RTC.chipPresent()) disabled = true; //don't waste time if H/W error

View File

@ -138,6 +138,7 @@ void setCountdown();
byte weekdayMondayFirst(); byte weekdayMondayFirst();
void checkTimers(); void checkTimers();
void calculateSunriseAndSunset(); void calculateSunriseAndSunset();
void setTimeFromAPI(uint32_t timein);
//overlay.cpp //overlay.cpp
void initCronixie(); void initCronixie();

View File

@ -210,14 +210,7 @@ bool deserializeState(JsonObject root)
unsigned long timein = root[F("time")] | UINT32_MAX; //backup time source if NTP not synced unsigned long timein = root[F("time")] | UINT32_MAX; //backup time source if NTP not synced
if (timein != UINT32_MAX) { if (timein != UINT32_MAX) {
time_t prev = toki.second(); setTimeFromAPI(timein);
if (millis() - ntpLastSyncTime > 50000000L) {
toki.setTime(timein,toki.millisecond());
if (abs(timein - prev) > 60L) {
updateLocalTime();
calculateSunriseAndSunset();
}
}
if (presetsModifiedTime == 0) presetsModifiedTime = timein; if (presetsModifiedTime == 0) presetsModifiedTime = timein;
} }

View File

@ -219,7 +219,7 @@ bool checkNTPResponse()
#endif #endif
toki.adjust(departed, offset); toki.adjust(departed, offset);
toki.setTime(departed); toki.setTime(departed, TOKI_TS_NTP);
#ifdef WLED_DEBUG_NTP #ifdef WLED_DEBUG_NTP
Serial.print("Arrived: "); Serial.print("Arrived: ");
@ -433,3 +433,18 @@ void calculateSunriseAndSunset() {
} }
} }
} }
//time from JSON and HTTP API
void setTimeFromAPI(uint32_t timein) {
if (timein == 0 || timein == UINT32_MAX) return;
time_t prev = toki.second();
//only apply if more accurate or there is a significant difference to the "more accurate" time source
if (toki.getTimeSource() > TOKI_TS_JSON && abs(timein - prev) < 60L) return;
toki.setTime(timein, TOKI_NO_MS_ACCURACY, TOKI_TS_JSON);
if (abs(timein - prev) > 60L) {
updateLocalTime();
calculateSunriseAndSunset();
}
if (presetsModifiedTime == 0) presetsModifiedTime = timein;
}

View File

@ -790,7 +790,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
//set time (unix timestamp) //set time (unix timestamp)
pos = req.indexOf(F("ST=")); pos = req.indexOf(F("ST="));
if (pos > 0) { if (pos > 0) {
toki.setTime(getNumVal(&req, pos),toki.millisecond()); setTimeFromAPI(getNumVal(&req, pos));
} }
//set countdown goal (unix timestamp) //set countdown goal (unix timestamp)

View File

@ -25,7 +25,21 @@
#define YEARS_70 2208988800UL #define YEARS_70 2208988800UL
//3:13 24.05.2012 ==> 2021-4-29, 06:41:37. #define TOKI_NO_MS_ACCURACY 1000
//Time source. Sub-100 is second accuracy, higher ms accuracy. Higher value generally means more accurate
#define TOKI_TS_NONE 0 //unsynced (e.g. just after boot)
#define TOKI_TS_UDP 5 //synced via UDP from an instance whose time source is unsynced
#define TOKI_TS_BAD 10 //synced from a time source less than about +- 2s accurate
#define TOKI_TS_UDP_SEC 20 //synced via UDP from an instance whose time source is set from RTC/JSON
#define TOKI_TS_SEC 40 //general second-accurate time source
#define TOKI_TS_RTC 60 //second-accurate real time clock
#define TOKI_TS_JSON 70 //synced second-accurate from a client via JSON-API
#define TOKI_TS_UDP_NTP 110 //synced via UDP from an instance whose time source is NTP
#define TOKI_TS_MS 120 //general better-than-second accuracy time source
#define TOKI_TS_NTP 150 //NTP time, simple round trip estimation. Depending on network, mostly +- 50ms accurate
#define TOKI_TS_NTP_P 170 //NTP time with multi-step sync, higher accuracy. Not implemented in WLED
class Toki { class Toki {
typedef enum { typedef enum {
@ -42,16 +56,22 @@ class Toki {
uint32_t fullSecondMillis = 0; uint32_t fullSecondMillis = 0;
uint32_t unix = 0; uint32_t unix = 0;
TickT tick = TickT::inactive; TickT tick = TickT::inactive;
uint8_t timeSrc = TOKI_TS_NONE;
public: public:
void setTime(Time t) { void setTime(Time t, uint8_t timeSource = TOKI_TS_MS) {
fullSecondMillis = millis() - t.ms; fullSecondMillis = millis() - t.ms;
unix = t.sec; unix = t.sec;
timeSrc = timeSource;
} }
void setTime(uint32_t sec, uint16_t ms) { void setTime(uint32_t sec, uint16_t ms=TOKI_NO_MS_ACCURACY, uint8_t timeSource = TOKI_TS_MS) {
if (ms >= TOKI_NO_MS_ACCURACY) {
ms = millisecond(); //just keep current ms if not provided
if (timeSource > 99) timeSource = TOKI_TS_SEC; //lies
}
Time t = {sec, ms}; Time t = {sec, ms};
setTime(t); setTime(t, timeSource);
} }
Time fromNTP(byte *timestamp) { //ntp timestamp is 8 bytes, 4 bytes second and 4 bytes sub-second fraction Time fromNTP(byte *timestamp) { //ntp timestamp is 8 bytes, 4 bytes second and 4 bytes sub-second fraction
@ -110,6 +130,10 @@ class Toki {
return t; return t;
} }
uint8_t getTimeSource() {
return timeSrc;
}
void setTick() { void setTick() {
if (tick == TickT::marked) tick = TickT::active; if (tick == TickT::marked) tick = TickT::active;
} }

View File

@ -4,8 +4,9 @@
* UDP sync notifier / Realtime / Hyperion / TPM2.NET * UDP sync notifier / Realtime / Hyperion / TPM2.NET
*/ */
#define WLEDPACKETSIZE 29 #define WLEDPACKETSIZE 36
#define UDP_IN_MAXSIZE 1472 #define UDP_IN_MAXSIZE 1472
#define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times
void notify(byte callMode, bool followUp) void notify(byte callMode, bool followUp)
{ {
@ -37,8 +38,8 @@ void notify(byte callMode, bool followUp)
//compatibilityVersionByte: //compatibilityVersionByte:
//0: old 1: supports white 2: supports secondary color //0: old 1: supports white 2: supports secondary color
//3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette //3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette
//6: supports timebase syncing, 29 byte packet 7: supports tertiary color //6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet
udpOut[11] = 7; udpOut[11] = 8;
udpOut[12] = colSec[0]; udpOut[12] = colSec[0];
udpOut[13] = colSec[1]; udpOut[13] = colSec[1];
udpOut[14] = colSec[2]; udpOut[14] = colSec[2];
@ -59,6 +60,18 @@ void notify(byte callMode, bool followUp)
udpOut[26] = (t >> 16) & 0xFF; udpOut[26] = (t >> 16) & 0xFF;
udpOut[27] = (t >> 8) & 0xFF; udpOut[27] = (t >> 8) & 0xFF;
udpOut[28] = (t >> 0) & 0xFF; udpOut[28] = (t >> 0) & 0xFF;
//sync system time
udpOut[29] = toki.getTimeSource();
Toki::Time tm = toki.getTime();
uint32_t unix = tm.sec;
udpOut[30] = (unix >> 24) & 0xFF;
udpOut[31] = (unix >> 16) & 0xFF;
udpOut[32] = (unix >> 8) & 0xFF;
udpOut[33] = (unix >> 0) & 0xFF;
uint16_t ms = tm.ms;
udpOut[34] = (ms >> 8) & 0xFF;
udpOut[35] = (ms >> 0) & 0xFF;
IPAddress broadcastIp; IPAddress broadcastIp;
broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP()); broadcastIp = ~uint32_t(Network.subnetMask()) | uint32_t(Network.gatewayIP());
@ -204,6 +217,9 @@ void handleNotifications()
//ignore notification if received within a second after sending a notification ourselves //ignore notification if received within a second after sending a notification ourselves
if (millis() - notificationSentTime < 1000) return; if (millis() - notificationSentTime < 1000) return;
if (udpIn[1] > 199) return; //do not receive custom versions if (udpIn[1] > 199) return; //do not receive custom versions
//compatibilityVersionByte:
byte version = udpIn[11];
bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects);
//apply colors from notification //apply colors from notification
@ -212,24 +228,17 @@ void handleNotifications()
col[0] = udpIn[3]; col[0] = udpIn[3];
col[1] = udpIn[4]; col[1] = udpIn[4];
col[2] = udpIn[5]; col[2] = udpIn[5];
if (udpIn[11] > 0) //sending module's white val is intended if (version > 0) //sending module's white val is intended
{ {
col[3] = udpIn[10]; col[3] = udpIn[10];
if (udpIn[11] > 1) if (version > 1)
{ {
colSec[0] = udpIn[12]; colSec[0] = udpIn[12];
colSec[1] = udpIn[13]; colSec[1] = udpIn[13];
colSec[2] = udpIn[14]; colSec[2] = udpIn[14];
colSec[3] = udpIn[15]; colSec[3] = udpIn[15];
} }
if (udpIn[11] > 5) if (version > 6)
{
uint32_t t = (udpIn[25] << 24) | (udpIn[26] << 16) | (udpIn[27] << 8) | (udpIn[28]);
t += 2;
t -= millis();
strip.timebase = t;
}
if (udpIn[11] > 6)
{ {
strip.setColor(2, udpIn[20], udpIn[21], udpIn[22], udpIn[23]); //tertiary color strip.setColor(2, udpIn[20], udpIn[21], udpIn[22], udpIn[23]); //tertiary color
} }
@ -237,15 +246,36 @@ void handleNotifications()
} }
//apply effects from notification //apply effects from notification
if (udpIn[11] < 200 && (receiveNotificationEffects || !someSel)) if (version < 200 && (receiveNotificationEffects || !someSel))
{ {
if (udpIn[8] < strip.getModeCount()) effectCurrent = udpIn[8]; if (udpIn[8] < strip.getModeCount()) effectCurrent = udpIn[8];
effectSpeed = udpIn[9]; effectSpeed = udpIn[9];
if (udpIn[11] > 2) effectIntensity = udpIn[16]; if (version > 2) effectIntensity = udpIn[16];
if (udpIn[11] > 4 && udpIn[19] < strip.getPaletteCount()) effectPalette = udpIn[19]; if (version > 4 && udpIn[19] < strip.getPaletteCount()) effectPalette = udpIn[19];
if (version > 5)
{
uint32_t t = (udpIn[25] << 24) | (udpIn[26] << 16) | (udpIn[27] << 8) | (udpIn[28]);
t += PRESUMED_NETWORK_DELAY; //adjust trivially for network delay
t -= millis();
strip.timebase = t;
}
}
//adjust system time, but only if sender is more accurate than self
if (version > 7 && udpIn[29] > toki.getTimeSource())
{
Toki::Time tm;
tm.sec = (udpIn[30] << 24) | (udpIn[31] << 16) | (udpIn[32] << 8) | (udpIn[33]);
tm.ms = (udpIn[34] << 8) | (udpIn[35]);
toki.adjust(tm, PRESUMED_NETWORK_DELAY); //adjust trivially for network delay
uint8_t ts = TOKI_TS_UDP;
if (udpIn[29] > 99) ts = TOKI_TS_UDP_NTP;
else if (udpIn[29] >= TOKI_TS_SEC) ts = TOKI_TS_UDP_SEC;
toki.setTime(tm, ts);
//TODO: even receive this if own time source is better, to offset network delay from timebase
} }
if (udpIn[11] > 3) if (version > 3)
{ {
transitionDelayTemp = ((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00); transitionDelayTemp = ((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00);
} }