From c2892d78873c45e6df33a2acd18ff410d5ad07bb Mon Sep 17 00:00:00 2001 From: cschwinne Date: Thu, 27 May 2021 02:02:02 +0200 Subject: [PATCH] Add UDP sync of system time --- usermods/RTC/usermod_rtc.h | 2 +- wled00/fcn_declare.h | 1 + wled00/json.cpp | 9 +--- wled00/ntp.cpp | 17 +++++++- wled00/set.cpp | 2 +- wled00/src/dependencies/toki/Toki.h | 32 +++++++++++++-- wled00/udp.cpp | 64 +++++++++++++++++++++-------- 7 files changed, 95 insertions(+), 32 deletions(-) diff --git a/usermods/RTC/usermod_rtc.h b/usermods/RTC/usermod_rtc.h index 782244f6..75d91b31 100644 --- a/usermods/RTC/usermod_rtc.h +++ b/usermods/RTC/usermod_rtc.h @@ -14,7 +14,7 @@ class RTCUsermod : public Usermod { void setup() { time_t rtcTime = RTC.get(); if (rtcTime) { - toki.setTime(rtcTime,0); + toki.setTime(rtcTime,TOKI_NO_MS_ACCURACY,TOKI_TS_RTC); updateLocalTime(); } else { if (!RTC.chipPresent()) disabled = true; //don't waste time if H/W error diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a6312b7b..135d77af 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -138,6 +138,7 @@ void setCountdown(); byte weekdayMondayFirst(); void checkTimers(); void calculateSunriseAndSunset(); +void setTimeFromAPI(uint32_t timein); //overlay.cpp void initCronixie(); diff --git a/wled00/json.cpp b/wled00/json.cpp index 29d2a015..bd0c31e7 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -210,14 +210,7 @@ bool deserializeState(JsonObject root) unsigned long timein = root[F("time")] | UINT32_MAX; //backup time source if NTP not synced if (timein != UINT32_MAX) { - time_t prev = toki.second(); - if (millis() - ntpLastSyncTime > 50000000L) { - toki.setTime(timein,toki.millisecond()); - if (abs(timein - prev) > 60L) { - updateLocalTime(); - calculateSunriseAndSunset(); - } - } + setTimeFromAPI(timein); if (presetsModifiedTime == 0) presetsModifiedTime = timein; } diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 5486ae18..c4f47c8e 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -219,7 +219,7 @@ bool checkNTPResponse() #endif toki.adjust(departed, offset); - toki.setTime(departed); + toki.setTime(departed, TOKI_TS_NTP); #ifdef WLED_DEBUG_NTP 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; +} \ No newline at end of file diff --git a/wled00/set.cpp b/wled00/set.cpp index 698b6fdd..3b3f86aa 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -790,7 +790,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set time (unix timestamp) pos = req.indexOf(F("ST=")); if (pos > 0) { - toki.setTime(getNumVal(&req, pos),toki.millisecond()); + setTimeFromAPI(getNumVal(&req, pos)); } //set countdown goal (unix timestamp) diff --git a/wled00/src/dependencies/toki/Toki.h b/wled00/src/dependencies/toki/Toki.h index bc6cd3d0..8a6892f8 100644 --- a/wled00/src/dependencies/toki/Toki.h +++ b/wled00/src/dependencies/toki/Toki.h @@ -25,7 +25,21 @@ #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 { typedef enum { @@ -42,16 +56,22 @@ class Toki { uint32_t fullSecondMillis = 0; uint32_t unix = 0; TickT tick = TickT::inactive; + uint8_t timeSrc = TOKI_TS_NONE; public: - void setTime(Time t) { + void setTime(Time t, uint8_t timeSource = TOKI_TS_MS) { fullSecondMillis = millis() - t.ms; 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}; - setTime(t); + setTime(t, timeSource); } 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; } + uint8_t getTimeSource() { + return timeSrc; + } + void setTick() { if (tick == TickT::marked) tick = TickT::active; } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index f30358f3..e58bd714 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -4,8 +4,9 @@ * UDP sync notifier / Realtime / Hyperion / TPM2.NET */ -#define WLEDPACKETSIZE 29 +#define WLEDPACKETSIZE 36 #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) { @@ -37,8 +38,8 @@ void notify(byte callMode, bool followUp) //compatibilityVersionByte: //0: old 1: supports white 2: supports secondary color //3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette - //6: supports timebase syncing, 29 byte packet 7: supports tertiary color - udpOut[11] = 7; + //6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet + udpOut[11] = 8; udpOut[12] = colSec[0]; udpOut[13] = colSec[1]; udpOut[14] = colSec[2]; @@ -59,6 +60,18 @@ void notify(byte callMode, bool followUp) udpOut[26] = (t >> 16) & 0xFF; udpOut[27] = (t >> 8) & 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; 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 if (millis() - notificationSentTime < 1000) return; if (udpIn[1] > 199) return; //do not receive custom versions + + //compatibilityVersionByte: + byte version = udpIn[11]; bool someSel = (receiveNotificationBrightness || receiveNotificationColor || receiveNotificationEffects); //apply colors from notification @@ -212,24 +228,17 @@ void handleNotifications() col[0] = udpIn[3]; col[1] = udpIn[4]; 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]; - if (udpIn[11] > 1) + if (version > 1) { colSec[0] = udpIn[12]; colSec[1] = udpIn[13]; colSec[2] = udpIn[14]; colSec[3] = udpIn[15]; } - if (udpIn[11] > 5) - { - 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) + if (version > 6) { strip.setColor(2, udpIn[20], udpIn[21], udpIn[22], udpIn[23]); //tertiary color } @@ -237,15 +246,36 @@ void handleNotifications() } //apply effects from notification - if (udpIn[11] < 200 && (receiveNotificationEffects || !someSel)) + if (version < 200 && (receiveNotificationEffects || !someSel)) { if (udpIn[8] < strip.getModeCount()) effectCurrent = udpIn[8]; effectSpeed = udpIn[9]; - if (udpIn[11] > 2) effectIntensity = udpIn[16]; - if (udpIn[11] > 4 && udpIn[19] < strip.getPaletteCount()) effectPalette = udpIn[19]; + if (version > 2) effectIntensity = udpIn[16]; + 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); }