diff --git a/tools/WLED_ESP32_4MB_1MB_FS.csv b/tools/WLED_ESP32_4MB_1MB_FS.csv new file mode 100644 index 000000000..09455c6a9 --- /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/TFTs.h b/usermods/EleksTube_IPS/TFTs.h index e96299047..ca2f4243f 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/usermods/EleksTube_IPS/usermod_elekstube_ips.h b/usermods/EleksTube_IPS/usermod_elekstube_ips.h index 4f820c268..f2ce8eb08 100644 --- a/usermods/EleksTube_IPS/usermod_elekstube_ips.h +++ b/usermods/EleksTube_IPS/usermod_elekstube_ips.h @@ -40,19 +40,16 @@ class ElekstubeIPSUsermod : public Usermod { void setup() { tfts.begin(); tfts.fillScreen(TFT_BLACK); - tfts.setTextColor(TFT_WHITE, TFT_BLACK); - tfts.setCursor(0, 0, 2); - tfts.println(""); + + for (int8_t i = 5; i >= 0; i--) { + tfts.setDigit(i, 255, TFTs::force); //turn all off + } } 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/usermods/RTC/usermod_rtc.h b/usermods/RTC/usermod_rtc.h index bf0047a72..75d91b31c 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,TOKI_NO_MS_ACCURACY,TOKI_TS_RTC); 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 62176ce92..ef1bb37ec 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/FX.cpp b/wled00/FX.cpp index 725f6b1d7..9a399afd2 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 12eb11f6a..77638aedf 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/data/index.js b/wled00/data/index.js index 6501a5f41..9e44f0ff4 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 5baad3504..2913078b1 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(); @@ -137,6 +138,7 @@ void setCountdown(); byte weekdayMondayFirst(); void checkTimers(); void calculateSunriseAndSunset(); +void setTimeFromAPI(uint32_t timein); //overlay.cpp void initCronixie(); diff --git a/wled00/html_ui.h b/wled00/html_ui.h index 1bdb27034..2fa744bc8 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/json.cpp b/wled00/json.cpp index a2cf126da..bd0c31e7d 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 = now(); - if (millis() - ntpLastSyncTime > 50000000L) { - setTime(timein); - if (abs(now() - prev) > 60L) { - updateLocalTime(); - calculateSunriseAndSunset(); - } - } + setTimeFromAPI(timein); if (presetsModifiedTime == 0) presetsModifiedTime = timein; } diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 449ad044b..4014e1fbc 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -5,6 +5,9 @@ /* * Acquires time from NTP server */ +//#define WLED_DEBUG_NTP +#define NTP_SYNC_INTERVAL 42000UL //Get fresh NTP time about twice per day + Timezone* tz; #define TZ_UTC 0 @@ -133,9 +136,28 @@ 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 + { + #ifdef WLED_DEBUG_NTP + Serial.print(F("TICK! ")); + toki.printTime(toki.getTime()); + #endif + updateLocalTime(); + checkTimers(); + checkCountdown(); + handleOverlays(); + } +} + 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) { @@ -182,35 +204,52 @@ void sendNTPPacket() bool checkNTPResponse() { int cb = ntpUdp.parsePacket(); - if (cb) { - 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; - 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; - - DEBUG_PRINT(F("Unix time = ")); - unsigned long epoch = secsSince1900 - 2208988799UL; //subtract 70 years -1sec (on avg. more precision) - setTime(epoch); - 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, TOKI_TS_NTP); + + #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() { if (currentTimezone != tzCurrent) updateTimezone(); - unsigned long tmc = now()+ utcOffsetSecs; + unsigned long tmc = toki.second()+ utcOffsetSecs; localTime = tz->toLocal(tmc); } @@ -234,13 +273,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; @@ -399,3 +438,19 @@ void calculateSunriseAndSunset() { } } } + +//time from JSON and HTTP API +void setTimeFromAPI(uint32_t timein) { + if (timein == 0 || timein == UINT32_MAX) return; + uint32_t prev = toki.second(); + //only apply if more accurate or there is a significant difference to the "more accurate" time source + 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 (diff >= 60U) { + updateLocalTime(); + calculateSunriseAndSunset(); + } + if (presetsModifiedTime == 0) presetsModifiedTime = timein; +} \ No newline at end of file diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 419d82a70..a4caa85ee 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -6,29 +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 (millis() - overlayRefreshedTime > overlayRefreshMs) - { - initCronixie(); - updateLocalTime(); - checkTimers(); - checkCountdown(); - if (overlayCurrent == 3) _overlayCronixie();//Diamex cronixie clock kit - overlayRefreshedTime = millis(); + initCronixie(); + if (overlayCurrent == 3) { + _overlayCronixie();//Diamex cronixie clock kit + strip.trigger(); } } @@ -73,15 +68,14 @@ void _overlayAnalogClock() if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, 0xFF0000); strip.setPixelColor(minutePixel, 0x00FF00); strip.setPixelColor(hourPixel, 0x0000FF); - overlayRefreshMs = 998; } 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 { @@ -115,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; } @@ -219,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++) { @@ -241,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/presets.cpp b/wled00/presets.cpp index a4635bcae..c3c15afc3 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 cbeae9d59..73c2d9dc5 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -806,14 +806,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)); + setTimeFromAPI(getNumVal(&req, pos)); } //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=")); @@ -841,7 +841,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/src/dependencies/time/Readme.txt b/wled00/src/dependencies/time/Readme.txt index 234242abe..8982657de 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 6b1df7c5f..21f2e989e 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 @@ -37,7 +38,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) { @@ -46,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 ) @@ -69,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); @@ -231,57 +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; -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() - 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; -#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 - - sysTime = (uint32_t)t; - nextSyncTime = (uint32_t)t + syncInterval; - Status = timeSet; - 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); @@ -297,29 +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){ - setTime(getUnixTime(hr,min,sec,dy,mnth,yr)); -} - -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 d8ed73912..5004f0716 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 @@ -95,33 +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 -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); 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) @@ -129,13 +113,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 000000000..e7f12a453 --- /dev/null +++ b/wled00/src/dependencies/toki/Toki.h @@ -0,0 +1,161 @@ +/* + 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 + +#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 { + 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; + uint8_t timeSrc = TOKI_TS_NONE; + + public: + 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=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, timeSource); + } + + 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[4], timestamp[5]); //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; + } + + //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*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; + 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; + } + + Time getTime() { + Time t; + t.ms = millisecond(); + t.sec = unix; + return t; + } + + uint8_t getTimeSource() { + return timeSrc; + } + + void setTick() { + if (tick == TickT::marked) tick = TickT::active; + } + + void resetTick() { + if (tick == TickT::active) 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/udp.cpp b/wled00/udp.cpp index f30358f3f..ad6e727ae 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,40 +228,66 @@ 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 } } } + bool timebaseUpdated = false; //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; + timebaseUpdated = true; + } + } + + //adjust system time, but only if sender is more accurate than self + 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]); + 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 (udpIn[11] > 3) + if (version > 3) { transitionDelayTemp = ((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00); } diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 708008d81..ce875e462 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -1,728 +1,728 @@ -#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp! -#include "wled.h" -#include - -#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) -#include "soc/soc.h" -#include "soc/rtc_cntl_reg.h" -#endif - -/* - * Main WLED class implementation. Mostly initialization and connection logic - */ - -WLED::WLED() -{ -} - -#ifdef WLED_USE_ETHERNET -// settings for various ethernet boards -typedef struct EthernetSettings { - uint8_t eth_address; - int eth_power; - int eth_mdc; - int eth_mdio; - eth_phy_type_t eth_type; - eth_clock_mode_t eth_clk_mode; -} ethernet_settings; - -ethernet_settings ethernetBoards[] = { - // None - { - }, - - // WT32-EHT01 - // Please note, from my testing only these pins work for LED outputs: - // IO2, IO4, IO12, IO14, IO15 - // These pins do not appear to work from my testing: - // IO35, IO36, IO39 - { - 1, // eth_address, - 16, // eth_power, - 23, // eth_mdc, - 18, // eth_mdio, - ETH_PHY_LAN8720, // eth_type, - ETH_CLOCK_GPIO0_IN // eth_clk_mode - }, - - // ESP32-POE - { - 0, // eth_address, - 12, // eth_power, - 23, // eth_mdc, - 18, // eth_mdio, - ETH_PHY_LAN8720, // eth_type, - ETH_CLOCK_GPIO17_OUT // eth_clk_mode - }, - - // WESP32 - { - 0, // eth_address, - -1, // eth_power, - 16, // eth_mdc, - 17, // eth_mdio, - ETH_PHY_LAN8720, // eth_type, - ETH_CLOCK_GPIO0_IN // eth_clk_mode - }, - - // QuinLed-ESP32-Ethernet - { - 0, // eth_address, - 5, // eth_power, - 23, // eth_mdc, - 18, // eth_mdio, - ETH_PHY_LAN8720, // eth_type, - ETH_CLOCK_GPIO17_OUT // eth_clk_mode - } -}; -#endif - -// turns all LEDs off and restarts ESP -void WLED::reset() -{ - briT = 0; - #ifdef WLED_ENABLE_WEBSOCKETS - ws.closeAll(1012); - #endif - long dly = millis(); - while (millis() - dly < 450) { - yield(); // enough time to send response to client - } - setAllLeds(); - DEBUG_PRINTLN(F("MODULE RESET")); - ESP.restart(); -} - -bool oappendi(int i) -{ - char s[11]; - sprintf(s, "%d", i); - return oappend(s); -} - -bool oappend(const char* txt) -{ - uint16_t len = strlen(txt); - if (olen + len >= OMAX) - return false; // buffer full - strcpy(obuf + olen, txt); - olen += len; - return true; -} - -void prepareHostname(char* hostname) -{ - const char *pC = serverDescription; - uint8_t pos = 5; - - while (*pC && pos < 24) { // while !null and not over length - if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname - hostname[pos] = *pC; - pos++; - } else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') { - hostname[pos] = '-'; - pos++; - } - // else do nothing - no leading hyphens and do not include hyphens for all other characters. - pC++; - } - // if the hostname is left blank, use the mac address/default mdns name - if (pos < 6) { - sprintf(hostname + 5, "%*s", 6, escapedMac.c_str() + 6); - } else { //last character must not be hyphen - while (pos > 0 && hostname[pos -1] == '-') { - hostname[pos -1] = 0; - pos--; - } - } -} - -//handle Ethernet connection event -void WiFiEvent(WiFiEvent_t event) -{ - #ifdef WLED_USE_ETHERNET - char hostname[25] = "wled-"; - #endif - - switch (event) { -#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) - case SYSTEM_EVENT_ETH_START: - DEBUG_PRINT(F("ETH Started")); - break; - case SYSTEM_EVENT_ETH_CONNECTED: - DEBUG_PRINT(F("ETH Connected")); - if (!apActive) { - WiFi.disconnect(true); - } - if (staticIP != (uint32_t)0x00000000 && staticGateway != (uint32_t)0x00000000) { - ETH.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); - } else { - ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); - } - // convert the "serverDescription" into a valid DNS hostname (alphanumeric) - prepareHostname(hostname); - ETH.setHostname(hostname); - showWelcomePage = false; - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - DEBUG_PRINT(F("ETH Disconnected")); - forceReconnect = true; - break; -#endif - default: - break; - } -} - -void WLED::loop() -{ - handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too - handleConnection(); - handleSerial(); - handleNotifications(); - handleTransitions(); -#ifdef WLED_ENABLE_DMX - handleDMX(); -#endif - userLoop(); - usermods.loop(); - - yield(); - handleIO(); - handleIR(); - handleNetworkTime(); - handleAlexa(); - - handleOverlays(); - yield(); - - if (doReboot) - reset(); - if (doCloseFile) { - closeFile(); - yield(); - } - - if (!realtimeMode || realtimeOverride) // block stuff if WARLS/Adalight is enabled - { - if (apActive) - dnsServer.processNextRequest(); -#ifndef WLED_DISABLE_OTA - if (WLED_CONNECTED && aOtaEnabled) - ArduinoOTA.handle(); -#endif - handleNightlight(); - handlePlaylist(); - yield(); - - handleHue(); - handleBlynk(); - - yield(); - - if (!offMode || strip.isOffRefreshRequred) - strip.service(); -#ifdef ESP8266 - else if (!noWifiSleep) - delay(1); //required to make sure ESP enters modem sleep (see #1184) -#endif - } - yield(); -#ifdef ESP8266 - MDNS.update(); -#endif - - //millis() rolls over every 50 days - if (lastMqttReconnectAttempt > millis()) { - rolloverMillis++; - lastMqttReconnectAttempt = 0; - } - if (millis() - lastMqttReconnectAttempt > 30000) { - lastMqttReconnectAttempt = millis(); - initMqtt(); - yield(); - // refresh WLED nodes list - refreshNodeList(); - if (nodeBroadcastEnabled) sendSysInfoUDP(); - yield(); - } - - //LED settings have been saved, re-init busses - //This code block causes severe FPS drop on ESP32 with the original "if (busConfigs[0] != nullptr)" conditional. Investigate! - if (doInitBusses) { - doInitBusses = false; - DEBUG_PRINTLN(F("Re-init busses.")); - busses.removeAll(); - uint32_t mem = 0; - strip.isRgbw = false; - for (uint8_t i = 0; i < WLED_MAX_BUSSES; i++) { - if (busConfigs[i] == nullptr) break; - mem += busses.memUsage(*busConfigs[i]); - if (mem <= MAX_LED_MEMORY) busses.add(*busConfigs[i]); - //if (BusManager::isRgbw(busConfigs[i]->type)) strip.isRgbw = true; - strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(busConfigs[i]->type)); - delete busConfigs[i]; busConfigs[i] = nullptr; - } - strip.finalizeInit(ledCount); - yield(); - serializeConfig(); - } - - yield(); - handleWs(); - handleStatusLED(); - -// DEBUG serial logging -#ifdef WLED_DEBUG - if (millis() - debugTime > 9999) { - DEBUG_PRINTLN(F("---DEBUG INFO---")); - DEBUG_PRINT(F("Runtime: ")); DEBUG_PRINTLN(millis()); - DEBUG_PRINT(F("Unix time: ")); DEBUG_PRINTLN(now()); - DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) - if (psramFound()) { - DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); - DEBUG_PRINT(F("Free PSRAM: ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); - } else - DEBUG_PRINTLN(F("No PSRAM")); - #endif - DEBUG_PRINT(F("Wifi state: ")); DEBUG_PRINTLN(WiFi.status()); - - if (WiFi.status() != lastWifiState) { - wifiStateChangedTime = millis(); - } - lastWifiState = WiFi.status(); - DEBUG_PRINT(F("State time: ")); DEBUG_PRINTLN(wifiStateChangedTime); - DEBUG_PRINT(F("NTP last sync: ")); DEBUG_PRINTLN(ntpLastSyncTime); - DEBUG_PRINT(F("Client IP: ")); DEBUG_PRINTLN(Network.localIP()); - DEBUG_PRINT(F("Loops/sec: ")); DEBUG_PRINTLN(loops / 10); - loops = 0; - debugTime = millis(); - } - loops++; -#endif // WLED_DEBUG -} - -void WLED::setup() -{ - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detection - #endif - - Serial.begin(115200); - Serial.setTimeout(50); - DEBUG_PRINTLN(); - DEBUG_PRINT(F("---WLED ")); - DEBUG_PRINT(versionString); - DEBUG_PRINT(" "); - DEBUG_PRINT(VERSION); - DEBUG_PRINTLN(F(" INIT---")); -#ifdef ARDUINO_ARCH_ESP32 - DEBUG_PRINT(F("esp32 ")); - DEBUG_PRINTLN(ESP.getSdkVersion()); -#else - DEBUG_PRINT(F("esp8266 ")); - DEBUG_PRINTLN(ESP.getCoreVersion()); -#endif - DEBUG_PRINT(F("heap ")); - DEBUG_PRINTLN(ESP.getFreeHeap()); - registerUsermods(); - - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) - if (psramFound()) { - pinManager.allocatePin(16); // GPIO16 reserved for SPI RAM - pinManager.allocatePin(17); // GPIO17 reserved for SPI RAM - } - #endif - - //DEBUG_PRINT(F("LEDs inited. heap usage ~")); - //DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); - -#ifdef WLED_DEBUG - pinManager.allocatePin(1,true); // GPIO1 reserved for debug output -#endif -#ifdef WLED_USE_DMX //reserve GPIO2 as hardcoded DMX pin - pinManager.allocatePin(2); -#endif - - bool fsinit = false; - DEBUGFS_PRINTLN(F("Mount FS")); -#ifdef ARDUINO_ARCH_ESP32 - fsinit = WLED_FS.begin(true); -#else - fsinit = WLED_FS.begin(); -#endif - if (!fsinit) { - DEBUGFS_PRINTLN(F("FS failed!")); - errorFlag = ERR_FS_BEGIN; - } else deEEP(); - updateFSInfo(); - - DEBUG_PRINTLN(F("Reading config")); - deserializeConfigFromFS(); - -#if STATUSLED - if (!pinManager.isPinAllocated(STATUSLED)) pinMode(STATUSLED, OUTPUT); -#endif - - DEBUG_PRINTLN(F("Initializing strip")); - beginStrip(); - - DEBUG_PRINTLN(F("Usermods setup")); - userSetup(); - usermods.setup(); - if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) - showWelcomePage = true; - WiFi.persistent(false); - #ifdef WLED_USE_ETHERNET - WiFi.onEvent(WiFiEvent); - #endif - - #ifdef WLED_ENABLE_ADALIGHT - if (!pinManager.isPinAllocated(3)) { - Serial.println(F("Ada")); - } - #endif - - // generate module IDs - escapedMac = WiFi.macAddress(); - escapedMac.replace(":", ""); - escapedMac.toLowerCase(); - if (strcmp(cmDNS, "x") == 0) // fill in unique mdns default - { - strcpy_P(cmDNS, PSTR("wled-")); - sprintf(cmDNS + 5, "%*s", 6, escapedMac.c_str() + 6); - } - if (mqttDeviceTopic[0] == 0) { - strcpy_P(mqttDeviceTopic, PSTR("wled/")); - sprintf(mqttDeviceTopic + 5, "%*s", 6, escapedMac.c_str() + 6); - } - if (mqttClientID[0] == 0) { - strcpy_P(mqttClientID, PSTR("WLED-")); - sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); - } - - strip.service(); - -#ifndef WLED_DISABLE_OTA - if (aOtaEnabled) { - ArduinoOTA.onStart([]() { -#ifdef ESP8266 - wifi_set_sleep_type(NONE_SLEEP_T); -#endif - DEBUG_PRINTLN(F("Start ArduinoOTA")); - }); - if (strlen(cmDNS) > 0) - ArduinoOTA.setHostname(cmDNS); - } -#endif -#ifdef WLED_ENABLE_DMX - initDMX(); -#endif - // HTTP server page init - initServer(); - - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) - WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector - #endif -} - -void WLED::beginStrip() -{ - // Initialize NeoPixel Strip and button - - if (ledCount > MAX_LEDS || ledCount == 0) - ledCount = 30; - - strip.finalizeInit(ledCount); - strip.setBrightness(0); - strip.setShowCallback(handleOverlayDraw); - - if (bootPreset > 0) { - applyPreset(bootPreset); - } else if (turnOnAtBoot) { - if (briS > 0) bri = briS; - else if (bri == 0) bri = 128; - } else { - briLast = briS; bri = 0; - } - colorUpdated(NOTIFIER_CALL_MODE_INIT); - - // init relay pin - if (rlyPin>=0) - digitalWrite(rlyPin, (rlyMde ? bri : !bri)); - - // disable button if it is "pressed" unintentionally - //if (btnPin>=0 && buttonType == BTN_TYPE_PUSH && isButtonPressed()) - // buttonType = BTN_TYPE_NONE; -} - -void WLED::initAP(bool resetAP) -{ - if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) - return; - - if (!apSSID[0] || resetAP) - strcpy_P(apSSID, PSTR("WLED-AP")); - if (resetAP) - strcpy_P(apPass, PSTR(DEFAULT_AP_PASS)); - DEBUG_PRINT(F("Opening access point ")); - DEBUG_PRINTLN(apSSID); - WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); - WiFi.softAP(apSSID, apPass, apChannel, apHide); - - if (!apActive) // start captive portal if AP active - { - DEBUG_PRINTLN(F("Init AP interfaces")); - server.begin(); - if (udpPort > 0 && udpPort != ntpLocalPort) { - udpConnected = notifierUdp.begin(udpPort); - } - if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) { - udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - if (udpPort2 > 0 && udpPort2 != ntpLocalPort && udpPort2 != udpPort && udpPort2 != udpRgbPort) { - udp2Connected = notifier2Udp.begin(udpPort2); - } - e131.begin(false, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); - - dnsServer.setErrorReplyCode(DNSReplyCode::NoError); - dnsServer.start(53, "*", WiFi.softAPIP()); - } - apActive = true; -} - -void WLED::initConnection() -{ - #ifdef WLED_ENABLE_WEBSOCKETS - ws.onEvent(wsEvent); - #endif - -#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) - // Only initialize ethernet board if not NONE - if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { - ethernet_settings es = ethernetBoards[ethernetType]; - ETH.begin( - (uint8_t) es.eth_address, - (int) es.eth_power, - (int) es.eth_mdc, - (int) es.eth_mdio, - (eth_phy_type_t) es.eth_type, - (eth_clock_mode_t) es.eth_clk_mode - ); - } -#endif - - WiFi.disconnect(true); // close old connections -#ifdef ESP8266 - WiFi.setPhyMode(WIFI_PHY_MODE_11N); -#endif - - if (staticIP[0] != 0 && staticGateway[0] != 0) { - WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); - } else { - WiFi.config(0U, 0U, 0U); - } - - lastReconnectAttempt = millis(); - - if (!WLED_WIFI_CONFIGURED) { - DEBUG_PRINT(F("No connection configured. ")); - if (!apActive) - initAP(); // instantly go to ap mode - return; - } else if (!apActive) { - if (apBehavior == AP_BEHAVIOR_ALWAYS) { - initAP(); - } else { - DEBUG_PRINTLN(F("Access point disabled.")); - WiFi.softAPdisconnect(true); - WiFi.mode(WIFI_STA); - } - } - showWelcomePage = false; - - DEBUG_PRINT(F("Connecting to ")); - DEBUG_PRINT(clientSSID); - DEBUG_PRINTLN("..."); - - // convert the "serverDescription" into a valid DNS hostname (alphanumeric) - char hostname[25] = "wled-"; - prepareHostname(hostname); - -#ifdef ESP8266 - WiFi.hostname(hostname); -#endif - - WiFi.begin(clientSSID, clientPass); - -#ifdef ARDUINO_ARCH_ESP32 - WiFi.setSleep(!noWifiSleep); - WiFi.setHostname(hostname); -#else - wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); -#endif -} - -void WLED::initInterfaces() -{ - DEBUG_PRINTLN(F("Init STA interfaces")); - -#ifndef WLED_DISABLE_HUESYNC - if (hueIP[0] == 0) { - hueIP[0] = Network.localIP()[0]; - hueIP[1] = Network.localIP()[1]; - hueIP[2] = Network.localIP()[2]; - } -#endif - - // init Alexa hue emulation - if (alexaEnabled) - alexaInit(); - -#ifndef WLED_DISABLE_OTA - if (aOtaEnabled) - ArduinoOTA.begin(); -#endif - - strip.service(); - // Set up mDNS responder: - if (strlen(cmDNS) > 0) { - #ifndef WLED_DISABLE_OTA - if (!aOtaEnabled) //ArduinoOTA begins mDNS for us if enabled - MDNS.begin(cmDNS); - #else - MDNS.begin(cmDNS); - #endif - - DEBUG_PRINTLN(F("mDNS started")); - MDNS.addService("http", "tcp", 80); - MDNS.addService("wled", "tcp", 80); - MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); - } - server.begin(); - - if (udpPort > 0 && udpPort != ntpLocalPort) { - udpConnected = notifierUdp.begin(udpPort); - if (udpConnected && udpRgbPort != udpPort) - udpRgbConnected = rgbUdp.begin(udpRgbPort); - if (udpConnected && udpPort2 != udpPort && udpPort2 != udpRgbPort) - udp2Connected = notifier2Udp.begin(udpPort2); - } - if (ntpEnabled) - ntpConnected = ntpUdp.begin(ntpLocalPort); - -#ifndef WLED_DISABLE_BLYNK - initBlynk(blynkApiKey, blynkHost, blynkPort); -#endif - e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); - reconnectHue(); - initMqtt(); - interfacesInited = true; - wasConnected = true; -} - -byte stacO = 0; -uint32_t lastHeap; -unsigned long heapTime = 0; - -void WLED::handleConnection() -{ - if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) - return; - if (lastReconnectAttempt == 0) - initConnection(); - - // reconnect WiFi to clear stale allocations if heap gets too low - if (millis() - heapTime > 5000) { - uint32_t heap = ESP.getFreeHeap(); - if (heap < JSON_BUFFER_SIZE+512 && lastHeap < JSON_BUFFER_SIZE+512) { - DEBUG_PRINT(F("Heap too low! ")); - DEBUG_PRINTLN(heap); - forceReconnect = true; - } - lastHeap = heap; - heapTime = millis(); - } - - byte stac = 0; - if (apActive) { -#ifdef ESP8266 - stac = wifi_softap_get_station_num(); -#else - wifi_sta_list_t stationList; - esp_wifi_ap_get_sta_list(&stationList); - stac = stationList.num; -#endif - if (stac != stacO) { - stacO = stac; - DEBUG_PRINT(F("Connected AP clients: ")); - DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { // trying to connect, but not connected - if (stac) - WiFi.disconnect(); // disable search so that AP can work - else - initConnection(); // restart search - } - } - } - if (forceReconnect) { - DEBUG_PRINTLN(F("Forcing reconnect.")); - initConnection(); - interfacesInited = false; - forceReconnect = false; - wasConnected = false; - return; - } - if (!Network.isConnected()) { - if (interfacesInited) { - DEBUG_PRINTLN(F("Disconnected!")); - interfacesInited = false; - initConnection(); - } - if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) - initConnection(); - if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) - initAP(); - } else if (!interfacesInited) { // newly connected - DEBUG_PRINTLN(""); - DEBUG_PRINT(F("Connected! IP address: ")); - DEBUG_PRINTLN(Network.localIP()); - initInterfaces(); - userConnected(); - usermods.connected(); - - // shut down AP - if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { - dnsServer.stop(); - WiFi.softAPdisconnect(true); - apActive = false; - DEBUG_PRINTLN(F("Access point disabled.")); - } - } -} - -void WLED::handleStatusLED() -{ - #if STATUSLED - if (pinManager.isPinAllocated(STATUSLED)) return; //lower priority if something else uses the same pin - - ledStatusType = WLED_CONNECTED ? 0 : 2; - if (mqttEnabled && ledStatusType != 2) // Wi-Fi takes presendence over MQTT - ledStatusType = WLED_MQTT_CONNECTED ? 0 : 4; - if (ledStatusType) { - if (millis() - ledStatusLastMillis >= (1000/ledStatusType)) { - ledStatusLastMillis = millis(); - ledStatusState = ledStatusState ? 0 : 1; - digitalWrite(STATUSLED, ledStatusState); - } - } else { - #ifdef STATUSLEDINVERTED - digitalWrite(STATUSLED, HIGH); - #else - digitalWrite(STATUSLED, LOW); - #endif - - } - #endif -} +#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp! +#include "wled.h" +#include + +#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) +#include "soc/soc.h" +#include "soc/rtc_cntl_reg.h" +#endif + +/* + * Main WLED class implementation. Mostly initialization and connection logic + */ + +WLED::WLED() +{ +} + +#ifdef WLED_USE_ETHERNET +// settings for various ethernet boards +typedef struct EthernetSettings { + uint8_t eth_address; + int eth_power; + int eth_mdc; + int eth_mdio; + eth_phy_type_t eth_type; + eth_clock_mode_t eth_clk_mode; +} ethernet_settings; + +ethernet_settings ethernetBoards[] = { + // None + { + }, + + // WT32-EHT01 + // Please note, from my testing only these pins work for LED outputs: + // IO2, IO4, IO12, IO14, IO15 + // These pins do not appear to work from my testing: + // IO35, IO36, IO39 + { + 1, // eth_address, + 16, // eth_power, + 23, // eth_mdc, + 18, // eth_mdio, + ETH_PHY_LAN8720, // eth_type, + ETH_CLOCK_GPIO0_IN // eth_clk_mode + }, + + // ESP32-POE + { + 0, // eth_address, + 12, // eth_power, + 23, // eth_mdc, + 18, // eth_mdio, + ETH_PHY_LAN8720, // eth_type, + ETH_CLOCK_GPIO17_OUT // eth_clk_mode + }, + + // WESP32 + { + 0, // eth_address, + -1, // eth_power, + 16, // eth_mdc, + 17, // eth_mdio, + ETH_PHY_LAN8720, // eth_type, + ETH_CLOCK_GPIO0_IN // eth_clk_mode + }, + + // QuinLed-ESP32-Ethernet + { + 0, // eth_address, + 5, // eth_power, + 23, // eth_mdc, + 18, // eth_mdio, + ETH_PHY_LAN8720, // eth_type, + ETH_CLOCK_GPIO17_OUT // eth_clk_mode + } +}; +#endif + +// turns all LEDs off and restarts ESP +void WLED::reset() +{ + briT = 0; + #ifdef WLED_ENABLE_WEBSOCKETS + ws.closeAll(1012); + #endif + long dly = millis(); + while (millis() - dly < 450) { + yield(); // enough time to send response to client + } + setAllLeds(); + DEBUG_PRINTLN(F("MODULE RESET")); + ESP.restart(); +} + +bool oappendi(int i) +{ + char s[11]; + sprintf(s, "%d", i); + return oappend(s); +} + +bool oappend(const char* txt) +{ + uint16_t len = strlen(txt); + if (olen + len >= OMAX) + return false; // buffer full + strcpy(obuf + olen, txt); + olen += len; + return true; +} + +void prepareHostname(char* hostname) +{ + const char *pC = serverDescription; + uint8_t pos = 5; + + while (*pC && pos < 24) { // while !null and not over length + if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname + hostname[pos] = *pC; + pos++; + } else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') { + hostname[pos] = '-'; + pos++; + } + // else do nothing - no leading hyphens and do not include hyphens for all other characters. + pC++; + } + // if the hostname is left blank, use the mac address/default mdns name + if (pos < 6) { + sprintf(hostname + 5, "%*s", 6, escapedMac.c_str() + 6); + } else { //last character must not be hyphen + while (pos > 0 && hostname[pos -1] == '-') { + hostname[pos -1] = 0; + pos--; + } + } +} + +//handle Ethernet connection event +void WiFiEvent(WiFiEvent_t event) +{ + #ifdef WLED_USE_ETHERNET + char hostname[25] = "wled-"; + #endif + + switch (event) { +#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) + case SYSTEM_EVENT_ETH_START: + DEBUG_PRINT(F("ETH Started")); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + DEBUG_PRINT(F("ETH Connected")); + if (!apActive) { + WiFi.disconnect(true); + } + if (staticIP != (uint32_t)0x00000000 && staticGateway != (uint32_t)0x00000000) { + ETH.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); + } else { + ETH.config(INADDR_NONE, INADDR_NONE, INADDR_NONE); + } + // convert the "serverDescription" into a valid DNS hostname (alphanumeric) + prepareHostname(hostname); + ETH.setHostname(hostname); + showWelcomePage = false; + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + DEBUG_PRINT(F("ETH Disconnected")); + forceReconnect = true; + break; +#endif + default: + break; + } +} + +void WLED::loop() +{ + handleTime(); + handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too + handleConnection(); + handleSerial(); + handleNotifications(); + handleTransitions(); +#ifdef WLED_ENABLE_DMX + handleDMX(); +#endif + userLoop(); + usermods.loop(); + + yield(); + handleIO(); + handleIR(); + handleAlexa(); + + yield(); + + if (doReboot) + reset(); + if (doCloseFile) { + closeFile(); + yield(); + } + + if (!realtimeMode || realtimeOverride) // block stuff if WARLS/Adalight is enabled + { + if (apActive) + dnsServer.processNextRequest(); +#ifndef WLED_DISABLE_OTA + if (WLED_CONNECTED && aOtaEnabled) + ArduinoOTA.handle(); +#endif + handleNightlight(); + handlePlaylist(); + yield(); + + handleHue(); + handleBlynk(); + + yield(); + + if (!offMode || strip.isOffRefreshRequred) + strip.service(); +#ifdef ESP8266 + else if (!noWifiSleep) + delay(1); //required to make sure ESP enters modem sleep (see #1184) +#endif + } + yield(); +#ifdef ESP8266 + MDNS.update(); +#endif + + //millis() rolls over every 50 days + if (lastMqttReconnectAttempt > millis()) { + rolloverMillis++; + lastMqttReconnectAttempt = 0; + } + if (millis() - lastMqttReconnectAttempt > 30000) { + lastMqttReconnectAttempt = millis(); + initMqtt(); + yield(); + // refresh WLED nodes list + refreshNodeList(); + if (nodeBroadcastEnabled) sendSysInfoUDP(); + yield(); + } + + //LED settings have been saved, re-init busses + //This code block causes severe FPS drop on ESP32 with the original "if (busConfigs[0] != nullptr)" conditional. Investigate! + if (doInitBusses) { + doInitBusses = false; + DEBUG_PRINTLN(F("Re-init busses.")); + busses.removeAll(); + uint32_t mem = 0; + strip.isRgbw = false; + for (uint8_t i = 0; i < WLED_MAX_BUSSES; i++) { + if (busConfigs[i] == nullptr) break; + mem += busses.memUsage(*busConfigs[i]); + if (mem <= MAX_LED_MEMORY) busses.add(*busConfigs[i]); + //if (BusManager::isRgbw(busConfigs[i]->type)) strip.isRgbw = true; + strip.isRgbw = (strip.isRgbw || BusManager::isRgbw(busConfigs[i]->type)); + delete busConfigs[i]; busConfigs[i] = nullptr; + } + strip.finalizeInit(ledCount); + yield(); + serializeConfig(); + } + + yield(); + handleWs(); + handleStatusLED(); + +// DEBUG serial logging +#ifdef WLED_DEBUG + if (millis() - debugTime > 9999) { + DEBUG_PRINTLN(F("---DEBUG INFO---")); + DEBUG_PRINT(F("Runtime: ")); DEBUG_PRINTLN(millis()); + DEBUG_PRINT(F("Unix time: ")); toki.printTime(toki.getTime()); + DEBUG_PRINT(F("Free heap: ")); DEBUG_PRINTLN(ESP.getFreeHeap()); + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) + if (psramFound()) { + DEBUG_PRINT(F("Total PSRAM: ")); DEBUG_PRINT(ESP.getPsramSize()/1024); DEBUG_PRINTLN("kB"); + DEBUG_PRINT(F("Free PSRAM: ")); DEBUG_PRINT(ESP.getFreePsram()/1024); DEBUG_PRINTLN("kB"); + } else + DEBUG_PRINTLN(F("No PSRAM")); + #endif + DEBUG_PRINT(F("Wifi state: ")); DEBUG_PRINTLN(WiFi.status()); + + if (WiFi.status() != lastWifiState) { + wifiStateChangedTime = millis(); + } + lastWifiState = WiFi.status(); + DEBUG_PRINT(F("State time: ")); DEBUG_PRINTLN(wifiStateChangedTime); + DEBUG_PRINT(F("NTP last sync: ")); DEBUG_PRINTLN(ntpLastSyncTime); + DEBUG_PRINT(F("Client IP: ")); DEBUG_PRINTLN(Network.localIP()); + DEBUG_PRINT(F("Loops/sec: ")); DEBUG_PRINTLN(loops / 10); + loops = 0; + debugTime = millis(); + } + loops++; +#endif // WLED_DEBUG + toki.resetTick(); +} + +void WLED::setup() +{ + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detection + #endif + + Serial.begin(115200); + Serial.setTimeout(50); + DEBUG_PRINTLN(); + DEBUG_PRINT(F("---WLED ")); + DEBUG_PRINT(versionString); + DEBUG_PRINT(" "); + DEBUG_PRINT(VERSION); + DEBUG_PRINTLN(F(" INIT---")); +#ifdef ARDUINO_ARCH_ESP32 + DEBUG_PRINT(F("esp32 ")); + DEBUG_PRINTLN(ESP.getSdkVersion()); +#else + DEBUG_PRINT(F("esp8266 ")); + DEBUG_PRINTLN(ESP.getCoreVersion()); +#endif + DEBUG_PRINT(F("heap ")); + DEBUG_PRINTLN(ESP.getFreeHeap()); + registerUsermods(); + + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) + if (psramFound()) { + pinManager.allocatePin(16); // GPIO16 reserved for SPI RAM + pinManager.allocatePin(17); // GPIO17 reserved for SPI RAM + } + #endif + + //DEBUG_PRINT(F("LEDs inited. heap usage ~")); + //DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); + +#ifdef WLED_DEBUG + pinManager.allocatePin(1,true); // GPIO1 reserved for debug output +#endif +#ifdef WLED_USE_DMX //reserve GPIO2 as hardcoded DMX pin + pinManager.allocatePin(2); +#endif + + bool fsinit = false; + DEBUGFS_PRINTLN(F("Mount FS")); +#ifdef ARDUINO_ARCH_ESP32 + fsinit = WLED_FS.begin(true); +#else + fsinit = WLED_FS.begin(); +#endif + if (!fsinit) { + DEBUGFS_PRINTLN(F("FS failed!")); + errorFlag = ERR_FS_BEGIN; + } else deEEP(); + updateFSInfo(); + + DEBUG_PRINTLN(F("Reading config")); + deserializeConfigFromFS(); + +#if STATUSLED + if (!pinManager.isPinAllocated(STATUSLED)) pinMode(STATUSLED, OUTPUT); +#endif + + DEBUG_PRINTLN(F("Initializing strip")); + beginStrip(); + + DEBUG_PRINTLN(F("Usermods setup")); + userSetup(); + usermods.setup(); + if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) + showWelcomePage = true; + WiFi.persistent(false); + #ifdef WLED_USE_ETHERNET + WiFi.onEvent(WiFiEvent); + #endif + + #ifdef WLED_ENABLE_ADALIGHT + if (!pinManager.isPinAllocated(3)) { + Serial.println(F("Ada")); + } + #endif + + // generate module IDs + escapedMac = WiFi.macAddress(); + escapedMac.replace(":", ""); + escapedMac.toLowerCase(); + if (strcmp(cmDNS, "x") == 0) // fill in unique mdns default + { + strcpy_P(cmDNS, PSTR("wled-")); + sprintf(cmDNS + 5, "%*s", 6, escapedMac.c_str() + 6); + } + if (mqttDeviceTopic[0] == 0) { + strcpy_P(mqttDeviceTopic, PSTR("wled/")); + sprintf(mqttDeviceTopic + 5, "%*s", 6, escapedMac.c_str() + 6); + } + if (mqttClientID[0] == 0) { + strcpy_P(mqttClientID, PSTR("WLED-")); + sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); + } + + strip.service(); + +#ifndef WLED_DISABLE_OTA + if (aOtaEnabled) { + ArduinoOTA.onStart([]() { +#ifdef ESP8266 + wifi_set_sleep_type(NONE_SLEEP_T); +#endif + DEBUG_PRINTLN(F("Start ArduinoOTA")); + }); + if (strlen(cmDNS) > 0) + ArduinoOTA.setHostname(cmDNS); + } +#endif +#ifdef WLED_ENABLE_DMX + initDMX(); +#endif + // HTTP server page init + initServer(); + + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DISABLE_BROWNOUT_DET) + WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 1); //enable brownout detector + #endif +} + +void WLED::beginStrip() +{ + // Initialize NeoPixel Strip and button + + if (ledCount > MAX_LEDS || ledCount == 0) + ledCount = 30; + + strip.finalizeInit(ledCount); + strip.setBrightness(0); + strip.setShowCallback(handleOverlayDraw); + + if (bootPreset > 0) { + applyPreset(bootPreset); + } else if (turnOnAtBoot) { + if (briS > 0) bri = briS; + else if (bri == 0) bri = 128; + } else { + briLast = briS; bri = 0; + } + colorUpdated(NOTIFIER_CALL_MODE_INIT); + + // init relay pin + if (rlyPin>=0) + digitalWrite(rlyPin, (rlyMde ? bri : !bri)); + + // disable button if it is "pressed" unintentionally + //if (btnPin>=0 && buttonType == BTN_TYPE_PUSH && isButtonPressed()) + // buttonType = BTN_TYPE_NONE; +} + +void WLED::initAP(bool resetAP) +{ + if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) + return; + + if (!apSSID[0] || resetAP) + strcpy_P(apSSID, PSTR("WLED-AP")); + if (resetAP) + strcpy_P(apPass, PSTR(DEFAULT_AP_PASS)); + DEBUG_PRINT(F("Opening access point ")); + DEBUG_PRINTLN(apSSID); + WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); + WiFi.softAP(apSSID, apPass, apChannel, apHide); + + if (!apActive) // start captive portal if AP active + { + DEBUG_PRINTLN(F("Init AP interfaces")); + server.begin(); + if (udpPort > 0 && udpPort != ntpLocalPort) { + udpConnected = notifierUdp.begin(udpPort); + } + if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) { + udpRgbConnected = rgbUdp.begin(udpRgbPort); + } + if (udpPort2 > 0 && udpPort2 != ntpLocalPort && udpPort2 != udpPort && udpPort2 != udpRgbPort) { + udp2Connected = notifier2Udp.begin(udpPort2); + } + e131.begin(false, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); + + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(53, "*", WiFi.softAPIP()); + } + apActive = true; +} + +void WLED::initConnection() +{ + #ifdef WLED_ENABLE_WEBSOCKETS + ws.onEvent(wsEvent); + #endif + +#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) + // Only initialize ethernet board if not NONE + if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { + ethernet_settings es = ethernetBoards[ethernetType]; + ETH.begin( + (uint8_t) es.eth_address, + (int) es.eth_power, + (int) es.eth_mdc, + (int) es.eth_mdio, + (eth_phy_type_t) es.eth_type, + (eth_clock_mode_t) es.eth_clk_mode + ); + } +#endif + + WiFi.disconnect(true); // close old connections +#ifdef ESP8266 + WiFi.setPhyMode(WIFI_PHY_MODE_11N); +#endif + + if (staticIP[0] != 0 && staticGateway[0] != 0) { + WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); + } else { + WiFi.config(0U, 0U, 0U); + } + + lastReconnectAttempt = millis(); + + if (!WLED_WIFI_CONFIGURED) { + DEBUG_PRINT(F("No connection configured. ")); + if (!apActive) + initAP(); // instantly go to ap mode + return; + } else if (!apActive) { + if (apBehavior == AP_BEHAVIOR_ALWAYS) { + initAP(); + } else { + DEBUG_PRINTLN(F("Access point disabled.")); + WiFi.softAPdisconnect(true); + WiFi.mode(WIFI_STA); + } + } + showWelcomePage = false; + + DEBUG_PRINT(F("Connecting to ")); + DEBUG_PRINT(clientSSID); + DEBUG_PRINTLN("..."); + + // convert the "serverDescription" into a valid DNS hostname (alphanumeric) + char hostname[25] = "wled-"; + prepareHostname(hostname); + +#ifdef ESP8266 + WiFi.hostname(hostname); +#endif + + WiFi.begin(clientSSID, clientPass); + +#ifdef ARDUINO_ARCH_ESP32 + WiFi.setSleep(!noWifiSleep); + WiFi.setHostname(hostname); +#else + wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); +#endif +} + +void WLED::initInterfaces() +{ + DEBUG_PRINTLN(F("Init STA interfaces")); + +#ifndef WLED_DISABLE_HUESYNC + if (hueIP[0] == 0) { + hueIP[0] = Network.localIP()[0]; + hueIP[1] = Network.localIP()[1]; + hueIP[2] = Network.localIP()[2]; + } +#endif + + // init Alexa hue emulation + if (alexaEnabled) + alexaInit(); + +#ifndef WLED_DISABLE_OTA + if (aOtaEnabled) + ArduinoOTA.begin(); +#endif + + strip.service(); + // Set up mDNS responder: + if (strlen(cmDNS) > 0) { + #ifndef WLED_DISABLE_OTA + if (!aOtaEnabled) //ArduinoOTA begins mDNS for us if enabled + MDNS.begin(cmDNS); + #else + MDNS.begin(cmDNS); + #endif + + DEBUG_PRINTLN(F("mDNS started")); + MDNS.addService("http", "tcp", 80); + MDNS.addService("wled", "tcp", 80); + MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); + } + server.begin(); + + if (udpPort > 0 && udpPort != ntpLocalPort) { + udpConnected = notifierUdp.begin(udpPort); + if (udpConnected && udpRgbPort != udpPort) + udpRgbConnected = rgbUdp.begin(udpRgbPort); + if (udpConnected && udpPort2 != udpPort && udpPort2 != udpRgbPort) + udp2Connected = notifier2Udp.begin(udpPort2); + } + if (ntpEnabled) + ntpConnected = ntpUdp.begin(ntpLocalPort); + +#ifndef WLED_DISABLE_BLYNK + initBlynk(blynkApiKey, blynkHost, blynkPort); +#endif + e131.begin(e131Multicast, e131Port, e131Universe, E131_MAX_UNIVERSE_COUNT); + reconnectHue(); + initMqtt(); + interfacesInited = true; + wasConnected = true; +} + +byte stacO = 0; +uint32_t lastHeap; +unsigned long heapTime = 0; + +void WLED::handleConnection() +{ + if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) + return; + if (lastReconnectAttempt == 0) + initConnection(); + + // reconnect WiFi to clear stale allocations if heap gets too low + if (millis() - heapTime > 5000) { + uint32_t heap = ESP.getFreeHeap(); + if (heap < JSON_BUFFER_SIZE+512 && lastHeap < JSON_BUFFER_SIZE+512) { + DEBUG_PRINT(F("Heap too low! ")); + DEBUG_PRINTLN(heap); + forceReconnect = true; + } + lastHeap = heap; + heapTime = millis(); + } + + byte stac = 0; + if (apActive) { +#ifdef ESP8266 + stac = wifi_softap_get_station_num(); +#else + wifi_sta_list_t stationList; + esp_wifi_ap_get_sta_list(&stationList); + stac = stationList.num; +#endif + if (stac != stacO) { + stacO = stac; + DEBUG_PRINT(F("Connected AP clients: ")); + DEBUG_PRINTLN(stac); + if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { // trying to connect, but not connected + if (stac) + WiFi.disconnect(); // disable search so that AP can work + else + initConnection(); // restart search + } + } + } + if (forceReconnect) { + DEBUG_PRINTLN(F("Forcing reconnect.")); + initConnection(); + interfacesInited = false; + forceReconnect = false; + wasConnected = false; + return; + } + if (!Network.isConnected()) { + if (interfacesInited) { + DEBUG_PRINTLN(F("Disconnected!")); + interfacesInited = false; + initConnection(); + } + if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) + initConnection(); + if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) + initAP(); + } else if (!interfacesInited) { // newly connected + DEBUG_PRINTLN(""); + DEBUG_PRINT(F("Connected! IP address: ")); + DEBUG_PRINTLN(Network.localIP()); + initInterfaces(); + userConnected(); + usermods.connected(); + + // shut down AP + if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { + dnsServer.stop(); + WiFi.softAPdisconnect(true); + apActive = false; + DEBUG_PRINTLN(F("Access point disabled.")); + } + } +} + +void WLED::handleStatusLED() +{ + #if STATUSLED + if (pinManager.isPinAllocated(STATUSLED)) return; //lower priority if something else uses the same pin + + ledStatusType = WLED_CONNECTED ? 0 : 2; + if (mqttEnabled && ledStatusType != 2) // Wi-Fi takes presendence over MQTT + ledStatusType = WLED_MQTT_CONNECTED ? 0 : 4; + if (ledStatusType) { + if (millis() - ledStatusLastMillis >= (1000/ledStatusType)) { + ledStatusLastMillis = millis(); + ledStatusState = ledStatusState ? 0 : 1; + digitalWrite(STATUSLED, ledStatusState); + } + } else { + #ifdef STATUSLEDINVERTED + digitalWrite(STATUSLED, HIGH); + #else + digitalWrite(STATUSLED, LOW); + #endif + + } + #endif +} \ No newline at end of file diff --git a/wled00/wled.h b/wled00/wled.h index 381e613c6..ee230add6 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 @@ -354,7 +355,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 @@ -476,13 +477,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); @@ -545,6 +542,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; diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index ef90a077b..28762392c 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;