From b455f432d5eeec73b5c458b9e8a37d6d396d8e63 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Mon, 24 May 2021 14:34:03 +0200 Subject: [PATCH 1/8] Toki 1st experiment --- tools/WLED_ESP32_4MB_1MB_FS.csv | 6 + .../EleksTube_IPS/usermod_elekstube_ips.h | 10 +- wled00/ntp.cpp | 26 ++-- wled00/overlay.cpp | 7 +- wled00/src/dependencies/time/Time.cpp | 66 ++------- wled00/src/dependencies/time/TimeLib.h | 14 +- wled00/src/dependencies/toki/Toki.h | 128 ++++++++++++++++++ wled00/wled.cpp | 3 + wled00/wled.h | 2 + 9 files changed, 181 insertions(+), 81 deletions(-) create mode 100644 tools/WLED_ESP32_4MB_1MB_FS.csv create mode 100644 wled00/src/dependencies/toki/Toki.h diff --git a/tools/WLED_ESP32_4MB_1MB_FS.csv b/tools/WLED_ESP32_4MB_1MB_FS.csv new file mode 100644 index 00000000..09455c6a --- /dev/null +++ b/tools/WLED_ESP32_4MB_1MB_FS.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x17B000, +app1, app, ota_1, 0x18B000,0x17B000, +spiffs, data, spiffs, 0x306000,0x0FA000, \ No newline at end of file diff --git a/usermods/EleksTube_IPS/usermod_elekstube_ips.h b/usermods/EleksTube_IPS/usermod_elekstube_ips.h index 4f820c26..f2f03466 100644 --- a/usermods/EleksTube_IPS/usermod_elekstube_ips.h +++ b/usermods/EleksTube_IPS/usermod_elekstube_ips.h @@ -41,18 +41,14 @@ class ElekstubeIPSUsermod : public Usermod { tfts.begin(); tfts.fillScreen(TFT_BLACK); tfts.setTextColor(TFT_WHITE, TFT_BLACK); - tfts.setCursor(0, 0, 2); + tfts.setCursor(0, 100, 2); tfts.println(""); } void loop() { - if (lastTime == 0) { - tfts.fillScreen(TFT_BLACK); - updateClockDisplay(TFTs::force); - } - if (millis() - lastTime > 100) { + if (toki.isTick()) { + updateLocalTime(); updateClockDisplay(); - lastTime = millis(); } } diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 449ad044..dd823486 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -183,20 +183,30 @@ bool checkNTPResponse() { int cb = ntpUdp.parsePacket(); if (cb) { + uint32_t ntpPacketReceivedTime = millis(); DEBUG_PRINT(F("NTP recv, l=")); DEBUG_PRINTLN(cb); byte pbuf[NTP_PACKET_SIZE]; ntpUdp.read(pbuf, NTP_PACKET_SIZE); // read the packet into the buffer - unsigned long highWord = word(pbuf[40], pbuf[41]); - unsigned long lowWord = word(pbuf[42], pbuf[43]); - if (highWord == 0 && lowWord == 0) return false; - - unsigned long secsSince1900 = highWord << 16 | lowWord; + Toki::Time arrived = toki.fromNTP(pbuf + 32); + Toki::Time departed = toki.fromNTP(pbuf + 40); + //basic half roundtrip estimation + uint32_t offset = (ntpPacketReceivedTime - ntpPacketSentTime - toki.msDifference(arrived, departed)) >> 1; + offset += millis() - ntpPacketReceivedTime +1; + toki.adjust(departed, offset); + toki.setTime(departed); + Serial.print("Roundtrip: "); + Serial.println(ntpPacketReceivedTime - ntpPacketSentTime); + Serial.print("Offset: "); + Serial.println(offset); + Serial.print("Time: "); + toki.printTime(toki.getTime()); DEBUG_PRINT(F("Unix time = ")); - unsigned long epoch = secsSince1900 - 2208988799UL; //subtract 70 years -1sec (on avg. more precision) - setTime(epoch); + uint32_t epoch = toki.second(); + if (epoch == 0) return false; + setTime(epoch); //legacy DEBUG_PRINTLN(epoch); if (countdownTime - now() > 0) countdownOverTriggered = false; // if time changed re-calculate sunrise/sunset @@ -210,7 +220,7 @@ bool checkNTPResponse() void updateLocalTime() { if (currentTimezone != tzCurrent) updateTimezone(); - unsigned long tmc = now()+ utcOffsetSecs; + unsigned long tmc = toki.second()+ utcOffsetSecs; localTime = tz->toLocal(tmc); } diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 419d82a7..e8f53ea2 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -21,13 +21,16 @@ void initCronixie() void handleOverlays() { - if (millis() - overlayRefreshedTime > overlayRefreshMs) + if (toki.isTick()) { initCronixie(); updateLocalTime(); checkTimers(); checkCountdown(); - if (overlayCurrent == 3) _overlayCronixie();//Diamex cronixie clock kit + if (overlayCurrent == 3) { + _overlayCronixie();//Diamex cronixie clock kit + strip.trigger(); + } overlayRefreshedTime = millis(); } } diff --git a/wled00/src/dependencies/time/Time.cpp b/wled00/src/dependencies/time/Time.cpp index 6b1df7c5..326ce85c 100644 --- a/wled00/src/dependencies/time/Time.cpp +++ b/wled00/src/dependencies/time/Time.cpp @@ -37,7 +37,6 @@ static tmElements_t tm; // a cache of time elements static time_t cacheTime; // the time the cache was updated -static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds void refreshCache(time_t t) { if (t != cacheTime) { @@ -234,18 +233,9 @@ time_t makeTime(tmElements_t &tm){ /*=====================================================*/ /* Low level system time functions */ -static uint32_t sysTime = 0; +static uint32_t sysTime = 0; //seconds +static uint16_t sysMillis = 0; static uint32_t prevMillis = 0; -static uint32_t nextSyncTime = 0; -static timeStatus_t Status = timeNotSet; - -getExternalTime getTimePtr; // pointer to external sync function -//setExternalTime setTimePtr; // not used in this version - -#ifdef TIME_DRIFT_INFO // define this to get drift data -time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync -#endif - time_t now() { // calculate number of seconds passed since last call to now() @@ -253,33 +243,18 @@ time_t now() { // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference sysTime++; prevMillis += 1000; -#ifdef TIME_DRIFT_INFO - sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift -#endif } - if (nextSyncTime <= sysTime) { - if (getTimePtr != 0) { - time_t t = getTimePtr(); - if (t != 0) { - setTime(t); - } else { - nextSyncTime = sysTime + syncInterval; - Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; - } - } - } + return (time_t)sysTime; } -void setTime(time_t t) { -#ifdef TIME_DRIFT_INFO - if(sysUnsyncedTime == 0) - sysUnsyncedTime = t; // store the time of the first call to set a valid Time -#endif +uint16_t millisecond() { // the millisecond (0-999) now + return (sysMillis - millis()) % 1000; +} - sysTime = (uint32_t)t; - nextSyncTime = (uint32_t)t + syncInterval; - Status = timeSet; +void setTime(time_t t, uint16_t ms) { + sysTime = (uint32_t)t; + sysMillis = ms; prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) } @@ -299,27 +274,10 @@ time_t getUnixTime(int hr,int min,int sec,int dy, int mnth, int yr){ return makeTime(tm); } -void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ - setTime(getUnixTime(hr,min,sec,dy,mnth,yr)); +void setTime(int hr,int min,int sec,int dy, int mnth, int yr, uint16_t ms){ + setTime(getUnixTime(hr,min,sec,dy,mnth,yr), ms); } void adjustTime(long adjustment) { sysTime += adjustment; -} - -// indicates if time has been set and recently synchronized -timeStatus_t timeStatus() { - now(); // required to actually update the status - return Status; -} - -void setSyncProvider( getExternalTime getTimeFunction){ - getTimePtr = getTimeFunction; - nextSyncTime = sysTime; - now(); // this will sync the clock -} - -void setSyncInterval(time_t interval){ // set the number of seconds between re-sync - syncInterval = (uint32_t)interval; - nextSyncTime = sysTime + syncInterval; -} +} \ No newline at end of file diff --git a/wled00/src/dependencies/time/TimeLib.h b/wled00/src/dependencies/time/TimeLib.h index d8ed7391..1064638a 100644 --- a/wled00/src/dependencies/time/TimeLib.h +++ b/wled00/src/dependencies/time/TimeLib.h @@ -31,8 +31,6 @@ typedef unsigned long time_t; // but at least this hack lets us define C++ functions as intended. Hopefully // nothing too terrible will result from overriding the C library header?! extern "C++" { -typedef enum {timeNotSet, timeNeedsSync, timeSet -} timeStatus_t ; typedef enum { dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday @@ -116,10 +114,11 @@ int month(); // the month now (Jan is month 1) int month(time_t t); // the month for the given time int year(); // the full four digit year: (2009, 2010 etc) int year(time_t t); // the year for the given time +uint16_t millisecond(); // the millisecond now time_t now(); // return the current time as seconds since Jan 1 1970 -void setTime(time_t t); -void setTime(int hr,int min,int sec,int day, int month, int yr); +void setTime(time_t t, uint16_t ms = 0); +void setTime(int hr,int min,int sec,int day, int month, int yr, uint16_t ms = 0); time_t getUnixTime(int hr,int min,int sec,int day, int month, int yr); //added by Aircoookie to get epoch time void adjustTime(long adjustment); @@ -129,13 +128,8 @@ char* monthStr(uint8_t month); char* dayStr(uint8_t day); char* monthShortStr(uint8_t month); char* dayShortStr(uint8_t day); - -/* time sync functions */ -timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized -void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider -void setSyncInterval(time_t interval); // set the number of seconds between re-sync -/* low level functions to convert to and from system time */ +/* low level functions to convert to and from system time */ void breakTime(time_t time, tmElements_t &tm); // break time_t into elements time_t makeTime(tmElements_t &tm); // convert time elements into time_t diff --git a/wled00/src/dependencies/toki/Toki.h b/wled00/src/dependencies/toki/Toki.h new file mode 100644 index 00000000..6ab404c2 --- /dev/null +++ b/wled00/src/dependencies/toki/Toki.h @@ -0,0 +1,128 @@ +/* + Toki.h - Minimal millisecond accurate timekeeping. + + LICENSE + The MIT License (MIT) + Copyright (c) 2021 Christian Schwinne + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include + +#define YEARS_70 2208988800UL + +//3:13 24.05.2012 ==> 2021-4-29, 06:41:37. + +class Toki { + typedef enum { + inactive, marked, active + } TickT; + + public: + typedef struct { + uint32_t sec; + uint16_t ms; + } Time; + + private: + uint32_t fullSecondMillis = 0; + uint32_t unix = 0; + TickT tick = TickT::inactive; + + public: + void setTime(Time t) { + fullSecondMillis = millis() - t.ms; + unix = t.sec; + } + + void setTime(uint32_t sec, uint16_t ms) { + Time t = {sec, ms}; + setTime(t); + } + + Time fromNTP(byte *timestamp) { //ntp timestamp is 8 bytes, 4 bytes second and 4 bytes sub-second fraction + unsigned long highWord = word(timestamp[0], timestamp[1]); + unsigned long lowWord = word(timestamp[2], timestamp[3]); + + unsigned long unix = highWord << 16 | lowWord; + if (!unix) return {0,0}; + unix -= YEARS_70; //NTP begins 1900, Unix 1970 + + unsigned long frac = word(timestamp[5], timestamp[6]); //65536ths of a second + frac = (frac*1000) >> 16; //convert to ms + return {unix, (uint16_t)frac}; + } + + uint16_t millisecond() { + uint32_t ms = millis() - fullSecondMillis; + while (ms > 999) { + ms -= 1000; + fullSecondMillis += 1000; + unix++; + if (tick == TickT::inactive) tick = TickT::marked; //marked, will be active on next loop + } + return ms; + } + + uint32_t second() { + millisecond(); + return unix; + } + + uint32_t msDifference(Time &t0, Time &t1) { + bool t1BiggerSec = (t1.sec > t0.sec); + uint32_t secDiff = (t1BiggerSec) ? t1.sec - t0.sec : t0.sec - t1.sec; + uint32_t t0ms = t0.ms, t1ms = t1.ms; + if (t1BiggerSec) t1ms += secDiff; + else t0ms += secDiff; + uint32_t msDiff = (t1ms > t0ms) ? t1ms - t0ms : t0ms - t1ms; + return msDiff; + } + + void adjust(Time&t, int32_t offset) { + int32_t secs = offset /1000; + int32_t ms = offset - secs*1000; + t.sec += offset /1000; + int32_t nms = t.ms + ms; + if (nms > 1000) {nms -= 1000; t.sec++;} + if (nms < 0) {nms += 1000; t.sec--;} + t.ms += nms; + } + + Time getTime() { + Time t; + t.ms = millisecond(); + t.sec = unix; + return t; + } + + void setTick() { + if (tick == TickT::marked) tick = TickT::active; + } + + void resetTick() { + tick = TickT::inactive; + } + + bool isTick() { + return (tick == TickT::active); + } + + void printTime(const Time& t) { + Serial.printf_P(PSTR("%u,%03u\n"),t.sec,t.ms); + } +}; \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index df17a180..66a2c020 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -177,6 +177,8 @@ void WiFiEvent(WiFiEvent_t event) void WLED::loop() { + toki.millisecond(); + toki.setTick(); handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too handleConnection(); handleSerial(); @@ -287,6 +289,7 @@ void WLED::loop() } loops++; #endif // WLED_DEBUG + toki.resetTick(); } void WLED::setup() diff --git a/wled00/wled.h b/wled00/wled.h index 33d7ab22..db046201 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -90,6 +90,7 @@ #include #include "src/dependencies/time/TimeLib.h" #include "src/dependencies/timezone/Timezone.h" +#include "src/dependencies/toki/Toki.h" #ifndef WLED_DISABLE_ALEXA #define ESPALEXA_ASYNC @@ -516,6 +517,7 @@ WLED_GLOBAL float longitude _INIT(0.0); WLED_GLOBAL float latitude _INIT(0.0); WLED_GLOBAL time_t sunrise _INIT(0); WLED_GLOBAL time_t sunset _INIT(0); +WLED_GLOBAL Toki toki _INIT(Toki()); // Temp buffer WLED_GLOBAL char* obuf; From 852f758be3ef92594819f09d11d2353a18991db4 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Mon, 24 May 2021 19:05:34 +0200 Subject: [PATCH 2/8] Subsecond accuracy NTP --- platformio.ini | 1 + usermods/EleksTube_IPS/usermod_elekstube_ips.h | 7 ++++--- wled00/ntp.cpp | 13 +++++++++---- wled00/src/dependencies/toki/Toki.h | 6 +++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index d5c936f1..183a93c2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -481,6 +481,7 @@ build_flags = ${common.build_flags_esp8266} board = esp32dev platform = espressif32@3.2 upload_speed = 921600 +upload_port = COM8 lib_deps = ${env.lib_deps} TFT_eSPI build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED diff --git a/usermods/EleksTube_IPS/usermod_elekstube_ips.h b/usermods/EleksTube_IPS/usermod_elekstube_ips.h index f2f03466..f2ce8eb0 100644 --- a/usermods/EleksTube_IPS/usermod_elekstube_ips.h +++ b/usermods/EleksTube_IPS/usermod_elekstube_ips.h @@ -40,9 +40,10 @@ class ElekstubeIPSUsermod : public Usermod { void setup() { tfts.begin(); tfts.fillScreen(TFT_BLACK); - tfts.setTextColor(TFT_WHITE, TFT_BLACK); - tfts.setCursor(0, 100, 2); - tfts.println(""); + + for (int8_t i = 5; i >= 0; i--) { + tfts.setDigit(i, 255, TFTs::force); //turn all off + } } void loop() { diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index dd823486..f61e00af 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -192,16 +192,21 @@ bool checkNTPResponse() Toki::Time arrived = toki.fromNTP(pbuf + 32); Toki::Time departed = toki.fromNTP(pbuf + 40); //basic half roundtrip estimation - uint32_t offset = (ntpPacketReceivedTime - ntpPacketSentTime - toki.msDifference(arrived, departed)) >> 1; - offset += millis() - ntpPacketReceivedTime +1; + uint32_t serverDelay = toki.msDifference(arrived, departed); + uint32_t offset = (ntpPacketReceivedTime - ntpPacketSentTime - serverDelay) >> 1; + toki.printTime(departed); toki.adjust(departed, offset); toki.setTime(departed); + Serial.print("Arrived: "); + toki.printTime(arrived); + Serial.print("Time: "); + toki.printTime(departed); Serial.print("Roundtrip: "); Serial.println(ntpPacketReceivedTime - ntpPacketSentTime); Serial.print("Offset: "); Serial.println(offset); - Serial.print("Time: "); - toki.printTime(toki.getTime()); + Serial.print("Serverdelay: "); + Serial.println(serverDelay); DEBUG_PRINT(F("Unix time = ")); uint32_t epoch = toki.second(); diff --git a/wled00/src/dependencies/toki/Toki.h b/wled00/src/dependencies/toki/Toki.h index 6ab404c2..bc6cd3d0 100644 --- a/wled00/src/dependencies/toki/Toki.h +++ b/wled00/src/dependencies/toki/Toki.h @@ -62,7 +62,7 @@ class Toki { if (!unix) return {0,0}; unix -= YEARS_70; //NTP begins 1900, Unix 1970 - unsigned long frac = word(timestamp[5], timestamp[6]); //65536ths of a second + unsigned long frac = word(timestamp[4], timestamp[5]); //65536ths of a second frac = (frac*1000) >> 16; //convert to ms return {unix, (uint16_t)frac}; } @@ -96,11 +96,11 @@ class Toki { void adjust(Time&t, int32_t offset) { int32_t secs = offset /1000; int32_t ms = offset - secs*1000; - t.sec += offset /1000; + t.sec += secs; int32_t nms = t.ms + ms; if (nms > 1000) {nms -= 1000; t.sec++;} if (nms < 0) {nms += 1000; t.sec--;} - t.ms += nms; + t.ms = nms; } Time getTime() { From 8431d0bd5cad0abdd7ede90bf97c37daf524fdd5 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Tue, 25 May 2021 09:59:19 +0200 Subject: [PATCH 3/8] Replace Time with Toki --- platformio.ini | 1 - usermods/RTC/usermod_rtc.h | 8 +- .../UserMod_SunRiseAndSet.h | 2 +- wled00/json.cpp | 6 +- wled00/ntp.cpp | 82 ++++++++++--------- wled00/overlay.cpp | 4 +- wled00/presets.cpp | 4 +- wled00/set.cpp | 4 +- wled00/src/dependencies/time/Readme.txt | 43 ++-------- wled00/src/dependencies/time/Time.cpp | 76 +---------------- wled00/src/dependencies/time/TimeLib.h | 17 +--- wled00/wled.cpp | 2 +- 12 files changed, 65 insertions(+), 184 deletions(-) diff --git a/platformio.ini b/platformio.ini index 183a93c2..d5c936f1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -481,7 +481,6 @@ build_flags = ${common.build_flags_esp8266} board = esp32dev platform = espressif32@3.2 upload_speed = 921600 -upload_port = COM8 lib_deps = ${env.lib_deps} TFT_eSPI build_flags = ${common.build_flags_esp32} -D WLED_DISABLE_BROWNOUT_DET -D WLED_DISABLE_INFRARED diff --git a/usermods/RTC/usermod_rtc.h b/usermods/RTC/usermod_rtc.h index bf0047a7..782244f6 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) { - setTime(rtcTime); + toki.setTime(rtcTime,0); updateLocalTime(); } else { if (!RTC.chipPresent()) disabled = true; //don't waste time if H/W error @@ -22,11 +22,9 @@ class RTCUsermod : public Usermod { } void loop() { - if (!disabled && millis() - lastTime > 500) { - time_t t = now(); + if (!disabled && toki.isTick()) { + time_t t = toki.second(); if (t != RTC.get()) RTC.set(t); //set RTC to NTP/UI-provided value - - lastTime = millis(); } } diff --git a/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h b/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h index 62176ce9..ef1bb37e 100644 --- a/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h +++ b/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h @@ -85,7 +85,7 @@ public: if (m_pD2D && (999000000L != ntpLastSyncTime)) { // to prevent needing to import all the timezone stuff from other modules, work completely in UTC - time_t timeUTC = now(); + time_t timeUTC = toki.second(); tmElements_t tmNow; breakTime(timeUTC, tmNow); int nCurMinute = tmNow.Minute; diff --git a/wled00/json.cpp b/wled00/json.cpp index a2cf126d..29d2a015 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -210,10 +210,10 @@ 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 = now(); + time_t prev = toki.second(); if (millis() - ntpLastSyncTime > 50000000L) { - setTime(timein); - if (abs(now() - prev) > 60L) { + toki.setTime(timein,toki.millisecond()); + if (abs(timein - prev) > 60L) { updateLocalTime(); calculateSunriseAndSunset(); } diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index f61e00af..2bfae793 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -5,6 +5,8 @@ /* * Acquires time from NTP server */ +//#define WLED_DEBUG_NTP + Timezone* tz; #define TZ_UTC 0 @@ -182,44 +184,46 @@ void sendNTPPacket() bool checkNTPResponse() { int cb = ntpUdp.parsePacket(); - if (cb) { - uint32_t ntpPacketReceivedTime = millis(); - DEBUG_PRINT(F("NTP recv, l=")); - DEBUG_PRINTLN(cb); - byte pbuf[NTP_PACKET_SIZE]; - ntpUdp.read(pbuf, NTP_PACKET_SIZE); // read the packet into the buffer + if (!cb) return false; - Toki::Time arrived = toki.fromNTP(pbuf + 32); - Toki::Time departed = toki.fromNTP(pbuf + 40); - //basic half roundtrip estimation - uint32_t serverDelay = toki.msDifference(arrived, departed); - uint32_t offset = (ntpPacketReceivedTime - ntpPacketSentTime - serverDelay) >> 1; - toki.printTime(departed); - toki.adjust(departed, offset); - toki.setTime(departed); - Serial.print("Arrived: "); - toki.printTime(arrived); - Serial.print("Time: "); - toki.printTime(departed); - Serial.print("Roundtrip: "); - Serial.println(ntpPacketReceivedTime - ntpPacketSentTime); - Serial.print("Offset: "); - Serial.println(offset); - Serial.print("Serverdelay: "); - Serial.println(serverDelay); - - DEBUG_PRINT(F("Unix time = ")); - uint32_t epoch = toki.second(); - if (epoch == 0) return false; - setTime(epoch); //legacy - DEBUG_PRINTLN(epoch); - if (countdownTime - now() > 0) countdownOverTriggered = false; - // if time changed re-calculate sunrise/sunset - updateLocalTime(); - calculateSunriseAndSunset(); - return true; - } - return false; + uint32_t ntpPacketReceivedTime = millis(); + DEBUG_PRINT(F("NTP recv, l=")); + DEBUG_PRINTLN(cb); + byte pbuf[NTP_PACKET_SIZE]; + ntpUdp.read(pbuf, NTP_PACKET_SIZE); // read the packet into the buffer + + Toki::Time arrived = toki.fromNTP(pbuf + 32); + Toki::Time departed = toki.fromNTP(pbuf + 40); + if (departed.sec == 0) return false; + //basic half roundtrip estimation + uint32_t serverDelay = toki.msDifference(arrived, departed); + uint32_t offset = (ntpPacketReceivedTime - ntpPacketSentTime - serverDelay) >> 1; + #ifdef WLED_DEBUG_NTP + //the time the packet departed the NTP server + toki.printTime(departed); + #endif + + toki.adjust(departed, offset); + toki.setTime(departed); + + #ifdef WLED_DEBUG_NTP + Serial.print("Arrived: "); + toki.printTime(arrived); + Serial.print("Time: "); + toki.printTime(departed); + Serial.print("Roundtrip: "); + Serial.println(ntpPacketReceivedTime - ntpPacketSentTime); + Serial.print("Offset: "); + Serial.println(offset); + Serial.print("Serverdelay: "); + Serial.println(serverDelay); + #endif + + if (countdownTime - toki.second() > 0) countdownOverTriggered = false; + // if time changed re-calculate sunrise/sunset + updateLocalTime(); + calculateSunriseAndSunset(); + return true; } void updateLocalTime() @@ -249,13 +253,13 @@ void setCountdown() { if (currentTimezone != tzCurrent) updateTimezone(); countdownTime = tz->toUTC(getUnixTime(countdownHour, countdownMin, countdownSec, countdownDay, countdownMonth, countdownYear)); - if (countdownTime - now() > 0) countdownOverTriggered = false; + if (countdownTime - toki.second() > 0) countdownOverTriggered = false; } //returns true if countdown just over bool checkCountdown() { - unsigned long n = now(); + unsigned long n = toki.second(); if (countdownMode) localTime = countdownTime - n + utcOffsetSecs; if (n > countdownTime) { if (countdownMode) localTime = n - countdownTime + utcOffsetSecs; diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index e8f53ea2..f852033a 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -82,9 +82,9 @@ void _overlayAnalogClock() void _overlayAnalogCountdown() { - if ((unsigned long)now() < countdownTime) + if ((unsigned long)toki.second() < countdownTime) { - long diff = countdownTime - now(); + long diff = countdownTime - toki.second(); double pval = 60; if (diff > 31557600L) //display in years if more than 365 days { diff --git a/wled00/presets.cpp b/wled00/presets.cpp index a4635bca..c3c15afc 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -69,13 +69,13 @@ void savePreset(byte index, bool persist, const char* pname, JsonObject saveobj) writeObjectToFileUsingId("/presets.json", index, fileDoc); } - presetsModifiedTime = now(); //unix time + presetsModifiedTime = toki.second(); //unix time updateFSInfo(); } void deletePreset(byte index) { StaticJsonDocument<24> empty; writeObjectToFileUsingId("/presets.json", index, &empty); - presetsModifiedTime = now(); //unix time + presetsModifiedTime = toki.second(); //unix time updateFSInfo(); } \ No newline at end of file diff --git a/wled00/set.cpp b/wled00/set.cpp index ee6e102a..89c63e9c 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -790,14 +790,14 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set time (unix timestamp) pos = req.indexOf(F("ST=")); if (pos > 0) { - setTime(getNumVal(&req, pos)); + toki.setTime(getNumVal(&req, pos),toki.millisecond()); } //set countdown goal (unix timestamp) pos = req.indexOf(F("CT=")); if (pos > 0) { countdownTime = getNumVal(&req, pos); - if (countdownTime - now() > 0) countdownOverTriggered = false; + if (countdownTime - toki.second() > 0) countdownOverTriggered = false; } pos = req.indexOf(F("LO=")); diff --git a/wled00/src/dependencies/time/Readme.txt b/wled00/src/dependencies/time/Readme.txt index 234242ab..8982657d 100644 --- a/wled00/src/dependencies/time/Readme.txt +++ b/wled00/src/dependencies/time/Readme.txt @@ -1,5 +1,9 @@ Readme file for Arduino Time Library +! MODIFIED DISTRIBUTION FOR WLED +All timekeeping functions are removed, only conversion functions used. +Please see https://github.com/PaulStoffregen/Time for the full, original library + Time is a library that provides timekeeping functionality for Arduino. The code is derived from the Playground DateTime library but is updated @@ -14,26 +18,10 @@ for time synchronization. The functions available in the library include: -hour(); // the hour now (0-23) -minute(); // the minute now (0-59) -second(); // the second now (0-59) -day(); // the day now (1-31) -weekday(); // day of the week (1-7), Sunday is day 1 -month(); // the month now (1-12) -year(); // the full four digit year: (2009, 2010 etc) - -there are also functions to return the hour in 12 hour format -hourFormat12(); // the hour now in 12 hour format -isAM(); // returns true if time now is AM -isPM(); // returns true if time now is PM - -now(); // returns the current time as seconds since Jan 1 1970 - -The time and date functions can take an optional parameter for the time. This prevents +The time and date functions take a parameter for the time. This prevents errors if the time rolls over between elements. For example, if a new minute begins between getting the minute and second, the values will be inconsistent. Using the -following functions eliminates this probglem - time_t t = now(); // store the current time in time variable t +following functions eliminates this problem hour(t); // returns the hour for the given time t minute(t); // returns the minute for the given time t second(t); // returns the second for the given time t @@ -43,25 +31,6 @@ following functions eliminates this probglem year(t); // the year for the given time t -Functions for managing the timer services are: - - setTime(t); // set the system time to the give time t - setTime(hr,min,sec,day,mnth,yr); // alternative to above, yr is 2 or 4 digit yr - // (2010 or 10 sets year to 2010) - adjustTime(adjustment); // adjust system time by adding the adjustment value - timeStatus(); // indicates if time has been set and recently synchronized - // returns one of the following enumerations: - timeNotSet // the time has never been set, the clock started at Jan 1 1970 - timeNeedsSync // the time had been set but a sync attempt did not succeed - timeSet // the time is set and is synced - -Time and Date values are not valid if the status is timeNotSet. Otherwise values can be used but -the returned time may have drifted if the status is timeNeedsSync. - - setSyncProvider(getTimeFunction); // set the external time provider - setSyncInterval(interval); // set the number of seconds between re-sync - - There are many convenience macros in the time.h file for time constants and conversion of time units. diff --git a/wled00/src/dependencies/time/Time.cpp b/wled00/src/dependencies/time/Time.cpp index 326ce85c..21f2e989 100644 --- a/wled00/src/dependencies/time/Time.cpp +++ b/wled00/src/dependencies/time/Time.cpp @@ -25,6 +25,7 @@ examples, add error checking and messages to RTC examples, add examples to DS1307RTC library. 1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 + 2.0 25 May 2021 - removed timing code, only used for conversion between unix and time */ #if ARDUINO >= 100 @@ -45,19 +46,11 @@ void refreshCache(time_t t) { } } -int hour() { // the hour now - return hour(now()); -} - int hour(time_t t) { // the hour for the given time refreshCache(t); return tm.Hour; } -int hourFormat12() { // the hour now in 12 hour format - return hourFormat12(now()); -} - int hourFormat12(time_t t) { // the hour for the given time in 12 hour format refreshCache(t); if( tm.Hour == 0 ) @@ -68,71 +61,39 @@ int hourFormat12(time_t t) { // the hour for the given time in 12 hour format return tm.Hour ; } -uint8_t isAM() { // returns true if time now is AM - return !isPM(now()); -} - uint8_t isAM(time_t t) { // returns true if given time is AM return !isPM(t); } -uint8_t isPM() { // returns true if PM - return isPM(now()); -} - uint8_t isPM(time_t t) { // returns true if PM return (hour(t) >= 12); } -int minute() { - return minute(now()); -} - int minute(time_t t) { // the minute for the given time refreshCache(t); return tm.Minute; } -int second() { - return second(now()); -} - int second(time_t t) { // the second for the given time refreshCache(t); return tm.Second; } -int day(){ - return(day(now())); -} - int day(time_t t) { // the day for the given time (0-6) refreshCache(t); return tm.Day; } -int weekday() { // Sunday is day 1 - return weekday(now()); -} - int weekday(time_t t) { refreshCache(t); return tm.Wday; } - -int month(){ - return month(now()); -} int month(time_t t) { // the month for the given time refreshCache(t); return tm.Month; } -int year() { // as in Processing, the full four digit year: (2009, 2010 etc) - return year(now()); -} - int year(time_t t) { // the year for the given time refreshCache(t); return tmYearToCalendar(tm.Year); @@ -230,33 +191,6 @@ time_t makeTime(tmElements_t &tm){ seconds+= tm.Second; return (time_t)seconds; } -/*=====================================================*/ -/* Low level system time functions */ - -static uint32_t sysTime = 0; //seconds -static uint16_t sysMillis = 0; -static uint32_t prevMillis = 0; - -time_t now() { - // calculate number of seconds passed since last call to now() - while (millis() - prevMillis >= 1000) { - // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference - sysTime++; - prevMillis += 1000; - } - - return (time_t)sysTime; -} - -uint16_t millisecond() { // the millisecond (0-999) now - return (sysMillis - millis()) % 1000; -} - -void setTime(time_t t, uint16_t ms) { - sysTime = (uint32_t)t; - sysMillis = ms; - prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) -} time_t getUnixTime(int hr,int min,int sec,int dy, int mnth, int yr){ // year can be given as full four digit year or two digts (2010 or 10 for 2010); @@ -272,12 +206,4 @@ time_t getUnixTime(int hr,int min,int sec,int dy, int mnth, int yr){ tm.Minute = min; tm.Second = sec; return makeTime(tm); -} - -void setTime(int hr,int min,int sec,int dy, int mnth, int yr, uint16_t ms){ - setTime(getUnixTime(hr,min,sec,dy,mnth,yr), ms); -} - -void adjustTime(long adjustment) { - sysTime += adjustment; } \ No newline at end of file diff --git a/wled00/src/dependencies/time/TimeLib.h b/wled00/src/dependencies/time/TimeLib.h index 1064638a..5004f071 100644 --- a/wled00/src/dependencies/time/TimeLib.h +++ b/wled00/src/dependencies/time/TimeLib.h @@ -93,34 +93,19 @@ typedef time_t(*getExternalTime)(); #define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) /*============================================================================*/ -/* time and date functions */ -int hour(); // the hour now +/* time and date functions */ int hour(time_t t); // the hour for the given time -int hourFormat12(); // the hour now in 12 hour format int hourFormat12(time_t t); // the hour for the given time in 12 hour format -uint8_t isAM(); // returns true if time now is AM uint8_t isAM(time_t t); // returns true the given time is AM -uint8_t isPM(); // returns true if time now is PM uint8_t isPM(time_t t); // returns true the given time is PM -int minute(); // the minute now int minute(time_t t); // the minute for the given time -int second(); // the second now int second(time_t t); // the second for the given time -int day(); // the day now int day(time_t t); // the day for the given time -int weekday(); // the weekday now (Sunday is day 1) int weekday(time_t t); // the weekday for the given time -int month(); // the month now (Jan is month 1) int month(time_t t); // the month for the given time -int year(); // the full four digit year: (2009, 2010 etc) int year(time_t t); // the year for the given time -uint16_t millisecond(); // the millisecond now -time_t now(); // return the current time as seconds since Jan 1 1970 -void setTime(time_t t, uint16_t ms = 0); -void setTime(int hr,int min,int sec,int day, int month, int yr, uint16_t ms = 0); time_t getUnixTime(int hr,int min,int sec,int day, int month, int yr); //added by Aircoookie to get epoch time -void adjustTime(long adjustment); /* date strings */ #define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 66a2c020..d50f7127 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -272,7 +272,7 @@ void WLED::loop() if (millis() - debugTime > 9999) { DEBUG_PRINTLN("---DEBUG INFO---"); DEBUG_PRINT("Runtime: "); DEBUG_PRINTLN(millis()); - DEBUG_PRINT("Unix time: "); DEBUG_PRINTLN(now()); + DEBUG_PRINT("Unix time: "); toki.printTime(toki.getTime()); DEBUG_PRINT("Free heap: "); DEBUG_PRINTLN(ESP.getFreeHeap()); DEBUG_PRINT("Wifi state: "); DEBUG_PRINTLN(WiFi.status()); From 6c8bf090fec94c8b7b532f950c4de12e3cff08e6 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Thu, 27 May 2021 00:09:52 +0200 Subject: [PATCH 4/8] Small optimizations --- wled00/data/index.js | 5 +- wled00/fcn_declare.h | 1 + wled00/html_ui.h | 540 ++++++++++++++++++++++--------------------- wled00/ntp.cpp | 15 ++ wled00/overlay.cpp | 30 +-- wled00/set.cpp | 1 - wled00/wled.cpp | 6 +- wled00/wled.h | 8 +- 8 files changed, 304 insertions(+), 302 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index 6501a5f4..9e44f0ff 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1036,7 +1036,10 @@ function requestJson(command, rinfo = true, verbose = true) { d.getElementById('sliderIntensity').value = i.ix; // Effects - e1.querySelector(`input[name="fx"][value="${i.fx}"]`).checked = true; + var selFx = e1.querySelector(`input[name="fx"][value="${i.fx}"]`); + if (selFx) selFx.checked = true; + else location.reload(); //effect list is gone (e.g. if restoring tab). Reload. + var selElement = e1.querySelector('.selected'); if (selElement) { selElement.classList.remove('selected') diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 7e9deb8b..a6312b7b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -127,6 +127,7 @@ bool initMqtt(); void publishMqtt(); //ntp.cpp +void handleTime(); void handleNetworkTime(); void sendNTPPacket(); bool checkNTPResponse(); diff --git a/wled00/html_ui.h b/wled00/html_ui.h index 1bdb2703..2fa744bc 100644 --- a/wled00/html_ui.h +++ b/wled00/html_ui.h @@ -7,7 +7,7 @@ */ // Autogenerated from wled00/data/index.htm, do not edit!! -const uint16_t PAGE_index_L = 33357; +const uint16_t PAGE_index_L = 33378; const uint8_t PAGE_index[] PROGMEM = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xcc, 0xbd, 0x79, 0x5b, 0xe2, 0xca, 0xb6, 0x38, 0xfc, 0xbf, 0x9f, 0x82, 0xa6, 0xf7, 0xee, 0x86, 0x26, 0x40, 0x18, 0x55, 0xe8, 0xb4, @@ -1826,272 +1826,274 @@ const uint8_t PAGE_index[] PROGMEM = { 0x5f, 0x79, 0x1f, 0x5c, 0xa1, 0x72, 0x68, 0x49, 0x56, 0x72, 0xef, 0x80, 0xf0, 0xa5, 0xe4, 0x22, 0x8b, 0xbc, 0x27, 0x57, 0x74, 0x09, 0x5d, 0x98, 0xf4, 0xa3, 0xdf, 0xbf, 0xa6, 0x6b, 0x44, 0x8d, 0x18, 0x16, 0x8b, 0x27, 0xea, 0xc5, 0x22, 0x57, 0xf6, 0x7e, 0x45, 0xe2, 0x6e, 0x7a, 0x6f, 0xef, - 0x41, 0x3d, 0x17, 0xd9, 0xfd, 0x7b, 0x3b, 0xa8, 0x48, 0x16, 0xa6, 0xdc, 0x1c, 0x9e, 0x4b, 0xb1, - 0x97, 0xf7, 0xe6, 0xd5, 0xa7, 0x42, 0xba, 0x1d, 0x77, 0x97, 0xf7, 0xb9, 0x79, 0x35, 0x2d, 0xb7, - 0xc2, 0x01, 0x87, 0xa9, 0x8d, 0x1b, 0xd4, 0x6e, 0x89, 0xa4, 0xbf, 0x43, 0xe0, 0x07, 0x37, 0x87, - 0x87, 0x1b, 0x45, 0x4e, 0xc1, 0x1d, 0xc0, 0x4a, 0x5a, 0x84, 0x72, 0x60, 0x25, 0xeb, 0x5a, 0x25, - 0x53, 0xba, 0x6a, 0xfa, 0xa4, 0x88, 0xef, 0x8b, 0x1e, 0x90, 0x8f, 0x69, 0xa5, 0x4e, 0x6f, 0xb1, - 0x50, 0x2b, 0x54, 0xbd, 0x2d, 0x62, 0x19, 0xdb, 0xdb, 0x36, 0x4a, 0x79, 0xe3, 0xa7, 0x0d, 0x15, - 0x12, 0xab, 0x63, 0xb5, 0x3b, 0x1b, 0xd7, 0xdb, 0x32, 0x50, 0x6b, 0xd7, 0x38, 0xeb, 0xdd, 0x68, - 0x18, 0x61, 0xd9, 0x70, 0xeb, 0xe8, 0xe0, 0x94, 0x58, 0x77, 0xd3, 0x79, 0x12, 0x05, 0x01, 0xac, - 0x6a, 0xf4, 0xdf, 0x3e, 0xbb, 0xeb, 0x3c, 0xce, 0xd8, 0xca, 0xbb, 0xf5, 0x31, 0xfc, 0x98, 0x34, - 0x9d, 0x27, 0x34, 0x0e, 0x58, 0x1c, 0xc8, 0x11, 0xa0, 0x67, 0x79, 0x20, 0x14, 0x32, 0x93, 0x26, - 0x53, 0x09, 0xf1, 0xcc, 0xc9, 0x86, 0x25, 0xe9, 0x25, 0x73, 0xf1, 0x5f, 0xf1, 0x81, 0x8b, 0xfd, - 0x9c, 0x21, 0x7c, 0x2c, 0x85, 0xa1, 0x14, 0x5b, 0xd0, 0x58, 0x16, 0x2a, 0xda, 0x07, 0x9a, 0xe0, - 0xaf, 0xdf, 0xc7, 0xdc, 0x68, 0x43, 0xc0, 0xc2, 0x68, 0x73, 0xbd, 0x32, 0xd2, 0xd8, 0x9b, 0x33, - 0x34, 0x8d, 0x4b, 0xd1, 0x34, 0x91, 0x9b, 0x36, 0x54, 0x8a, 0x0c, 0xb0, 0xc8, 0xc7, 0x15, 0x93, - 0x84, 0x37, 0x5b, 0x14, 0xe6, 0x84, 0x11, 0x4b, 0xa9, 0x51, 0x76, 0x8f, 0xd3, 0xa0, 0x17, 0x7b, - 0x89, 0xc5, 0x5e, 0x29, 0x3d, 0x31, 0xa8, 0xdb, 0xc6, 0xca, 0x4b, 0x8d, 0x68, 0x0e, 0xc8, 0x1d, - 0x05, 0xde, 0xb9, 0x4a, 0x3e, 0xd1, 0x67, 0xf3, 0x48, 0x8c, 0xef, 0xc8, 0x1c, 0xc2, 0xcb, 0x92, - 0x0c, 0x2f, 0x4a, 0xac, 0x93, 0xff, 0x69, 0x51, 0xad, 0xf0, 0x3c, 0x81, 0xfc, 0x7b, 0x21, 0x97, - 0x79, 0x8c, 0xc2, 0x21, 0x77, 0x4b, 0x8a, 0xbf, 0x78, 0x7b, 0x5f, 0x22, 0xe6, 0x12, 0x6d, 0xf4, - 0x9d, 0xef, 0x77, 0x60, 0x74, 0xf4, 0xff, 0xa1, 0x30, 0x27, 0xb5, 0x56, 0x3f, 0x04, 0x9a, 0x85, - 0x7e, 0x87, 0x5c, 0xa0, 0xc2, 0x8f, 0x35, 0x99, 0x22, 0x15, 0x91, 0x08, 0x17, 0x0d, 0x5d, 0xe3, - 0x0f, 0xb4, 0xf7, 0xe4, 0x16, 0xfb, 0x64, 0x6e, 0x49, 0xd8, 0x19, 0x98, 0x3b, 0x3c, 0x48, 0xc7, - 0x40, 0x01, 0x00, 0xd1, 0x3f, 0x34, 0xa3, 0xe5, 0xd2, 0xcc, 0x29, 0x15, 0x8f, 0x53, 0x48, 0xbc, - 0x25, 0x07, 0xce, 0xde, 0x32, 0x83, 0xbf, 0xfc, 0x03, 0x1c, 0xc3, 0x39, 0x6a, 0xd5, 0x91, 0x70, - 0x79, 0x3a, 0x34, 0x79, 0x2b, 0x0b, 0x46, 0xed, 0xa0, 0xdd, 0x00, 0xde, 0x26, 0xa8, 0x7d, 0x86, - 0x32, 0x43, 0x9c, 0x0d, 0xe8, 0x54, 0x9e, 0xd7, 0xfa, 0x8f, 0x32, 0x08, 0x7d, 0x04, 0xa5, 0x83, - 0x56, 0xf9, 0x64, 0x4d, 0xcc, 0x5f, 0xe9, 0xde, 0x82, 0xba, 0x8f, 0x06, 0xa6, 0x64, 0xf2, 0x19, - 0xb2, 0xec, 0x2e, 0x4a, 0x6e, 0xf8, 0x70, 0x80, 0xb5, 0x30, 0x30, 0x3f, 0x02, 0x1e, 0x19, 0x91, - 0x02, 0x21, 0xd3, 0x85, 0xae, 0x7f, 0xc4, 0x67, 0x3e, 0x6c, 0x32, 0x2b, 0xdd, 0x5d, 0x8f, 0x11, - 0x44, 0xe1, 0x35, 0x64, 0xc2, 0xda, 0xba, 0xa6, 0x74, 0x43, 0xf3, 0x88, 0x54, 0xc4, 0xf0, 0x11, - 0xc9, 0x88, 0xa1, 0xec, 0x57, 0x9e, 0x8f, 0x14, 0x8a, 0x8f, 0x98, 0x1f, 0xa2, 0x35, 0x12, 0xa4, - 0xff, 0x8a, 0xce, 0xef, 0x58, 0x40, 0xa4, 0x40, 0x6f, 0x71, 0x3b, 0xc3, 0xa9, 0x4a, 0x7e, 0x6b, - 0xf1, 0xb7, 0x89, 0x75, 0x17, 0xf9, 0x6a, 0xa4, 0x1d, 0xe6, 0xaf, 0xfa, 0x4f, 0x2c, 0x5c, 0xe7, - 0x4c, 0x6a, 0x77, 0x24, 0xb2, 0x1e, 0x58, 0xba, 0xe2, 0xd9, 0x1c, 0x6d, 0x6f, 0x2f, 0x99, 0xf3, - 0x56, 0x30, 0xaa, 0xe4, 0x0c, 0xfd, 0x9a, 0xce, 0x02, 0x2f, 0xbc, 0xd9, 0x22, 0x58, 0xaa, 0xca, - 0xb1, 0xb0, 0x87, 0x8a, 0x48, 0x89, 0x7b, 0x7f, 0xa9, 0xce, 0x04, 0x91, 0x96, 0x38, 0x0b, 0x44, - 0xc2, 0x4a, 0x87, 0x2e, 0xe2, 0x22, 0xc8, 0xee, 0x48, 0x6f, 0xbe, 0xfc, 0xaf, 0x55, 0x21, 0x49, - 0x25, 0xc9, 0xda, 0xe8, 0xe3, 0x0b, 0xd2, 0x1b, 0x84, 0x30, 0xbc, 0xa2, 0xbd, 0x44, 0x2f, 0xad, - 0x23, 0x7d, 0x57, 0x19, 0x28, 0xaf, 0xb1, 0x1c, 0x6a, 0x6d, 0xbb, 0x8a, 0x6b, 0x2d, 0x49, 0x56, - 0xab, 0x23, 0x2f, 0xbd, 0x14, 0x8b, 0x87, 0x86, 0x56, 0x43, 0x8d, 0xba, 0x57, 0xc7, 0x42, 0x25, - 0xfe, 0xdc, 0x60, 0x24, 0xeb, 0xa0, 0x0e, 0x48, 0x54, 0xab, 0x2c, 0x5e, 0xb1, 0x3e, 0xca, 0x35, - 0x5d, 0x39, 0xcc, 0xb5, 0x77, 0x83, 0x04, 0x66, 0x81, 0x07, 0xe9, 0x3e, 0x57, 0x53, 0x49, 0xdc, - 0x22, 0xd7, 0xe5, 0xaa, 0x7c, 0x4a, 0xe6, 0xe3, 0x7e, 0x79, 0x89, 0x30, 0xca, 0xce, 0x25, 0xd7, - 0xc5, 0x43, 0xd6, 0x73, 0x73, 0xe0, 0xb0, 0xdd, 0x3c, 0xa5, 0xc1, 0xae, 0x32, 0x64, 0x77, 0x4a, - 0x0e, 0xe3, 0x03, 0xbb, 0x93, 0x0a, 0x8d, 0x86, 0xde, 0x72, 0x93, 0x61, 0x47, 0x69, 0x28, 0x5e, - 0xb3, 0xbb, 0x28, 0x0c, 0x2d, 0x4a, 0xb5, 0xf7, 0x56, 0xad, 0x7b, 0x4d, 0x25, 0x7e, 0x6f, 0xcb, - 0x8a, 0x3d, 0x4d, 0x2b, 0x34, 0xdd, 0xfb, 0x5d, 0x2d, 0xed, 0xa7, 0xe7, 0xae, 0xce, 0xca, 0xd7, - 0x19, 0x5a, 0xb0, 0x56, 0xb5, 0x77, 0xb5, 0xee, 0xaa, 0x5e, 0xfe, 0x5f, 0xd2, 0xe1, 0xaf, 0xb1, - 0xbc, 0x50, 0xd2, 0xbe, 0xbc, 0xd7, 0x9a, 0xed, 0x83, 0xa6, 0xb9, 0xaf, 0xab, 0xee, 0xb7, 0x75, - 0x98, 0x54, 0xf8, 0x95, 0x89, 0x64, 0x39, 0x5d, 0x90, 0x7d, 0x91, 0xc6, 0x39, 0x50, 0x44, 0xc1, - 0x52, 0x53, 0x3b, 0x57, 0x5b, 0x68, 0xd4, 0x40, 0xd7, 0x86, 0x34, 0x32, 0x14, 0x8d, 0xdd, 0x51, - 0x93, 0x6a, 0x7a, 0x4d, 0x47, 0x79, 0x3a, 0xda, 0x4f, 0xc3, 0x58, 0x8d, 0xb3, 0x55, 0xb6, 0xf1, - 0xb8, 0xaf, 0x7a, 0x72, 0xb3, 0x87, 0x06, 0xb2, 0xbb, 0x3f, 0xf6, 0x95, 0x81, 0x15, 0x68, 0x08, - 0x61, 0x46, 0x9f, 0x2c, 0xca, 0x8a, 0xaa, 0x87, 0x34, 0xac, 0xfe, 0x0b, 0x8f, 0x86, 0xf5, 0x6a, - 0xb1, 0x90, 0x98, 0xa0, 0xd0, 0xcf, 0xc0, 0x0d, 0xff, 0x4c, 0x47, 0x6d, 0x17, 0xa5, 0xc2, 0xcb, - 0x94, 0x6c, 0x05, 0x5b, 0xd5, 0x26, 0xe3, 0xec, 0x3e, 0x33, 0x04, 0x84, 0x2a, 0x66, 0xe2, 0x19, - 0x66, 0xf1, 0x36, 0xe8, 0x69, 0x6f, 0x1d, 0xe3, 0x6d, 0xa8, 0x4b, 0x44, 0x17, 0x82, 0x23, 0xe7, - 0xb8, 0xdd, 0x93, 0x81, 0xb2, 0x71, 0x80, 0x32, 0x93, 0x76, 0xde, 0xdc, 0x3a, 0x43, 0xd3, 0xc2, - 0x7c, 0x4b, 0x8e, 0xf5, 0x48, 0x1d, 0xa6, 0xdb, 0x35, 0x7b, 0x85, 0x2b, 0x0b, 0x15, 0xd0, 0xe6, - 0xaa, 0x63, 0x03, 0x83, 0xe4, 0x6a, 0xc3, 0x2d, 0xdd, 0x4e, 0xb5, 0x6e, 0x97, 0xbd, 0x52, 0x3a, - 0x55, 0xb8, 0x71, 0xc9, 0x95, 0x71, 0x7d, 0x0e, 0x9a, 0x86, 0xd5, 0x53, 0xac, 0x26, 0x74, 0xe8, - 0x1f, 0x03, 0xc7, 0x8e, 0xfc, 0x01, 0x23, 0x09, 0x27, 0x9c, 0x61, 0x48, 0x79, 0x29, 0x1d, 0xe5, - 0x4b, 0x60, 0x29, 0xc5, 0x77, 0xaa, 0xf8, 0xf3, 0xe9, 0x32, 0x7f, 0x2d, 0x3c, 0x87, 0x00, 0x73, - 0xb3, 0x32, 0xa4, 0x5c, 0xfb, 0x77, 0xe0, 0x20, 0x84, 0xfc, 0x47, 0xa4, 0x09, 0x15, 0x95, 0x76, - 0x03, 0x00, 0x1a, 0xd8, 0x3c, 0xcd, 0xae, 0x35, 0xab, 0x44, 0x78, 0x7d, 0x93, 0x0a, 0x33, 0x7c, - 0xd9, 0x26, 0xd4, 0x2f, 0x6d, 0x00, 0x84, 0x01, 0xc0, 0xbe, 0xda, 0xff, 0x52, 0x07, 0xac, 0x69, - 0xe9, 0xe2, 0x68, 0xa0, 0x3a, 0x18, 0x18, 0x88, 0x71, 0xbe, 0xba, 0x78, 0x87, 0xb7, 0x27, 0xe8, - 0xb6, 0xa6, 0x38, 0x92, 0x4a, 0x17, 0x27, 0x3a, 0x6e, 0x54, 0xca, 0x7b, 0xb1, 0xaf, 0xf9, 0x3d, - 0xe1, 0xad, 0x37, 0xad, 0x4e, 0x1c, 0xf5, 0xd5, 0x76, 0xfb, 0xa2, 0xdd, 0x3d, 0x8c, 0x2c, 0xde, - 0xf1, 0x80, 0x6c, 0xc6, 0x2c, 0x91, 0xae, 0xf1, 0xa5, 0x19, 0xd2, 0xf6, 0x49, 0xf6, 0x67, 0x34, - 0xc9, 0x62, 0x0e, 0x25, 0x8e, 0xd9, 0xd7, 0x80, 0x42, 0xd3, 0xa3, 0xdb, 0xa3, 0x97, 0x97, 0x08, - 0x78, 0xf2, 0xd8, 0x9f, 0xa1, 0x5c, 0x68, 0xcf, 0x6e, 0xa6, 0x7f, 0x59, 0x37, 0x9b, 0x27, 0x1e, - 0xf6, 0x2a, 0xf5, 0x0d, 0x78, 0x98, 0x77, 0x3f, 0x1a, 0x8d, 0x87, 0x9d, 0x3a, 0x69, 0x8b, 0xea, - 0xd9, 0x56, 0x9c, 0x56, 0xa5, 0x3f, 0x2c, 0x01, 0xa7, 0x78, 0xda, 0x0d, 0xce, 0x1c, 0x3a, 0xfd, - 0xfa, 0x62, 0x2b, 0x73, 0xd8, 0x65, 0xc3, 0xba, 0x57, 0xae, 0x7c, 0xdc, 0xda, 0x3f, 0x3e, 0x92, - 0x66, 0x34, 0xec, 0xd7, 0xdc, 0xe5, 0xa0, 0x08, 0xe0, 0x42, 0xba, 0xac, 0xd8, 0x8e, 0x88, 0xe5, - 0xf9, 0x22, 0x76, 0x14, 0xcd, 0x03, 0xdf, 0x72, 0x29, 0xfa, 0x33, 0x2a, 0x65, 0x09, 0x66, 0xa9, - 0x40, 0xa7, 0x6c, 0xfb, 0x67, 0x7b, 0xf7, 0x69, 0xc1, 0x82, 0x8b, 0xce, 0xb3, 0x23, 0x76, 0xf4, - 0x6c, 0x67, 0x9f, 0xa4, 0xe9, 0xd4, 0x8f, 0xc2, 0x8b, 0x09, 0xb5, 0x5f, 0x3a, 0xfa, 0x19, 0x3e, - 0xdb, 0xc3, 0x63, 0x90, 0xea, 0x77, 0xd4, 0x1c, 0xbf, 0x21, 0x25, 0xf7, 0xb2, 0x8a, 0x7c, 0xcb, - 0x2e, 0x44, 0x3f, 0x62, 0x12, 0xc1, 0xcc, 0xb8, 0xa5, 0xb0, 0x31, 0x57, 0xd6, 0x1f, 0xbf, 0xf3, - 0x05, 0xd1, 0xea, 0x90, 0xf3, 0xa1, 0xa3, 0x5b, 0x80, 0x27, 0x1c, 0xb3, 0x74, 0x4b, 0x84, 0x95, - 0x4d, 0x2b, 0x47, 0x5b, 0xeb, 0x51, 0x1c, 0xd7, 0x0e, 0xe2, 0x66, 0x67, 0x28, 0x02, 0x38, 0x76, - 0x92, 0xdd, 0x48, 0x75, 0xcb, 0xb9, 0x2c, 0xc9, 0x88, 0x5d, 0x34, 0xf5, 0xb7, 0x8f, 0xfc, 0x00, - 0x76, 0xa4, 0xc7, 0x13, 0xa9, 0x6a, 0xbf, 0x5d, 0x2f, 0x20, 0x76, 0xd0, 0x4b, 0x6d, 0x85, 0xea, - 0xf8, 0x92, 0xb1, 0x7e, 0x19, 0xd1, 0x51, 0xac, 0xf4, 0x7e, 0x64, 0xc7, 0x1b, 0xf2, 0x16, 0x5d, - 0x85, 0xac, 0x0a, 0xe1, 0xc1, 0x4f, 0x9c, 0x5d, 0x9e, 0xed, 0xe8, 0xa0, 0x2a, 0xa5, 0x97, 0xa3, - 0x96, 0x6c, 0x51, 0x7f, 0x5a, 0x0b, 0xfc, 0xb0, 0x33, 0x62, 0x84, 0x28, 0x3a, 0x68, 0x2a, 0x4a, - 0x45, 0x86, 0xa2, 0x06, 0xc5, 0x8d, 0xa4, 0xb4, 0x9d, 0x2f, 0xfa, 0x8d, 0xee, 0x0b, 0x86, 0x9f, - 0xae, 0xf2, 0x8a, 0x05, 0x07, 0x37, 0xfa, 0x22, 0x8b, 0x1d, 0xbc, 0x41, 0xe0, 0x2a, 0xe7, 0x90, - 0x37, 0x18, 0x86, 0x18, 0x50, 0x12, 0x1d, 0xd1, 0x95, 0xf2, 0x13, 0x35, 0x02, 0x88, 0x70, 0x0f, - 0xc0, 0x74, 0x4f, 0xb5, 0xd4, 0x0a, 0x5a, 0xc4, 0x30, 0x14, 0x09, 0x0f, 0xdb, 0xd5, 0xbb, 0x98, - 0x32, 0x59, 0x79, 0x4e, 0xce, 0x6d, 0x95, 0xaa, 0x33, 0x51, 0xf5, 0xbf, 0x5d, 0x35, 0x8e, 0x74, - 0xe1, 0xce, 0x81, 0x9b, 0x5d, 0xc8, 0xe1, 0x90, 0x9e, 0xbf, 0xb8, 0xda, 0x55, 0x47, 0x83, 0x6c, - 0xe1, 0x30, 0xb3, 0xd1, 0x2a, 0x7c, 0x18, 0xe6, 0xf9, 0x68, 0x9b, 0x5a, 0xdf, 0x7f, 0x54, 0x39, - 0x8e, 0x6e, 0x7e, 0xd0, 0xe6, 0xda, 0x0d, 0x6c, 0xfe, 0x0c, 0x9f, 0x5d, 0x2f, 0x57, 0x97, 0x25, - 0x55, 0xb5, 0xa1, 0x8a, 0xa1, 0x3e, 0x96, 0xe6, 0x7a, 0x13, 0x45, 0x6e, 0x8b, 0x8e, 0xed, 0x42, - 0xc6, 0x7d, 0xef, 0x91, 0x83, 0xbb, 0xf5, 0x26, 0xc8, 0x7c, 0xa0, 0x2b, 0x4b, 0xeb, 0x43, 0xf8, - 0x22, 0x7c, 0x4b, 0x01, 0xf8, 0x1d, 0xa0, 0x43, 0xff, 0xd2, 0xd9, 0xce, 0x15, 0x8f, 0xbe, 0xc4, - 0x6b, 0x3e, 0x3e, 0xb6, 0xdb, 0xc0, 0x03, 0x27, 0xd2, 0xe1, 0x4b, 0xaf, 0xaf, 0x3d, 0x5a, 0xac, - 0xb6, 0x83, 0x15, 0x50, 0x13, 0xed, 0x60, 0x05, 0x1f, 0xb7, 0x82, 0xd5, 0x2f, 0xfe, 0x96, 0x9a, - 0xd7, 0x7e, 0x7b, 0xc5, 0x6b, 0x7f, 0x17, 0xb8, 0xa2, 0x65, 0x76, 0x7b, 0xdd, 0x51, 0x38, 0x3c, - 0x90, 0x06, 0xdf, 0xec, 0x4a, 0x93, 0xc4, 0x96, 0xa6, 0xd5, 0xed, 0xc5, 0x81, 0xa2, 0x1b, 0xee, - 0x06, 0x0b, 0x32, 0xc2, 0x94, 0xca, 0xae, 0x95, 0x26, 0xfe, 0xd9, 0x61, 0x3c, 0xae, 0xe0, 0x23, - 0x8f, 0x15, 0x8b, 0xf1, 0x75, 0x55, 0x48, 0xd3, 0xef, 0x49, 0x9e, 0x7d, 0xc3, 0xb5, 0x5a, 0x8c, - 0xea, 0xe5, 0xd4, 0x50, 0xcc, 0xc1, 0x33, 0xd9, 0xce, 0x70, 0xef, 0xa2, 0xf2, 0xb2, 0x07, 0x0e, - 0xc2, 0xda, 0x85, 0x56, 0x56, 0x0f, 0x38, 0x23, 0xab, 0x51, 0x2f, 0xb6, 0x32, 0x54, 0x6b, 0xda, - 0x7a, 0xe1, 0x53, 0xad, 0x66, 0x5a, 0x54, 0x53, 0xbd, 0xf8, 0x61, 0xbb, 0x2e, 0x7d, 0xea, 0x4b, - 0xb1, 0xbc, 0x1f, 0x2a, 0xae, 0x6c, 0xab, 0x33, 0x2c, 0xae, 0xdc, 0xbf, 0x70, 0x9e, 0x75, 0xdb, - 0xb1, 0xc6, 0x5b, 0xb2, 0x7d, 0xe6, 0x7c, 0x8f, 0x6a, 0xbe, 0x74, 0xfe, 0x2b, 0x56, 0x6d, 0x7f, - 0x72, 0x19, 0x1a, 0x6c, 0xe4, 0xfe, 0xe4, 0x6a, 0xc0, 0xc0, 0x86, 0xac, 0xba, 0x08, 0xb8, 0x8d, - 0x8a, 0x4b, 0xa0, 0xed, 0xbb, 0xa6, 0xae, 0xf8, 0x60, 0xfd, 0x3b, 0x2e, 0x8a, 0x70, 0x7b, 0xe3, - 0xa5, 0x71, 0xa7, 0x69, 0x73, 0xa7, 0xf7, 0xbb, 0x3b, 0xa8, 0x5d, 0x39, 0x5b, 0x0d, 0xc8, 0xa7, - 0xb8, 0x68, 0x6e, 0x6c, 0xc2, 0xdf, 0xa3, 0x89, 0xda, 0x55, 0x75, 0x53, 0x33, 0xef, 0xa3, 0x3a, - 0x82, 0xc3, 0x88, 0x70, 0xac, 0x7e, 0xad, 0xf4, 0xe6, 0x8f, 0x72, 0x11, 0xe2, 0x60, 0x78, 0xdc, - 0xcf, 0x47, 0xfb, 0x68, 0x7a, 0x1c, 0x1e, 0x76, 0x3a, 0xbc, 0x84, 0x63, 0xa3, 0xde, 0xc5, 0xf0, - 0x11, 0x18, 0xa6, 0x6d, 0xbd, 0x57, 0xf5, 0x3f, 0x2c, 0xd4, 0xf6, 0xd9, 0x91, 0x99, 0x95, 0x99, - 0x51, 0xa5, 0x63, 0xb8, 0x7b, 0x81, 0x95, 0x09, 0xb1, 0xbe, 0x16, 0x34, 0xb6, 0xc2, 0x86, 0xf0, - 0x2b, 0x58, 0x92, 0x69, 0x71, 0x0a, 0x33, 0x3a, 0x52, 0x8e, 0xe3, 0xf7, 0xc2, 0x65, 0x6c, 0x2c, - 0x7d, 0x11, 0x4b, 0x91, 0x94, 0xf0, 0x9f, 0x45, 0xae, 0xb3, 0xec, 0x56, 0x32, 0x8d, 0x78, 0x40, - 0xa8, 0xbe, 0x73, 0xd0, 0x89, 0xdf, 0x6d, 0xa3, 0x2d, 0x2a, 0x0e, 0x9a, 0x31, 0xb6, 0x44, 0xfc, - 0xee, 0xbc, 0x8f, 0xaa, 0xd6, 0x50, 0xb0, 0x8d, 0x59, 0xb5, 0x28, 0xa4, 0x4a, 0x27, 0xfe, 0xd0, - 0x46, 0x18, 0x67, 0xf7, 0x59, 0x51, 0x25, 0x56, 0xf5, 0x41, 0xf1, 0xaa, 0x1c, 0xbf, 0x93, 0x86, - 0xd1, 0x8f, 0x2d, 0x04, 0x55, 0x13, 0x6d, 0x0d, 0x94, 0xaa, 0x3f, 0x6b, 0xf5, 0x30, 0x3d, 0xd3, - 0xf2, 0x62, 0xcc, 0x8e, 0xd6, 0xbc, 0xa9, 0x9e, 0xb7, 0xa4, 0xfc, 0xc2, 0xb6, 0x12, 0x5e, 0xec, - 0x97, 0x17, 0x22, 0xc9, 0xc3, 0x63, 0xa6, 0x7a, 0xe3, 0x0c, 0xa5, 0xff, 0xe0, 0x94, 0x74, 0x76, - 0xc8, 0x69, 0xb3, 0x1b, 0xda, 0x52, 0x45, 0xf8, 0x7c, 0x20, 0xb5, 0x72, 0x50, 0xf1, 0xa6, 0xb3, - 0xb7, 0xa7, 0x6b, 0xe9, 0xbe, 0x5a, 0x0b, 0xe7, 0x41, 0xfe, 0x83, 0x15, 0x51, 0x94, 0xb1, 0xf4, - 0x13, 0xf2, 0x85, 0x4f, 0xb1, 0x6e, 0x7c, 0xa0, 0xc4, 0xee, 0x7f, 0x5d, 0x76, 0xcc, 0x47, 0xd3, - 0x1a, 0x1f, 0xf7, 0xff, 0x5c, 0xbb, 0x97, 0x0f, 0x61, 0xe6, 0xdd, 0x8b, 0xcb, 0x7e, 0x60, 0x0c, - 0xe7, 0x1b, 0x20, 0xdd, 0xd6, 0x06, 0x0e, 0x5c, 0xed, 0x01, 0x6f, 0xdb, 0x71, 0x5d, 0xa5, 0x79, - 0xde, 0x65, 0xd3, 0xfa, 0x4b, 0x06, 0x9e, 0xb0, 0x25, 0xc0, 0xcd, 0x8a, 0x2e, 0x8d, 0x63, 0xef, - 0x9a, 0x19, 0x33, 0x06, 0xac, 0x0c, 0x50, 0xa7, 0xd1, 0xc2, 0x5f, 0x3e, 0xe0, 0x0e, 0xa1, 0x9b, - 0x67, 0x21, 0xa6, 0xb0, 0xf2, 0xac, 0x1b, 0xa1, 0x73, 0xcb, 0xac, 0x1b, 0xe3, 0x1e, 0x70, 0xe3, - 0x77, 0x00, 0x0b, 0xb0, 0x71, 0x3f, 0x14, 0x91, 0x29, 0x1a, 0xbb, 0xf1, 0x39, 0x28, 0x16, 0x38, - 0x2d, 0x7c, 0xd6, 0x92, 0xb5, 0xc1, 0xe7, 0xc0, 0x4d, 0x35, 0x0f, 0xd5, 0xc2, 0x8f, 0x32, 0x6e, - 0x48, 0xbe, 0x15, 0xe3, 0x77, 0x4d, 0x7b, 0x11, 0x9a, 0x8d, 0x26, 0xdc, 0x8d, 0xe5, 0xa7, 0xf8, - 0xdd, 0x95, 0x9b, 0xe9, 0x6e, 0x59, 0x21, 0x89, 0xf7, 0xb0, 0x9e, 0x1c, 0xd5, 0x93, 0x6e, 0xeb, - 0x49, 0xe4, 0x6c, 0x70, 0xa8, 0x34, 0xf0, 0x08, 0x38, 0xf3, 0x83, 0x8d, 0x1e, 0xc3, 0xcd, 0xb6, - 0xa9, 0x43, 0xfd, 0x10, 0x26, 0xfc, 0x3d, 0x87, 0xec, 0x2e, 0x78, 0x20, 0x3c, 0xb1, 0x90, 0x6b, - 0xd9, 0x35, 0x73, 0x9b, 0xc0, 0x57, 0x3a, 0x9d, 0xa5, 0x86, 0x10, 0x9c, 0x29, 0x15, 0x87, 0xf4, - 0x39, 0xd0, 0xbe, 0xc1, 0xe4, 0x60, 0x9a, 0x65, 0xd5, 0xbd, 0x0f, 0x6b, 0xb1, 0x5f, 0x34, 0x2e, - 0xe4, 0xa2, 0x76, 0x9e, 0xc4, 0x90, 0x8a, 0x07, 0x8a, 0x3e, 0x4a, 0x76, 0xd5, 0xe0, 0xd3, 0x58, - 0x65, 0x44, 0xa5, 0xba, 0x17, 0x1a, 0xd6, 0x62, 0xa4, 0x57, 0x36, 0xda, 0x2f, 0x74, 0xcc, 0x36, - 0x73, 0xf4, 0x4c, 0xd1, 0x60, 0x8b, 0x12, 0x12, 0xf8, 0x63, 0xc4, 0xc2, 0x14, 0x5d, 0x61, 0x18, - 0xa4, 0x2b, 0x66, 0xda, 0x4a, 0x9e, 0xb5, 0x97, 0x5c, 0xe3, 0xdd, 0x16, 0x06, 0x19, 0x54, 0xd3, - 0x29, 0x68, 0xa9, 0x6b, 0x9e, 0x62, 0x90, 0xc3, 0x51, 0xc6, 0xb5, 0xcb, 0x2a, 0xd5, 0x9e, 0xd5, - 0xab, 0x55, 0xb3, 0xc9, 0x9a, 0x07, 0xbc, 0x66, 0xf5, 0x93, 0xa8, 0xfc, 0x0c, 0xe3, 0x1b, 0x8a, - 0xe8, 0x34, 0x3c, 0xb8, 0x26, 0x1e, 0x29, 0x95, 0xc6, 0x74, 0x2d, 0x3c, 0xcb, 0xde, 0x62, 0xc3, - 0xd1, 0xa4, 0x10, 0xb7, 0xb7, 0xf9, 0x47, 0x8b, 0xe5, 0x86, 0x5d, 0xf1, 0xa7, 0x40, 0xb7, 0xc7, - 0xa8, 0x36, 0xb0, 0x42, 0xff, 0x8b, 0x85, 0x89, 0xf2, 0x1b, 0x61, 0x31, 0x67, 0x26, 0x14, 0xe2, - 0x18, 0xcf, 0x2d, 0xa0, 0x08, 0x56, 0x40, 0x10, 0xa4, 0xf0, 0xef, 0x76, 0x48, 0x3e, 0x70, 0xbb, - 0x5a, 0xb8, 0x88, 0xc2, 0xf3, 0xa9, 0x30, 0xd4, 0x3f, 0x3a, 0x73, 0xac, 0xd1, 0x22, 0x7a, 0x64, - 0xdd, 0x95, 0x9a, 0xed, 0xe4, 0x79, 0x25, 0x9f, 0x95, 0xc3, 0x00, 0x03, 0xd6, 0xa1, 0x44, 0x6f, - 0x86, 0x6e, 0x4f, 0x57, 0x14, 0x3e, 0x75, 0x65, 0x9d, 0x63, 0x15, 0xbc, 0x73, 0x90, 0x98, 0x57, - 0x67, 0x96, 0x51, 0xd0, 0x1e, 0x1e, 0xe9, 0xc7, 0xa9, 0x99, 0xfd, 0xd1, 0x70, 0x05, 0xf5, 0xa3, - 0x96, 0x4c, 0xae, 0x67, 0x76, 0x13, 0x48, 0xf2, 0xb9, 0xfb, 0x0d, 0xe9, 0x6a, 0x31, 0xf1, 0xac, - 0x9b, 0x68, 0xf3, 0x9d, 0xd9, 0xfd, 0x66, 0xeb, 0x28, 0x51, 0xf6, 0x6f, 0xe8, 0xcc, 0x5e, 0x16, - 0xbd, 0xae, 0x14, 0x1d, 0x6c, 0x2d, 0xfa, 0x5a, 0x2d, 0x3a, 0xab, 0x14, 0x3d, 0xa9, 0x8d, 0x8d, - 0x47, 0xb4, 0xac, 0x8f, 0x6d, 0xc5, 0xee, 0x2f, 0xc9, 0x96, 0x60, 0x84, 0xf5, 0xa4, 0x9b, 0x19, - 0xb7, 0x2c, 0x90, 0x3e, 0x6f, 0x33, 0x0d, 0x96, 0x46, 0x19, 0x61, 0x54, 0x76, 0xe4, 0xa2, 0x0d, - 0xe5, 0xa5, 0xc8, 0xf9, 0xdc, 0x6a, 0x82, 0x49, 0xa8, 0x78, 0x5e, 0x00, 0x24, 0x6b, 0xc9, 0x10, - 0x2e, 0xdb, 0xf5, 0xfa, 0xa5, 0x45, 0xd8, 0x89, 0xa5, 0x48, 0xc7, 0xa0, 0x10, 0x5d, 0xde, 0x35, - 0xca, 0x20, 0xf7, 0xad, 0xf1, 0xb9, 0x65, 0xda, 0xfd, 0x13, 0x80, 0x51, 0x0c, 0x25, 0xd4, 0xbd, - 0x61, 0x0f, 0x6f, 0x28, 0xd4, 0x0e, 0x1a, 0x00, 0xd1, 0x34, 0x95, 0xcd, 0x15, 0x49, 0xad, 0xc1, - 0x13, 0xd4, 0x61, 0x8e, 0xd4, 0xb9, 0x52, 0x62, 0xa7, 0x28, 0xd3, 0xfa, 0xdc, 0xb2, 0x61, 0xbe, - 0x88, 0x02, 0xa9, 0xc2, 0xa6, 0xf9, 0x0d, 0xba, 0x54, 0x2d, 0xb3, 0x3a, 0x36, 0x4c, 0x6c, 0x19, - 0xd7, 0xa0, 0x9e, 0x7d, 0xb9, 0xf4, 0x3c, 0x0c, 0xce, 0x99, 0x73, 0xf3, 0x49, 0xb5, 0x71, 0xab, - 0xd4, 0xb6, 0xe5, 0x9d, 0x71, 0x14, 0xc0, 0x1f, 0x54, 0x06, 0xa8, 0x82, 0xfd, 0x16, 0x20, 0xe7, - 0x63, 0xb4, 0xb7, 0x02, 0xb3, 0xc8, 0x13, 0x6e, 0x83, 0x5a, 0x31, 0x57, 0xd5, 0xf1, 0x08, 0x67, - 0x19, 0x0c, 0xfd, 0x64, 0x64, 0xf8, 0x13, 0xe6, 0xd6, 0xb4, 0x65, 0xbb, 0x16, 0x89, 0xed, 0xb2, - 0xdd, 0xea, 0x79, 0xd1, 0xa7, 0xa0, 0x28, 0x7a, 0x08, 0x31, 0xd7, 0xdd, 0x8a, 0x69, 0x0f, 0x0f, - 0x2b, 0x7d, 0xc4, 0x38, 0x5c, 0x21, 0x0b, 0x60, 0xd1, 0xd3, 0x5b, 0xd3, 0x36, 0xe1, 0x1f, 0x79, - 0xfb, 0xd8, 0x5a, 0x49, 0x15, 0x91, 0xf0, 0x4d, 0x63, 0x0f, 0x28, 0x70, 0x8a, 0xbe, 0x46, 0x3b, - 0xf1, 0xba, 0xf4, 0xc6, 0x52, 0xc5, 0x4d, 0x52, 0xdc, 0x09, 0x29, 0xc3, 0x4f, 0x9f, 0x42, 0xc0, - 0x41, 0x21, 0x20, 0x93, 0x10, 0xb0, 0x82, 0x5a, 0xff, 0x95, 0xfd, 0x09, 0xff, 0xbf, 0xca, 0x73, - 0x9c, 0x0c, 0x4c, 0x9a, 0xe8, 0x05, 0xe1, 0xeb, 0xb6, 0xb2, 0x50, 0x70, 0x38, 0xe0, 0x05, 0xc9, - 0xde, 0xb8, 0x52, 0x74, 0x5b, 0x69, 0x14, 0x9b, 0xb5, 0x1e, 0x2f, 0xe9, 0x5f, 0xc2, 0xb8, 0xa7, - 0xfc, 0x5c, 0x5a, 0xcd, 0xd5, 0x43, 0x09, 0x83, 0x6a, 0xfd, 0xed, 0x67, 0x00, 0x6e, 0x61, 0xbd, - 0x81, 0xef, 0x9d, 0x22, 0x82, 0xa2, 0x08, 0x9b, 0x88, 0x21, 0x1e, 0x5f, 0x15, 0xc1, 0x14, 0x7b, - 0x68, 0xda, 0xd7, 0xbb, 0xf3, 0x6f, 0x7c, 0xd8, 0x58, 0x82, 0x45, 0x4f, 0xa0, 0x3d, 0x2d, 0x4c, - 0x94, 0xd4, 0x42, 0xeb, 0x74, 0x56, 0xf3, 0x23, 0xb7, 0xff, 0xc2, 0x1a, 0x9f, 0x50, 0xac, 0x20, - 0x6c, 0xdd, 0xb2, 0x57, 0xf3, 0xf1, 0x40, 0xbe, 0x9e, 0x38, 0xb8, 0xe3, 0x4f, 0x4f, 0x5d, 0x77, - 0x35, 0xa7, 0x94, 0x23, 0xf7, 0x04, 0x53, 0x9c, 0x17, 0x4a, 0x0a, 0x54, 0xd0, 0x88, 0x1f, 0x31, - 0x04, 0x9a, 0x44, 0x66, 0xb4, 0xda, 0xee, 0x74, 0x95, 0x62, 0xbc, 0xc2, 0xd5, 0x3c, 0xc7, 0xa8, - 0x9e, 0xce, 0x77, 0xb6, 0x71, 0xe6, 0x7c, 0x87, 0x46, 0xd1, 0x96, 0xfd, 0xb2, 0x2f, 0x42, 0xb7, - 0x01, 0x0a, 0x24, 0xc7, 0x3b, 0xa5, 0x05, 0x60, 0xb8, 0xfc, 0x8d, 0x58, 0x55, 0xee, 0xeb, 0x1f, - 0xbf, 0xb7, 0x6f, 0x77, 0xa2, 0xd2, 0xc8, 0x6f, 0x5b, 0x11, 0x3d, 0x48, 0xeb, 0x01, 0xf7, 0x6b, - 0x60, 0x33, 0x95, 0xdc, 0x7f, 0x13, 0x85, 0xc0, 0xc4, 0xac, 0x8d, 0xdf, 0xd8, 0x2c, 0x8a, 0x32, - 0x93, 0xcc, 0x13, 0x3a, 0xbc, 0x1b, 0xb0, 0x6b, 0xab, 0xf1, 0x12, 0x57, 0x40, 0xd3, 0xba, 0x66, - 0x4f, 0xdc, 0x49, 0xca, 0x1e, 0x5f, 0x6a, 0x5d, 0x4e, 0xd0, 0xd1, 0xdd, 0x16, 0xa4, 0x24, 0x5c, - 0xcb, 0x8d, 0xe4, 0x78, 0x2e, 0xad, 0x2f, 0xee, 0x2c, 0x6f, 0xbf, 0xec, 0xeb, 0x25, 0x76, 0x76, - 0x24, 0xbb, 0x62, 0xd7, 0x2a, 0x5a, 0x2e, 0x2b, 0x15, 0xd1, 0x9c, 0x16, 0x82, 0x75, 0x19, 0x18, - 0x88, 0x5f, 0xd6, 0x3c, 0xf2, 0x4b, 0x07, 0x87, 0xcb, 0xca, 0xa5, 0x4e, 0x12, 0x5d, 0xac, 0x00, - 0x63, 0xa3, 0x5c, 0xe5, 0x60, 0x08, 0x27, 0x26, 0xae, 0x72, 0x58, 0xf5, 0x2a, 0x87, 0xcb, 0xd9, - 0xdb, 0x2f, 0x72, 0xea, 0xe6, 0x42, 0x35, 0x6f, 0x51, 0x7c, 0x5a, 0x76, 0x86, 0x84, 0x84, 0x5a, - 0xee, 0xf9, 0x74, 0x32, 0x0b, 0xcf, 0x26, 0xe6, 0x6a, 0x61, 0x20, 0x47, 0xd2, 0x16, 0x93, 0x72, - 0x1c, 0x1e, 0x92, 0x7b, 0x76, 0xd7, 0x95, 0xba, 0xa3, 0xe4, 0xac, 0x5d, 0x65, 0x14, 0xd5, 0x2e, - 0x00, 0x55, 0x11, 0xab, 0xe1, 0x79, 0xb4, 0x6f, 0x8f, 0x39, 0xfa, 0x60, 0xd7, 0xc6, 0xe0, 0x68, - 0x71, 0x19, 0xb7, 0xc4, 0x1b, 0xc0, 0x2e, 0x57, 0x43, 0x0e, 0x3c, 0xc6, 0x43, 0xb5, 0x7e, 0x1b, - 0x3a, 0x36, 0x54, 0x7b, 0x99, 0x5b, 0x75, 0x72, 0xd6, 0xd2, 0xc3, 0xa0, 0x69, 0x9d, 0x51, 0x5d, - 0xed, 0x70, 0x73, 0x4f, 0xa8, 0xfe, 0x7e, 0x82, 0x9c, 0x98, 0x8b, 0xa1, 0xa5, 0xb8, 0xed, 0x5b, - 0xd8, 0x60, 0xed, 0x19, 0xe6, 0x85, 0x2b, 0x95, 0x50, 0x77, 0xa5, 0xf2, 0x27, 0x6c, 0x33, 0xbf, - 0xd2, 0x28, 0x53, 0x9b, 0xf2, 0x7a, 0xe4, 0x2a, 0x75, 0xc2, 0x42, 0x58, 0x2c, 0x9b, 0x9d, 0x87, - 0xdd, 0xf5, 0xa4, 0x36, 0x17, 0x47, 0x7d, 0x98, 0x8d, 0x61, 0xf6, 0xd7, 0x45, 0xa5, 0x51, 0xce, - 0x73, 0x72, 0x5e, 0xa7, 0x38, 0x11, 0x21, 0x0c, 0x0f, 0x34, 0xe6, 0xef, 0x71, 0xcc, 0x92, 0x37, - 0xc0, 0x09, 0xa3, 0x1b, 0x2f, 0xe1, 0xe4, 0x43, 0x9a, 0x2c, 0xb6, 0x3b, 0x70, 0xa8, 0x79, 0xcb, - 0x53, 0xbc, 0x99, 0x14, 0x37, 0xb0, 0x52, 0xb8, 0xd3, 0x2f, 0xae, 0x62, 0x85, 0x3b, 0xca, 0xc2, - 0x5e, 0x5c, 0x6f, 0x4e, 0x7f, 0xab, 0x3b, 0x93, 0x53, 0xbd, 0xc7, 0xf9, 0xa8, 0xbf, 0x7b, 0x2e, - 0xe5, 0x47, 0x23, 0x5f, 0xfa, 0x4f, 0x4a, 0xdd, 0x10, 0x2d, 0xbd, 0xd2, 0x46, 0x4f, 0x71, 0xe4, - 0xb4, 0x4a, 0xc8, 0x51, 0x3e, 0xb2, 0xfb, 0x4c, 0x1f, 0x7e, 0x21, 0x9b, 0xc9, 0x50, 0x30, 0x34, - 0x49, 0xab, 0xa6, 0xf8, 0xe6, 0x30, 0x6d, 0xf4, 0xcc, 0xa9, 0x5c, 0x1f, 0x6a, 0x9e, 0x02, 0x95, - 0xc9, 0xfe, 0x92, 0x69, 0x15, 0xfe, 0x5a, 0x04, 0x5d, 0x8f, 0x3a, 0xe9, 0xc2, 0xf1, 0x20, 0x8a, - 0x16, 0x96, 0xd1, 0x7c, 0xa3, 0x71, 0xf8, 0x32, 0x6e, 0x87, 0xf0, 0xc3, 0x83, 0x46, 0xb6, 0xda, - 0x75, 0xa3, 0xf2, 0xd2, 0xc8, 0xd7, 0xb2, 0x6b, 0x13, 0xef, 0x3c, 0x2a, 0x76, 0xf2, 0x65, 0xa1, - 0x9d, 0x17, 0xf0, 0xe4, 0x1d, 0x5e, 0xa9, 0x43, 0x71, 0x25, 0xa0, 0xd6, 0x52, 0x31, 0x76, 0x1d, - 0xca, 0x04, 0xfe, 0x6a, 0xb3, 0x31, 0x12, 0x7a, 0x02, 0x11, 0xb0, 0x63, 0x78, 0x69, 0x95, 0x79, - 0x86, 0xe8, 0x53, 0xa3, 0xee, 0xdf, 0x42, 0x69, 0xed, 0xe9, 0x29, 0x3c, 0x70, 0x35, 0x8b, 0xca, - 0x89, 0xea, 0xd0, 0x63, 0xa8, 0xb1, 0x29, 0x28, 0xa2, 0x6d, 0x9f, 0x97, 0xe2, 0x34, 0x6a, 0x51, - 0x04, 0xd5, 0x33, 0xb9, 0x5c, 0x0f, 0x24, 0x94, 0x76, 0xf1, 0x32, 0xd4, 0x5d, 0x68, 0x8d, 0x5a, - 0x86, 0xa2, 0x08, 0x4d, 0xdd, 0xd4, 0x76, 0x5c, 0x37, 0xad, 0x4b, 0x08, 0x9b, 0xdc, 0x45, 0xf0, - 0xd2, 0x15, 0xe1, 0x2f, 0x9e, 0xaf, 0x36, 0x57, 0xc9, 0x08, 0xad, 0x5c, 0xf5, 0xf1, 0x11, 0x12, - 0xdc, 0x50, 0x78, 0xd2, 0x24, 0x6d, 0x56, 0x6a, 0x55, 0x2e, 0x7c, 0x66, 0xc1, 0x26, 0xe9, 0x58, - 0x3b, 0xed, 0xfc, 0x76, 0x64, 0x53, 0x2f, 0x4e, 0x78, 0xd6, 0x9c, 0x7b, 0x31, 0xfc, 0x9f, 0x37, - 0xf5, 0x1b, 0xb5, 0x2e, 0xba, 0x0b, 0xf6, 0x00, 0x37, 0x24, 0xa6, 0x65, 0x7f, 0x70, 0x4f, 0xc9, - 0xc7, 0xe1, 0x9d, 0x88, 0xa8, 0xea, 0x3a, 0xf6, 0xbd, 0xc3, 0xed, 0xd9, 0xb9, 0xa1, 0xd9, 0x25, - 0x45, 0xab, 0xe0, 0x43, 0x56, 0x68, 0x9a, 0x4d, 0xc8, 0xcd, 0xf5, 0x4b, 0xa7, 0x0d, 0x5c, 0x4d, - 0x6b, 0xf1, 0x91, 0x42, 0xd8, 0xa6, 0x93, 0x6a, 0x02, 0x6c, 0xb4, 0xa1, 0xe2, 0x04, 0x6e, 0xe5, - 0xa5, 0xef, 0x92, 0x88, 0x36, 0x24, 0xd6, 0x52, 0x86, 0x59, 0xae, 0x3b, 0xa9, 0xc4, 0x53, 0x9a, - 0xbb, 0x8f, 0x29, 0xc3, 0xb8, 0x40, 0xd9, 0x42, 0x9c, 0x0b, 0x34, 0x88, 0x78, 0xe8, 0xab, 0xd4, - 0xc4, 0xfc, 0xa6, 0x08, 0x7b, 0x48, 0xd1, 0x67, 0x6b, 0x3e, 0x9d, 0x8a, 0x4b, 0x3e, 0x8a, 0x97, - 0xa3, 0x79, 0x5e, 0x92, 0xd8, 0xa3, 0xc8, 0x02, 0x38, 0x42, 0xcc, 0x5a, 0xda, 0x29, 0x94, 0xf0, - 0xac, 0xa7, 0x27, 0x75, 0x18, 0x59, 0xe5, 0x3d, 0xc4, 0xa8, 0xd8, 0x30, 0x99, 0x72, 0xaa, 0xa0, - 0x36, 0xf4, 0x72, 0xf5, 0xcf, 0x62, 0x62, 0xb7, 0x06, 0xed, 0x9d, 0xf3, 0xd3, 0x14, 0x30, 0x14, - 0x5f, 0x98, 0x2b, 0x61, 0xf8, 0xf7, 0x31, 0x8a, 0x77, 0x04, 0xce, 0xed, 0xc8, 0xe5, 0x72, 0x2c, - 0xcd, 0x19, 0x27, 0xdd, 0x8e, 0xf2, 0x29, 0xe1, 0x39, 0x0e, 0x0f, 0x2b, 0x53, 0x53, 0xeb, 0x69, - 0xe8, 0x66, 0xc7, 0xf7, 0x8e, 0x0c, 0x8b, 0x4a, 0x67, 0x6c, 0x88, 0x61, 0x50, 0x8f, 0x3a, 0xe9, - 0xf7, 0x61, 0xef, 0xae, 0x8c, 0x08, 0x34, 0x28, 0x42, 0x92, 0xf1, 0xde, 0x8e, 0x1d, 0x38, 0xcd, - 0xcf, 0xc9, 0xef, 0x11, 0x4f, 0x38, 0x3f, 0x81, 0x04, 0x72, 0x84, 0x14, 0x8c, 0xbb, 0xfd, 0xc1, - 0xe1, 0xe1, 0xd7, 0x0e, 0x1e, 0xb6, 0x2d, 0x9f, 0x3e, 0xa8, 0x7a, 0x67, 0x7c, 0xe0, 0x63, 0x14, - 0x95, 0x07, 0x6e, 0xff, 0x38, 0xa8, 0x06, 0x0a, 0x16, 0xf1, 0x83, 0xdb, 0x63, 0x0c, 0x2f, 0x31, - 0xe2, 0xcd, 0xbe, 0x33, 0xdd, 0x87, 0x8a, 0xc4, 0xb6, 0x51, 0x26, 0x9c, 0x1b, 0x38, 0x3d, 0xde, - 0xb9, 0x35, 0x73, 0xfb, 0x2f, 0xb4, 0xdb, 0x2f, 0x88, 0x3e, 0x69, 0xbe, 0xdf, 0x60, 0xae, 0x3f, - 0x6a, 0xe3, 0x2e, 0x80, 0xee, 0x36, 0xe5, 0x8a, 0xfe, 0xcc, 0x50, 0x23, 0x77, 0x94, 0x02, 0x33, - 0x8f, 0x63, 0xa4, 0xa8, 0x43, 0x47, 0x66, 0x7c, 0x8f, 0x96, 0xb3, 0x22, 0x6d, 0xb6, 0x6a, 0x34, - 0x0d, 0x8a, 0xb2, 0x4a, 0x25, 0xa2, 0x18, 0x5a, 0x75, 0xa1, 0x10, 0xed, 0xd8, 0x3d, 0x2d, 0xeb, - 0xc8, 0xe2, 0xb2, 0x5e, 0x2d, 0x20, 0x77, 0xdd, 0x5c, 0x92, 0xa7, 0x33, 0x8a, 0xce, 0x4c, 0xb1, - 0xe5, 0x28, 0xe1, 0x95, 0x2b, 0x20, 0xf3, 0x55, 0x4b, 0x8c, 0x2e, 0x8c, 0xf6, 0x6d, 0x8b, 0x2c, - 0x96, 0x78, 0x70, 0x8b, 0xf7, 0xbb, 0xf3, 0x3e, 0xc5, 0x88, 0x3d, 0x90, 0x41, 0xab, 0x0f, 0x48, - 0x3c, 0x21, 0x52, 0x71, 0x36, 0xf9, 0xf3, 0xd3, 0xd3, 0xdd, 0xd8, 0x55, 0x12, 0xf9, 0x0b, 0x6e, - 0x5b, 0x19, 0xc0, 0x9a, 0xe2, 0x73, 0x89, 0xfa, 0x26, 0xa2, 0x99, 0x83, 0xfe, 0x50, 0xb4, 0x84, - 0xbe, 0xa6, 0x8a, 0x3e, 0x97, 0xcd, 0xd7, 0xa3, 0xb7, 0xb7, 0x7a, 0x7b, 0xc2, 0xa0, 0xe5, 0x8a, - 0xa5, 0x15, 0xaf, 0x62, 0x97, 0xdb, 0xa6, 0xa8, 0x64, 0xa4, 0x57, 0xb4, 0x16, 0x6e, 0x11, 0x5f, - 0xbb, 0x0c, 0x3d, 0x37, 0x5f, 0x43, 0xbe, 0x89, 0xe9, 0x20, 0x25, 0xbe, 0xc9, 0x22, 0xf3, 0x6b, - 0x17, 0xb8, 0xd8, 0x1f, 0x5c, 0xc8, 0x2f, 0x7b, 0x88, 0xfc, 0x3a, 0x54, 0x7d, 0x8a, 0x7f, 0xc8, - 0x53, 0x04, 0x40, 0xb9, 0xb2, 0xba, 0x45, 0x08, 0x3e, 0xe5, 0x84, 0x38, 0x3c, 0x34, 0x23, 0x4a, - 0x44, 0x51, 0x15, 0xf0, 0x08, 0xd1, 0x12, 0xd3, 0x0e, 0x74, 0x7f, 0xba, 0xea, 0xb5, 0xf4, 0x9a, - 0x01, 0x4a, 0xfe, 0x91, 0xb1, 0x18, 0x48, 0xae, 0x6e, 0xb7, 0xcb, 0xa9, 0xae, 0x03, 0x49, 0xea, - 0x16, 0x4c, 0xf2, 0x88, 0x1f, 0x75, 0x78, 0x29, 0x24, 0x5d, 0x6c, 0x43, 0x3e, 0xa5, 0x07, 0x87, - 0x87, 0xc5, 0x4b, 0x68, 0x95, 0x51, 0xc7, 0x78, 0x94, 0xe1, 0xd0, 0x2a, 0x3f, 0xc2, 0x29, 0x63, - 0x4d, 0xe8, 0xb0, 0x79, 0x7a, 0xd2, 0x39, 0x0b, 0x66, 0x3f, 0x42, 0xea, 0xf0, 0x11, 0x45, 0x3f, - 0x4a, 0xb7, 0x20, 0xcd, 0xa6, 0x52, 0xd6, 0xb0, 0x31, 0x3f, 0x7e, 0xcb, 0x0b, 0xd9, 0x43, 0x6d, - 0x3c, 0x39, 0xc7, 0x10, 0xad, 0x38, 0x28, 0x34, 0x6d, 0xd8, 0x55, 0x02, 0x7b, 0xd4, 0xa3, 0xc5, - 0x03, 0xb9, 0x05, 0xe5, 0xb9, 0x21, 0xa5, 0x8c, 0x97, 0x5e, 0xcf, 0xb5, 0x8e, 0xf0, 0x8a, 0x3c, - 0xba, 0x83, 0xca, 0x10, 0x5f, 0xb4, 0x67, 0x54, 0x43, 0xd5, 0x6f, 0xcf, 0x49, 0x55, 0x46, 0x1b, - 0xc8, 0x87, 0x67, 0xca, 0x8e, 0x7c, 0x9b, 0x78, 0x57, 0xb6, 0x32, 0xea, 0xbd, 0xcc, 0xf7, 0xbf, - 0xce, 0x7b, 0x80, 0xe5, 0xfd, 0x38, 0x1b, 0x1b, 0xe7, 0x3d, 0x74, 0xe1, 0x83, 0x7f, 0x57, 0xd9, - 0x3a, 0x18, 0x1b, 0xff, 0x0f, 0x49, 0xe3, 0x72, 0x63, 0xcd, 0x85, 0x01, 0x00 + 0x41, 0x3d, 0x17, 0xd9, 0xfd, 0xfb, 0x11, 0xf7, 0x8d, 0x1e, 0x54, 0xe4, 0x0b, 0x53, 0x6e, 0x14, + 0xcf, 0x65, 0xd9, 0xcb, 0x7b, 0xf3, 0xea, 0x53, 0x21, 0xe3, 0x8e, 0xbb, 0xcb, 0xfb, 0xdc, 0xbc, + 0x82, 0x63, 0x7f, 0x33, 0xd9, 0x14, 0x9b, 0xe2, 0xc0, 0x19, 0x16, 0x82, 0xcf, 0x84, 0x05, 0xe4, + 0x5d, 0x82, 0xea, 0x5e, 0xd7, 0xea, 0x36, 0xbb, 0xd2, 0x17, 0x22, 0xf0, 0x8a, 0xeb, 0xc3, 0xc3, + 0xb5, 0x22, 0xc3, 0xe0, 0xce, 0x61, 0x25, 0x9d, 0x42, 0x39, 0x28, 0x72, 0x71, 0xbd, 0x83, 0x74, + 0x0d, 0xf5, 0x49, 0x11, 0xed, 0x97, 0xfd, 0x02, 0x90, 0x5b, 0x2a, 0x75, 0x7a, 0x8b, 0x85, 0x5a, + 0xa1, 0xea, 0x89, 0x11, 0xcb, 0xd8, 0xde, 0xb6, 0xb1, 0xcb, 0xdb, 0x40, 0x6d, 0x02, 0x20, 0x91, + 0x5a, 0x52, 0x46, 0x6f, 0x77, 0xd6, 0xae, 0xb7, 0x65, 0xa0, 0xd6, 0xae, 0x71, 0xd6, 0xbb, 0xd1, + 0x30, 0xc2, 0xb2, 0xe1, 0xd6, 0xd1, 0xc1, 0x09, 0xb2, 0xec, 0xa6, 0xf3, 0x24, 0x0a, 0x02, 0x58, + 0xf1, 0xe8, 0xbf, 0x7d, 0x76, 0xd7, 0x79, 0x9c, 0xb1, 0x95, 0x77, 0xeb, 0x63, 0x68, 0x32, 0x69, + 0x56, 0x4f, 0x28, 0x1e, 0x30, 0x3c, 0x90, 0x2a, 0x40, 0xeb, 0xf2, 0x20, 0x29, 0x64, 0x42, 0x4d, + 0x66, 0x14, 0xe2, 0x99, 0x93, 0x14, 0xb7, 0xa4, 0xb3, 0xcc, 0x45, 0x83, 0xc5, 0x07, 0x2e, 0x12, + 0x74, 0x86, 0xf0, 0xb1, 0x14, 0x94, 0x52, 0xdc, 0x41, 0x63, 0x59, 0xa8, 0x6f, 0x1f, 0x68, 0x42, + 0xc1, 0x7e, 0x1f, 0x73, 0xa3, 0x7d, 0x01, 0x0b, 0xa3, 0xcd, 0xf5, 0xca, 0x48, 0x63, 0x6f, 0xce, + 0xd0, 0x6c, 0x2e, 0x45, 0xb3, 0x45, 0x6e, 0xf6, 0x50, 0x29, 0x32, 0xc0, 0x22, 0x1f, 0x57, 0x4c, + 0x12, 0xe5, 0x6c, 0x51, 0x98, 0x1a, 0x46, 0x2c, 0xa5, 0x46, 0xd9, 0x3d, 0x4e, 0x83, 0x5e, 0xec, + 0x25, 0x16, 0x7b, 0xa5, 0xf4, 0xc4, 0xa0, 0x6e, 0x1b, 0x2b, 0x2f, 0x35, 0xa2, 0x39, 0x20, 0x7e, + 0x14, 0x86, 0xe7, 0x2a, 0x69, 0x45, 0x9f, 0xcd, 0x23, 0x31, 0xbe, 0x23, 0x73, 0x08, 0x2f, 0xb7, + 0x64, 0x94, 0x51, 0x62, 0xa4, 0xfc, 0x4f, 0x8b, 0x71, 0x85, 0x57, 0x0a, 0xe4, 0xed, 0x0b, 0x99, + 0xcd, 0x63, 0x14, 0x0e, 0xb9, 0xcb, 0x52, 0xfc, 0xc5, 0x9b, 0xfd, 0x12, 0x69, 0x97, 0x28, 0xa5, + 0xef, 0x7c, 0xbf, 0x03, 0xdb, 0xa3, 0x6f, 0x10, 0x85, 0x71, 0xa9, 0xb5, 0xfa, 0x21, 0xd0, 0xac, + 0xf7, 0x3b, 0xe4, 0x1e, 0x15, 0x7e, 0xac, 0xc9, 0x14, 0x29, 0x8c, 0x44, 0xb8, 0x6f, 0xe8, 0x1a, + 0x7f, 0xa0, 0x2d, 0x28, 0xb7, 0xe6, 0x27, 0x53, 0x4c, 0xc2, 0xdc, 0xc0, 0xf8, 0xe1, 0x21, 0x3b, + 0x06, 0xea, 0x00, 0x18, 0x82, 0xa1, 0x19, 0x2d, 0x97, 0x66, 0x4e, 0xa9, 0x78, 0xd4, 0x42, 0xe2, + 0x2d, 0x39, 0x77, 0xf6, 0x96, 0x19, 0xfc, 0xe5, 0x1f, 0xe0, 0x88, 0xce, 0x51, 0xe3, 0x8e, 0x04, + 0xcf, 0xd3, 0xa1, 0xc9, 0x5b, 0x59, 0x30, 0x6a, 0x07, 0x6d, 0x0a, 0xf0, 0xa6, 0x41, 0xed, 0x33, + 0x94, 0x19, 0xe2, 0x6c, 0x40, 0xa7, 0xf2, 0xbc, 0xd6, 0x7f, 0x94, 0x4f, 0xe8, 0x23, 0x28, 0x9d, + 0xb7, 0xca, 0x27, 0x6b, 0x62, 0xfe, 0x4a, 0x77, 0x1a, 0xd4, 0x7d, 0x34, 0x3e, 0x25, 0x73, 0xd0, + 0x90, 0x65, 0x77, 0x51, 0x72, 0xc3, 0x87, 0x03, 0x6c, 0x87, 0x81, 0xf9, 0x11, 0xf0, 0xc8, 0xc0, + 0x14, 0x88, 0x9c, 0x2e, 0x74, 0xfd, 0x23, 0x3e, 0xf3, 0x61, 0x93, 0xc9, 0xe9, 0xee, 0x7a, 0x8c, + 0x20, 0x0a, 0xaf, 0x21, 0x13, 0xd6, 0xd6, 0x35, 0xa5, 0x8b, 0x9a, 0x47, 0xa4, 0x30, 0x86, 0x8f, + 0x48, 0x62, 0x0c, 0x65, 0xbf, 0xf2, 0x7c, 0xa4, 0x50, 0x83, 0xc4, 0x18, 0x11, 0x1d, 0x92, 0x20, + 0x6d, 0x58, 0x74, 0x7e, 0xc7, 0x02, 0x22, 0x75, 0x7a, 0x8b, 0xdb, 0x19, 0x4e, 0x5c, 0xf2, 0x69, + 0x8b, 0xbf, 0x4d, 0x6c, 0xbd, 0xc8, 0x57, 0x23, 0xfb, 0x30, 0x7f, 0xd5, 0xb7, 0x62, 0xe1, 0x56, + 0x67, 0x52, 0xbb, 0x3f, 0x91, 0xf5, 0xc0, 0xd2, 0x15, 0xcf, 0xe6, 0x68, 0x7b, 0x7b, 0xc9, 0x9c, + 0xb7, 0x82, 0x11, 0x27, 0x67, 0xe8, 0xf3, 0x74, 0x16, 0x78, 0xe1, 0xcd, 0x16, 0xa1, 0x53, 0x55, + 0xc6, 0x85, 0x3d, 0x54, 0xc4, 0x4d, 0xdc, 0x33, 0x4c, 0x75, 0x26, 0x88, 0xec, 0xc4, 0x59, 0x20, + 0xf2, 0x56, 0x3a, 0x7b, 0x11, 0x97, 0x44, 0x76, 0x47, 0x7a, 0xfa, 0xe5, 0x7f, 0xad, 0x0a, 0xb9, + 0x2a, 0xc9, 0xd9, 0x46, 0xff, 0x5f, 0x90, 0xde, 0x20, 0xa0, 0xe1, 0x15, 0xed, 0x25, 0x96, 0x69, + 0x1d, 0xe9, 0xbb, 0xca, 0x40, 0x79, 0x8d, 0xe5, 0x50, 0x6b, 0xdb, 0x55, 0x5c, 0x79, 0x49, 0x92, + 0x5b, 0x1d, 0x79, 0xe9, 0xc1, 0x58, 0x3c, 0x34, 0xb4, 0x1a, 0x6a, 0x94, 0xbf, 0x3a, 0x16, 0x2a, + 0xf1, 0xe7, 0x06, 0x23, 0xd9, 0x0a, 0x75, 0x40, 0xa2, 0x5a, 0x65, 0xf1, 0x8a, 0xf5, 0x51, 0xae, + 0xf0, 0xca, 0x61, 0xae, 0xbd, 0x1b, 0x24, 0x3e, 0x0b, 0x3c, 0x48, 0x77, 0xbd, 0x9a, 0xba, 0xe2, + 0x16, 0x99, 0x2f, 0x57, 0xf3, 0x53, 0x32, 0x1f, 0xf7, 0xcb, 0x0b, 0x86, 0x51, 0x76, 0x2e, 0x39, + 0x32, 0x1e, 0xce, 0x9e, 0x9b, 0x0a, 0x87, 0xed, 0xa6, 0x2b, 0x0d, 0x36, 0x97, 0x21, 0xbb, 0x53, + 0x72, 0x18, 0x1f, 0xd8, 0x9d, 0x54, 0x76, 0x34, 0xf4, 0x96, 0x9b, 0x8c, 0x3e, 0x4a, 0x23, 0xf2, + 0x9a, 0x4d, 0x46, 0x61, 0x84, 0x51, 0xaa, 0xc4, 0xb7, 0x6a, 0xe4, 0x6b, 0xea, 0xf2, 0x7b, 0x5b, + 0x5d, 0xec, 0x69, 0x76, 0xa1, 0xe9, 0xe5, 0xef, 0x6a, 0x69, 0x3f, 0x1d, 0x78, 0x75, 0x56, 0xbe, + 0xce, 0x08, 0x83, 0xb5, 0xaa, 0xc4, 0xab, 0x75, 0x57, 0x75, 0xf6, 0xff, 0x92, 0x0e, 0x7f, 0x8d, + 0x55, 0x86, 0x92, 0xf6, 0xe5, 0xbd, 0xd6, 0xec, 0x22, 0x34, 0xad, 0x7e, 0x5d, 0xad, 0xbf, 0xad, + 0xc3, 0xa4, 0xde, 0xaf, 0x4c, 0x24, 0xcb, 0xe9, 0xf2, 0xec, 0x8b, 0xb4, 0xd1, 0x81, 0x22, 0x0a, + 0x96, 0x9a, 0x4a, 0xba, 0xda, 0x42, 0xa3, 0x76, 0xba, 0x36, 0xa4, 0x91, 0xa1, 0x68, 0xf3, 0x8e, + 0x9a, 0xd4, 0xd6, 0x6b, 0xfa, 0xcb, 0xd3, 0xd1, 0x7e, 0xda, 0xc7, 0x6a, 0x0c, 0xae, 0xb2, 0x8d, + 0xc7, 0x7d, 0x55, 0x97, 0x9b, 0xbd, 0x37, 0x90, 0x4d, 0xfe, 0xb1, 0xaf, 0x0c, 0xac, 0x40, 0x43, + 0x08, 0x33, 0xfa, 0x64, 0x51, 0x56, 0x54, 0x4b, 0xa4, 0x61, 0xf5, 0x5f, 0x78, 0x34, 0xac, 0x57, + 0x8b, 0x85, 0xc4, 0x04, 0x85, 0xee, 0x06, 0x6e, 0xf8, 0x67, 0x3a, 0x6a, 0xbb, 0x28, 0x95, 0x61, + 0xa6, 0x64, 0x47, 0xd8, 0xaa, 0x52, 0x19, 0x67, 0xf7, 0x99, 0x21, 0x20, 0x54, 0x31, 0x21, 0xcf, + 0x30, 0x8b, 0xb7, 0x41, 0x2f, 0x7c, 0xeb, 0x18, 0x6f, 0x4a, 0x5d, 0x22, 0xba, 0x10, 0x1c, 0x39, + 0x37, 0xee, 0x9e, 0x0c, 0x94, 0x8d, 0x03, 0x94, 0x99, 0xb4, 0x01, 0xe7, 0x96, 0x1b, 0x9a, 0x86, + 0xe6, 0x5b, 0x72, 0xba, 0x47, 0xaa, 0x32, 0xdd, 0xae, 0xd9, 0x2b, 0xdc, 0x5c, 0xa8, 0x80, 0x36, + 0x57, 0x9d, 0x1e, 0x18, 0x24, 0x73, 0x1b, 0x6e, 0xe9, 0x76, 0xaa, 0x75, 0xbb, 0xec, 0x95, 0xd2, + 0xa9, 0xc2, 0xc5, 0x4b, 0xae, 0x8c, 0xeb, 0x73, 0xd0, 0x34, 0xac, 0x9e, 0x62, 0x51, 0xa1, 0x43, + 0xff, 0x18, 0xb8, 0x79, 0xe4, 0x0f, 0x18, 0x49, 0x3f, 0xe1, 0x0c, 0x43, 0xca, 0x4b, 0xe9, 0x28, + 0x5f, 0x02, 0x4b, 0x29, 0xbe, 0x53, 0xfd, 0x9f, 0x4f, 0x97, 0xf9, 0x6b, 0xe1, 0x55, 0x04, 0x98, + 0x9b, 0x95, 0x21, 0x65, 0xde, 0xbf, 0x03, 0x07, 0x21, 0x64, 0x43, 0x22, 0x4d, 0xa8, 0xaf, 0xb4, + 0x1b, 0x07, 0xd0, 0xc0, 0xe6, 0x69, 0x76, 0xad, 0x59, 0x2c, 0xc2, 0xeb, 0x9b, 0x54, 0x98, 0xe8, + 0xcb, 0x36, 0xa1, 0x7e, 0x69, 0x1f, 0x20, 0x8c, 0x03, 0xf6, 0xb5, 0x0c, 0x90, 0xfa, 0x61, 0x4d, + 0x4b, 0x17, 0x47, 0x03, 0xd5, 0xf9, 0xc0, 0x40, 0x8c, 0xf3, 0xd5, 0xc5, 0x3b, 0xbc, 0x59, 0x41, + 0x97, 0x36, 0xc5, 0x91, 0x54, 0xba, 0x3f, 0xd1, 0x71, 0xa3, 0x52, 0xde, 0x8b, 0x7d, 0xcd, 0x27, + 0x0a, 0x6f, 0xbd, 0x69, 0x75, 0xe2, 0xa8, 0xaf, 0xb6, 0xdb, 0x17, 0xed, 0xee, 0x61, 0x80, 0xf1, + 0x8e, 0x07, 0x6b, 0x33, 0x66, 0x89, 0x74, 0x9b, 0x2f, 0x4d, 0x94, 0xb6, 0x4f, 0xb2, 0x3f, 0xa3, + 0x49, 0x16, 0x73, 0x28, 0x71, 0xcc, 0xbe, 0xc6, 0x15, 0x9a, 0x8e, 0xdd, 0x1e, 0xbd, 0xbc, 0x44, + 0xc0, 0x93, 0xc7, 0xfe, 0x0c, 0x65, 0x46, 0x7b, 0x76, 0x33, 0xfd, 0xcb, 0xba, 0xd9, 0x3c, 0xf1, + 0xb0, 0x57, 0xa9, 0x6f, 0xc0, 0xc3, 0xbc, 0xfb, 0xd1, 0x68, 0x3c, 0xec, 0xd4, 0x49, 0x5b, 0x54, + 0xcf, 0xb6, 0xe2, 0xb4, 0x2a, 0x7d, 0x65, 0x09, 0x38, 0xc5, 0xd3, 0x6e, 0x70, 0xe6, 0xd0, 0xe9, + 0xd7, 0x17, 0x5b, 0x99, 0xc3, 0x2e, 0x1b, 0xd6, 0x3d, 0x76, 0xe5, 0xe3, 0xd6, 0xfe, 0xf1, 0x91, + 0x34, 0xa3, 0x61, 0xbf, 0xe6, 0x4a, 0x07, 0x45, 0x00, 0x17, 0xd2, 0x9d, 0xc5, 0x76, 0x44, 0x2c, + 0xcf, 0x17, 0xb1, 0xa3, 0x68, 0x1e, 0xf8, 0x96, 0x4b, 0xd1, 0xd7, 0x51, 0x29, 0x4b, 0x30, 0x4b, + 0xe5, 0x3a, 0x65, 0xdb, 0x3f, 0xdb, 0xbb, 0x4f, 0x0b, 0x16, 0x5c, 0x74, 0x9e, 0x1d, 0xb1, 0xa3, + 0x67, 0x3b, 0xfb, 0x24, 0xcd, 0xaa, 0x7e, 0x14, 0x1e, 0x4e, 0xa8, 0xfd, 0xd2, 0x09, 0xd0, 0xf0, + 0xd9, 0x1e, 0xde, 0x84, 0x54, 0x9f, 0xa4, 0xe6, 0xf8, 0x0d, 0x29, 0xc0, 0x97, 0x55, 0xe4, 0x5b, + 0x76, 0x21, 0xfa, 0x18, 0x93, 0x08, 0x66, 0xc6, 0xad, 0x88, 0x8d, 0xb9, 0xb2, 0xfe, 0xf8, 0x9d, + 0x2f, 0x88, 0x56, 0x87, 0x9c, 0x0f, 0x1d, 0xdd, 0x02, 0x3c, 0xe1, 0x98, 0xa5, 0xcb, 0x22, 0xac, + 0x6c, 0x5a, 0x39, 0xda, 0x5a, 0x8f, 0xe2, 0xb8, 0x76, 0x10, 0x37, 0x3b, 0x4a, 0x11, 0xc0, 0xb1, + 0x93, 0xec, 0x46, 0xaa, 0x5b, 0xce, 0x65, 0x49, 0x46, 0xec, 0xa2, 0xa9, 0xbf, 0x7d, 0xe4, 0x07, + 0xb0, 0x23, 0xbd, 0xa1, 0x48, 0x35, 0xfc, 0xed, 0x3a, 0x03, 0xb1, 0x83, 0x1e, 0x6c, 0x2b, 0x54, + 0xc7, 0x97, 0x8c, 0xf5, 0xcb, 0x88, 0x8e, 0x62, 0xa5, 0xf7, 0x23, 0x3b, 0xde, 0x90, 0x27, 0xe9, + 0x2a, 0x64, 0x55, 0x08, 0x0f, 0x7e, 0xe2, 0xec, 0xf2, 0x7a, 0x47, 0x07, 0x55, 0x29, 0xbd, 0x1c, + 0xb5, 0x64, 0x8b, 0xfa, 0xd3, 0x5a, 0x50, 0x88, 0x9d, 0xd1, 0x24, 0x44, 0xd1, 0x41, 0x53, 0x51, + 0x2a, 0x32, 0x14, 0x35, 0x28, 0x2e, 0x26, 0xa5, 0x5d, 0x7d, 0xd1, 0x6f, 0x74, 0x6d, 0x30, 0xfc, + 0x74, 0x95, 0x57, 0xac, 0x3b, 0xb8, 0x41, 0x18, 0x59, 0xf3, 0xe0, 0xed, 0x02, 0x57, 0x47, 0x87, + 0xbc, 0xc1, 0x30, 0xc4, 0x60, 0x93, 0xe8, 0xa4, 0xae, 0x94, 0x9f, 0xa8, 0xd1, 0x41, 0x84, 0xeb, + 0x00, 0xa6, 0x7b, 0xb1, 0xa5, 0x56, 0xd0, 0x5a, 0x86, 0xa1, 0x48, 0x78, 0xd8, 0xae, 0xfa, 0xc5, + 0x94, 0xc9, 0xca, 0x73, 0x72, 0x7c, 0xab, 0x54, 0x9d, 0x89, 0xaa, 0xff, 0xed, 0x6a, 0x73, 0xa4, + 0x27, 0x77, 0x0e, 0xdc, 0xec, 0x42, 0x0e, 0x87, 0x6c, 0x00, 0xc4, 0xb5, 0xaf, 0x3a, 0x1a, 0x64, + 0x0b, 0x87, 0x99, 0x8d, 0x16, 0xe3, 0xc3, 0x30, 0xcf, 0x47, 0xdb, 0x54, 0xfe, 0xfe, 0xa3, 0x8a, + 0x73, 0x74, 0x2b, 0x84, 0xf6, 0xd8, 0x6e, 0x60, 0xf3, 0x67, 0xf8, 0xec, 0x7a, 0xb9, 0xba, 0x2c, + 0xa9, 0xaa, 0x29, 0x55, 0x0c, 0xf5, 0xb1, 0x34, 0xe5, 0x9b, 0x28, 0x72, 0x5b, 0x74, 0x7a, 0x17, + 0x32, 0xee, 0x97, 0x8f, 0x9c, 0xdf, 0xad, 0x37, 0x41, 0xe6, 0x03, 0x5d, 0x59, 0x5a, 0x26, 0xc2, + 0x17, 0xe1, 0x77, 0x0a, 0xc0, 0xef, 0x00, 0x9d, 0xfd, 0x97, 0x8e, 0x78, 0xae, 0x78, 0x64, 0x26, + 0x5e, 0xf3, 0xf1, 0xb1, 0xdd, 0x06, 0x1e, 0x38, 0x91, 0x0e, 0x5f, 0x7a, 0x7d, 0xed, 0xd1, 0x9a, + 0xb5, 0x1d, 0xac, 0x80, 0x9a, 0x68, 0x07, 0x2b, 0xf8, 0xb8, 0x15, 0xac, 0x7e, 0xf1, 0xb7, 0xd4, + 0xbc, 0xf6, 0xdb, 0x2b, 0x5e, 0xfb, 0xbb, 0xc0, 0x15, 0xad, 0xb6, 0xdb, 0xeb, 0x8e, 0xc2, 0xe1, + 0x81, 0x34, 0x06, 0x67, 0x57, 0x9a, 0x24, 0xb6, 0x34, 0xbb, 0x6e, 0x2f, 0x0e, 0x14, 0xdd, 0x70, + 0x37, 0x58, 0x90, 0x81, 0xa6, 0x54, 0x84, 0xad, 0x34, 0xf1, 0xcf, 0x0e, 0xe3, 0x31, 0x07, 0x1f, + 0x79, 0x1c, 0x59, 0x8c, 0xbd, 0xab, 0x42, 0x9a, 0x7e, 0x4f, 0xf2, 0xec, 0x1b, 0xae, 0xf1, 0x62, + 0x54, 0xaf, 0xac, 0x86, 0x62, 0x0e, 0x9e, 0xc9, 0x76, 0x86, 0x7b, 0x17, 0x95, 0x97, 0x3d, 0x70, + 0x10, 0xaa, 0x97, 0x3c, 0x23, 0x89, 0x56, 0x2b, 0xf7, 0x3c, 0xb2, 0x1a, 0xf5, 0x62, 0x2b, 0x43, + 0x95, 0xa7, 0xad, 0x17, 0x3e, 0xd5, 0x6a, 0xa6, 0x45, 0x35, 0xd5, 0x8b, 0x1f, 0xb6, 0xeb, 0xd2, + 0xa7, 0xbe, 0x14, 0xcb, 0xfb, 0xa1, 0xe2, 0xe6, 0xb6, 0x3a, 0xc3, 0xe2, 0x3a, 0xfe, 0x0b, 0xe7, + 0x59, 0xb7, 0x2b, 0x6b, 0xbc, 0x25, 0xdb, 0x67, 0xce, 0xf7, 0xa8, 0xe6, 0x4b, 0xe7, 0xbf, 0x62, + 0xf1, 0xf6, 0x27, 0x97, 0xa1, 0xc1, 0x7e, 0xee, 0x4f, 0xae, 0x06, 0x0c, 0x6c, 0xc8, 0xaa, 0x8b, + 0x80, 0xdb, 0xa8, 0xb8, 0x04, 0xda, 0xbe, 0x6b, 0xea, 0x4a, 0x11, 0xd6, 0xbf, 0xe3, 0xa2, 0x08, + 0xb7, 0x37, 0x5e, 0x28, 0x77, 0x9a, 0x36, 0x77, 0x7a, 0xbf, 0xbb, 0x83, 0xda, 0x75, 0xb4, 0xd5, + 0x80, 0x7c, 0x8a, 0x4b, 0xe8, 0xc6, 0x26, 0xfc, 0x3d, 0x9a, 0xa8, 0x5d, 0x63, 0x37, 0x35, 0xf3, + 0x3e, 0xaa, 0x23, 0x38, 0x8c, 0x16, 0xc7, 0xea, 0xd7, 0x4a, 0x6f, 0xfe, 0x28, 0x17, 0x21, 0x0e, + 0x86, 0xc7, 0xfd, 0x7c, 0xb4, 0x8f, 0x16, 0xc8, 0xe1, 0x61, 0xa7, 0xc3, 0x4b, 0x38, 0x36, 0xea, + 0x64, 0x0c, 0x1f, 0x81, 0x61, 0xda, 0xd6, 0x7b, 0x55, 0x37, 0xc4, 0x42, 0x4d, 0xa0, 0x1d, 0x99, + 0x59, 0x99, 0x19, 0xd5, 0x3d, 0x86, 0xbb, 0x17, 0x58, 0x99, 0x10, 0xeb, 0x6b, 0x41, 0x63, 0x2b, + 0x6c, 0x08, 0x9f, 0x83, 0x25, 0x99, 0x16, 0xa7, 0x30, 0xa3, 0x23, 0xe5, 0x38, 0x7e, 0x2f, 0xdc, + 0xc9, 0xc6, 0xd2, 0x4f, 0xb1, 0x14, 0x49, 0x09, 0xdf, 0x5a, 0xe4, 0x56, 0xcb, 0x6e, 0x25, 0xd3, + 0x88, 0x07, 0x84, 0xea, 0x3b, 0x07, 0x9d, 0xf8, 0xdd, 0x36, 0xda, 0xa2, 0xe2, 0xbc, 0x19, 0xe3, + 0x4e, 0xc4, 0xef, 0xce, 0xfb, 0xa8, 0x86, 0x0d, 0x05, 0xdb, 0x98, 0x55, 0x8b, 0xc2, 0xad, 0x74, + 0xe2, 0x0f, 0x6d, 0x84, 0x71, 0x76, 0x9f, 0x15, 0x55, 0x62, 0x55, 0x1f, 0x14, 0x8f, 0xcb, 0xf1, + 0x3b, 0x69, 0x34, 0xfd, 0xd8, 0x42, 0x50, 0x35, 0xd1, 0xd6, 0x40, 0xa9, 0xfa, 0xb3, 0x56, 0xef, + 0xd3, 0x33, 0x2d, 0x2f, 0xc6, 0xf3, 0x68, 0xcd, 0x9b, 0xea, 0x79, 0x4b, 0xca, 0x2f, 0x6c, 0x2b, + 0xe1, 0xc5, 0x7e, 0x79, 0x21, 0x92, 0x3c, 0x3c, 0x66, 0xaa, 0xa7, 0xce, 0x50, 0xfa, 0x16, 0x4e, + 0x49, 0x9f, 0x87, 0x1c, 0x3a, 0xbb, 0xa1, 0x2d, 0xd5, 0x87, 0xcf, 0x07, 0x52, 0x63, 0x07, 0x95, + 0x72, 0x3a, 0x7b, 0x7b, 0xc1, 0x96, 0xae, 0xad, 0xb5, 0x50, 0x1f, 0xe4, 0x5b, 0x58, 0x11, 0x45, + 0x19, 0x4b, 0x3f, 0x21, 0x3f, 0xf9, 0x14, 0x07, 0xc7, 0x07, 0x4a, 0xec, 0xfe, 0xd7, 0x65, 0xc7, + 0x7c, 0x34, 0xad, 0xf1, 0x71, 0xff, 0xcf, 0xb5, 0x7b, 0xf9, 0x10, 0x66, 0xde, 0xbd, 0xb8, 0xec, + 0x07, 0xc6, 0x70, 0xbe, 0x01, 0xd2, 0x6d, 0x6d, 0xe0, 0xc0, 0xd5, 0x1e, 0xf0, 0xb6, 0x1d, 0xd7, + 0x55, 0x9a, 0xe7, 0x5d, 0x36, 0xad, 0xbf, 0x64, 0xe0, 0x09, 0x5b, 0x02, 0xdc, 0xac, 0xe8, 0xd2, + 0x38, 0xf6, 0xae, 0x99, 0x31, 0x63, 0xc0, 0xca, 0x00, 0x75, 0x1a, 0x2d, 0xfc, 0xe5, 0x03, 0xee, + 0x10, 0xba, 0x79, 0x16, 0x62, 0x0a, 0x2b, 0xcf, 0xba, 0x11, 0x3a, 0xbe, 0xcc, 0xba, 0x31, 0xee, + 0x01, 0x37, 0x7e, 0x07, 0xb0, 0x00, 0x1b, 0xf7, 0x43, 0x11, 0xb5, 0xa2, 0xb1, 0x1b, 0x9f, 0x83, + 0x62, 0x81, 0xd3, 0xc2, 0x9f, 0x2d, 0x59, 0x22, 0x7c, 0x0e, 0xdc, 0x54, 0xf3, 0x5e, 0x2d, 0x7c, + 0x2c, 0xe3, 0x86, 0xe4, 0x5b, 0x31, 0x7e, 0xd7, 0xb4, 0x17, 0xa1, 0xd9, 0x68, 0xc2, 0x5d, 0x5c, + 0x7e, 0x8a, 0xdf, 0x5d, 0xb9, 0x99, 0xee, 0xb2, 0x15, 0x92, 0x78, 0x0f, 0xeb, 0xc9, 0x51, 0x3d, + 0xe9, 0xb6, 0x9e, 0x44, 0x8e, 0x08, 0x87, 0x4a, 0x03, 0x8f, 0x80, 0x33, 0x3f, 0xd8, 0xe8, 0x4d, + 0xdc, 0x6c, 0x9b, 0x3a, 0xd4, 0x0f, 0x61, 0xc2, 0x17, 0x74, 0xc8, 0xee, 0x82, 0x07, 0xc2, 0x13, + 0x0b, 0xb9, 0x96, 0x5d, 0x33, 0xb7, 0x09, 0x7c, 0xa5, 0x43, 0x5a, 0x6a, 0x08, 0xc1, 0x99, 0x52, + 0x71, 0x48, 0x9f, 0x03, 0xed, 0x1b, 0x4c, 0x0e, 0xa6, 0x59, 0x56, 0xdd, 0x33, 0xb1, 0x16, 0x17, + 0x46, 0xe3, 0x42, 0x2e, 0x6a, 0xe7, 0x49, 0x0c, 0xa9, 0x78, 0xa0, 0xe8, 0xa3, 0x64, 0x57, 0x0d, + 0xfe, 0x8e, 0x55, 0x46, 0x54, 0xaa, 0x82, 0xa1, 0xd1, 0x2d, 0x46, 0x81, 0x65, 0xa3, 0xfd, 0xc2, + 0xca, 0x6c, 0x33, 0x55, 0xcf, 0x14, 0xed, 0xb6, 0x28, 0x21, 0x81, 0x3f, 0x46, 0x33, 0x4c, 0xd1, + 0x4d, 0x86, 0x41, 0x7a, 0x64, 0xa6, 0xad, 0xe4, 0x59, 0x7b, 0xc9, 0x35, 0xde, 0x6d, 0x61, 0x00, + 0x42, 0x35, 0x9d, 0x02, 0x9a, 0xba, 0xe6, 0x29, 0x06, 0x40, 0x1c, 0x65, 0x5c, 0xf3, 0xac, 0x52, + 0xed, 0x59, 0xbd, 0x5a, 0x35, 0x9b, 0xac, 0x79, 0xc0, 0x6b, 0x56, 0x3f, 0x89, 0xca, 0xcf, 0x30, + 0xf6, 0xa1, 0x88, 0x5c, 0xc3, 0x03, 0x6f, 0xe2, 0x91, 0x52, 0x69, 0x4c, 0xd7, 0xd0, 0xb3, 0xec, + 0x2d, 0xf6, 0x1d, 0x4d, 0xca, 0x72, 0x7b, 0x9b, 0x86, 0xb4, 0x58, 0x75, 0xd8, 0x15, 0x5f, 0x0b, + 0x74, 0x7b, 0x8c, 0x6a, 0x03, 0x2b, 0xf4, 0xcd, 0x58, 0x98, 0x2f, 0xbf, 0x11, 0xd6, 0x74, 0x66, + 0x42, 0xe1, 0x8f, 0xf1, 0xdc, 0x02, 0x8a, 0x60, 0x05, 0x04, 0x41, 0x0a, 0xff, 0x6e, 0x87, 0xe4, + 0x1f, 0xb7, 0xab, 0x85, 0x92, 0x28, 0xbc, 0xa2, 0x0a, 0x23, 0xfe, 0xa3, 0x33, 0xc7, 0x1a, 0x2d, + 0xa2, 0x47, 0xd6, 0x5d, 0xa9, 0xd9, 0x4e, 0x9e, 0x57, 0xf2, 0x59, 0x39, 0x0c, 0x30, 0x60, 0x1d, + 0x4a, 0xf4, 0x66, 0xe8, 0x12, 0x75, 0x45, 0xa1, 0x55, 0x57, 0xd6, 0x39, 0x56, 0xc1, 0x3b, 0x07, + 0x89, 0x79, 0x75, 0x66, 0x19, 0x05, 0xf4, 0xe1, 0x51, 0x80, 0x9c, 0x9a, 0x49, 0x20, 0x0d, 0x57, + 0x50, 0x3f, 0x6a, 0xc9, 0xe4, 0x7a, 0x66, 0x37, 0x81, 0x24, 0x9f, 0xbb, 0xdf, 0x90, 0xae, 0x16, + 0x13, 0xcf, 0xba, 0x89, 0x36, 0xdf, 0x99, 0xdd, 0x6f, 0xb6, 0x9c, 0x12, 0x65, 0xff, 0x86, 0x8e, + 0xee, 0x65, 0xd1, 0xeb, 0x4a, 0xd1, 0xc1, 0xd6, 0xa2, 0xaf, 0xd5, 0xa2, 0xb3, 0x4a, 0xd1, 0x93, + 0xda, 0xd8, 0x78, 0xb4, 0xcb, 0xfa, 0xd8, 0x56, 0xec, 0xfe, 0x92, 0xec, 0x0c, 0x46, 0x58, 0x4f, + 0xba, 0x99, 0x71, 0xab, 0x03, 0xe9, 0x0f, 0x37, 0xd3, 0x60, 0x69, 0x94, 0x11, 0x46, 0x65, 0x47, + 0x2e, 0xda, 0x57, 0x5e, 0x8a, 0x9c, 0xcf, 0xad, 0x26, 0x98, 0x84, 0x8a, 0xe7, 0x05, 0x40, 0xb2, + 0x96, 0x0c, 0xe1, 0xb2, 0x5d, 0xe7, 0x5f, 0x5a, 0x8b, 0x9d, 0x58, 0x8a, 0x74, 0x0c, 0x0a, 0xd1, + 0xe5, 0x5d, 0xa3, 0x0c, 0x72, 0xdf, 0x1a, 0x9f, 0x5b, 0xa6, 0xdd, 0x3f, 0x01, 0x18, 0xc5, 0x30, + 0x43, 0xdd, 0x1b, 0xf6, 0xf0, 0x86, 0xc2, 0xf0, 0xa0, 0x71, 0x10, 0x4d, 0x53, 0xd9, 0x5c, 0x91, + 0xd4, 0x1a, 0x58, 0x41, 0x1d, 0xe6, 0x48, 0x9d, 0x2b, 0x25, 0xae, 0x8a, 0x32, 0xad, 0xcf, 0x2d, + 0x1b, 0xe6, 0x8b, 0x28, 0x90, 0x2a, 0x6c, 0x9a, 0xdf, 0xa0, 0xbb, 0xd5, 0x32, 0xab, 0x63, 0xc3, + 0xc4, 0x96, 0x31, 0x0f, 0xea, 0xd9, 0x97, 0x4b, 0xcf, 0xc3, 0xc0, 0x9d, 0x39, 0x37, 0xad, 0x54, + 0x1b, 0xb7, 0x4a, 0x4d, 0x5c, 0xde, 0x19, 0x47, 0x01, 0xfc, 0x41, 0x65, 0x80, 0x2a, 0xd8, 0x6f, + 0x01, 0x72, 0x3e, 0x46, 0x7b, 0x2b, 0x30, 0x8b, 0x3c, 0xe1, 0x36, 0xa8, 0x15, 0x73, 0x55, 0x1d, + 0x8f, 0x70, 0xa4, 0xc1, 0xd0, 0x87, 0x46, 0x86, 0x3f, 0x61, 0x6e, 0x4d, 0x5b, 0xb6, 0x6b, 0x91, + 0xd8, 0x2e, 0xdb, 0xad, 0x9e, 0x17, 0x7d, 0x0a, 0x98, 0xa2, 0x87, 0x17, 0x73, 0xdd, 0xad, 0x98, + 0xf6, 0xf0, 0xb0, 0xd2, 0x47, 0x8c, 0xd1, 0x15, 0xb2, 0x00, 0x16, 0x3d, 0xbd, 0x35, 0x6d, 0x13, + 0xfe, 0x91, 0x27, 0x90, 0xad, 0x95, 0x54, 0x11, 0x09, 0xdf, 0x34, 0xf6, 0x80, 0x82, 0xaa, 0xe8, + 0x6b, 0xb4, 0x13, 0xaf, 0x4b, 0x4f, 0x2d, 0x55, 0xdc, 0x24, 0xc5, 0x9d, 0x90, 0x32, 0xfc, 0xf4, + 0x29, 0x04, 0x1c, 0x14, 0x02, 0x32, 0x09, 0x01, 0x2b, 0xa8, 0xf5, 0x5f, 0xd9, 0x9f, 0xf0, 0xff, + 0xab, 0x3c, 0xc7, 0xc9, 0xc0, 0xa4, 0x89, 0x5e, 0x10, 0xbe, 0x6e, 0x2b, 0x0b, 0x05, 0x87, 0x03, + 0x5e, 0x90, 0x6c, 0x91, 0x2b, 0x45, 0xb7, 0x95, 0x46, 0xb1, 0x59, 0xeb, 0xf1, 0x92, 0xfe, 0x25, + 0x8c, 0x7b, 0xca, 0xcf, 0xa5, 0xd5, 0x5c, 0x3d, 0x94, 0x30, 0xe0, 0xd6, 0xdf, 0x7e, 0x06, 0xe0, + 0x16, 0x96, 0x1d, 0xf8, 0xde, 0x29, 0xa2, 0x2b, 0x8a, 0x90, 0x8a, 0x18, 0xfe, 0xf1, 0x55, 0x11, + 0x68, 0xb1, 0x87, 0x66, 0x7f, 0xbd, 0x3b, 0xff, 0xc6, 0x87, 0x8d, 0x25, 0x58, 0xf4, 0x04, 0xda, + 0xd3, 0x42, 0x48, 0x49, 0x2d, 0xb4, 0x4e, 0x67, 0x35, 0x3f, 0x72, 0xfb, 0x2f, 0xac, 0xf1, 0x09, + 0xc5, 0x11, 0xc2, 0xd6, 0x2d, 0x7b, 0x35, 0x1f, 0x0f, 0xe4, 0xeb, 0x89, 0x83, 0x3b, 0xfe, 0xf4, + 0xd4, 0x75, 0x57, 0x73, 0x4a, 0x39, 0x72, 0x4f, 0x30, 0xc5, 0x79, 0xa1, 0xa4, 0x40, 0x05, 0x8d, + 0xf8, 0x11, 0xc3, 0xa3, 0x49, 0x64, 0x46, 0xab, 0xed, 0x4e, 0x57, 0x29, 0xc6, 0x32, 0x5c, 0xcd, + 0x73, 0x8c, 0xf8, 0xe9, 0x7c, 0x67, 0x1b, 0x67, 0xce, 0x77, 0x68, 0x30, 0x6d, 0xd9, 0x2f, 0xfb, + 0x22, 0xac, 0x1b, 0xa0, 0x40, 0x72, 0xca, 0x53, 0x5a, 0x07, 0x86, 0xcb, 0xdf, 0x88, 0x55, 0xe5, + 0x71, 0x00, 0xf0, 0x7b, 0xfb, 0x76, 0x27, 0x2a, 0x8d, 0x7c, 0xba, 0x15, 0x91, 0x85, 0xb4, 0x1e, + 0x70, 0x9f, 0x07, 0x36, 0x53, 0xc9, 0xfd, 0x37, 0x51, 0x08, 0x4c, 0xcc, 0xda, 0xf8, 0x8d, 0xcd, + 0xa2, 0x28, 0x33, 0xc9, 0x74, 0xa1, 0xc3, 0xbb, 0x01, 0xbb, 0xb6, 0x1a, 0x4b, 0x71, 0x05, 0x34, + 0xad, 0x6b, 0xf6, 0xc4, 0x9d, 0xa4, 0xec, 0xf1, 0xa5, 0xd6, 0xe5, 0x04, 0x9d, 0xe0, 0x6d, 0x41, + 0x4a, 0xc2, 0xed, 0xdc, 0x48, 0x8e, 0xe7, 0xd2, 0xfa, 0xe2, 0xce, 0xf2, 0xf6, 0xcb, 0xbe, 0x5e, + 0x62, 0x67, 0x47, 0xb2, 0x2b, 0x76, 0xad, 0xa2, 0xe5, 0xb2, 0x52, 0x11, 0xcd, 0x69, 0x21, 0x58, + 0x97, 0x41, 0x83, 0xf8, 0x65, 0xcd, 0x23, 0xbf, 0x74, 0x70, 0xb8, 0xac, 0x5c, 0xea, 0x24, 0xd1, + 0xc5, 0x0a, 0x30, 0x36, 0xca, 0x55, 0x0e, 0x86, 0x77, 0x62, 0xe2, 0x2a, 0x87, 0x55, 0xaf, 0x72, + 0xb8, 0x9c, 0xbd, 0xfd, 0x22, 0xa7, 0x6e, 0x4a, 0x54, 0xf3, 0x24, 0xc5, 0xa7, 0x65, 0x67, 0xb8, + 0x48, 0xa8, 0xe5, 0x9e, 0x4f, 0x27, 0xb3, 0xf0, 0x6c, 0x62, 0xae, 0x16, 0x22, 0x72, 0x24, 0xed, + 0x34, 0x29, 0xc7, 0xe1, 0x21, 0xb9, 0x6e, 0x77, 0x5d, 0xa9, 0x3b, 0x4a, 0x8e, 0xdc, 0x55, 0x46, + 0x51, 0xed, 0x02, 0x50, 0x15, 0xb1, 0x1a, 0xba, 0x47, 0xfb, 0xf6, 0x98, 0xa3, 0x7f, 0x76, 0x6d, + 0x0c, 0x8e, 0x16, 0xb3, 0x71, 0x4b, 0x2c, 0x02, 0xec, 0x72, 0x35, 0x1c, 0xc1, 0x63, 0x3c, 0x54, + 0xeb, 0xb7, 0xa1, 0x63, 0x43, 0xb5, 0x97, 0xb9, 0x55, 0x27, 0x67, 0x2d, 0x3d, 0x44, 0x9a, 0xd6, + 0x19, 0xd5, 0x0d, 0x0f, 0x37, 0x05, 0x85, 0xea, 0xef, 0x27, 0xc8, 0x89, 0xb9, 0x18, 0x76, 0x8a, + 0xdb, 0xc5, 0x85, 0x0d, 0x96, 0xa0, 0x61, 0x5e, 0xb8, 0x59, 0x09, 0x75, 0x37, 0x2b, 0x7f, 0xc2, + 0x6e, 0xf3, 0x2b, 0x0d, 0x36, 0xb5, 0x29, 0xaf, 0x47, 0xb5, 0x52, 0x27, 0x2c, 0x84, 0xc5, 0xb2, + 0xd9, 0x79, 0xd8, 0x5d, 0x4f, 0x6a, 0x73, 0x71, 0xd4, 0x87, 0xd9, 0x18, 0x66, 0x7f, 0x5d, 0xc4, + 0x1a, 0xe5, 0x3c, 0x27, 0xc7, 0x76, 0x8a, 0x83, 0x11, 0xc2, 0xf0, 0x40, 0x63, 0xfe, 0x1e, 0xc7, + 0x2c, 0x79, 0x03, 0x9c, 0x30, 0xba, 0xf8, 0x12, 0x0e, 0x40, 0xa4, 0x39, 0x63, 0xbb, 0x73, 0x87, + 0x9a, 0x27, 0x3d, 0xc5, 0xd3, 0x49, 0x71, 0x03, 0x2b, 0x85, 0x3b, 0xfd, 0xe2, 0x2a, 0x56, 0xb8, + 0xaa, 0x2c, 0x6c, 0xc9, 0xf5, 0xe6, 0xf4, 0xb7, 0xba, 0xa3, 0x39, 0xd5, 0xb3, 0x9c, 0x8f, 0xfa, + 0xbb, 0xe7, 0x52, 0x7e, 0x34, 0xf2, 0xa5, 0x6f, 0xa5, 0xd4, 0x0d, 0xd1, 0x0a, 0x2c, 0x6d, 0xf4, + 0x22, 0x47, 0x0e, 0xad, 0x84, 0x1c, 0xe5, 0x23, 0xbb, 0xcf, 0xf4, 0xe1, 0x17, 0xb2, 0x99, 0x0c, + 0x05, 0x43, 0x93, 0xb4, 0x6a, 0xa6, 0x6f, 0x0e, 0xd3, 0x46, 0xaf, 0x9d, 0xca, 0xf5, 0xa1, 0xe6, + 0x45, 0x50, 0x99, 0xec, 0x2f, 0x99, 0x56, 0xe1, 0xcb, 0x45, 0xd0, 0xf5, 0xa8, 0x93, 0x2e, 0x9c, + 0x12, 0xa2, 0x68, 0x61, 0x19, 0xcd, 0x37, 0x1a, 0x87, 0x2f, 0x63, 0x7a, 0x08, 0x1f, 0x3d, 0x68, + 0x80, 0xab, 0x5d, 0x37, 0x2a, 0x2f, 0x8d, 0x7c, 0x2d, 0xbb, 0x36, 0xf1, 0xce, 0xa3, 0x62, 0x43, + 0x5f, 0x16, 0xda, 0x79, 0x01, 0x4f, 0x9e, 0xe3, 0x95, 0x3a, 0x14, 0x37, 0x03, 0x6a, 0x2d, 0x15, + 0x43, 0xd8, 0xa1, 0x4c, 0xe0, 0xaf, 0x36, 0x1b, 0x23, 0xa1, 0x27, 0x10, 0x01, 0x3b, 0x86, 0x97, + 0x56, 0x99, 0x67, 0x88, 0xfe, 0x36, 0xea, 0xbe, 0x2f, 0x94, 0xd6, 0x9e, 0x9e, 0xc2, 0x03, 0x57, + 0xb3, 0xb6, 0x9c, 0xa8, 0xce, 0x3e, 0x86, 0x1a, 0x9b, 0x82, 0x22, 0xda, 0xf6, 0x79, 0x29, 0x4e, + 0xa3, 0x16, 0x45, 0x50, 0x3d, 0x93, 0xcb, 0xf5, 0x40, 0x42, 0x69, 0x33, 0x2f, 0xc3, 0xe0, 0x85, + 0xd6, 0xa8, 0x65, 0x28, 0x8a, 0xd0, 0xd4, 0x4d, 0x6d, 0xc7, 0x75, 0xd3, 0xba, 0x84, 0xb0, 0xc9, + 0x95, 0x04, 0x2f, 0x5d, 0x11, 0xfe, 0xe2, 0xf9, 0x6a, 0x73, 0x95, 0x8c, 0xd0, 0xca, 0x55, 0xff, + 0x1f, 0x21, 0xc1, 0x0d, 0x85, 0x2e, 0x4d, 0xd2, 0x66, 0xa5, 0x56, 0xe5, 0xc2, 0x67, 0x16, 0x6c, + 0x92, 0x8e, 0xb5, 0xd3, 0x06, 0x70, 0x47, 0x36, 0xf5, 0xe2, 0x84, 0x67, 0xcd, 0xb9, 0x87, 0xc3, + 0xff, 0x79, 0x53, 0xbf, 0x51, 0xeb, 0xa2, 0x2b, 0x61, 0x0f, 0x70, 0x43, 0x62, 0x5a, 0xf6, 0x07, + 0xf7, 0x94, 0xfc, 0x1f, 0xde, 0x89, 0x68, 0xab, 0xae, 0x63, 0xdf, 0x3b, 0xdc, 0xd6, 0x9d, 0x1b, + 0x9a, 0x5d, 0x52, 0x24, 0x0b, 0x3e, 0x64, 0x85, 0xa6, 0xd9, 0x84, 0xdc, 0x94, 0xbf, 0x74, 0xe8, + 0xc0, 0xd5, 0xb4, 0x16, 0x1f, 0x29, 0xbc, 0x6d, 0x3a, 0xa9, 0x26, 0xc0, 0x46, 0x1b, 0x2a, 0x0e, + 0xe2, 0x56, 0x5e, 0xfa, 0x2e, 0x89, 0x68, 0x43, 0x62, 0x2d, 0x65, 0x08, 0xe6, 0xba, 0x03, 0x4b, + 0x3c, 0xa5, 0xb9, 0x6b, 0x99, 0x32, 0xc4, 0x0b, 0x94, 0x2d, 0xc4, 0xb9, 0x40, 0x83, 0x88, 0x87, + 0xbe, 0x4a, 0x4d, 0xcc, 0x6f, 0x8a, 0x90, 0x88, 0x14, 0x99, 0xb6, 0xe6, 0xef, 0xa9, 0xb8, 0xe4, + 0xa3, 0x58, 0x3a, 0x9a, 0x57, 0x26, 0x89, 0x3d, 0x8a, 0x2c, 0x80, 0x23, 0xc4, 0xac, 0xa5, 0x9d, + 0x42, 0x09, 0xcf, 0x7a, 0x7a, 0x52, 0x87, 0x91, 0x55, 0xde, 0x43, 0x8c, 0x98, 0x0d, 0x93, 0x29, + 0xa7, 0x0a, 0x6a, 0x43, 0x0f, 0x58, 0xff, 0x2c, 0x26, 0x76, 0x6b, 0x40, 0xdf, 0x39, 0x3f, 0x4d, + 0x01, 0x43, 0xf1, 0x85, 0xb9, 0x12, 0x86, 0x7f, 0x1f, 0xa3, 0x78, 0x47, 0x50, 0xdd, 0x8e, 0x5c, + 0x2e, 0xc7, 0xd2, 0x1c, 0x75, 0xd2, 0xed, 0x28, 0x9f, 0x12, 0x9e, 0xe3, 0xf0, 0xb0, 0x32, 0x35, + 0xb5, 0x9e, 0x86, 0x6e, 0x76, 0x7c, 0xef, 0xc8, 0x90, 0xa9, 0x74, 0xc6, 0x86, 0x18, 0x22, 0xf5, + 0xa8, 0x93, 0x7e, 0x1f, 0xf6, 0xee, 0xca, 0x68, 0x41, 0x83, 0x22, 0x5c, 0x19, 0xef, 0xed, 0xd8, + 0x81, 0xd3, 0xfc, 0x9c, 0x7c, 0x22, 0xf1, 0x84, 0xf3, 0x13, 0x48, 0x20, 0x27, 0x49, 0xc1, 0xb8, + 0xdb, 0x1f, 0x1c, 0x1e, 0x7e, 0xed, 0xe0, 0x61, 0xdb, 0xf2, 0xe9, 0x83, 0xaa, 0x77, 0xc6, 0x0e, + 0x3e, 0x46, 0x51, 0x79, 0xe0, 0xf6, 0x8f, 0x83, 0x6a, 0x10, 0x61, 0x11, 0x5b, 0xb8, 0x3d, 0xfe, + 0xf0, 0x12, 0xa3, 0xe1, 0xec, 0x3b, 0xd3, 0x7d, 0xa8, 0x48, 0x6c, 0x1b, 0x65, 0xc2, 0xb9, 0x81, + 0xd3, 0xe3, 0x9d, 0x5b, 0x33, 0xc5, 0xff, 0x42, 0x9b, 0xfe, 0x82, 0xe8, 0x93, 0xa6, 0xfd, 0x0d, + 0xa6, 0xfc, 0xa3, 0x36, 0xee, 0x02, 0xe8, 0x6e, 0x53, 0xae, 0xe8, 0xcf, 0x0c, 0x35, 0x72, 0x47, + 0x29, 0x30, 0xf3, 0x38, 0x46, 0x8a, 0x48, 0x74, 0x64, 0xc6, 0xf7, 0x68, 0x39, 0x2b, 0xd2, 0x66, + 0xab, 0x46, 0xd3, 0xa0, 0x28, 0xab, 0x54, 0x22, 0x8a, 0xa1, 0x55, 0x17, 0x0a, 0xd1, 0x8e, 0xdd, + 0xd3, 0xb2, 0x8e, 0x2c, 0x2e, 0xeb, 0xd5, 0x82, 0x75, 0xd7, 0xcd, 0x25, 0x79, 0x3a, 0xa3, 0xc8, + 0xcd, 0x14, 0x77, 0x8e, 0x12, 0x5e, 0xb9, 0x02, 0x32, 0x5f, 0xb5, 0xc4, 0xef, 0xc2, 0x48, 0xe0, + 0xb6, 0xc8, 0x62, 0x89, 0x07, 0xb7, 0x78, 0xbf, 0x3b, 0xef, 0x53, 0xfc, 0xd8, 0x03, 0x19, 0xd0, + 0xfa, 0x80, 0xc4, 0x13, 0x22, 0x15, 0x67, 0x93, 0x3f, 0x3f, 0x3d, 0xdd, 0x8d, 0x5d, 0x25, 0x91, + 0xbf, 0xe0, 0xb6, 0x95, 0xc1, 0xad, 0x29, 0x76, 0x97, 0xa8, 0x6f, 0x22, 0x9a, 0x39, 0xe8, 0x0f, + 0x45, 0x4b, 0xe8, 0x87, 0xaa, 0xe8, 0x73, 0xd9, 0x7c, 0x3d, 0xb2, 0x7b, 0xab, 0x27, 0x28, 0x0c, + 0x68, 0xae, 0x58, 0x5a, 0xf1, 0x2a, 0x76, 0xb9, 0x74, 0x8a, 0x4a, 0x46, 0x7a, 0x45, 0x6b, 0xe1, + 0x16, 0xb1, 0xb7, 0xcb, 0xb0, 0x74, 0xf3, 0x35, 0xe4, 0x9b, 0x98, 0x0e, 0x52, 0xe2, 0x9b, 0x2c, + 0x32, 0xbf, 0x76, 0x81, 0x8b, 0xfd, 0xc1, 0x85, 0xfc, 0xb2, 0x87, 0xc8, 0xaf, 0x43, 0xd5, 0xa7, + 0xf8, 0x87, 0xbc, 0x48, 0x00, 0x94, 0x2b, 0xab, 0x5b, 0x84, 0xe7, 0x53, 0x4e, 0x88, 0xc3, 0x43, + 0x33, 0xa2, 0x44, 0x14, 0x55, 0x01, 0x8f, 0x10, 0x2d, 0x31, 0xed, 0x40, 0xf7, 0xb5, 0xab, 0x5e, + 0x4b, 0xaf, 0x19, 0xa0, 0xe4, 0x1f, 0x19, 0x8b, 0x81, 0xe4, 0xea, 0x76, 0xbb, 0x9c, 0xea, 0x3a, + 0x90, 0xa4, 0x6e, 0xc1, 0x24, 0x8f, 0xf8, 0x51, 0x87, 0x97, 0x42, 0xd2, 0xfd, 0x36, 0xe4, 0x53, + 0x7a, 0x70, 0x78, 0x58, 0xbc, 0x84, 0x56, 0x19, 0x91, 0x8c, 0x47, 0x20, 0x0e, 0xad, 0xf2, 0x23, + 0x9c, 0x32, 0xd6, 0x84, 0x0e, 0x9b, 0xa7, 0x27, 0x9d, 0xb3, 0x60, 0xf6, 0x23, 0xa4, 0x0e, 0x1f, + 0x51, 0xf4, 0xa3, 0x74, 0x0b, 0xd2, 0x6c, 0x2a, 0x65, 0x0d, 0x1b, 0xf3, 0xe3, 0xb7, 0xbc, 0x90, + 0x3d, 0xd4, 0xc6, 0x93, 0x73, 0x0c, 0xd1, 0x8a, 0x83, 0x42, 0xd3, 0x86, 0x5d, 0x25, 0xb0, 0x47, + 0x3d, 0x92, 0x3c, 0x90, 0x5b, 0x50, 0x9e, 0x1b, 0x52, 0xca, 0x58, 0xea, 0xf5, 0x5c, 0xeb, 0x08, + 0xaf, 0xc8, 0xa3, 0x3b, 0xa8, 0x0c, 0xf1, 0x45, 0x7b, 0x46, 0x35, 0x8c, 0xfd, 0xf6, 0x9c, 0x54, + 0x65, 0xb4, 0x81, 0x7c, 0x78, 0xa6, 0xec, 0xc8, 0xb7, 0x89, 0x77, 0x65, 0xa3, 0x86, 0x19, 0xc6, + 0x16, 0x93, 0xf9, 0xfe, 0xd7, 0x79, 0x0f, 0xb0, 0xbc, 0x1f, 0x67, 0x63, 0xe3, 0xbc, 0x87, 0xee, + 0x7d, 0xf0, 0xef, 0x2a, 0x5b, 0x07, 0x63, 0xe3, 0xff, 0x01, 0x93, 0x9b, 0x38, 0x6c, 0xe9, 0x85, + 0x01, 0x00 }; diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 2bfae793..5486ae18 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -135,6 +135,21 @@ void updateTimezone() { tz = new Timezone(tcrDaylight, tcrStandard); } +void handleTime() { + handleNetworkTime(); + + toki.millisecond(); + toki.setTick(); + + if (toki.isTick()) //true only in the first loop after a new second started + { + updateLocalTime(); + checkTimers(); + checkCountdown(); + handleOverlays(); + } +} + void handleNetworkTime() { if (ntpEnabled && ntpConnected && millis() - ntpLastSyncTime > 50000000L && WLED_CONNECTED) diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index f852033a..a4caa85e 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -6,32 +6,24 @@ void initCronixie() { - if (overlayCurrent == 3 && !cronixieInit) + if (overlayCurrent == 3 && dP[0] == 255) //if dP[0] is 255, cronixie is not yet init'ed { setCronixie(); strip.getSegment(0).grouping = 10; //10 LEDs per digit - cronixieInit = true; - } else if (cronixieInit && overlayCurrent != 3) + } else if (dP[0] < 255 && overlayCurrent != 3) { strip.getSegment(0).grouping = 1; - cronixieInit = false; + dP[0] = 255; } } void handleOverlays() { - if (toki.isTick()) - { - initCronixie(); - updateLocalTime(); - checkTimers(); - checkCountdown(); - if (overlayCurrent == 3) { - _overlayCronixie();//Diamex cronixie clock kit - strip.trigger(); - } - overlayRefreshedTime = millis(); + initCronixie(); + if (overlayCurrent == 3) { + _overlayCronixie();//Diamex cronixie clock kit + strip.trigger(); } } @@ -76,7 +68,6 @@ void _overlayAnalogClock() if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, 0xFF0000); strip.setPixelColor(minutePixel, 0x00FF00); strip.setPixelColor(hourPixel, 0x0000FF); - overlayRefreshMs = 998; } @@ -118,7 +109,6 @@ void _overlayAnalogCountdown() strip.setRange(analogClock12pixel, analogClock12pixel + pixelCnt, ((uint32_t)colSec[3] << 24)| ((uint32_t)colSec[0] << 16) | ((uint32_t)colSec[1] << 8) | colSec[2]); } } - overlayRefreshMs = 998; } @@ -222,8 +212,6 @@ void setCronixie() DEBUG_PRINT("cset "); DEBUG_PRINTLN(cronixieDisplay); - - overlayRefreshMs = 1997; //Only refresh every 2secs if no seconds are displayed for (int i = 0; i < 6; i++) { @@ -244,8 +232,8 @@ void setCronixie() case 'a': dP[i] = 58; i++; break; case 'm': dP[i] = 74 + getSameCodeLength('m',i,cronixieDisplay); i = i+dP[i]-74; break; case 'M': dP[i] = 24 + getSameCodeLength('M',i,cronixieDisplay); i = i+dP[i]-24; break; - case 's': dP[i] = 80 + getSameCodeLength('s',i,cronixieDisplay); i = i+dP[i]-80; overlayRefreshMs = 497; break; //refresh more often bc. of secs - case 'S': dP[i] = 30 + getSameCodeLength('S',i,cronixieDisplay); i = i+dP[i]-30; overlayRefreshMs = 497; break; + case 's': dP[i] = 80 + getSameCodeLength('s',i,cronixieDisplay); i = i+dP[i]-80; break; //refresh more often bc. of secs + case 'S': dP[i] = 30 + getSameCodeLength('S',i,cronixieDisplay); i = i+dP[i]-30; break; case 'Y': dP[i] = 36 + getSameCodeLength('Y',i,cronixieDisplay); i = i+dP[i]-36; break; case 'y': dP[i] = 86 + getSameCodeLength('y',i,cronixieDisplay); i = i+dP[i]-86; break; case 'I': dP[i] = 39 + getSameCodeLength('I',i,cronixieDisplay); i = i+dP[i]-39; break; //Month. Don't ask me why month and minute both start with M. diff --git a/wled00/set.cpp b/wled00/set.cpp index 89c63e9c..698b6fdd 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -825,7 +825,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) if (pos > 0) //sets backlight { cronixieBacklight = (req.charAt(pos+3) != '0'); - overlayRefreshedTime = 0; } #endif diff --git a/wled00/wled.cpp b/wled00/wled.cpp index d50f7127..31050817 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -177,8 +177,8 @@ void WiFiEvent(WiFiEvent_t event) void WLED::loop() { - toki.millisecond(); - toki.setTick(); + handleTime(); + handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too handleConnection(); handleSerial(); @@ -193,10 +193,8 @@ void WLED::loop() yield(); handleIO(); handleIR(); - handleNetworkTime(); handleAlexa(); - handleOverlays(); yield(); if (doReboot) diff --git a/wled00/wled.h b/wled00/wled.h index db046201..1aa5a28f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -331,7 +331,7 @@ WLED_GLOBAL bool useAMPM _INIT(false); // 12h/24h clock format WLED_GLOBAL byte currentTimezone _INIT(0); // Timezone ID. Refer to timezones array in wled10_ntp.ino WLED_GLOBAL int utcOffsetSecs _INIT(0); // Seconds to offset from UTC before timzone calculation -WLED_GLOBAL byte overlayDefault _INIT(0); // 0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +WLED_GLOBAL byte overlayDefault _INIT(0); // 0: no overlay 1: analog clock 2: single-digit clock 3: cronixie WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(ledCount - 1); // boundaries of overlay mode WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be @@ -448,13 +448,9 @@ WLED_GLOBAL bool hueStoreAllowed _INIT(false), hueNewKey _INIT(false); // overlays WLED_GLOBAL byte overlayCurrent _INIT(overlayDefault); -WLED_GLOBAL byte overlaySpeed _INIT(200); -WLED_GLOBAL unsigned long overlayRefreshMs _INIT(200); -WLED_GLOBAL unsigned long overlayRefreshedTime; // cronixie -WLED_GLOBAL byte dP[] _INIT_N(({ 0, 0, 0, 0, 0, 0 })); -WLED_GLOBAL bool cronixieInit _INIT(false); +WLED_GLOBAL byte dP[] _INIT_N(({ 255, 255, 255, 255, 255, 255 })); // countdown WLED_GLOBAL unsigned long countdownTime _INIT(1514764800L); From c2892d78873c45e6df33a2acd18ff410d5ad07bb Mon Sep 17 00:00:00 2001 From: cschwinne Date: Thu, 27 May 2021 02:02:02 +0200 Subject: [PATCH 5/8] 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); } From 55b26751ae175b4a357404fdd41e1b9ca42c3d79 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Thu, 27 May 2021 11:09:57 +0200 Subject: [PATCH 6/8] Cache CORS preflight request --- wled00/wled_server.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index ef90a077..28762392 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -224,7 +224,10 @@ void initServer() //make API CORS compatible if (request->method() == HTTP_OPTIONS) { - request->send(200); return; + AsyncWebServerResponse *response = request->beginResponse(200); + response->addHeader(F("Access-Control-Max-Age"), F("7200")); + request->send(response); + return; } if(handleSet(request, request->url())) return; From 7cbc9d21b5d78a5bdfb908979f25b0ab6bc9fb1f Mon Sep 17 00:00:00 2001 From: cschwinne Date: Thu, 27 May 2021 23:29:11 +0200 Subject: [PATCH 7/8] Fixed difference calculation --- wled00/ntp.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index c4f47c8e..bedad668 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -437,12 +437,13 @@ 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(); + uint32_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; + uint32_t diff = (timein > prev) ? timein - prev : prev - timein; + if (toki.getTimeSource() > TOKI_TS_JSON && diff < 60U) return; toki.setTime(timein, TOKI_NO_MS_ACCURACY, TOKI_TS_JSON); - if (abs(timein - prev) > 60L) { + if (diff >= 60U) { updateLocalTime(); calculateSunriseAndSunset(); } From bfc7f56c4dfea196fe496adb68f45c181b376f62 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Sun, 30 May 2021 00:08:24 +0200 Subject: [PATCH 8/8] Accurate UDP sync for NTP enabled instances --- usermods/EleksTube_IPS/TFTs.h | 4 +++- wled00/FX.cpp | 30 ++++++++++++----------------- wled00/FX.h | 2 +- wled00/ntp.cpp | 7 ++++++- wled00/src/dependencies/toki/Toki.h | 17 ++++++++++++---- wled00/udp.cpp | 26 ++++++++++++++++++------- 6 files changed, 54 insertions(+), 32 deletions(-) diff --git a/usermods/EleksTube_IPS/TFTs.h b/usermods/EleksTube_IPS/TFTs.h index e9629904..ca2f4243 100644 --- a/usermods/EleksTube_IPS/TFTs.h +++ b/usermods/EleksTube_IPS/TFTs.h @@ -1,6 +1,7 @@ #ifndef TFTS_H #define TFTS_H +#include "wled.h" #include #include @@ -92,9 +93,10 @@ private: uint16_t padding = (4 - ((w * 3) & 3)) & 3; uint8_t lineBuffer[w * 3 + padding]; + uint8_t serviceStrip = (!realtimeMode || realtimeOverride) ? 7 : 0; // row is decremented as the BMP image is drawn bottom up for (row = h-1; row >= 0; row--) { - if (row & 0b00000111 == 7) strip.service(); //still refresh backlight to mitigate stutter every few rows + if ((row & 0b00000111) == serviceStrip) strip.service(); //still refresh backlight to mitigate stutter every few rows bmpFS.read(lineBuffer, sizeof(lineBuffer)); uint8_t* bptr = lineBuffer; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 725f6b1d..9a399afd 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -42,30 +42,24 @@ uint16_t WS2812FX::mode_static(void) { * Blink/strobe function * Alternate between color1 and color2 * if(strobe == true) then create a strobe effect - * NOTE: Maybe re-rework without timer */ uint16_t WS2812FX::blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { - uint16_t stateTime = SEGENV.aux1; uint32_t cycleTime = (255 - SEGMENT.speed)*20; - uint32_t onTime = 0; - uint32_t offTime = cycleTime; - - if (!strobe) { - onTime = (cycleTime * SEGMENT.intensity) >> 8; - offTime = cycleTime - onTime; + uint32_t onTime = FRAMETIME; + if (!strobe) onTime += ((cycleTime * SEGMENT.intensity) >> 8); + cycleTime += FRAMETIME*2; + uint32_t it = now / cycleTime; + uint32_t rem = now % cycleTime; + + bool on = false; + if (it != SEGENV.step //new iteration, force on state for one frame, even if set time is too brief + || rem <= onTime) { + on = true; } - stateTime = ((SEGENV.aux0 & 1) == 0) ? onTime : offTime; - stateTime += 20; - - if (now - SEGENV.step > stateTime) - { - SEGENV.aux0++; - SEGENV.aux1 = stateTime; - SEGENV.step = now; - } + SEGENV.step = it; //save previous iteration - uint32_t color = ((SEGENV.aux0 & 1) == 0) ? color1 : color2; + uint32_t color = on ? color1 : color2; if (color == color1 && do_palette) { for(uint16_t i = 0; i < SEGLEN; i++) { diff --git a/wled00/FX.h b/wled00/FX.h index 12eb11f6..77638aed 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -75,7 +75,7 @@ #define SEGENV _segment_runtimes[_segment_index] #define SEGLEN _virtualSegmentLength #define SEGACT SEGMENT.stop -#define SPEED_FORMULA_L 5 + (50*(255 - SEGMENT.speed))/SEGLEN +#define SPEED_FORMULA_L 5U + (50U*(255U - SEGMENT.speed))/SEGLEN #define RESET_RUNTIME memset(_segment_runtimes, 0, sizeof(_segment_runtimes)) // some common colors diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index bedad668..4014e1fb 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -6,6 +6,7 @@ * Acquires time from NTP server */ //#define WLED_DEBUG_NTP +#define NTP_SYNC_INTERVAL 42000UL //Get fresh NTP time about twice per day Timezone* tz; @@ -143,6 +144,10 @@ void handleTime() { if (toki.isTick()) //true only in the first loop after a new second started { + #ifdef WLED_DEBUG_NTP + Serial.print(F("TICK! ")); + toki.printTime(toki.getTime()); + #endif updateLocalTime(); checkTimers(); checkCountdown(); @@ -152,7 +157,7 @@ void handleTime() { void handleNetworkTime() { - if (ntpEnabled && ntpConnected && millis() - ntpLastSyncTime > 50000000L && WLED_CONNECTED) + if (ntpEnabled && ntpConnected && millis() - ntpLastSyncTime > (1000*NTP_SYNC_INTERVAL) && WLED_CONNECTED) { if (millis() - ntpPacketSentTime > 10000) { diff --git a/wled00/src/dependencies/toki/Toki.h b/wled00/src/dependencies/toki/Toki.h index 8a6892f8..e7f12a45 100644 --- a/wled00/src/dependencies/toki/Toki.h +++ b/wled00/src/dependencies/toki/Toki.h @@ -103,16 +103,25 @@ class Toki { return unix; } - uint32_t msDifference(Time &t0, Time &t1) { + //gets the absolute difference between two timestamps in milliseconds + uint32_t msDifference(const Time &t0, const Time &t1) { bool t1BiggerSec = (t1.sec > t0.sec); uint32_t secDiff = (t1BiggerSec) ? t1.sec - t0.sec : t0.sec - t1.sec; uint32_t t0ms = t0.ms, t1ms = t1.ms; - if (t1BiggerSec) t1ms += secDiff; - else t0ms += secDiff; + if (t1BiggerSec) t1ms += secDiff*1000; + else t0ms += secDiff*1000; uint32_t msDiff = (t1ms > t0ms) ? t1ms - t0ms : t0ms - t1ms; return msDiff; } + //return true if t1 is later than t0 + bool isLater(const Time &t0, const Time &t1) { + if (t1.sec > t0.sec) return true; + if (t1.sec < t0.sec) return false; + if (t1.ms > t0.ms) return true; + return false; + } + void adjust(Time&t, int32_t offset) { int32_t secs = offset /1000; int32_t ms = offset - secs*1000; @@ -139,7 +148,7 @@ class Toki { } void resetTick() { - tick = TickT::inactive; + if (tick == TickT::active) tick = TickT::inactive; } bool isTick() { diff --git a/wled00/udp.cpp b/wled00/udp.cpp index e58bd714..ad6e727a 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -245,6 +245,7 @@ void handleNotifications() } } + bool timebaseUpdated = false; //apply effects from notification if (version < 200 && (receiveNotificationEffects || !someSel)) { @@ -258,21 +259,32 @@ void handleNotifications() t += PRESUMED_NETWORK_DELAY; //adjust trivially for network delay t -= millis(); strip.timebase = t; + timebaseUpdated = true; } } //adjust system time, but only if sender is more accurate than self - if (version > 7 && udpIn[29] > toki.getTimeSource()) + if (version > 7) { 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[29] > toki.getTimeSource()) { //if sender's time source is more accurate + 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); + } else if (timebaseUpdated && toki.getTimeSource() > 99) { //if we both have good times, get a more accurate timebase + Toki::Time myTime = toki.getTime(); + uint32_t diff = toki.msDifference(tm, myTime); + strip.timebase -= PRESUMED_NETWORK_DELAY; //no need to presume, use difference between NTP times at send and receive points + if (toki.isLater(tm, myTime)) { + strip.timebase += diff; + } else { + strip.timebase -= diff; + } + } } if (version > 3)