diff --git a/SW/burn_efuse/burn_efuse/burn_efuse.ino b/SW/burn_efuse/burn_efuse/burn_efuse.ino new file mode 100755 index 0000000..2b6607f --- /dev/null +++ b/SW/burn_efuse/burn_efuse/burn_efuse.ino @@ -0,0 +1,70 @@ +#include + +extern "C" { + #include "esp_efuse.h" + #include "esp_efuse_table.h" + #include "esp_system.h" +} + +static bool isVddSdioFixedTo3v3(){ + bool force = esp_efuse_read_field_bit(ESP_EFUSE_XPD_SDIO_FORCE); + bool reg = esp_efuse_read_field_bit(ESP_EFUSE_XPD_SDIO_REG); + bool tieh = esp_efuse_read_field_bit(ESP_EFUSE_XPD_SDIO_TIEH); + + return force && reg && tieh; +} + +static void burnVddSdio3v3Once(){ + Serial.println(); + Serial.println("Checking VDD_SDIO eFuse..."); + + bool force = esp_efuse_read_field_bit(ESP_EFUSE_XPD_SDIO_FORCE); + bool reg = esp_efuse_read_field_bit(ESP_EFUSE_XPD_SDIO_REG); + bool tieh = esp_efuse_read_field_bit(ESP_EFUSE_XPD_SDIO_TIEH); + + Serial.printf("XPD_SDIO_FORCE: %d\n", force); + Serial.printf("XPD_SDIO_REG: %d\n", reg); + Serial.printf("XPD_SDIO_TIEH: %d\n", tieh); + + if (isVddSdioFixedTo3v3()){ + Serial.println("VDD_SDIO already fixed to 3.3V."); + return; + } + + Serial.println("Burning eFuse: VDD_SDIO fixed to 3.3V."); + Serial.println("This is permanent."); + + esp_err_t err; + + err = esp_efuse_write_field_bit(ESP_EFUSE_XPD_SDIO_FORCE); + if (err != ESP_OK){ + Serial.printf("Failed to burn XPD_SDIO_FORCE: %d\n", err); + return; + } + + err = esp_efuse_write_field_bit(ESP_EFUSE_XPD_SDIO_REG); + if (err != ESP_OK){ + Serial.printf("Failed to burn XPD_SDIO_REG: %d\n", err); + return; + } + + err = esp_efuse_write_field_bit(ESP_EFUSE_XPD_SDIO_TIEH); + if (err != ESP_OK){ + Serial.printf("Failed to burn XPD_SDIO_TIEH: %d\n", err); + return; + } + + Serial.println("eFuse burned. Restarting..."); + delay(1000); + esp_restart(); +} + +void setup(){ + Serial.begin(115200); + delay(5000); + + burnVddSdio3v3Once(); +} + +void loop(){ +} \ No newline at end of file diff --git a/SW/esplan_v2_renke_weather_station/Config.h b/SW/esplan_v2_renke_weather_station/Config.h new file mode 100755 index 0000000..361fb3c --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/Config.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#define ETH_PHY_TYPE ETH_PHY_LAN8720 +#define ETH_PHY_ADDR 0 +#define ETH_PHY_MDC 23 +#define ETH_PHY_MDIO 18 +#define ETH_PHY_POWER -1 +#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT + +static constexpr int PIN_RS485_RX = 36; +static constexpr int PIN_RS485_TX = 4; + +static constexpr uint8_t DEFAULT_MODBUS_ADDR = 0x01; +static constexpr uint32_t DEFAULT_MODBUS_BAUD = 4800; + +static constexpr uint16_t REG_DATA_START = 500; +static constexpr uint16_t REG_DATA_COUNT = 16; + +static constexpr uint16_t REG_CFG_WIND_DIR_OFFSET = 0x6000; +static constexpr uint16_t REG_CFG_RAIN_SENSITIVITY = 0x6003; + +// Wind zero command prefix without CRC. CRC is appended automatically in software. +// User-requested raw frame prefix: 01 AB 01 06 01 02 00 5A A9 CD +static constexpr const char* CMD_WIND_ZERO = "01AB01060102005AA9CD"; + +static constexpr uint16_t REG_CFG_RAIN_ZERO = 0x0104; +static constexpr uint16_t CFG_RAIN_ZERO = 0x005A; + +static constexpr unsigned long SENSOR_POLL_MS = 2000; +static constexpr unsigned long DETECT_RECHECK_MS = 30000; +static constexpr unsigned long ETH_LOG_MS = 10000; + +static constexpr size_t HTTP_RESERVE_LARGE = 32000; +static constexpr size_t HTTP_RESERVE_MED = 12000; + +enum class AirQualityMode : uint8_t { + Auto = 0, + PM = 1, + CO2 = 2 +}; diff --git a/SW/esplan_v2_renke_weather_station/Lang.h b/SW/esplan_v2_renke_weather_station/Lang.h new file mode 100755 index 0000000..5b7f1b6 --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/Lang.h @@ -0,0 +1,262 @@ +#pragma once + +#include + +struct Texts { + const char* appTitle; + const char* tabMain; + const char* tabConfig; + const char* language; + const char* sensorStatus; + const char* values; + const char* present; + const char* absent; + const char* pollAge; + const char* seconds; + const char* windSpeed; + const char* beaufort; + const char* windDir8; + const char* windDirDeg; + const char* humidity; + const char* temperature; + const char* noise; + const char* pressure; + const char* lightHigh; + const char* lightLow; + const char* lightLux100; + const char* rain; + const char* compass; + const char* solarRadiation; + const char* sensorWind; + const char* sensorHumidityTemp; + const char* sensorNoise; + const char* sensorAir; + const char* sensorPressure; + const char* sensorLight; + const char* sensorRain; + const char* sensorCompass; + const char* sensorSolar; + const char* configTitle; + const char* address; + const char* baudrate; + const char* save; + const char* writeOk; + const char* writeFail; + const char* execute; + const char* windOffset; + const char* rainSensitivity; + const char* commandWindZero; + const char* commandRainZero; + const char* tableItem; + const char* tableSetting; + const char* tableNote; + const char* modbusLog; + const char* modbusFrames; + const char* modbusLastStatus; + const char* modbusAction; + const char* modbusDuration; + const char* modbusCounters; + const char* modbusError; + const char* modbusTx; + const char* modbusRx; + const char* noteAddress; + const char* noteBaudrate; + const char* noteWindOffset; + const char* noteRainSensitivity; + const char* noteWindZero; + const char* noteRainZero; + const char* rawRegisters; + const char* manualCommand; + const char* manualAddress; + const char* manualRegisterHex; + const char* manualValueHex; + const char* manualSend; + const char* manualPreview; + const char* manualNote; + const char* rawHexTitle; + const char* rawHexInput; + const char* rawHexSend; + const char* rawHexNote; + const char* noResult; + const char* statusOkShort; + const char* statusErrorShort; +}; + +enum class UiLang : uint8_t { + CZ = 0, + EN = 1 +}; + +inline UiLang parseLang(const String& v) { + String s = v; + s.toLowerCase(); + return (s == "en") ? UiLang::EN : UiLang::CZ; +} + +inline const char* langCode(UiLang lang) { + return (lang == UiLang::EN) ? "en" : "cz"; +} + +inline const Texts& T(UiLang lang) { + static const Texts cz = { + "ESPlan Meteostanice RS-FSXCS-N01", + "Hlavní stránka", + "Konfigurace", + "Jazyk", + "Stav čidel", + "Aktuální hodnoty", + "Přítomno", + "Nepřítomno", + "Stáří dat", + "s", + "Rychlost větru", + "Síla větru", + "Směr větru 0-7", + "Směr větru", + "Vlhkost", + "Teplota", + "Hluk", + "Tlak", + "Osvětlení high", + "Osvětlení low", + "Osvětlení", + "Déšť", + "Kompas", + "Sluneční radiace", + "Vítr", + "Vlhkost / Teplota", + "Hluk", + "Kvalita vzduchu", + "Tlak", + "Světlo", + "Déšť", + "Kompas", + "Solární", + "Konfigurace snímače", + "Adresa (ESP32)", + "Baudrate (ESP32)", + "Uložit", + "Zápis OK", + "Zápis CHYBA", + "Provést", + "Offset směru větru", + "Citlivost optického deště", + "Nulování větru", + "Nulování deště", + "Položka", + "Nastavení", + "Poznámka", + "Modbus log", + "Modbus rámce", + "Stav", + "Akce", + "Doba", + "Počítadla", + "Chyba", + "TX", + "RX", + "Toto nastavení mění pouze komunikaci ESP32, ne konfiguraci samotného snímače.", + "Toto nastavení mění pouze komunikaci ESP32, ne konfiguraci samotného snímače.", + "Registr 0x6000, HEX hodnota.", + "Registr 0x6003, HEX hodnota.", + "Zapíše kalibrační příkaz do snímače.", + "Zapíše nulovací příkaz do snímače.", + "Surové registry", + "Ruční Modbus příkaz", + "Adresa", + "Registr (HEX)", + "Hodnota (HEX)", + "Odeslat", + "Rámec s CRC", + "Odešle se funkce 0x06. Adresa je decimálně, registr a hodnota jsou HEX. CRC se počítá automaticky.", + "Celý HEX příkaz", + "HEX rámec", + "Odeslat HEX", + "Zadej celý rámec v HEX bez CRC(dopočítá se automaticky). Neověřuje se správnost rámce, odesílá se tak jak je zadán.", + "Zatím bez výsledku", + "OK", + "CHYBA" + }; + + static const Texts en = { + "ESPlan Weather Station RS-FSXCS-N01", + "Main page", + "Configuration", + "Language", + "Sensor status", + "Current values", + "Present", + "Absent", + "Data age", + "s", + "Wind speed", + "Beaufort", + "Wind direction 0-7", + "Wind direction", + "Humidity", + "Temperature", + "Noise", + "Pressure", + "Light high", + "Light low", + "Light", + "Rain", + "Compass", + "Solar radiation", + "Wind", + "Humidity / Temperature", + "Noise", + "Air quality", + "Pressure", + "Light", + "Rain", + "Compass", + "Solar", + "Sensor configuration", + "Address (ESP32)", + "Baudrate (ESP32)", + "Save", + "Write OK", + "Write FAIL", + "Execute", + "Wind direction offset", + "Optical rain sensitivity", + "Zero wind", + "Zero rain", + "Item", + "Setting", + "Note", + "Modbus log", + "Modbus frames", + "Status", + "Action", + "Duration", + "Counters", + "Error", + "TX", + "RX", + "This setting affects only ESP32 communication, not the sensor configuration itself.", + "This setting affects only ESP32 communication, not the sensor configuration itself.", + "Register 0x6000, HEX value.", + "Register 0x6003, HEX value.", + "Writes the calibration command to the sensor.", + "Writes the reset command to the sensor.", + "Raw registers", + "Manual Modbus command", + "Address", + "Register (HEX)", + "Value (HEX)", + "Send", + "Frame with CRC", + "Function 0x06 will be sent. Address is decimal, register and value are HEX. CRC is calculated automatically.", + "Full HEX command", + "HEX frame", + "Send HEX", + "Enter the entire frame in HEX without CRC (it will be calculated automatically). The frame is not verified for correctness, it is sent as entered.", + "No result yet", + "OK", + "ERROR" + }; + + return (lang == UiLang::EN) ? en : cz; +} diff --git a/SW/esplan_v2_renke_weather_station/WeatherStation.cpp b/SW/esplan_v2_renke_weather_station/WeatherStation.cpp new file mode 100755 index 0000000..3b9f5e6 --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/WeatherStation.cpp @@ -0,0 +1,531 @@ +#include "WeatherStation.h" +#include +#include + +static bool parseHexStringLocal(const String& input, uint8_t* out, size_t& outLen, size_t maxLen) { + String s = input; + s.trim(); + s.replace(" ", ""); + s.replace("\t", ""); + s.replace("\r", ""); + s.replace("\n", ""); + + if (s.length() == 0 || (s.length() % 2) != 0) { + outLen = 0; + return false; + } + + const size_t bytes = s.length() / 2; + if (bytes > maxLen) { + outLen = 0; + return false; + } + + outLen = 0; + for (size_t i = 0; i < bytes; i++) { + const String tok = s.substring(i * 2, i * 2 + 2); + char* endptr = nullptr; + const unsigned long v = strtoul(tok.c_str(), &endptr, 16); + if (*endptr != '\0' || v > 0xFFUL) { + outLen = 0; + return false; + } + out[outLen++] = static_cast(v); + } + + return true; +} + +WeatherStation::WeatherStation(HardwareSerial& serial) + : _serial(serial) {} + +void WeatherStation::begin() { + _serial.begin(_config.baudrate, SERIAL_8N1, PIN_RS485_RX, PIN_RS485_TX); +} + +bool WeatherStation::setBaudrate(uint32_t baud) { + _config.baudrate = baud; + _serial.updateBaudRate(baud); + return true; +} + +uint16_t WeatherStation::crc16(const uint8_t* data, size_t len) const { + uint16_t crc = 0xFFFF; + for (size_t pos = 0; pos < len; pos++) { + crc ^= static_cast(data[pos]); + for (uint8_t i = 0; i < 8; i++) { + if (crc & 0x0001) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + return crc; +} + +int16_t WeatherStation::toSigned(uint16_t v) { + return static_cast(v); +} + +String WeatherStation::toHex(const uint8_t* data, size_t len) { + static const char* HEX_CHARS = "0123456789ABCDEF"; + String out; + out.reserve(len * 3); + for (size_t i = 0; i < len; i++) { + if (i) out += ' '; + out += HEX_CHARS[(data[i] >> 4) & 0x0F]; + out += HEX_CHARS[data[i] & 0x0F]; + } + return out; +} + +const __FlashStringHelper* WeatherStation::exceptionText(uint8_t code) { + switch (code) { + case 0x01: return F("illegal function"); + case 0x02: return F("illegal data address"); + case 0x03: return F("illegal data value"); + case 0x04: return F("slave device failure"); + case 0x05: return F("acknowledge"); + case 0x06: return F("slave device busy"); + case 0x08: return F("memory parity error"); + case 0x0A: return F("gateway path unavailable"); + case 0x0B: return F("gateway target failed"); + default: return F("modbus exception"); + } +} + +void WeatherStation::setSimpleError(const String& error) { + _commLog.lastOk = false; + _commLog.lastDurationMs = 0; + _commLog.lastAction = String(); + _commLog.lastRequestHex = String(); + _commLog.lastResponseHex = String(); + _commLog.lastError = error; + _commLog.errorCount++; +} + +void WeatherStation::setCommResult( + bool ok, + uint32_t durationMs, + const String& action, + const uint8_t* req, + size_t reqLen, + const uint8_t* resp, + size_t respLen, + const String& error) { + _commLog.lastOk = ok; + _commLog.lastDurationMs = durationMs; + _commLog.lastAction = action; + _commLog.lastRequestHex = req ? toHex(req, reqLen) : String(); + _commLog.lastResponseHex = resp ? toHex(resp, respLen) : String(); + _commLog.lastError = error; + if (ok) { + _commLog.successCount++; + } else { + _commLog.errorCount++; + } +} + +bool WeatherStation::transact( + const uint8_t* request, + size_t reqLen, + uint8_t* response, + size_t responseCap, + uint32_t timeoutMs, + const String& action, + size_t* actualLen) { + while (_serial.available()) { + _serial.read(); + } + + const uint32_t t0 = millis(); + _serial.write(request, reqLen); + _serial.flush(); + + size_t got = 0; + uint32_t lastByteAt = 0; + bool seenAnyByte = false; + + while ((millis() - t0) < timeoutMs && got < responseCap) { + bool gotByteNow = false; + + while (_serial.available() && got < responseCap) { + response[got++] = static_cast(_serial.read()); + lastByteAt = millis(); + seenAnyByte = true; + gotByteNow = true; + } + + if (seenAnyByte && !gotByteNow && (millis() - lastByteAt) >= 20) { + break; + } + delay(1); + } + + if (actualLen) { + *actualLen = got; + } + + if (!seenAnyByte) { + setCommResult(false, millis() - t0, action, request, reqLen, response, 0, F("timeout")); + return false; + } + + setCommResult(true, millis() - t0, action, request, reqLen, response, got, String()); + return true; +} + +bool WeatherStation::sendRawFrame(const uint8_t* request, size_t reqLen, uint8_t* response, size_t responseCapacity, uint32_t timeoutMs, const String& action) { + if (request == nullptr || reqLen == 0) { + setSimpleError(F("invalid args")); + return false; + } + + uint8_t frame[260] = {0}; + if (reqLen > sizeof(frame)) { + setSimpleError(F("raw frame too long")); + return false; + } + + memcpy(frame, request, reqLen); + size_t frameLen = reqLen; + + bool hasValidCrc = false; + if (frameLen >= 4) { + const uint16_t crcProvided = static_cast(frame[frameLen - 2]) | (static_cast(frame[frameLen - 1]) << 8); + const uint16_t crcCalculated = crc16(frame, frameLen - 2); + hasValidCrc = (crcProvided == crcCalculated); + } + + if (!hasValidCrc) { + if ((frameLen + 2) > sizeof(frame)) { + setSimpleError(F("raw frame too long")); + return false; + } + const uint16_t crc = crc16(frame, frameLen); + frame[frameLen++] = static_cast(crc & 0xFF); + frame[frameLen++] = static_cast((crc >> 8) & 0xFF); + } + + size_t actualLen = 0; + const uint32_t effectiveTimeoutMs = timeoutMs < 2000 ? 2000 : timeoutMs; + return transact(frame, frameLen, response, responseCapacity, effectiveTimeoutMs, action, &actualLen); +} + +bool WeatherStation::sendRawFrame(const uint8_t* request, size_t reqLen, uint32_t timeoutMs, const String& action) { + uint8_t resp[128] = {0}; + return sendRawFrame(request, reqLen, resp, sizeof(resp), timeoutMs, action); +} + +bool WeatherStation::readHoldingRegisters(uint16_t startReg, uint16_t count, uint16_t* out) { + if (count == 0 || out == nullptr) { + setSimpleError(F("invalid args")); + return false; + } + + uint8_t req[8]; + req[0] = _config.address; + req[1] = 0x03; + req[2] = static_cast((startReg >> 8) & 0xFF); + req[3] = static_cast(startReg & 0xFF); + req[4] = static_cast((count >> 8) & 0xFF); + req[5] = static_cast(count & 0xFF); + const uint16_t crc = crc16(req, 6); + req[6] = static_cast(crc & 0xFF); + req[7] = static_cast((crc >> 8) & 0xFF); + + uint8_t resp[64] = {0}; + size_t respLen = 0; + + char regHex[5]; + snprintf(regHex, sizeof(regHex), "%04X", startReg); + String action = F("read 0x"); + action += regHex; + action += F(" + "); + action += String(count); + + if (!transact(req, sizeof(req), resp, sizeof(resp), 2000, action, &respLen)) { + return false; + } + + if (respLen < 5) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("short response")); + return false; + } + + if (resp[0] != _config.address) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("wrong slave address")); + return false; + } + + const uint16_t crcResp = static_cast(resp[respLen - 2]) | (static_cast(resp[respLen - 1]) << 8); + const uint16_t crcCalc = crc16(resp, respLen - 2); + if (crcResp != crcCalc) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("crc mismatch")); + return false; + } + + if (resp[1] == static_cast(0x03 | 0x80)) { + String err = F("modbus exception: "); + err += String(exceptionText(resp[2])); + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, err); + return false; + } + + if (resp[1] != 0x03) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("bad function")); + return false; + } + + const size_t expectedLen = 5 + static_cast(count) * 2; + if (respLen != expectedLen) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("bad length")); + return false; + } + + if (resp[2] != count * 2) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("bad byte count")); + return false; + } + + for (uint16_t i = 0; i < count; i++) { + out[i] = (static_cast(resp[3 + i * 2]) << 8) | static_cast(resp[4 + i * 2]); + } + + _commLog.lastOk = true; + _commLog.lastError = String(); + return true; +} + +bool WeatherStation::writeRegister(uint16_t reg, uint16_t value) { + uint8_t req[8]; + req[0] = _config.address; + req[1] = 0x06; + req[2] = static_cast((reg >> 8) & 0xFF); + req[3] = static_cast(reg & 0xFF); + req[4] = static_cast((value >> 8) & 0xFF); + req[5] = static_cast(value & 0xFF); + const uint16_t crc = crc16(req, 6); + req[6] = static_cast(crc & 0xFF); + req[7] = static_cast((crc >> 8) & 0xFF); + + uint8_t resp[16] = {0}; + size_t respLen = 0; + + char regHex[5]; + char valHex[5]; + snprintf(regHex, sizeof(regHex), "%04X", reg); + snprintf(valHex, sizeof(valHex), "%04X", value); + String action = F("write 0x"); + action += regHex; + action += F(" = 0x"); + action += valHex; + + if (!transact(req, sizeof(req), resp, sizeof(resp), 2000, action, &respLen)) { + return false; + } + + if (respLen < 5) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("short response")); + return false; + } + + if (resp[0] != _config.address) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("wrong slave address")); + return false; + } + + const uint16_t crcResp = static_cast(resp[respLen - 2]) | (static_cast(resp[respLen - 1]) << 8); + const uint16_t crcCalc = crc16(resp, respLen - 2); + if (crcResp != crcCalc) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("crc mismatch")); + return false; + } + + if (resp[1] == static_cast(0x06 | 0x80)) { + String err = F("modbus exception: "); + err += String(exceptionText(resp[2])); + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, err); + return false; + } + + if (resp[1] != 0x06) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("bad function")); + return false; + } + + if (respLen != 8) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("bad length")); + return false; + } + + if (memcmp(req, resp, 6) != 0) { + setCommResult(false, _commLog.lastDurationMs, action, req, sizeof(req), resp, respLen, F("write echo mismatch")); + return false; + } + + _commLog.lastOk = true; + _commLog.lastError = String(); + return true; +} + +bool WeatherStation::readAll() { + uint16_t regs[REG_DATA_COUNT] = {0}; + if (!readHoldingRegisters(REG_DATA_START, REG_DATA_COUNT, regs)) { + _values.registersValid = false; + return false; + } + + for (uint8_t i = 0; i < REG_DATA_COUNT; i++) { + _values.raw[i] = regs[i]; + } + + _values.registersValid = true; + _values.lastReadMs = millis(); + decodeRegisters(); + detectPresence(); + return true; +} + +bool WeatherStation::readSensorConfig() { + uint16_t regs[4] = {0}; + if (!readHoldingRegisters(REG_CFG_WIND_DIR_OFFSET, 4, regs)) { + _config.sensorConfigReadOk = false; + return false; + } + + _config.windOffset = regs[0]; + _config.rainSensitivity = regs[3]; + _config.sensorConfigReadOk = true; + return true; +} + +void WeatherStation::decodeRegisters() { + _values.windSpeed = _values.raw[0] / 10.0f; + _values.windForce = static_cast(_values.raw[1]); + _values.windDir8 = static_cast(_values.raw[2]); + _values.windDirDeg = static_cast(_values.raw[3]); + _values.humidity = _values.raw[4] / 10.0f; + _values.temperature = toSigned(_values.raw[5]) / 10.0f; + _values.noise = _values.raw[6] / 10.0f; + _values.air1 = static_cast(_values.raw[7]); + _values.air2 = static_cast(_values.raw[8]); + _values.pressureKpa = _values.raw[9] / 10.0f; + _values.lightHigh = _values.raw[10]; + _values.lightLow = _values.raw[11]; + _values.lightLux = _values.raw[12]; + _values.rain = _values.raw[13] / 10.0f; + _values.compassDeg = _values.raw[14] / 100.0f; + _values.solarRadiation = static_cast(_values.raw[15]); +} + +void WeatherStation::detectPresence() { + _presence.wind = + (_values.windSpeed >= 0.0f) && + (_values.windDirDeg >= 0) && + (_values.windDirDeg <= 359); + + _presence.humidityTemp = + isfinite(_values.humidity) && + isfinite(_values.temperature) && + (_values.humidity >= 0.0f && _values.humidity <= 100.0f) && + (_values.temperature > -50.0f && _values.temperature < 90.0f); + + if (isfinite(_values.noise) && + _values.noise >= 29.5f && + _values.noise <= 30.5f) { + _presence.noise = false; + _values.noise = NAN; + } else { + _presence.noise = isfinite(_values.noise) && (_values.noise > 0.0f) && (_values.noise <= 150.0f); + } + + _presence.air = (_values.air1 > 0 || _values.air2 > 0); + if (!_presence.air) { + _values.air1 = -1; + _values.air2 = -1; + } + + _presence.pressure = isfinite(_values.pressureKpa) && (_values.pressureKpa > 10.0f); + if (!_presence.pressure) _values.pressureKpa = NAN; + + _presence.light = (_values.lightLux > 0) || (_values.lightHigh > 0) || (_values.lightLow > 0); + if (!_presence.light) { + _values.lightLux = 0; + _values.lightHigh = 0; + _values.lightLow = 0; + } + + _presence.rain = isfinite(_values.rain) && (_values.rain >= 0.0f); + if (!_presence.rain) _values.rain = NAN; + + _presence.compass = isfinite(_values.compassDeg) && (_values.compassDeg > 0.01f); + if (!_presence.compass) _values.compassDeg = NAN; + + _presence.solar = (_values.solarRadiation > 0); + if (!_presence.solar) _values.solarRadiation = -1; +} + +bool WeatherStation::commandZeroWind() { + uint8_t req[64]; + size_t reqLen = 0; + + if (!parseHexStringLocal(String(CMD_WIND_ZERO), req, reqLen, sizeof(req))) { + setSimpleError(F("bad wind zero prefix")); + return false; + } + + if (reqLen + 2 > sizeof(req)) { + setSimpleError(F("wind zero frame too long")); + return false; + } + + const uint16_t crc = crc16(req, reqLen); + req[reqLen++] = static_cast(crc & 0xFF); + req[reqLen++] = static_cast((crc >> 8) & 0xFF); + + uint8_t resp[32] = {0}; + size_t respLen = 0; + + if (!transact(req, reqLen, resp, sizeof(resp), 2000, F("wind zero raw"), &respLen)) { + return false; + } + + if (respLen == 0) { + setCommResult(false, _commLog.lastDurationMs, F("wind zero raw"), req, reqLen, resp, respLen, F("no response")); + return false; + } + + _commLog.lastOk = true; + _commLog.lastError = String(); + return true; +} + +bool WeatherStation::commandZeroRain() { + return writeRegister(REG_CFG_RAIN_ZERO, CFG_RAIN_ZERO); +} + +void WeatherStation::pushConfigLog() { + _lastConfigLog = _commLog; + _hasConfigLog = true; +} + +const CommLog& WeatherStation::configLogAt(size_t reverseIndex) const { + static const CommLog empty; + if (!_hasConfigLog || reverseIndex != 0) { + return empty; + } + return _lastConfigLog; +} + +String WeatherStation::airLabel1() const { + if (_config.airMode == AirQualityMode::CO2) return F("CO2"); + return F("PM2.5"); +} + +String WeatherStation::airLabel2() const { + if (_config.airMode == AirQualityMode::CO2) return F("CO2"); + return F("PM10"); +} \ No newline at end of file diff --git a/SW/esplan_v2_renke_weather_station/WeatherStation.h b/SW/esplan_v2_renke_weather_station/WeatherStation.h new file mode 100755 index 0000000..9fbbf7d --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/WeatherStation.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include "Config.h" + +struct VariantPresence { + bool wind = false; + bool humidityTemp = false; + bool noise = false; + bool air = false; + bool pressure = false; + bool light = false; + bool rain = false; + bool compass = false; + bool solar = false; +}; + +struct WeatherValues { + uint16_t raw[REG_DATA_COUNT] = {0}; + bool registersValid = false; + uint32_t lastReadMs = 0; + + float windSpeed = NAN; + int windForce = -1; + int windDir8 = -1; + int windDirDeg = -1; + float humidity = NAN; + float temperature = NAN; + float noise = NAN; + int air1 = -1; + int air2 = -1; + float pressureKpa = NAN; + uint16_t lightHigh = 0; + uint16_t lightLow = 0; + uint32_t lightLux = 0; + float rain = NAN; + float compassDeg = NAN; + int solarRadiation = -1; +}; + +struct RuntimeConfig { + uint8_t address = DEFAULT_MODBUS_ADDR; + uint32_t baudrate = DEFAULT_MODBUS_BAUD; + AirQualityMode airMode = AirQualityMode::Auto; + uint16_t windOffset = 0; + uint16_t rainSensitivity = 0x0011; + bool sensorConfigReadOk = false; +}; + +struct CommLog { + bool lastOk = false; + uint32_t lastDurationMs = 0; + uint32_t successCount = 0; + uint32_t errorCount = 0; + String lastAction; + String lastRequestHex; + String lastResponseHex; + String lastError; +}; + +class WeatherStation { +public: + explicit WeatherStation(HardwareSerial& serial); + + void begin(); + bool setBaudrate(uint32_t baud); + + bool readAll(); + bool readSensorConfig(); + bool writeRegister(uint16_t reg, uint16_t value); + + bool commandZeroWind(); + bool commandZeroRain(); + + bool sendRawFrame(const uint8_t* request, size_t reqLen, uint8_t* response, size_t responseCapacity, uint32_t timeoutMs, const String& action); + bool sendRawFrame(const uint8_t* request, size_t reqLen, uint32_t timeoutMs, const String& action); + + void pushConfigLog(); + size_t configLogCount() const { return _hasConfigLog ? 1 : 0; } + const CommLog& configLogAt(size_t reverseIndex) const; + + const WeatherValues& values() const { return _values; } + const VariantPresence& presence() const { return _presence; } + RuntimeConfig& config() { return _config; } + const RuntimeConfig& config() const { return _config; } + const CommLog& commLog() const { return _commLog; } + + String airLabel1() const; + String airLabel2() const; + +private: + HardwareSerial& _serial; + WeatherValues _values; + VariantPresence _presence; + RuntimeConfig _config; + CommLog _commLog; + + CommLog _lastConfigLog; + bool _hasConfigLog = false; + + uint16_t crc16(const uint8_t* data, size_t len) const; + bool transact(const uint8_t* request, size_t reqLen, uint8_t* response, size_t responseCap, uint32_t timeoutMs, const String& action, size_t* actualLen = nullptr); + bool readHoldingRegisters(uint16_t startReg, uint16_t count, uint16_t* out); + void decodeRegisters(); + void detectPresence(); + void setCommResult(bool ok, uint32_t durationMs, const String& action, const uint8_t* req, size_t reqLen, const uint8_t* resp, size_t respLen, const String& error); + void setSimpleError(const String& error); + static int16_t toSigned(uint16_t v); + static String toHex(const uint8_t* data, size_t len); + static const __FlashStringHelper* exceptionText(uint8_t code); +}; \ No newline at end of file diff --git a/SW/esplan_v2_renke_weather_station/WebUi.cpp b/SW/esplan_v2_renke_weather_station/WebUi.cpp new file mode 100755 index 0000000..4a32895 --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/WebUi.cpp @@ -0,0 +1,645 @@ +#include "WebUi.h" +#include "logo_web.h" + +String htmlEscape(const String& in) { + String out; + out.reserve(in.length() + 16); + for (size_t i = 0; i < in.length(); i++) { + switch (in[i]) { + case '&': out += F("&"); break; + case '<': out += F("<"); break; + case '>': out += F(">"); break; + case '"': out += F("""); break; + case '\'': out += F("'"); break; + default: out += in[i]; break; + } + } + return out; +} + +String fmtFloat(float v, uint8_t decimals) { + if (isnan(v) || !isfinite(v)) return F("-"); + return String(static_cast(v), static_cast(decimals)); +} + +static String jsonFloat(float v, uint8_t decimals = 1) { + if (isnan(v) || !isfinite(v)) return F("null"); + return String(static_cast(v), static_cast(decimals)); +} + +String jsonString(const String& in) { + String out; + out.reserve(in.length() + 8); + + for (size_t i = 0; i < in.length(); i++) { + char c = in[i]; + switch (c) { + case '\"': out += F("\\\""); break; + case '\\': out += F("\\\\"); break; + case '\n': out += F("\\n"); break; + case '\r': out += F("\\r"); break; + case '\t': out += F("\\t"); break; + case '\b': out += F("\\b"); break; + case '\f': out += F("\\f"); break; + default: + if ((uint8_t)c < 32) { + out += '?'; + } else { + out += c; + } + } + } + return out; +} + +static String hex16(uint16_t value) { + char buf[5]; + snprintf(buf, sizeof(buf), "%04X", value); + return String(buf); +} + +static String statusBadge(bool present, const Texts& t) { + String s; + s.reserve(64); + s += F("") : F("bad'>"); + s += present ? t.present : t.absent; + s += F(""); + return s; +} + +String buildStyle(const Texts&, UiLang) { + return F( + ""); +} + +static void appendTop(String& html, UiLang lang, const Texts& t, const char* active) { + html += F("

"); + html += t.appTitle; + html += F("

"); + + html += F("
"); + html += F("
"); + + html += F(""); +} + +static void appendRow(String& html, const String& id, const String& label, const String& value) { + html += F(""); + html += label; + html += F(""); + html += value; + html += F(""); +} + +static String buildConfigHistoryBox(const Texts& t, const WeatherStation& ws) { + String html; + html.reserve(1800); + + html += F("

"); + html += t.modbusLog; + html += F("

"); + + if (ws.configLogCount() == 0) { + html += F("
"); + html += t.noResult; + html += F("
"); + return html; + } + + const CommLog& c = ws.configLogAt(0); + + html += F(""); + appendRow(html, F("cfg-log-state"), t.modbusLastStatus, c.lastOk ? String(F("")) + t.statusOkShort + F("") : String(F("")) + t.statusErrorShort + F("")); + appendRow(html, F("cfg-log-action"), t.modbusAction, htmlEscape(c.lastAction)); + appendRow(html, F("cfg-log-duration"), t.modbusDuration, String(c.lastDurationMs) + F(" ms")); + appendRow(html, F("cfg-log-error"), t.modbusError, c.lastError.length() ? htmlEscape(c.lastError) : F("-")); + html += F("
"); + + html += F("
"); + html += t.modbusTx; + html += F("
"); + html += htmlEscape(c.lastRequestHex); + html += F("
"); + + html += F("
"); + html += t.modbusRx; + html += F("
"); + html += htmlEscape(c.lastResponseHex); + html += F("
"); + + html += F(""); + return html; +} + +static void appendMainCards(String& html, const Texts& t, const WeatherStation& ws, bool ethUp, const String& ip, const String& mac) { + (void)ethUp; + (void)ip; + (void)mac; + + const auto& p = ws.presence(); + const auto& v = ws.values(); + const auto& c = ws.commLog(); + + html += F("
"); + + html += F("

"); + html += t.sensorStatus; + html += F("

"); + appendRow(html, F("st-wind"), t.sensorWind, statusBadge(p.wind, t)); + appendRow(html, F("st-th"), t.sensorHumidityTemp, statusBadge(p.humidityTemp, t)); + appendRow(html, F("st-noise"), t.sensorNoise, statusBadge(p.noise, t)); + appendRow(html, F("st-air"), t.sensorAir, statusBadge(p.air, t)); + appendRow(html, F("st-pressure"), t.sensorPressure, statusBadge(p.pressure, t)); + appendRow(html, F("st-light"), t.sensorLight, statusBadge(p.light, t)); + appendRow(html, F("st-rain"), t.sensorRain, statusBadge(p.rain, t)); + appendRow(html, F("st-compass"), t.sensorCompass, statusBadge(p.compass, t)); + appendRow(html, F("st-solar"), t.sensorSolar, statusBadge(p.solar, t)); + html += F("
"); + + html += F("

"); + html += t.values; + html += F("

"); + appendRow(html, F("v-wind-short"), t.windSpeed, fmtFloat(v.windSpeed, 1)); + appendRow(html, F("v-beaufort-short"), t.beaufort, (v.windForce >= 0 ? String(v.windForce) : F("-"))); + appendRow(html, F("v-dir8-short"), t.windDir8, (v.windDir8 >= 0 ? String(v.windDir8) : F("-"))); + appendRow(html, F("v-dirdeg-short"), t.windDirDeg, (v.windDirDeg >= 0 ? String(v.windDirDeg) : F("-"))); + appendRow(html, F("v-hum-short"), t.humidity, fmtFloat(v.humidity, 1)); + appendRow(html, F("v-temp-short"), t.temperature, fmtFloat(v.temperature, 1)); + appendRow(html, F("v-noise-short"), t.noise, fmtFloat(v.noise, 1)); + appendRow(html, F("v-air1-short"), ws.airLabel1(), (v.air1 >= 0 ? String(v.air1) : F("-"))); + appendRow(html, F("v-air2-short"), ws.airLabel2(), (v.air2 >= 0 ? String(v.air2) : F("-"))); + appendRow(html, F("v-pressure-short"), t.pressure, fmtFloat(v.pressureKpa, 1)); + appendRow(html, F("v-luxh-short"), t.lightHigh, String(v.lightHigh)); + appendRow(html, F("v-luxl-short"), t.lightLow, String(v.lightLow)); + appendRow(html, F("v-lux-short"), t.lightLux100, String(v.lightLux)); + appendRow(html, F("v-rain-short"), t.rain, fmtFloat(v.rain, 1)); + appendRow(html, F("v-compass-short"), t.compass, fmtFloat(v.compassDeg, 2)); + appendRow(html, F("v-solar-short"), t.solarRadiation, (v.solarRadiation >= 0 ? String(v.solarRadiation) : F("-"))); + appendRow(html, F("data-age"), t.pollAge, String((millis() - v.lastReadMs) / 1000UL) + String(F(" ")) + t.seconds); + html += F("
"); + + html += F("

"); + html += t.modbusLog; + html += F("

"); + appendRow(html, F("mb-state"), t.modbusLastStatus, c.lastOk ? String(F("")) + t.statusOkShort + F("") : String(F("")) + t.statusErrorShort + F("")); + appendRow(html, F("mb-action"), t.modbusAction, htmlEscape(c.lastAction)); + appendRow(html, F("mb-dur"), t.modbusDuration, String(c.lastDurationMs) + F(" ms")); + appendRow(html, F("mb-stats"), t.modbusCounters, String(c.successCount) + F(" / ") + String(c.errorCount)); + appendRow(html, F("mb-err"), t.modbusError, c.lastError.length() ? htmlEscape(c.lastError) : F("-")); + html += F("
"); + + html += F("

"); + html += t.modbusFrames; + html += F("

"); + html += F("
"); + html += t.modbusTx; + html += F("
"); + html += htmlEscape(c.lastRequestHex); + html += F("
"); + html += F("
"); + html += t.modbusRx; + html += F("
"); + html += htmlEscape(c.lastResponseHex); + html += F("
"); + + html += F("
"); + html += t.rawRegisters; + html += F("
"); + for (uint8_t i = 0; i < REG_DATA_COUNT; i++) { + html += F("R"); + html += String(REG_DATA_START + i); + html += F(" = "); + html += String(v.raw[i]); + if (i + 1 < REG_DATA_COUNT) html += '\n'; + } + html += F("
"); + + html += F("
"); +} + +static String buildMainScript(UiLang lang, const Texts& t) { + String s; + s.reserve(9000); + + s += F(""); + return s; +} + +static String buildManualCommandScript() { + String s; + s.reserve(2400); + s += F(""); + return s; +} + +String buildMainPage(UiLang lang, const WeatherStation& ws, bool ethUp, const String& ip, const String& mac) { + const auto& t = T(lang); + String html; + html.reserve(24000); + html += F(""); + html += t.appTitle; + html += F(""); + html += buildStyle(t, lang); + html += F("
"); + appendTop(html, lang, t, "main"); + appendMainCards(html, t, ws, ethUp, ip, mac); + html += buildMainScript(lang, t); + html += F("
"); + return html; +} + +String buildConfigPage(UiLang lang, const WeatherStation& ws, const String& flashMsg) { + const auto& t = T(lang); + String html; + html.reserve(24000); + + (void)flashMsg; + + html += F(""); + html += t.tabConfig; + html += F(""); + html += buildStyle(t, lang); + html += F("
"); + appendTop(html, lang, t, "config"); + + html += F("
"); + html += buildConfigHistoryBox(t, ws); + + html += F("

"); + html += t.manualCommand; + html += F("

"); + html += t.manualNote; + html += F("
"); + + html += F("
"); + + html += F("
"); + + html += F("
"); + + html += F("
"); + + html += F("
"); + + html += F("
-
"); + + html += F("
"); + + html += F("
"); + + html += F("

"); + html += t.rawHexTitle; + html += F("

"); + html += t.rawHexNote; + html += F("
"); + + html += F("
"); + html += F(""); + html += F("
"); + + html += F("

"); + html += t.configTitle; + html += F("

"); + + html += F(""); + html += F(""); + + html += F(""); + + html += F(""); + + html += F(""); + + html += F(""); + + html += F(""); + + html += F(""); + + html += F("
"); + html += t.tableItem; + html += F(""); + html += t.tableSetting; + html += F(""); + html += t.tableNote; + html += F("
"); + html += t.address; + html += F(""); + html += F("
"); + html += F(""); + html += F(""); + html += F(""); + html += F("
"); + html += t.noteAddress; + html += F("
"); + html += t.baudrate; + html += F(""); + html += F("
"); + html += F(""); + html += F(""); + html += F(""); + html += F("
"); + html += t.noteBaudrate; + html += F("
"); + html += t.windOffset; + html += F(""); + html += F("
"); + html += F(""); + html += F(""); + html += F(""); + html += F("
"); + html += t.noteWindOffset; + html += F("
"); + html += t.rainSensitivity; + html += F(""); + html += F("
"); + html += F(""); + html += F(""); + html += F(""); + html += F("
"); + html += t.noteRainSensitivity; + html += F("
"); + html += t.commandWindZero; + html += F("
"); + html += t.noteWindZero; + html += F("
"); + html += t.commandRainZero; + html += F("
"); + html += t.noteRainZero; + html += F("
"); + + html += F("
"); + html += buildManualCommandScript(); + html += F("
"); + return html; +} + +String buildMainDataJson(const WeatherStation& ws, bool ethUp, const String& ip, const String& mac) { + const auto& v = ws.values(); + const auto& p = ws.presence(); + const auto& c = ws.commLog(); + + String json; + json.reserve(2400); + + json += F("{"); + json += F("\"eth\":"); json += ethUp ? F("true") : F("false"); + json += F(",\"ip\":\""); json += jsonString(ip); json += F("\""); + json += F(",\"mac\":\""); json += jsonString(mac); json += F("\""); + json += F(",\"age\":"); json += String((millis() - v.lastReadMs) / 1000UL); + + json += F(",\"wind\":"); json += jsonFloat(v.windSpeed); + json += F(",\"beaufort\":"); json += (v.windForce >= 0 ? String(v.windForce) : F("-1")); + json += F(",\"dir8\":"); json += (v.windDir8 >= 0 ? String(v.windDir8) : F("-1")); + json += F(",\"dirdeg\":"); json += (v.windDirDeg >= 0 ? String(v.windDirDeg) : F("-1")); + json += F(",\"temp\":"); json += jsonFloat(v.temperature); + json += F(",\"hum\":"); json += jsonFloat(v.humidity); + json += F(",\"noise\":"); json += jsonFloat(v.noise); + json += F(",\"air1\":"); json += String(v.air1); + json += F(",\"air2\":"); json += String(v.air2); + json += F(",\"press\":"); json += jsonFloat(v.pressureKpa); + json += F(",\"rain\":"); json += jsonFloat(v.rain); + json += F(",\"solar\":"); json += String(v.solarRadiation); + json += F(",\"light\":"); json += String(v.lightLux); + json += F(",\"lightHigh\":"); json += String(v.lightHigh); + json += F(",\"lightLow\":"); json += String(v.lightLow); + json += F(",\"compass\":"); json += jsonFloat(v.compassDeg, 2); + + json += F(",\"pwind\":"); json += p.wind ? F("true") : F("false"); + json += F(",\"pth\":"); json += p.humidityTemp ? F("true") : F("false"); + json += F(",\"pnoise\":"); json += p.noise ? F("true") : F("false"); + json += F(",\"pair\":"); json += p.air ? F("true") : F("false"); + json += F(",\"ppress\":"); json += p.pressure ? F("true") : F("false"); + json += F(",\"plight\":"); json += p.light ? F("true") : F("false"); + json += F(",\"prain\":"); json += p.rain ? F("true") : F("false"); + json += F(",\"pcomp\":"); json += p.compass ? F("true") : F("false"); + json += F(",\"psolar\":"); json += p.solar ? F("true") : F("false"); + + json += F(",\"mbOk\":"); json += c.lastOk ? F("true") : F("false"); + json += F(",\"mbDuration\":"); json += String(c.lastDurationMs); + json += F(",\"mbOkCount\":"); json += String(c.successCount); + json += F(",\"mbErrCount\":"); json += String(c.errorCount); + json += F(",\"mbAction\":\""); json += jsonString(c.lastAction); json += F("\""); + json += F(",\"mbError\":\""); json += jsonString(c.lastError); json += F("\""); + json += F(",\"mbTx\":\""); json += jsonString(c.lastRequestHex); json += F("\""); + json += F(",\"mbRx\":\""); json += jsonString(c.lastResponseHex); json += F("\""); + + json += F(",\"raw\":["); + for (uint8_t i = 0; i < REG_DATA_COUNT; i++) { + if (i) json += ','; + json += String(v.raw[i]); + } + json += F("]}"); + + return json; +} \ No newline at end of file diff --git a/SW/esplan_v2_renke_weather_station/WebUi.h b/SW/esplan_v2_renke_weather_station/WebUi.h new file mode 100755 index 0000000..8b45ab1 --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/WebUi.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include "Lang.h" +#include "WeatherStation.h" + +String htmlEscape(const String& in); +String fmtFloat(float v, uint8_t decimals = 1); +String buildMainPage(UiLang lang, const WeatherStation& ws, bool ethUp, const String& ip, const String& mac); +String buildConfigPage(UiLang lang, const WeatherStation& ws, const String& flashMsg); +String buildMainDataJson(const WeatherStation& ws, bool ethUp, const String& ip, const String& mac); +String buildStyle(const Texts& t, UiLang lang); diff --git a/SW/esplan_v2_renke_weather_station/esplan_v2_renke_weather_station.ino b/SW/esplan_v2_renke_weather_station/esplan_v2_renke_weather_station.ino new file mode 100755 index 0000000..77fc910 --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/esplan_v2_renke_weather_station.ino @@ -0,0 +1,455 @@ +#include +#include +#include +#include +#include +#include +#include "Config.h" +#include "Lang.h" +#include "WeatherStation.h" +#include "WebUi.h" +#include "logo_web.h" + +Preferences prefs; +WebServer server(80); +HardwareSerial RS485Serial(2); +WeatherStation station(RS485Serial); + +bool ethConnected = false; +bool ethGotIp = false; +unsigned long lastPollMs = 0; +unsigned long lastDetectRecheckMs = 0; +bool uiOnConfigPage = false; +String flashMessage; + +static void fwLog(const String& msg) { + Serial.println(String(F("[FW] ")) + msg); +} + + +static void sendNoCacheHeaders() { + server.sendHeader(F("Cache-Control"), F("no-store, no-cache, must-revalidate, max-age=0")); + server.sendHeader(F("Pragma"), F("no-cache")); + server.sendHeader(F("Expires"), F("0")); +} + +static void loadPreferences() { + prefs.begin("meteo", true); + station.config().address = prefs.getUChar("addr", DEFAULT_MODBUS_ADDR); + station.config().baudrate = prefs.getUInt("baud", DEFAULT_MODBUS_BAUD); + station.config().airMode = static_cast(prefs.getUChar("airMode", 0)); + prefs.end(); +} + +static void savePreferences() { + prefs.begin("meteo", false); + prefs.putUChar("addr", station.config().address); + prefs.putUInt("baud", station.config().baudrate); + prefs.putUChar("airMode", static_cast(station.config().airMode)); + prefs.end(); +} + +void WiFiEvent(WiFiEvent_t event) { + switch (event) { + case ARDUINO_EVENT_ETH_START: + ETH.setHostname("esplan-weather"); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + ethConnected = true; + fwLog(F("ETH link connected")); + break; + case ARDUINO_EVENT_ETH_GOT_IP: + ethConnected = true; + ethGotIp = true; + fwLog(String(F("ETH IP acquired: ")) + ETH.localIP().toString()); + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + ethConnected = false; + ethGotIp = false; + fwLog(F("ETH link disconnected")); + break; + case ARDUINO_EVENT_ETH_STOP: + ethConnected = false; + ethGotIp = false; + fwLog(F("ETH stopped")); + break; + default: + break; + } +} + +static UiLang currentLang() { + if (server.hasArg("lang")) return parseLang(server.arg("lang")); + return UiLang::CZ; +} + +static String currentIp() { + return ethGotIp ? ETH.localIP().toString() : String("0.0.0.0"); +} + +static String currentMac() { + return ETH.macAddress(); +} + +static void redirectWithMessage(const String& url, UiLang lang, const String& msg) { + flashMessage = msg; + const String target = url + (url.indexOf('?') >= 0 ? '&' : '?') + String(F("lang=")) + langCode(lang); + server.sendHeader("Location", target, true); + server.send(302, "text/plain", ""); +} + +static String buildSimpleMessage(UiLang lang, const String& title, const String& detail, bool ok) { + const auto& t = T(lang); + String msg; + msg.reserve(320); + msg += F("
"); + msg += htmlEscape(title); + msg += F(": "); + msg += ok ? String(F("")) + t.statusOkShort + F("") : String(F("")) + t.statusErrorShort + F(""); + if (detail.length()) { + msg += F("
"); + msg += htmlEscape(detail); + } + msg += F("
"); + return msg; +} + +static String buildConfigResultMessage(UiLang lang, const WeatherStation& ws, const String& title) { + const auto& t = T(lang); + const auto& c = ws.commLog(); + + String msg; + msg.reserve(900); + msg += F("
"); + msg += htmlEscape(title); + msg += F(": "); + msg += c.lastOk ? String(F("")) + t.statusOkShort + F("") : String(F("")) + t.statusErrorShort + F(""); + msg += F("
"); + msg += htmlEscape(t.modbusAction); + msg += F(": "); + msg += htmlEscape(c.lastAction.length() ? c.lastAction : String("-")); + msg += F("
"); + msg += htmlEscape(t.modbusDuration); + msg += F(": "); + msg += String(c.lastDurationMs); + msg += F(" ms"); + msg += F("
"); + msg += htmlEscape(t.modbusError); + msg += F(": "); + msg += (c.lastError.length() ? htmlEscape(c.lastError) : F("-")); + msg += F("
"); + return msg; +} + +static bool parseHex16(const String& s, uint16_t& out) { + String v = s; + v.trim(); + v.replace("0x", ""); + v.replace("0X", ""); + if (v.length() == 0 || v.length() > 4) return false; + char* endptr = nullptr; + unsigned long n = strtoul(v.c_str(), &endptr, 16); + if (*endptr != '\0' || n > 0xFFFFUL) return false; + out = static_cast(n); + return true; +} + +static bool parseUint32Strict(const String& s, uint32_t& out) { + String v = s; + v.trim(); + if (v.length() == 0) return false; + char* endptr = nullptr; + unsigned long n = strtoul(v.c_str(), &endptr, 10); + if (*endptr != '\0') return false; + out = static_cast(n); + return true; +} + +static bool parseHexBytesLoose(const String& input, uint8_t* out, size_t& outLen, size_t maxLen) { + String s = input; + s.trim(); + s.replace(" ", ""); + s.replace("\t", ""); + s.replace("\r", ""); + s.replace("\n", ""); + if (s.length() == 0 || (s.length() % 2) != 0) { + outLen = 0; + return false; + } + size_t bytes = s.length() / 2; + if (bytes > maxLen) { + outLen = 0; + return false; + } + outLen = 0; + for (size_t i = 0; i < bytes; i++) { + String tok = s.substring(i * 2, i * 2 + 2); + char* endptr = nullptr; + unsigned long v = strtoul(tok.c_str(), &endptr, 16); + if (*endptr != '\0' || v > 0xFFUL) { + outLen = 0; + return false; + } + out[outLen++] = static_cast(v); + } + return true; +} + +static bool writeRegisterToAddress(uint8_t slaveAddr, uint16_t reg, uint16_t value) { + const uint8_t oldAddr = station.config().address; + station.config().address = slaveAddr; + const bool ok = station.writeRegister(reg, value); + station.config().address = oldAddr; + return ok; +} + +static void handleRoot() { + uiOnConfigPage = false; + UiLang lang = currentLang(); + sendNoCacheHeaders(); + server.send(200, "text/html; charset=utf-8", buildMainPage(lang, station, ethConnected && ethGotIp, currentIp(), currentMac())); +} + +static void handleConfig() { + uiOnConfigPage = true; + UiLang lang = currentLang(); + const String msg = flashMessage; + flashMessage = String(); + sendNoCacheHeaders(); + server.send(200, "text/html; charset=utf-8", buildConfigPage(lang, station, msg)); +} + +static void handleLogo() { + server.send_P(200, "image/png", reinterpret_cast(logo_web_png), logo_web_png_len); +} + +static void handleJson() { + sendNoCacheHeaders(); + server.send(200, "application/json; charset=utf-8", buildMainDataJson(station, ethConnected && ethGotIp, currentIp(), currentMac())); +} + +static bool applyRuntimeComm(UiLang lang, String& errorText, bool& changed) { + changed = false; + + if (server.hasArg("address")) { + uint32_t addr = 0; + if (!parseUint32Strict(server.arg("address"), addr) || addr < 1 || addr > 247) { + errorText = T(lang).address; + return false; + } + if (station.config().address != static_cast(addr)) { + station.config().address = static_cast(addr); + changed = true; + } + } + + if (server.hasArg("baudrate")) { + uint32_t baud = 0; + if (!parseUint32Strict(server.arg("baudrate"), baud)) { + errorText = T(lang).baudrate; + return false; + } + switch (baud) { + case 1200: + case 2400: + case 4800: + case 9600: + case 19200: + case 38400: + case 57600: + case 115200: + if (station.config().baudrate != baud) { + station.setBaudrate(baud); + changed = true; + } + break; + default: + errorText = T(lang).baudrate; + return false; + } + } + + if (changed) savePreferences(); + return true; +} + +static void handleConfigSave() { + UiLang lang = currentLang(); + const auto& t = T(lang); + + String runtimeError; + bool runtimeChanged = false; + if (!applyRuntimeComm(lang, runtimeError, runtimeChanged)) { + redirectWithMessage("/config", lang, buildSimpleMessage(lang, t.save, runtimeError, false)); + return; + } + + bool didWrite = false; + bool ok = true; + String firstValidationError; + + const bool hasWindOffset = server.hasArg("windOffset") && server.arg("windOffset").length() > 0; + const bool hasRainSensitivity = server.hasArg("rainSensitivity") && server.arg("rainSensitivity").length() > 0; + + if (hasWindOffset) { + uint16_t v = 0; + if (!parseHex16(server.arg("windOffset"), v)) { + ok = false; + firstValidationError = t.windOffset; + } else { + ok = station.writeRegister(REG_CFG_WIND_DIR_OFFSET, v) && ok; + station.pushConfigLog(); + didWrite = true; + if (ok) station.config().windOffset = v; + } + } + + if (hasRainSensitivity) { + uint16_t v = 0; + if (!parseHex16(server.arg("rainSensitivity"), v)) { + ok = false; + if (!firstValidationError.length()) firstValidationError = t.rainSensitivity; + } else { + ok = station.writeRegister(REG_CFG_RAIN_SENSITIVITY, v) && ok; + station.pushConfigLog(); + didWrite = true; + if (ok) station.config().rainSensitivity = v; + } + } + + if (!didWrite) { + const String detail = runtimeChanged ? String() : firstValidationError; + redirectWithMessage("/config", lang, buildSimpleMessage(lang, t.save, detail, runtimeChanged || ok)); + return; + } + + if (!ok && firstValidationError.length()) { + String msg = buildConfigResultMessage(lang, station, t.save); + msg += F("
"); + msg += htmlEscape(firstValidationError); + msg += F("
"); + redirectWithMessage("/config", lang, msg); + return; + } + + redirectWithMessage("/config", lang, buildConfigResultMessage(lang, station, t.save)); +} + +static void handleCmdWindZero() { + UiLang lang = currentLang(); + station.commandZeroWind(); + station.pushConfigLog(); + redirectWithMessage("/config", lang, buildConfigResultMessage(lang, station, T(lang).commandWindZero)); +} + +static void handleCmdRainZero() { + UiLang lang = currentLang(); + station.commandZeroRain(); + station.pushConfigLog(); + redirectWithMessage("/config", lang, buildConfigResultMessage(lang, station, T(lang).commandRainZero)); +} + +static void handleCmdManual() { + UiLang lang = currentLang(); + const auto& t = T(lang); + if (!server.hasArg("address") || !server.hasArg("reg") || !server.hasArg("value")) { + redirectWithMessage("/config", lang, buildSimpleMessage(lang, t.manualCommand, t.writeFail, false)); + return; + } + + uint32_t addrParsed = 0; + uint16_t regIn = 0; + uint16_t valIn = 0; + if (!parseUint32Strict(server.arg("address"), addrParsed) || addrParsed < 1 || addrParsed > 247 || + !parseHex16(server.arg("reg"), regIn) || !parseHex16(server.arg("value"), valIn)) { + redirectWithMessage("/config", lang, buildSimpleMessage(lang, t.manualCommand, t.writeFail, false)); + return; + } + + writeRegisterToAddress(static_cast(addrParsed), regIn, valIn); + station.pushConfigLog(); + redirectWithMessage("/config", lang, buildConfigResultMessage(lang, station, t.manualCommand)); +} + +static void handleCmdRawHex() { + UiLang lang = currentLang(); + const auto& t = T(lang); + if (!server.hasArg("rawhex")) { + redirectWithMessage("/config", lang, buildSimpleMessage(lang, t.rawHexTitle, t.writeFail, false)); + return; + } + + uint8_t frame[128]; + size_t frameLen = 0; + if (!parseHexBytesLoose(server.arg("rawhex"), frame, frameLen, sizeof(frame))) { + redirectWithMessage("/config", lang, buildSimpleMessage(lang, t.rawHexTitle, t.writeFail, false)); + return; + } + + uint8_t resp[128] = {0}; + station.sendRawFrame(frame, frameLen, resp, sizeof(resp), 300, String(t.rawHexTitle)); + station.pushConfigLog(); + redirectWithMessage("/config", lang, buildConfigResultMessage(lang, station, t.rawHexTitle)); +} + +static void handleNotFound() { + sendNoCacheHeaders(); + server.send(404, "text/plain; charset=utf-8", "404 Not Found"); +} + +static void beginEthernet() { + WiFi.onEvent(WiFiEvent); + ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_POWER, ETH_CLK_MODE); +} + +static void beginWeb() { + server.on("/", HTTP_GET, handleRoot); + server.on("/config", HTTP_GET, handleConfig); + server.on("/data.json", HTTP_GET, handleJson); + server.on("/logo.png", HTTP_GET, handleLogo); + server.on("/config-save", HTTP_POST, handleConfigSave); + server.on("/cmd-wind-zero", HTTP_POST, handleCmdWindZero); + server.on("/cmd-rain-zero", HTTP_POST, handleCmdRainZero); + server.on("/cmd-manual", HTTP_POST, handleCmdManual); + server.on("/cmd-rawhex", HTTP_POST, handleCmdRawHex); + server.onNotFound(handleNotFound); + server.begin(); +} + +void setup() { + Serial.begin(115200); + delay(300); + Serial.println(); + Serial.println("ESPlan Weather Station start"); + fwLog(F("Boot")); + + loadPreferences(); + station.begin(); + station.readSensorConfig(); + station.readAll(); + + beginEthernet(); + beginWeb(); + fwLog(F("HTTP server started")); +} + +void loop() { + server.handleClient(); + + const unsigned long now = millis(); + if (!uiOnConfigPage && (now - lastPollMs >= SENSOR_POLL_MS)) { + lastPollMs = now; + const bool ok = station.readAll(); + if (!ok) { + fwLog(String(F("Modbus read failed: ")) + station.commLog().lastError); + } + } + + if (!uiOnConfigPage && (now - lastDetectRecheckMs >= DETECT_RECHECK_MS)) { + lastDetectRecheckMs = now; + const bool ok = station.readSensorConfig(); + if (!ok) { + fwLog(String(F("Sensor config refresh failed: ")) + station.commLog().lastError); + } + } +} diff --git a/SW/esplan_v2_renke_weather_station/logo_web.cpp b/SW/esplan_v2_renke_weather_station/logo_web.cpp new file mode 100755 index 0000000..fb24c40 --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/logo_web.cpp @@ -0,0 +1,936 @@ +#include "logo_web.h" +#include +#include + +const uint8_t logo_web_png[] PROGMEM = { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x01, 0x90, 0x00, 0x00, 0x00, 0x66, + 0x08, 0x06, 0x00, 0x00, 0x00, 0xE5, 0x03, 0xC7, 0x7C, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x01, 0xD9, 0xC9, 0x2C, 0x7F, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4D, 0x41, 0x00, 0x00, 0xB1, 0x8F, 0x0B, 0xFC, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52, 0x4D, 0x00, 0x00, + 0x7A, 0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xFA, 0x00, 0x00, 0x00, + 0x80, 0xE8, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0xEA, 0x60, 0x00, 0x00, + 0x3A, 0x98, 0x00, 0x00, 0x17, 0x70, 0x9C, 0xBA, 0x51, 0x3C, 0x00, 0x00, + 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x2C, 0x4B, 0x00, 0x00, + 0x2C, 0x4B, 0x01, 0xA5, 0x3D, 0x96, 0xA9, 0x00, 0x00, 0x20, 0x00, 0x49, + 0x44, 0x41, 0x54, 0x78, 0xDA, 0xED, 0x9D, 0x79, 0x98, 0x14, 0xC5, 0xF9, + 0xC7, 0x3F, 0x35, 0xBB, 0x0B, 0x28, 0x82, 0x88, 0x12, 0x4F, 0xBC, 0xEF, + 0xA8, 0xA8, 0x98, 0xA8, 0xC9, 0xA0, 0x02, 0x2A, 0x06, 0xD4, 0x78, 0xD1, + 0x6A, 0x34, 0xDE, 0x6D, 0xBC, 0x30, 0xC6, 0xC4, 0xC4, 0xF1, 0xF8, 0x99, + 0x78, 0x0D, 0x78, 0xDF, 0x1A, 0x6B, 0xA3, 0x46, 0x45, 0x4D, 0x71, 0x28, + 0x06, 0x24, 0x9A, 0xC4, 0x73, 0x8C, 0x07, 0x2A, 0x5E, 0xA8, 0xC8, 0x65, + 0xF0, 0x8A, 0x72, 0xDF, 0xC7, 0xEE, 0x4E, 0xFD, 0xFE, 0xA8, 0x1A, 0x76, + 0x58, 0x66, 0x76, 0xBB, 0x7B, 0x66, 0x76, 0x66, 0x77, 0xEB, 0xFB, 0x3C, + 0x3C, 0xC0, 0x6E, 0x77, 0x75, 0x75, 0x55, 0xF5, 0xFB, 0xAD, 0xF7, 0xAD, + 0xF7, 0x10, 0x94, 0x18, 0x42, 0xAA, 0xAE, 0x1A, 0x76, 0x02, 0x76, 0x04, + 0x7A, 0x01, 0xEB, 0x03, 0x31, 0xFB, 0xEB, 0xE5, 0xC0, 0x7C, 0x60, 0x96, + 0x80, 0xCF, 0xB5, 0xEF, 0xCD, 0xA7, 0x1D, 0x42, 0x48, 0xD5, 0x43, 0xC3, + 0xCE, 0xC0, 0xF6, 0xC0, 0xC6, 0x76, 0x0C, 0x00, 0x34, 0xB0, 0x0C, 0x98, + 0x0B, 0xCC, 0xB0, 0x63, 0xB0, 0x14, 0x07, 0x07, 0x07, 0x87, 0xB6, 0x20, + 0xDB, 0x4A, 0x20, 0x2C, 0x37, 0xD0, 0x70, 0x18, 0x30, 0x08, 0xE8, 0x07, + 0xEC, 0x02, 0x54, 0x05, 0xB8, 0x35, 0x0D, 0x7C, 0x09, 0xBC, 0x0E, 0xBC, + 0x00, 0x4C, 0xC4, 0xF7, 0xBE, 0x6F, 0xA3, 0x84, 0xB1, 0x89, 0x86, 0x23, + 0x80, 0xC3, 0x81, 0x38, 0xB0, 0x4D, 0x16, 0x69, 0x36, 0x87, 0x06, 0x60, + 0x1A, 0x90, 0x02, 0xFE, 0x21, 0xE0, 0x05, 0xED, 0x7B, 0x4B, 0xDC, 0x32, + 0x75, 0x70, 0x70, 0x68, 0xB7, 0x04, 0xD2, 0xB3, 0x76, 0x94, 0x98, 0xAF, + 0x75, 0x1C, 0xF8, 0x15, 0x70, 0x0C, 0xD0, 0xB5, 0x08, 0xCD, 0xD6, 0x03, + 0xFF, 0x02, 0x6A, 0xAB, 0x60, 0x5C, 0x83, 0xEF, 0xD5, 0x57, 0xF2, 0x40, + 0x56, 0x4B, 0x55, 0x55, 0x0F, 0x47, 0x02, 0x3E, 0x86, 0x40, 0x3B, 0x15, + 0xA1, 0xD9, 0xE5, 0xC0, 0xB3, 0xC0, 0x83, 0xDD, 0x85, 0x78, 0x79, 0xF1, + 0x39, 0x43, 0xB5, 0x5B, 0xB2, 0x0E, 0x0E, 0x0E, 0x19, 0xE8, 0x64, 0x7C, + 0xB0, 0x95, 0xBB, 0xB9, 0xF0, 0x85, 0x48, 0xA4, 0x7E, 0x5D, 0xD9, 0x04, + 0x22, 0xD5, 0x60, 0xE0, 0x1A, 0xE0, 0xC7, 0x25, 0xEC, 0xE7, 0x2C, 0x60, + 0x44, 0x15, 0x3C, 0xD4, 0xE0, 0x7B, 0x75, 0x15, 0x48, 0x1C, 0xA7, 0x03, + 0x09, 0x8C, 0x99, 0xAE, 0x54, 0x98, 0x0C, 0x5C, 0xDB, 0x4D, 0x88, 0x71, + 0x4B, 0x1C, 0x91, 0x38, 0x38, 0x38, 0x18, 0x02, 0xB9, 0x00, 0xB8, 0x37, + 0xCF, 0xAF, 0x3F, 0x12, 0x89, 0xD4, 0x5E, 0xA5, 0x7C, 0x7E, 0x2C, 0xF2, + 0x9D, 0x52, 0xED, 0x8E, 0x54, 0xFF, 0x06, 0x26, 0x94, 0x98, 0x3C, 0x00, + 0xB6, 0x03, 0x1E, 0x68, 0x80, 0x0F, 0x91, 0x6A, 0x50, 0xC5, 0xCC, 0x9E, + 0x54, 0xFD, 0xEB, 0xE1, 0x7D, 0xE0, 0x2F, 0x25, 0x26, 0x0F, 0x80, 0x7D, + 0x80, 0xA7, 0x97, 0x68, 0xFD, 0x0A, 0x52, 0xF5, 0x71, 0x9F, 0x8E, 0x83, + 0x83, 0x43, 0xD9, 0x37, 0xD0, 0x61, 0x6F, 0xA8, 0x91, 0xAA, 0xAA, 0x0E, + 0xFE, 0x00, 0xFC, 0x1F, 0xD0, 0xB9, 0x95, 0xFB, 0xBB, 0x2B, 0xF0, 0x0F, + 0xA4, 0x7A, 0x44, 0xC0, 0xAF, 0xB5, 0xEF, 0x2D, 0x2E, 0xC7, 0xA0, 0xD9, + 0x73, 0x9E, 0x5B, 0x31, 0xE6, 0x2A, 0xD1, 0xCA, 0x8F, 0xEF, 0x07, 0x4C, + 0x42, 0xAA, 0x1B, 0xAB, 0xE0, 0xFA, 0x4A, 0x37, 0xED, 0x39, 0x38, 0x38, + 0xB4, 0x5F, 0x84, 0xD2, 0x40, 0x84, 0x54, 0xBD, 0xEA, 0xE0, 0x79, 0xE0, + 0x86, 0x32, 0x90, 0x47, 0x36, 0xCE, 0xD0, 0xF0, 0x2E, 0x52, 0xED, 0xD5, + 0xEA, 0x4F, 0x96, 0x6A, 0x37, 0x0D, 0x6F, 0x03, 0xE7, 0x96, 0x81, 0x3C, + 0xD6, 0xF0, 0x38, 0x70, 0x4D, 0x03, 0xBC, 0x88, 0x54, 0x9B, 0xB9, 0x65, + 0xEC, 0xE0, 0xE0, 0x50, 0x96, 0xCD, 0x74, 0x08, 0xC1, 0xB9, 0x2B, 0xC6, + 0x5C, 0xB5, 0x7D, 0x05, 0xF5, 0x7F, 0x29, 0x70, 0x22, 0xBE, 0xF7, 0x5C, + 0x2B, 0x91, 0xC7, 0xA1, 0xC0, 0x68, 0x60, 0xC3, 0x0A, 0x1A, 0x83, 0x2F, + 0x81, 0x21, 0xF8, 0xDE, 0x47, 0x6E, 0x39, 0x3B, 0x38, 0xB4, 0x5F, 0xE8, + 0x64, 0xFC, 0xA7, 0x40, 0xF7, 0x26, 0x3F, 0x3E, 0x12, 0xB8, 0x20, 0xCF, + 0x2D, 0x5F, 0xE4, 0xF9, 0xDD, 0x1B, 0x22, 0x91, 0x5A, 0xD8, 0x7A, 0x04, + 0x62, 0x6C, 0xEE, 0xFF, 0xC4, 0xC4, 0x71, 0x54, 0x1A, 0xEA, 0x80, 0xD3, + 0xF1, 0xBD, 0x27, 0x4B, 0x4C, 0x1E, 0xC7, 0x01, 0x4F, 0x94, 0x59, 0xF3, + 0xCA, 0x87, 0xF9, 0xC0, 0x60, 0x7C, 0xEF, 0x2D, 0xF7, 0x99, 0x39, 0x38, + 0xB4, 0x5B, 0x02, 0x99, 0x0C, 0xEC, 0x5D, 0x84, 0xA6, 0x0E, 0x14, 0x89, + 0xD4, 0x9B, 0xC5, 0xE8, 0x53, 0xCB, 0x26, 0x2C, 0xA9, 0x76, 0xAF, 0x60, + 0xF2, 0x00, 0x63, 0xCE, 0x79, 0x14, 0xA9, 0x4E, 0x2A, 0x21, 0x79, 0x1C, + 0x03, 0x3C, 0x55, 0xA1, 0xE4, 0x01, 0xD0, 0x13, 0x73, 0x36, 0xB4, 0xB7, + 0xFB, 0xCC, 0x1C, 0x1C, 0x1C, 0x5A, 0x0B, 0xB1, 0x16, 0x04, 0xE7, 0x66, + 0xC0, 0xC4, 0x0A, 0x26, 0x8F, 0x0C, 0xAA, 0x81, 0xC7, 0x4A, 0x42, 0x22, + 0x52, 0x1D, 0x69, 0xC9, 0xA3, 0xA6, 0xC2, 0xC7, 0xA0, 0x07, 0xF0, 0x1C, + 0x52, 0xF5, 0x76, 0xCB, 0xDA, 0xC1, 0xC1, 0xA1, 0xB5, 0x04, 0x6F, 0x6E, + 0x66, 0x91, 0xAA, 0x53, 0xDA, 0xD8, 0xFB, 0xB7, 0x2E, 0xC2, 0x73, 0xA6, + 0x03, 0xEF, 0x00, 0x9F, 0x02, 0x73, 0x80, 0xD5, 0x98, 0xE8, 0xF4, 0x8D, + 0x80, 0x1D, 0x80, 0xBE, 0xC0, 0x5E, 0x44, 0xF0, 0x0A, 0xCB, 0x41, 0x22, + 0x1A, 0xDF, 0xFB, 0x5B, 0x91, 0xC8, 0x63, 0x08, 0x66, 0x0C, 0x0A, 0xD5, + 0x3C, 0xD2, 0xC0, 0x47, 0x76, 0x0C, 0xA6, 0x63, 0x4C, 0x4E, 0x0D, 0x96, + 0x94, 0x7A, 0x61, 0xBC, 0xCB, 0xF6, 0xC3, 0xA4, 0x3B, 0x29, 0x04, 0x9B, + 0x03, 0x63, 0x63, 0x52, 0xF5, 0x4B, 0xFB, 0xDE, 0x4A, 0xB7, 0xBC, 0x73, + 0xCE, 0xE9, 0xCD, 0xC0, 0xA6, 0x45, 0x68, 0xE9, 0x0F, 0xF8, 0xDE, 0xB7, + 0x6E, 0x40, 0x1D, 0x1C, 0x81, 0xE4, 0x96, 0x78, 0x7F, 0x02, 0x7E, 0x5A, + 0x40, 0xDB, 0xB3, 0x00, 0x09, 0x28, 0x7C, 0x6F, 0x46, 0x4B, 0x17, 0x0B, + 0xA9, 0x36, 0xD6, 0x70, 0x34, 0x70, 0x0E, 0xF0, 0x93, 0x02, 0xDE, 0xE7, + 0x71, 0x4B, 0x22, 0xAA, 0x08, 0xE4, 0x31, 0xA6, 0x40, 0xF2, 0x78, 0x1B, + 0xA8, 0x15, 0x30, 0x4E, 0x07, 0x49, 0xCB, 0x22, 0xD5, 0xB6, 0x80, 0x87, + 0x71, 0x0F, 0x8E, 0x1A, 0x57, 0xB2, 0x5F, 0x1A, 0x86, 0x03, 0x97, 0xB8, + 0xE5, 0x9D, 0x13, 0x3F, 0xC7, 0xE4, 0x66, 0x2B, 0x14, 0x37, 0x02, 0x8E, + 0x40, 0x1C, 0x3A, 0x34, 0x44, 0x1E, 0x41, 0xB6, 0x3F, 0x26, 0x27, 0x55, + 0x55, 0x84, 0x36, 0xBF, 0x02, 0xAE, 0xAE, 0x82, 0xC7, 0x23, 0xC7, 0x28, + 0x48, 0xD5, 0x0F, 0xB8, 0x19, 0xD8, 0x3F, 0xE2, 0x7B, 0xD5, 0x03, 0xA7, + 0x44, 0x26, 0x11, 0x23, 0xC8, 0x3F, 0x05, 0xBA, 0x44, 0x7C, 0xFE, 0x7B, + 0xC0, 0x65, 0x9B, 0x08, 0xF1, 0xD2, 0xDC, 0x08, 0x51, 0xE3, 0x36, 0xD6, + 0xC6, 0x03, 0x92, 0x98, 0x3C, 0x5A, 0x51, 0x34, 0x9E, 0x01, 0xF8, 0xDE, + 0x2B, 0x6E, 0x89, 0xAF, 0x33, 0xB7, 0x9F, 0x17, 0x89, 0x40, 0x76, 0xC3, + 0xF7, 0x3E, 0x73, 0x03, 0xEA, 0xD0, 0x5A, 0xA8, 0xC4, 0x43, 0xF4, 0x75, + 0x08, 0xC4, 0xA6, 0xE6, 0x98, 0x84, 0x89, 0x7C, 0x0E, 0x8B, 0x5A, 0x01, + 0x97, 0x16, 0x23, 0x01, 0x60, 0x27, 0xA9, 0x62, 0xAB, 0xE1, 0x42, 0xE0, + 0xA6, 0x88, 0x82, 0xBC, 0xCE, 0x92, 0xC8, 0xA8, 0x88, 0x82, 0xE6, 0xB7, + 0x96, 0xC4, 0xC2, 0xC4, 0x7A, 0xAC, 0x06, 0xAE, 0xAC, 0x86, 0xDB, 0xEB, + 0x7D, 0xAF, 0xA1, 0xE0, 0xC9, 0x31, 0x99, 0x8C, 0x47, 0x60, 0x5C, 0xF1, + 0xC2, 0xC6, 0x9C, 0x7C, 0x1A, 0x83, 0x3E, 0xE9, 0x0A, 0x4A, 0xFD, 0xA2, + 0x93, 0xF1, 0x0D, 0x03, 0xBE, 0x47, 0x5A, 0x24, 0x52, 0xA5, 0x09, 0x12, + 0x6D, 0xC7, 0x04, 0xA2, 0x93, 0xF1, 0xEE, 0x04, 0x8B, 0xED, 0xD2, 0x22, + 0x91, 0x5A, 0xD4, 0xA1, 0x77, 0xCE, 0x52, 0xF5, 0x08, 0xBA, 0x19, 0x2B, + 0x57, 0xC0, 0x72, 0x8E, 0xF9, 0x7D, 0x03, 0x63, 0xEA, 0x6F, 0x6A, 0x75, + 0xE9, 0xD4, 0xCC, 0x46, 0x32, 0x97, 0x29, 0xBB, 0xBF, 0x48, 0xA4, 0xDE, + 0x2E, 0x46, 0x9F, 0xAA, 0x73, 0x6C, 0xDD, 0x4F, 0x8B, 0x40, 0x1E, 0xAB, + 0x81, 0xF3, 0xF0, 0xBD, 0x87, 0x8B, 0x95, 0xA4, 0x69, 0xB5, 0xEF, 0xA5, + 0x81, 0xBB, 0x91, 0xEA, 0x0D, 0x4C, 0x42, 0xC1, 0xCD, 0xC3, 0x6E, 0xE4, + 0x81, 0x27, 0x90, 0x8A, 0x48, 0x24, 0xE2, 0x7B, 0xB7, 0x22, 0x95, 0xB0, + 0x24, 0x12, 0x04, 0xDF, 0x03, 0xC7, 0xE1, 0x7B, 0xAF, 0x17, 0x2B, 0x34, + 0x5C, 0xFB, 0xDE, 0x32, 0xE0, 0x22, 0xA4, 0xFA, 0x0F, 0xF0, 0x10, 0xE1, + 0xCC, 0x69, 0xBB, 0xA5, 0x8D, 0x29, 0xEC, 0xBE, 0x0A, 0xFA, 0x6E, 0xBF, + 0x01, 0xD6, 0x0B, 0x70, 0xDD, 0x4C, 0x4A, 0x9F, 0x1A, 0xA6, 0x3D, 0xE2, + 0x63, 0x60, 0xAB, 0x00, 0xD7, 0x2D, 0xD1, 0xC3, 0xFB, 0x6D, 0x24, 0x2E, + 0x7F, 0x2D, 0xDD, 0x61, 0x77, 0xF3, 0xE6, 0x1C, 0x92, 0xB6, 0xB5, 0x16, + 0xC5, 0x4F, 0xD7, 0xDD, 0x80, 0xE9, 0xF3, 0x81, 0xBB, 0xF3, 0xDC, 0x30, + 0x05, 0xC4, 0xBA, 0xB2, 0x5C, 0x54, 0x15, 0x6D, 0xDE, 0xD7, 0x22, 0x90, + 0x2A, 0xA9, 0xAA, 0x1B, 0xE0, 0xEA, 0x08, 0xE4, 0x71, 0x3C, 0xBE, 0x37, + 0xBE, 0x24, 0x63, 0xE6, 0x7B, 0xEF, 0x58, 0x93, 0xD6, 0x4B, 0x40, 0xEF, + 0x08, 0xEF, 0x37, 0xD2, 0x9E, 0x89, 0x8C, 0x8E, 0xF0, 0xEC, 0x5B, 0x90, + 0x8A, 0x00, 0x24, 0xF2, 0x35, 0x30, 0x10, 0xDF, 0x9B, 0x5A, 0xA2, 0x31, + 0x78, 0x02, 0xA9, 0xE6, 0x00, 0xE3, 0x02, 0x0A, 0xE0, 0x0C, 0xAE, 0x8C, + 0x49, 0xF5, 0x97, 0xB4, 0xEF, 0xAD, 0xAA, 0x90, 0xEF, 0x36, 0x16, 0x50, + 0x03, 0x89, 0xE1, 0x10, 0xD5, 0xA2, 0xE0, 0xC6, 0x37, 0xA2, 0xF5, 0xA5, + 0xC0, 0xEB, 0x4A, 0xDF, 0xE1, 0xC4, 0xBA, 0x84, 0xAF, 0x93, 0xF1, 0x74, + 0x0B, 0xF7, 0x34, 0x94, 0xFA, 0x83, 0x5E, 0x83, 0x06, 0x38, 0x01, 0x93, + 0xB8, 0x30, 0x04, 0x91, 0x73, 0x66, 0xC9, 0xC8, 0xA3, 0x51, 0x80, 0xCE, + 0x00, 0xFA, 0x63, 0xA2, 0xAE, 0xC3, 0x22, 0xA3, 0x89, 0x9C, 0x10, 0xF1, + 0xD9, 0xB7, 0x00, 0xBF, 0x6F, 0x61, 0x57, 0x3D, 0xA0, 0x64, 0xE4, 0xD1, + 0xD8, 0x8F, 0x7F, 0x02, 0xA7, 0x5A, 0xB5, 0x34, 0x28, 0xB6, 0x48, 0x9B, + 0x7B, 0x1C, 0x1C, 0x1C, 0x1C, 0x8A, 0x8E, 0xA6, 0x26, 0xAC, 0xF3, 0x43, + 0xDE, 0x7F, 0x3B, 0xBE, 0xF7, 0x44, 0xAB, 0xF4, 0xD4, 0xF7, 0x66, 0x20, + 0xD5, 0x00, 0xE0, 0xC5, 0x08, 0x9A, 0x48, 0x86, 0x44, 0x34, 0xBE, 0x37, + 0x26, 0xC2, 0xB3, 0x6F, 0xB6, 0xE6, 0xAC, 0x11, 0x79, 0xC8, 0xE3, 0xF3, + 0x56, 0x1A, 0x83, 0xB1, 0x48, 0x75, 0x23, 0x70, 0x55, 0x88, 0xBB, 0xCE, + 0xC3, 0x64, 0x0B, 0x76, 0x30, 0x78, 0x94, 0xE2, 0xC4, 0x35, 0x2D, 0x70, + 0x43, 0xE9, 0xE0, 0x08, 0x24, 0x03, 0xA9, 0xB6, 0xC3, 0x64, 0x7A, 0x0D, + 0x8A, 0x4F, 0x63, 0x70, 0x65, 0xAB, 0x1A, 0x51, 0x7D, 0x6F, 0xBA, 0x25, + 0x91, 0x97, 0x08, 0x66, 0xEB, 0x6D, 0x4A, 0x22, 0x4F, 0x22, 0xD5, 0xC9, + 0x11, 0x49, 0xE4, 0x26, 0x6B, 0xCE, 0x1A, 0x6E, 0xD5, 0xDA, 0xD6, 0xD1, + 0x3C, 0x9A, 0xA0, 0x0A, 0xAE, 0x6D, 0x80, 0xC1, 0xC0, 0xBE, 0x01, 0x6F, + 0xE9, 0x8B, 0x54, 0xBB, 0xE3, 0x7B, 0x9F, 0xB8, 0xE5, 0x0E, 0xF8, 0xDE, + 0xF5, 0x6E, 0x10, 0x1C, 0x1C, 0x8A, 0xAF, 0x81, 0x9C, 0x40, 0x38, 0x7B, + 0xDF, 0x6F, 0xCA, 0x12, 0xAC, 0x66, 0x48, 0xA4, 0x7F, 0x81, 0x24, 0x72, + 0x12, 0xBE, 0x37, 0x36, 0x22, 0x89, 0x08, 0xE0, 0xE2, 0x72, 0x90, 0x07, + 0x40, 0x83, 0xEF, 0xD5, 0x21, 0xD5, 0x25, 0xC0, 0x2B, 0x01, 0xE7, 0x4B, + 0x00, 0xC7, 0x03, 0x8E, 0x40, 0x1C, 0x1C, 0xDA, 0x1F, 0x5E, 0x02, 0xCE, + 0x0E, 0xA4, 0x25, 0x4B, 0x75, 0x16, 0x30, 0x20, 0x80, 0xC0, 0xB8, 0x54, + 0x07, 0x2C, 0x27, 0x9E, 0x4D, 0x20, 0x47, 0x84, 0xE8, 0xF4, 0x1B, 0xF8, + 0xDE, 0xF3, 0x65, 0xDC, 0x45, 0x4E, 0xCF, 0x32, 0x67, 0x15, 0xA2, 0x89, + 0x44, 0x21, 0x91, 0x11, 0x42, 0x2A, 0xA9, 0x7D, 0x6F, 0x7E, 0x19, 0xDF, + 0xFF, 0x35, 0x5B, 0xCC, 0xEB, 0xD0, 0x80, 0x77, 0x0C, 0x06, 0xAE, 0x73, + 0xDF, 0x9A, 0x83, 0x43, 0xFB, 0x82, 0x48, 0xA4, 0x3E, 0xC5, 0xC4, 0xAC, + 0x05, 0xC1, 0xFE, 0xC0, 0x29, 0x2D, 0x5D, 0xA4, 0x4D, 0xAD, 0xA7, 0x40, + 0x04, 0x12, 0x03, 0x88, 0x49, 0x55, 0x03, 0x1C, 0x10, 0xA2, 0xDF, 0x77, + 0x57, 0x80, 0x29, 0x62, 0x9A, 0x65, 0xD3, 0xAF, 0x23, 0xDC, 0xDD, 0x09, + 0x78, 0x0A, 0xA9, 0x8E, 0x8D, 0xF2, 0xE8, 0xB2, 0x92, 0x47, 0xB4, 0x39, + 0xE8, 0x2B, 0xA4, 0xEA, 0xEA, 0x3E, 0x37, 0x07, 0x07, 0x87, 0x62, 0x22, + 0x06, 0x90, 0x36, 0xC1, 0x29, 0xEB, 0x07, 0xBC, 0x67, 0xA9, 0x80, 0x67, + 0x2A, 0xA2, 0xF7, 0x86, 0x44, 0xFA, 0x47, 0x24, 0x91, 0x9A, 0x42, 0x48, + 0xA4, 0xDC, 0xA8, 0x32, 0x49, 0x2E, 0x83, 0x12, 0x59, 0x8D, 0x06, 0x57, + 0x06, 0xD7, 0xC1, 0xC1, 0xA1, 0xF8, 0x04, 0x82, 0x49, 0xE6, 0x17, 0x14, + 0xAF, 0x68, 0xDF, 0x5B, 0x51, 0x31, 0x6F, 0x50, 0x1C, 0x4D, 0xE4, 0x98, + 0xB6, 0x36, 0x71, 0x0D, 0x26, 0xC2, 0xFC, 0x9F, 0x21, 0x6E, 0xD9, 0xC3, + 0x2D, 0x77, 0x07, 0x07, 0x87, 0x52, 0x10, 0x48, 0x98, 0xD4, 0x0E, 0xFF, + 0xA9, 0xB8, 0xB7, 0x30, 0x6E, 0xB4, 0x03, 0x30, 0x9E, 0x51, 0x51, 0x48, + 0xE4, 0x6F, 0x6D, 0x91, 0x44, 0x80, 0x30, 0x05, 0xA4, 0x76, 0x72, 0xCB, + 0xDD, 0xC1, 0xC1, 0xA1, 0x98, 0xC8, 0x1C, 0xA2, 0x6F, 0x11, 0xE2, 0x9E, + 0x29, 0x15, 0xF9, 0x26, 0xBE, 0xF7, 0x79, 0xD6, 0xC1, 0xFA, 0x16, 0x21, + 0xEF, 0xCE, 0x90, 0x88, 0x87, 0xEF, 0x8D, 0x6B, 0x43, 0xF3, 0xF7, 0x61, + 0x88, 0x6B, 0x7F, 0xD0, 0xD1, 0x16, 0xB7, 0x4E, 0xC6, 0xBB, 0x61, 0x62, + 0x3E, 0x7A, 0x60, 0xD2, 0xC0, 0xD4, 0x03, 0xAB, 0x80, 0x79, 0xC0, 0x5C, + 0x91, 0x48, 0xAD, 0x2A, 0x43, 0x9F, 0xBA, 0xDB, 0x3E, 0x6D, 0x88, 0xC9, + 0xF1, 0x56, 0x87, 0xC9, 0x57, 0x34, 0x1F, 0x98, 0x27, 0x12, 0xA9, 0x0E, + 0x9B, 0x86, 0x5F, 0x27, 0xE3, 0x5D, 0x80, 0x4D, 0x80, 0x8D, 0xED, 0xD8, + 0xC4, 0xEC, 0xD8, 0x2C, 0xB0, 0xF3, 0xB5, 0xD4, 0x89, 0xEC, 0xCA, 0x24, + 0x90, 0xEE, 0x21, 0xEE, 0x99, 0x5D, 0xB1, 0x6F, 0xE3, 0x7B, 0x53, 0x0B, + 0x24, 0x11, 0xD5, 0xC6, 0x48, 0x24, 0x4C, 0x3A, 0xF1, 0xEE, 0xED, 0x79, + 0x21, 0xEB, 0x64, 0xBC, 0x07, 0x70, 0xB0, 0xFD, 0xB3, 0x37, 0xB0, 0x27, + 0xA6, 0x52, 0x63, 0xBE, 0xB4, 0x1D, 0x75, 0x3A, 0x19, 0x9F, 0x01, 0xBC, + 0x8B, 0x31, 0x05, 0x4E, 0x10, 0x89, 0xD4, 0xDC, 0xA2, 0xF6, 0x69, 0xC4, + 0x21, 0x82, 0x74, 0xFD, 0xBE, 0xC0, 0x10, 0x4C, 0x89, 0x82, 0x7D, 0xAD, + 0x70, 0xCC, 0xD7, 0xA7, 0x7A, 0x9D, 0x8C, 0xFF, 0x0F, 0x53, 0x3B, 0xE6, + 0x03, 0xE0, 0x35, 0xE0, 0xD5, 0x72, 0x0A, 0x4E, 0x9D, 0x8C, 0x1F, 0x82, + 0xA9, 0xD7, 0x13, 0x06, 0xE3, 0x44, 0x22, 0x35, 0xBD, 0xF9, 0x76, 0xFB, + 0xD5, 0x80, 0x3E, 0x10, 0xC8, 0xB4, 0xDF, 0x07, 0xD8, 0x92, 0xFC, 0x25, + 0x26, 0x1A, 0x74, 0x32, 0xFE, 0x9D, 0x9D, 0xAF, 0x57, 0x81, 0xBF, 0x8B, + 0x44, 0x6A, 0x6A, 0x45, 0x2D, 0x42, 0x53, 0x7C, 0x6E, 0x97, 0x90, 0x77, + 0x8D, 0xC4, 0xF7, 0xFE, 0xD7, 0xA4, 0x9D, 0xCD, 0x08, 0xE0, 0x2D, 0x65, + 0xF1, 0x09, 0xBE, 0x37, 0x31, 0x4F, 0x7F, 0x9A, 0xB6, 0xB3, 0x57, 0xC0, + 0x36, 0xCF, 0x41, 0xAA, 0x79, 0xB9, 0x7E, 0x21, 0xE0, 0xB1, 0x6C, 0x17, + 0xDF, 0xEA, 0x26, 0x7F, 0x07, 0x41, 0x65, 0xEF, 0x02, 0x8A, 0x43, 0x22, + 0x43, 0xF1, 0xBD, 0x67, 0xDB, 0x80, 0xDC, 0x0C, 0x73, 0x16, 0xD5, 0xEE, + 0xBC, 0xB0, 0x74, 0x32, 0xDE, 0x19, 0x38, 0x16, 0x93, 0x00, 0x74, 0x20, + 0xF9, 0xB3, 0x92, 0xE6, 0x42, 0x0D, 0xE6, 0xEC, 0x6F, 0x57, 0xFB, 0x91, + 0xAD, 0xD2, 0xC9, 0xF8, 0x68, 0xE0, 0x7A, 0x91, 0x48, 0x7D, 0x56, 0x60, + 0xBF, 0x6A, 0x80, 0x33, 0x49, 0xD7, 0xFF, 0x1A, 0xD8, 0x3D, 0xE4, 0x86, + 0x6E, 0x2B, 0xFB, 0xE7, 0x67, 0xC0, 0xE5, 0xC0, 0x0A, 0x9D, 0x8C, 0x3F, + 0x0F, 0x3C, 0x0C, 0x62, 0x42, 0xA9, 0x73, 0x1B, 0x35, 0x79, 0x8F, 0x13, + 0x80, 0x91, 0x21, 0xC7, 0xF5, 0x21, 0x44, 0xEC, 0xF6, 0x66, 0xDA, 0x8C, + 0x03, 0x67, 0x81, 0x3E, 0x06, 0x53, 0x50, 0x2E, 0x28, 0xAA, 0xEC, 0xF7, + 0xBC, 0x05, 0x70, 0x14, 0x70, 0x93, 0x4E, 0xC6, 0x53, 0xC0, 0x8D, 0x22, + 0x91, 0xFA, 0x47, 0x05, 0x90, 0xC7, 0x05, 0x18, 0xCF, 0xC8, 0x30, 0x79, + 0xC6, 0x6E, 0x58, 0x87, 0x3C, 0x0C, 0x7A, 0x03, 0xB7, 0x04, 0x6C, 0xE3, + 0x31, 0x8C, 0x43, 0x4D, 0x2E, 0x6C, 0x1D, 0xA2, 0x9D, 0x6C, 0x24, 0xF2, + 0xCE, 0x9F, 0x91, 0xAB, 0x6B, 0x08, 0x24, 0x4A, 0x52, 0xB5, 0xCA, 0x4F, + 0xC4, 0x66, 0x02, 0xFC, 0x06, 0x12, 0xAD, 0xE0, 0x4F, 0x86, 0x44, 0x8E, + 0x6E, 0x03, 0x32, 0x34, 0x4C, 0xBD, 0x96, 0x06, 0xDA, 0x09, 0x74, 0x32, + 0x5E, 0xA3, 0x93, 0xF1, 0x8B, 0x30, 0xD5, 0x1D, 0x9F, 0xB4, 0xC2, 0xB6, + 0x53, 0x81, 0xCD, 0x76, 0xB6, 0x44, 0xF2, 0xA1, 0x4E, 0xC6, 0xAF, 0xD5, + 0xC9, 0x7E, 0xD5, 0x11, 0xFB, 0xB6, 0x3F, 0xC6, 0xB4, 0xF8, 0xE7, 0x90, + 0xE4, 0x91, 0x0F, 0xEB, 0x01, 0xC7, 0x00, 0xE3, 0x40, 0xFF, 0xA3, 0x15, + 0xC7, 0xF8, 0x44, 0xE0, 0x89, 0x90, 0xE3, 0xFA, 0x04, 0x88, 0x73, 0xC5, + 0xE5, 0xAF, 0xE6, 0x4A, 0xFA, 0x77, 0x98, 0x4E, 0xC6, 0xDF, 0xB6, 0x5A, + 0xD5, 0x99, 0x21, 0xC9, 0x23, 0xCF, 0x66, 0x98, 0x7E, 0xC0, 0x44, 0x9D, + 0x8C, 0x8F, 0xD3, 0xC9, 0x78, 0xF9, 0xCA, 0x6E, 0x4B, 0x75, 0x31, 0x70, + 0x4F, 0x48, 0xD9, 0x78, 0x6B, 0xAF, 0x98, 0xB8, 0xBA, 0xAD, 0x7F, 0x8B, + 0x99, 0x17, 0x0E, 0x63, 0x0B, 0xDE, 0xB0, 0x4D, 0xBC, 0x99, 0xA9, 0xD5, + 0x30, 0x20, 0x22, 0x89, 0x74, 0x06, 0x46, 0x21, 0xD5, 0x51, 0x15, 0xFE, + 0x96, 0x61, 0xCC, 0x52, 0xCB, 0xDA, 0x91, 0xF2, 0x51, 0x6B, 0x77, 0x7B, + 0x5B, 0x95, 0xA0, 0xED, 0x1A, 0xE0, 0x6A, 0xD0, 0xCF, 0x58, 0x9B, 0x7C, + 0x18, 0xA1, 0x7B, 0x8A, 0x35, 0xAF, 0xEC, 0x5A, 0xA2, 0xF7, 0x7E, 0xB9, + 0x95, 0xC8, 0xE3, 0x14, 0xAB, 0x79, 0xD4, 0x84, 0xB8, 0x6D, 0x0C, 0x88, + 0xD3, 0x73, 0x69, 0x48, 0xB6, 0xBD, 0x17, 0x80, 0x1F, 0x95, 0xA8, 0xCB, + 0x47, 0x03, 0x6F, 0xE9, 0x64, 0xBC, 0xF5, 0x1D, 0x45, 0xA4, 0xBA, 0x14, + 0xB8, 0x83, 0x70, 0x59, 0x3C, 0xEE, 0x5B, 0x4F, 0x88, 0xCB, 0xE6, 0x9C, + 0x3D, 0x54, 0xB7, 0xF5, 0x0F, 0x31, 0x43, 0x20, 0x61, 0x8A, 0xCB, 0x6C, + 0xD7, 0x66, 0xDE, 0xCE, 0x90, 0x48, 0x21, 0x9A, 0x48, 0xA5, 0x93, 0x48, + 0x98, 0x7A, 0xF5, 0x0B, 0xDB, 0x11, 0x81, 0xB4, 0x46, 0x02, 0xCF, 0x21, + 0xC0, 0xE3, 0x7A, 0xF8, 0x41, 0x81, 0x04, 0x83, 0x4E, 0xC6, 0x8F, 0x06, + 0xFE, 0x5A, 0x04, 0x4D, 0x28, 0x1F, 0x56, 0x58, 0xAD, 0xA6, 0xD4, 0xE4, + 0x71, 0x06, 0x26, 0xE1, 0x64, 0x18, 0xED, 0x76, 0x3C, 0x70, 0x8A, 0x48, + 0xBC, 0x96, 0xAF, 0x14, 0xCE, 0x84, 0x56, 0x58, 0x7F, 0xDB, 0x01, 0xCF, + 0xB7, 0xAA, 0x26, 0x22, 0xD5, 0x15, 0xC0, 0xAD, 0x21, 0xC9, 0xE3, 0xA1, + 0x4E, 0x30, 0x6C, 0xC5, 0x39, 0x6D, 0x9F, 0x3C, 0xB2, 0x09, 0x24, 0xCC, + 0xC1, 0xF8, 0x9E, 0x6D, 0xEA, 0x0D, 0x7D, 0xEF, 0x53, 0x4B, 0x22, 0xFF, + 0x2B, 0x40, 0x13, 0x39, 0xB2, 0x42, 0xDF, 0x2E, 0x4C, 0x70, 0xE0, 0x37, + 0xB4, 0x13, 0x88, 0x44, 0xEA, 0x79, 0x4C, 0xBD, 0xF9, 0x52, 0xE3, 0x78, + 0x74, 0xFA, 0xBC, 0x00, 0x42, 0xB7, 0x17, 0xA6, 0xE0, 0x57, 0x55, 0x09, + 0xFB, 0xF2, 0x58, 0xB1, 0x0F, 0xF9, 0x73, 0xBC, 0xC7, 0xB9, 0x56, 0xBB, + 0x0B, 0x63, 0x8A, 0xF9, 0x27, 0xE0, 0x35, 0xE7, 0xD1, 0x26, 0x12, 0xA9, + 0x85, 0xC0, 0xBD, 0xAD, 0x30, 0x5F, 0xDB, 0x01, 0x0F, 0xB6, 0x12, 0x79, + 0x5C, 0x03, 0xDC, 0x10, 0x76, 0xE3, 0x53, 0x0D, 0xE7, 0xDA, 0x62, 0x79, + 0xED, 0x02, 0x99, 0x85, 0x12, 0xC6, 0x9B, 0xE1, 0xA0, 0x36, 0xF7, 0x96, + 0x86, 0x44, 0x06, 0x14, 0x40, 0x22, 0xA3, 0x2B, 0x94, 0x44, 0xE2, 0x21, + 0xAE, 0x6D, 0x6F, 0xF5, 0xBB, 0xAF, 0x6D, 0xA5, 0xE7, 0x5C, 0x6F, 0x3D, + 0xBC, 0x9A, 0xC3, 0xEF, 0x30, 0xDE, 0x55, 0xA5, 0x42, 0x1A, 0xB8, 0xB3, + 0xC4, 0xE4, 0x71, 0x21, 0xF0, 0x40, 0x48, 0x12, 0x7C, 0x05, 0x38, 0x56, + 0x24, 0x52, 0x41, 0x9C, 0x39, 0x6E, 0x07, 0x5A, 0xA3, 0x34, 0xEC, 0x31, + 0x3A, 0x19, 0x1F, 0x58, 0x62, 0xF2, 0xB8, 0x0E, 0xF8, 0x63, 0xC8, 0xBB, + 0xC6, 0xC4, 0xE0, 0x8C, 0x62, 0x94, 0xBA, 0xAE, 0x44, 0x02, 0x09, 0x13, + 0xDB, 0x71, 0x80, 0x90, 0x6A, 0x93, 0x36, 0x4A, 0x22, 0x85, 0x68, 0x22, + 0xA3, 0x91, 0x6A, 0x48, 0xC5, 0xEC, 0xC2, 0xA5, 0xEA, 0x1E, 0x92, 0x40, + 0x3E, 0x6E, 0x57, 0xF4, 0x21, 0x62, 0xCF, 0x01, 0xEF, 0x04, 0x95, 0x8F, + 0x05, 0x3C, 0xA9, 0x27, 0x70, 0x56, 0xDE, 0x86, 0x87, 0xF7, 0x8B, 0x11, + 0xAE, 0x68, 0xD7, 0x3C, 0x4C, 0x89, 0xE6, 0xBB, 0x2C, 0x09, 0xDE, 0x00, + 0xDC, 0x0F, 0xFC, 0x1D, 0x98, 0x95, 0xA7, 0xAF, 0x2F, 0x88, 0x44, 0xAA, + 0x64, 0xD9, 0x94, 0x75, 0x32, 0xFE, 0x6B, 0xCC, 0x99, 0x52, 0x18, 0x53, + 0xCC, 0x1B, 0xC0, 0xD1, 0x22, 0x91, 0x0A, 0x74, 0xB6, 0x26, 0x12, 0xA9, + 0x79, 0x21, 0xB4, 0x90, 0x42, 0xCD, 0x3B, 0xBF, 0x2B, 0xC5, 0x38, 0x6D, + 0x52, 0x3B, 0x4A, 0x20, 0xD5, 0x70, 0xC2, 0xD5, 0xE3, 0x01, 0x18, 0x1F, + 0x83, 0x53, 0xD2, 0x26, 0x7B, 0x44, 0xBB, 0x42, 0x35, 0xC0, 0xFA, 0x42, + 0x4C, 0x5B, 0xAE, 0xF5, 0x5C, 0x4C, 0x10, 0x4F, 0x4B, 0xA8, 0xD1, 0xF0, + 0x0B, 0xFB, 0x01, 0xB4, 0x35, 0x12, 0xF9, 0x04, 0xA9, 0x06, 0x02, 0xFF, + 0x06, 0x36, 0x8B, 0x40, 0x22, 0x63, 0x90, 0xEA, 0x78, 0x7C, 0x6F, 0x42, + 0xB9, 0x5F, 0x45, 0x83, 0x87, 0x09, 0xB6, 0x0A, 0x82, 0x45, 0xD5, 0x30, + 0xA5, 0xBE, 0x1D, 0x2D, 0x5C, 0x71, 0xF9, 0xAB, 0x5A, 0x27, 0xE3, 0x7F, + 0xB2, 0x82, 0x37, 0x1B, 0x33, 0x30, 0x36, 0xF9, 0x37, 0xED, 0xC6, 0x68, + 0x2E, 0x26, 0x80, 0xB0, 0x3B, 0xE6, 0x70, 0xFB, 0x70, 0xE0, 0x74, 0xC2, + 0x39, 0x83, 0x9C, 0x02, 0xDC, 0x96, 0x7B, 0x22, 0xF4, 0x0E, 0x04, 0x77, + 0x17, 0xBF, 0x01, 0xB8, 0xAE, 0x39, 0x73, 0x8F, 0x4E, 0xC6, 0xB7, 0xC4, + 0x9C, 0xBF, 0x9C, 0x82, 0xF1, 0x32, 0x12, 0x98, 0x43, 0xDA, 0x52, 0x91, + 0xC7, 0x65, 0xC0, 0x4D, 0x21, 0x6F, 0x7B, 0x17, 0x18, 0x22, 0x12, 0xA9, + 0xB0, 0x1A, 0xC5, 0xAD, 0xC0, 0x45, 0x40, 0xB7, 0xAC, 0x9F, 0x2D, 0x00, + 0x9E, 0xC3, 0x78, 0x67, 0xBD, 0x8F, 0x31, 0xB5, 0x2E, 0xC7, 0xB8, 0x9D, + 0xF7, 0xC6, 0xC4, 0xF5, 0x9C, 0x05, 0xEC, 0x10, 0xE2, 0x39, 0x87, 0xE9, + 0x64, 0x7C, 0x53, 0x91, 0x48, 0x7D, 0x57, 0xAC, 0x71, 0xEA, 0x5E, 0x3B, + 0x4A, 0xCC, 0xD5, 0xFA, 0x36, 0xE0, 0x92, 0x90, 0xB7, 0xFE, 0x53, 0x80, + 0x57, 0xA6, 0xB2, 0xD2, 0xF3, 0x80, 0xA7, 0xB2, 0xFE, 0xFF, 0xA3, 0x80, + 0xE3, 0x38, 0x9E, 0xFC, 0xE1, 0x1A, 0x6B, 0xA5, 0x88, 0x17, 0x59, 0x6A, + 0xD9, 0x28, 0x4C, 0x4D, 0x90, 0x20, 0x98, 0x56, 0x05, 0xBB, 0x37, 0xF8, + 0x5E, 0xDB, 0x94, 0x49, 0x52, 0xED, 0x8E, 0xF1, 0x67, 0xDE, 0x34, 0xC2, + 0xDD, 0x2B, 0x31, 0x35, 0xE0, 0x9F, 0x2B, 0x57, 0xF7, 0x3B, 0x4B, 0x15, + 0x5B, 0x65, 0x3E, 0xB6, 0xA0, 0xE7, 0x51, 0xE3, 0xF1, 0xBD, 0xB2, 0x3B, + 0x03, 0xE8, 0x64, 0x7C, 0x45, 0x40, 0xD2, 0x9B, 0x25, 0x12, 0xA9, 0xED, + 0x5B, 0x6C, 0x6F, 0x44, 0x7F, 0x41, 0xBA, 0x6E, 0x12, 0x26, 0x40, 0x6F, + 0x22, 0x30, 0x1C, 0x51, 0x95, 0x12, 0x97, 0xBF, 0xA2, 0x5B, 0xE8, 0xC7, + 0x66, 0xC0, 0x68, 0xE0, 0xA7, 0x21, 0x4C, 0x48, 0x9B, 0x89, 0x44, 0x6A, + 0x4E, 0x8E, 0xB6, 0x0E, 0x26, 0x98, 0x77, 0xD4, 0x1C, 0x44, 0xF5, 0xA6, + 0xE2, 0xF2, 0x97, 0x75, 0x88, 0xF1, 0xDA, 0x1B, 0x38, 0x03, 0x11, 0xBB, + 0x34, 0x97, 0x6B, 0x6C, 0x8E, 0xEB, 0xBF, 0x24, 0x98, 0x67, 0xDA, 0x52, + 0x84, 0xD8, 0x10, 0xAD, 0x13, 0x40, 0xD8, 0x02, 0x5B, 0x1F, 0x01, 0x03, + 0xA2, 0x9E, 0xC7, 0xE8, 0x64, 0x7C, 0x04, 0xA6, 0x44, 0xF4, 0x27, 0xC0, + 0x8D, 0xC0, 0xE8, 0x96, 0x32, 0x02, 0xE8, 0x64, 0xBC, 0x13, 0x70, 0x33, + 0xA6, 0x0E, 0x4F, 0x60, 0xD2, 0x17, 0x89, 0xD4, 0x13, 0xCD, 0xC8, 0x80, + 0xA0, 0xF3, 0x30, 0x73, 0x03, 0x21, 0x76, 0x5C, 0xAA, 0xF5, 0x5D, 0x96, + 0xFC, 0xC2, 0xE0, 0x55, 0x01, 0x43, 0xB4, 0xEF, 0x2D, 0x0D, 0x29, 0x9F, + 0x7E, 0x44, 0xF0, 0x33, 0xBE, 0xC7, 0xF0, 0xBD, 0xD3, 0x02, 0xB6, 0xFB, + 0x67, 0xE0, 0xDC, 0x00, 0x57, 0xEE, 0x80, 0xEF, 0xCD, 0x0C, 0x63, 0xC2, + 0xC2, 0xEE, 0x02, 0x82, 0x62, 0xA7, 0x06, 0x38, 0xA3, 0xCD, 0x6E, 0x5F, + 0x4D, 0x75, 0xBE, 0x01, 0x40, 0x94, 0x1D, 0x4A, 0x17, 0x60, 0x2C, 0x52, + 0xFD, 0xAC, 0x5C, 0xDD, 0x5F, 0x05, 0x27, 0x12, 0xCE, 0x99, 0x61, 0x3C, + 0xED, 0x10, 0xE2, 0x0F, 0x2F, 0x69, 0xE0, 0x4A, 0xE0, 0x28, 0x91, 0x48, + 0x0D, 0x11, 0x89, 0xD4, 0x6B, 0x2D, 0x91, 0x87, 0x35, 0xA7, 0xFC, 0x0F, + 0x13, 0x5B, 0x11, 0x54, 0x08, 0xC6, 0x80, 0xFD, 0xF2, 0xFC, 0x2E, 0xE8, + 0x99, 0xC1, 0xC6, 0xE8, 0xFA, 0xC3, 0x43, 0xBD, 0x5F, 0x22, 0xF5, 0xBE, + 0x48, 0xA4, 0x2E, 0x09, 0x42, 0x1E, 0xE1, 0x25, 0xB9, 0xFE, 0x63, 0x04, + 0xF2, 0xF8, 0x0C, 0x18, 0x54, 0xE0, 0x61, 0xFE, 0xAD, 0xC0, 0xE5, 0x20, + 0xFA, 0x88, 0x44, 0x6A, 0x64, 0x90, 0x74, 0x32, 0x22, 0x91, 0x5A, 0x8D, + 0xA8, 0xBA, 0x04, 0x08, 0x53, 0x83, 0x68, 0xDF, 0x22, 0x8D, 0x54, 0x6C, + 0xA9, 0xD6, 0xF7, 0x45, 0x20, 0x8F, 0x37, 0x05, 0x1C, 0x15, 0x9A, 0x3C, + 0xDA, 0x18, 0x62, 0x59, 0xAA, 0xC8, 0x38, 0xC2, 0xC5, 0x83, 0xDC, 0x28, + 0xA4, 0xEA, 0xD5, 0x66, 0xDF, 0xDC, 0x90, 0xC8, 0xC0, 0x88, 0x24, 0xD2, + 0xB9, 0x5C, 0x24, 0x22, 0xA4, 0xDA, 0x88, 0x70, 0xD1, 0xA5, 0x75, 0x02, + 0xC6, 0xB6, 0xD7, 0x05, 0x2C, 0x12, 0xA9, 0xE7, 0x45, 0x22, 0x35, 0x21, + 0xC2, 0x7D, 0x73, 0x31, 0x9E, 0x53, 0x41, 0x91, 0xCF, 0x7D, 0x3D, 0x0C, + 0x09, 0x4D, 0xD4, 0xC9, 0xF8, 0xCB, 0x3A, 0x19, 0xFF, 0x83, 0x4E, 0xC6, + 0x07, 0xE9, 0x64, 0x7C, 0x0B, 0x9D, 0xEC, 0x57, 0x55, 0x86, 0x61, 0xDB, + 0x00, 0x08, 0x1B, 0xC4, 0x36, 0x03, 0x38, 0x4C, 0x24, 0x52, 0xDF, 0x16, + 0x38, 0x5F, 0xDF, 0x8B, 0x44, 0x6A, 0x44, 0x33, 0x2E, 0xBF, 0xB9, 0xEF, + 0x33, 0x1B, 0x83, 0x5B, 0x43, 0xDC, 0xB2, 0x4B, 0x91, 0xC6, 0x6A, 0x5B, + 0xE0, 0xBC, 0x90, 0xF7, 0xBC, 0x67, 0x35, 0x8F, 0xC5, 0xB4, 0x73, 0xAC, + 0x89, 0xB6, 0xD5, 0xBE, 0x37, 0x1F, 0xA9, 0xC6, 0x61, 0x6C, 0xEB, 0x41, + 0xD0, 0x4B, 0xC3, 0x5F, 0xD6, 0xAB, 0x55, 0x3F, 0x5F, 0x71, 0x8E, 0xD7, + 0x36, 0x7D, 0x9A, 0x7D, 0x6F, 0x8A, 0x3D, 0x13, 0x79, 0x91, 0xF0, 0xC9, + 0x06, 0x33, 0x9A, 0xC8, 0xB1, 0xF8, 0x5E, 0xAB, 0x44, 0x08, 0x77, 0xAF, + 0x1D, 0x25, 0x16, 0x6B, 0xFD, 0x00, 0xE1, 0x52, 0xB4, 0x8C, 0xD7, 0xBE, + 0x37, 0x87, 0x0E, 0x08, 0x3D, 0xFC, 0x20, 0x81, 0x4E, 0x77, 0x26, 0x7F, + 0xAA, 0x9E, 0x30, 0xCE, 0x23, 0x5B, 0xE6, 0xF9, 0xF9, 0x34, 0x8C, 0x59, + 0x33, 0x88, 0x69, 0x4E, 0xD0, 0x98, 0xAF, 0x2B, 0xD3, 0xCB, 0x65, 0x3A, + 0x19, 0x9F, 0x66, 0x77, 0xF7, 0x53, 0x80, 0x49, 0xC0, 0x9B, 0x22, 0x91, + 0x5A, 0x54, 0x41, 0x43, 0x39, 0xDB, 0x92, 0xC7, 0x57, 0xAD, 0x30, 0x5F, + 0xD5, 0x76, 0x83, 0x96, 0x0B, 0x61, 0x92, 0x87, 0x6E, 0x5E, 0xA6, 0xB1, + 0xFA, 0x48, 0xC0, 0x11, 0x15, 0x52, 0x74, 0xAE, 0xF5, 0x08, 0xC4, 0xE2, + 0xEE, 0x10, 0x04, 0x02, 0x70, 0xD4, 0x0A, 0xCD, 0x0D, 0xC0, 0x15, 0x6D, + 0x58, 0x13, 0x99, 0x92, 0x75, 0xB0, 0x1E, 0x85, 0x44, 0x9E, 0x6E, 0x2D, + 0x12, 0x59, 0xAC, 0xF5, 0x95, 0x21, 0xE7, 0x27, 0x33, 0xA7, 0xED, 0x9F, + 0x2C, 0x92, 0xF1, 0xDD, 0x30, 0x07, 0xE4, 0xFB, 0x01, 0x3F, 0x04, 0x36, + 0x47, 0xA7, 0x37, 0x26, 0x5C, 0x34, 0x75, 0xB3, 0xFC, 0x9D, 0x67, 0x47, + 0xBD, 0x42, 0x27, 0xE3, 0x13, 0x31, 0x39, 0xB9, 0xA2, 0xA0, 0x2B, 0x26, + 0xF9, 0xE3, 0xDE, 0xD9, 0x5A, 0xA3, 0x4E, 0xC6, 0x5F, 0xC7, 0x44, 0x83, + 0x3F, 0x21, 0x12, 0xA9, 0xE5, 0x65, 0x1E, 0xDE, 0x53, 0x45, 0x22, 0x35, + 0xAB, 0xC8, 0xF3, 0xB5, 0x29, 0x30, 0x08, 0x53, 0x66, 0xB5, 0x0F, 0xD0, + 0x1B, 0x9D, 0xDE, 0x84, 0xE0, 0x85, 0xED, 0x82, 0x7C, 0x9B, 0xAD, 0x8D, + 0x34, 0x70, 0x7C, 0x47, 0xDA, 0xB0, 0xAD, 0x15, 0x30, 0xB4, 0xB1, 0x10, + 0xAF, 0x03, 0xAF, 0x87, 0x6C, 0x23, 0x81, 0x54, 0xBF, 0x6F, 0xD3, 0xA3, + 0xE0, 0x7B, 0x1F, 0x63, 0xCC, 0x59, 0x73, 0x23, 0x2E, 0xD4, 0xB1, 0x25, + 0x77, 0xF1, 0x35, 0x29, 0x13, 0xC2, 0xC6, 0x3E, 0xBC, 0xD5, 0x53, 0x88, + 0x97, 0xDB, 0x2F, 0x69, 0xF4, 0xAB, 0xD2, 0xC9, 0xF8, 0x99, 0x3A, 0x19, + 0xFF, 0x00, 0x73, 0x28, 0x7B, 0x07, 0xC6, 0xA5, 0x76, 0x1F, 0x8C, 0x97, + 0x5D, 0x4D, 0x11, 0x1F, 0xD7, 0x9C, 0x8B, 0xEB, 0x70, 0x2B, 0x3C, 0x8A, + 0x85, 0x1A, 0x4C, 0x96, 0x5A, 0x09, 0xCC, 0xD0, 0xC9, 0xF8, 0xE9, 0x65, + 0x1E, 0xEA, 0xC8, 0xB9, 0xC1, 0x72, 0x10, 0x47, 0x3F, 0x9D, 0x8C, 0x4F, + 0xC0, 0x14, 0x80, 0xFB, 0x2B, 0x70, 0x01, 0xC6, 0x99, 0x61, 0xEB, 0x22, + 0x92, 0x47, 0x39, 0xE5, 0xE9, 0x75, 0x3D, 0x6A, 0x47, 0x09, 0x3A, 0x08, + 0xD6, 0x22, 0x90, 0x79, 0x26, 0xBC, 0xFE, 0xAA, 0x08, 0xED, 0x0C, 0x47, + 0xAA, 0xCB, 0xDA, 0x01, 0x89, 0x1C, 0x4E, 0xF0, 0x32, 0xB1, 0xD9, 0x58, + 0xCF, 0x6A, 0x22, 0xE7, 0x15, 0xBB, 0x5B, 0x55, 0x52, 0x55, 0x23, 0xD5, + 0xAD, 0x98, 0x73, 0x8F, 0xB0, 0x0B, 0xF3, 0xBA, 0xF9, 0xED, 0x24, 0x65, + 0x42, 0x0E, 0x41, 0xB4, 0x23, 0xE8, 0xB7, 0x30, 0xE7, 0x18, 0x7B, 0x95, + 0xB3, 0x2F, 0x22, 0x91, 0x7A, 0x1B, 0xB8, 0xAE, 0x44, 0xCD, 0x6F, 0x06, + 0x3C, 0xA2, 0x93, 0x71, 0x59, 0xA6, 0xF3, 0x12, 0x0C, 0x99, 0xE9, 0x11, + 0x05, 0xCE, 0x57, 0x57, 0x9D, 0x8C, 0x3F, 0x82, 0x09, 0x3E, 0x1C, 0x4C, + 0x69, 0xA3, 0xF6, 0xCB, 0x89, 0x13, 0x17, 0x6A, 0xFD, 0x9B, 0x0E, 0x49, + 0x20, 0x56, 0x90, 0xBE, 0x0C, 0x8C, 0x89, 0xB0, 0x3B, 0x1B, 0x81, 0x54, + 0xBF, 0x6B, 0xD3, 0xA3, 0xE1, 0x7B, 0x93, 0x81, 0xC3, 0x22, 0x92, 0x48, + 0x0D, 0x70, 0x3F, 0x52, 0x3D, 0x25, 0xA4, 0x2A, 0x4E, 0xF1, 0x26, 0xA9, + 0x76, 0x69, 0x30, 0x2E, 0xA2, 0x97, 0x46, 0x20, 0x8F, 0x7F, 0x55, 0x42, + 0xBC, 0x4A, 0x89, 0xC8, 0x63, 0x37, 0x4C, 0x20, 0x5B, 0xDF, 0x4A, 0xE9, + 0x93, 0x48, 0xA4, 0xFE, 0x88, 0x39, 0x98, 0x2E, 0x55, 0xA4, 0xF1, 0x39, + 0xA0, 0xAF, 0x2B, 0xE3, 0x2B, 0xFE, 0xC6, 0x66, 0xE8, 0x8D, 0x32, 0x5F, + 0xEB, 0x63, 0xDC, 0xAC, 0x4F, 0x8F, 0xB0, 0x8E, 0xDB, 0x22, 0x46, 0x20, + 0xD5, 0x21, 0x1D, 0x93, 0x40, 0x0C, 0x2E, 0x21, 0x7C, 0xF2, 0x33, 0x01, + 0xDC, 0xD4, 0x0E, 0x48, 0xE4, 0xBD, 0x02, 0x48, 0x04, 0xE0, 0x44, 0x0D, + 0x9F, 0x21, 0xD5, 0xEF, 0x6D, 0xB4, 0x78, 0x14, 0xE2, 0xD8, 0xDC, 0x6A, + 0x1D, 0x1F, 0x10, 0x3C, 0x56, 0x21, 0x1B, 0x2B, 0x08, 0xEF, 0x76, 0xD8, + 0x56, 0xC8, 0xA3, 0x13, 0x26, 0x86, 0xA3, 0xE2, 0xB2, 0x21, 0x88, 0x44, + 0xEA, 0x7A, 0xE0, 0x40, 0x4C, 0x7E, 0xA8, 0x52, 0x68, 0x7E, 0xBF, 0xB7, + 0xE4, 0x59, 0x96, 0xD7, 0x03, 0x6A, 0x75, 0x32, 0xBE, 0x47, 0x84, 0x7B, + 0x6F, 0xC1, 0x04, 0x45, 0x76, 0x14, 0x54, 0x03, 0x4F, 0x21, 0xD5, 0x56, + 0x1D, 0xE1, 0x45, 0x73, 0x09, 0xD1, 0xAF, 0x90, 0xEA, 0x22, 0xE0, 0xF1, + 0x88, 0x24, 0xA2, 0xF1, 0xBD, 0x5B, 0xDB, 0xEC, 0xA8, 0xF8, 0xDE, 0x7B, + 0x48, 0x75, 0x38, 0x26, 0x05, 0x75, 0xCF, 0x08, 0x2D, 0x6C, 0x04, 0x8C, + 0xD0, 0x70, 0x05, 0x52, 0xFD, 0x0D, 0x18, 0x2B, 0xE0, 0x35, 0xED, 0x7B, + 0x79, 0x0F, 0x43, 0x85, 0x54, 0x1B, 0x69, 0x73, 0x0E, 0xE3, 0x61, 0xD2, + 0x53, 0x77, 0x2E, 0xE0, 0x0D, 0x12, 0xB6, 0x26, 0x4A, 0x7B, 0xC4, 0x99, + 0x84, 0xAB, 0xB3, 0xB1, 0x18, 0xE3, 0xD9, 0xF4, 0x55, 0x0E, 0xED, 0xA0, + 0xB7, 0xDD, 0x2C, 0x14, 0x93, 0x44, 0x26, 0x01, 0x87, 0xEB, 0x64, 0x7C, + 0x17, 0x3B, 0x97, 0x87, 0x5B, 0x4D, 0x69, 0xBD, 0x22, 0x34, 0x5F, 0x85, + 0x71, 0x29, 0xFD, 0x75, 0x99, 0xC6, 0x7E, 0x03, 0x60, 0xB4, 0x4E, 0xC6, + 0xF7, 0x0F, 0xEA, 0x25, 0x66, 0x4C, 0x8D, 0x81, 0x82, 0xD7, 0x32, 0xA8, + 0x03, 0x26, 0x63, 0x6A, 0xBD, 0xAC, 0xCC, 0x21, 0x5F, 0x4E, 0xA7, 0x2D, + 0xD4, 0x24, 0x32, 0x41, 0xCA, 0xA3, 0x62, 0x52, 0x1D, 0x9C, 0xF6, 0xBD, + 0xD5, 0x1D, 0x8B, 0x40, 0x8C, 0x10, 0x1D, 0x89, 0x54, 0x07, 0x85, 0x9C, + 0xFC, 0xCC, 0x24, 0xDF, 0x6C, 0x49, 0xE4, 0xB6, 0x36, 0x4C, 0x22, 0xEF, + 0x5A, 0x12, 0xF9, 0x27, 0xD1, 0x8B, 0xDF, 0x6C, 0x68, 0xC7, 0xEF, 0x5C, + 0x0D, 0x75, 0x48, 0xF5, 0x89, 0xFD, 0x30, 0x16, 0x58, 0x61, 0x56, 0x63, + 0x77, 0xD2, 0xBB, 0x6A, 0x93, 0x62, 0xA0, 0x18, 0x76, 0xE1, 0xA7, 0x37, + 0x14, 0xE2, 0xAE, 0x45, 0xB4, 0x5B, 0x9C, 0x16, 0xF0, 0xBA, 0x34, 0xC6, + 0x3B, 0xF0, 0x8E, 0x7C, 0xC1, 0x6A, 0x3A, 0x19, 0x1F, 0x52, 0x6C, 0x02, + 0xC9, 0x22, 0x92, 0xA9, 0x98, 0x73, 0x91, 0xEB, 0x6C, 0x85, 0xC2, 0xDD, + 0x2C, 0xF1, 0x6D, 0x0F, 0xEC, 0x8C, 0x89, 0x2B, 0xD9, 0x05, 0xE3, 0xF9, + 0x17, 0xC6, 0xAC, 0x73, 0x78, 0x99, 0xC7, 0x7F, 0x17, 0xE0, 0x11, 0x3D, + 0xFC, 0xA0, 0xE3, 0x03, 0x06, 0x38, 0x9E, 0x1A, 0x62, 0x5D, 0x8F, 0x02, + 0x86, 0xE5, 0x4B, 0x41, 0x62, 0xCE, 0x80, 0xF4, 0xA9, 0x6D, 0x84, 0x40, + 0x00, 0x0E, 0x48, 0x1B, 0xC7, 0x8E, 0x0B, 0x3A, 0x1E, 0x81, 0x98, 0x59, + 0x1A, 0x96, 0x36, 0x8B, 0xFD, 0x90, 0x08, 0x24, 0x72, 0x0B, 0x52, 0xD1, + 0x0E, 0x48, 0xE4, 0xB0, 0x02, 0x49, 0x24, 0x83, 0x1A, 0x8C, 0xBB, 0x62, + 0x9F, 0x12, 0xF6, 0xF8, 0x7D, 0x01, 0xA7, 0x2D, 0x6A, 0xBF, 0x07, 0xE7, + 0x9D, 0x09, 0x5E, 0x94, 0xE8, 0x71, 0x91, 0x48, 0x8D, 0xA8, 0x84, 0x7E, + 0x8B, 0x44, 0xAA, 0x0E, 0x13, 0xC3, 0xF0, 0x61, 0x8E, 0x77, 0xEA, 0x0D, + 0x1C, 0x61, 0xC9, 0x6E, 0xDB, 0x00, 0xCD, 0x6D, 0xA3, 0x93, 0xFD, 0xAA, + 0xC3, 0x06, 0xE2, 0x35, 0x83, 0xAF, 0x31, 0xF5, 0x72, 0xF6, 0x0B, 0x71, + 0xCF, 0x31, 0xE8, 0xF4, 0xE5, 0x98, 0x54, 0x24, 0x2D, 0xE1, 0xC0, 0x80, + 0x6D, 0xCE, 0xC6, 0xB8, 0x0B, 0x57, 0xF2, 0x6E, 0xFD, 0x33, 0x4B, 0x5E, + 0x3B, 0x87, 0xB8, 0xE7, 0x7C, 0xA4, 0x7A, 0x1B, 0xDF, 0x7B, 0xA4, 0x0D, + 0x7D, 0x6A, 0x81, 0xF3, 0xC4, 0x35, 0xCB, 0xE4, 0x69, 0xDF, 0x5B, 0x2D, + 0x8C, 0x7F, 0xFB, 0x7B, 0x51, 0xBE, 0x1B, 0x4B, 0x22, 0x6D, 0xDB, 0x23, + 0xC1, 0xF7, 0xDE, 0xB5, 0xBB, 0xBE, 0x05, 0x15, 0xDE, 0xD3, 0xE9, 0xC0, + 0xE0, 0x76, 0x9E, 0x3A, 0xE1, 0x07, 0x04, 0x77, 0xCD, 0x7D, 0x29, 0xC0, + 0x35, 0x9B, 0x15, 0xAB, 0x63, 0x7A, 0xC4, 0x21, 0x42, 0x27, 0xE3, 0x23, + 0x75, 0x32, 0x3E, 0x50, 0x8F, 0x38, 0x24, 0xB0, 0x46, 0x21, 0x12, 0xA9, + 0x2F, 0x45, 0x22, 0x25, 0x31, 0x09, 0x14, 0x83, 0xA0, 0x33, 0xE8, 0x62, + 0x79, 0x30, 0x7D, 0x87, 0x49, 0xE9, 0x73, 0x0C, 0xE1, 0xB3, 0x54, 0x5F, + 0xAB, 0x93, 0xF1, 0x20, 0xDA, 0x50, 0xD0, 0xA2, 0x67, 0x6F, 0xB6, 0x4C, + 0x1E, 0x7A, 0x73, 0xCA, 0xE7, 0xBD, 0x35, 0xDD, 0x8E, 0xD5, 0x71, 0xC0, + 0x92, 0x90, 0xF7, 0xDE, 0x87, 0x54, 0xFB, 0x56, 0xC0, 0xF7, 0xB3, 0x32, + 0xE0, 0x75, 0x41, 0x73, 0x22, 0xB6, 0xAC, 0x0A, 0x6A, 0xDF, 0x5B, 0x28, + 0x4C, 0xC0, 0xCF, 0x07, 0x11, 0x49, 0xE4, 0xD6, 0x76, 0x40, 0x22, 0xEF, + 0x10, 0xDD, 0xC5, 0xB7, 0xB5, 0x76, 0x46, 0x03, 0xF1, 0xBD, 0x6F, 0x69, + 0xDF, 0x08, 0x23, 0x3C, 0x36, 0x6F, 0x41, 0x9B, 0x59, 0x1F, 0x28, 0x9E, + 0xEB, 0x79, 0xBA, 0xFE, 0x50, 0x4C, 0x96, 0xEA, 0x7F, 0x91, 0xAE, 0x9F, + 0xA4, 0x93, 0xF1, 0x5F, 0x85, 0xAC, 0x8E, 0xD7, 0x23, 0xE0, 0x75, 0xAB, + 0x41, 0x14, 0xC3, 0xD3, 0xEB, 0x7B, 0x60, 0xA0, 0x48, 0xA4, 0x3E, 0x17, + 0x89, 0xD4, 0xD7, 0xC0, 0x50, 0xD3, 0x76, 0xA8, 0xB9, 0x78, 0x42, 0x27, + 0xE3, 0x2D, 0x69, 0x4D, 0x41, 0x2B, 0x34, 0x06, 0x49, 0x6C, 0xFA, 0x47, + 0xCA, 0x63, 0xBE, 0x9A, 0x01, 0x0C, 0xC0, 0xF7, 0xBE, 0xC5, 0xF7, 0xA6, + 0x60, 0xF2, 0x00, 0x86, 0xD1, 0xF2, 0xD7, 0x03, 0x46, 0x0B, 0xA9, 0x36, + 0x2E, 0xF3, 0xF7, 0x13, 0xB4, 0xA8, 0xDC, 0xEF, 0x90, 0xEA, 0x17, 0xD9, + 0x3F, 0xA8, 0x96, 0xAA, 0x0A, 0xA9, 0xBC, 0xA6, 0xA5, 0x3C, 0x02, 0x4D, + 0x86, 0xF6, 0xBD, 0xB9, 0xC2, 0xD8, 0x8A, 0x3F, 0x8C, 0xD0, 0xE9, 0x0C, + 0x89, 0x5C, 0x42, 0x5B, 0x86, 0x21, 0x91, 0x7E, 0x98, 0x9A, 0x0D, 0x95, + 0x84, 0x37, 0x04, 0x1C, 0x84, 0xEF, 0xCD, 0xA6, 0xFD, 0x63, 0x61, 0x88, + 0x0F, 0xF7, 0x02, 0x9B, 0x75, 0x37, 0x17, 0x79, 0x74, 0x03, 0xFE, 0x46, + 0xF1, 0xF2, 0x25, 0xC1, 0xDA, 0x69, 0xBE, 0xFB, 0x62, 0x8A, 0x33, 0x7D, + 0xAB, 0x93, 0xF1, 0x37, 0x75, 0x32, 0x7E, 0xB3, 0x4E, 0xC6, 0x4F, 0xD5, + 0xC9, 0xF8, 0xFE, 0x3A, 0x19, 0xDF, 0x5C, 0x27, 0xE3, 0x1B, 0xD8, 0xB8, + 0x88, 0x5E, 0xF6, 0x67, 0xBF, 0x07, 0x9E, 0x09, 0x2A, 0xCC, 0x8A, 0x60, + 0xBE, 0xAA, 0x03, 0x0E, 0x15, 0x89, 0xD4, 0x94, 0x2C, 0x4D, 0x28, 0x45, + 0xF8, 0x54, 0xE5, 0x1B, 0x63, 0x0E, 0xD5, 0x9B, 0x73, 0x10, 0x08, 0x1A, + 0x95, 0xDD, 0x2F, 0x5F, 0x21, 0x28, 0x3D, 0xFC, 0x20, 0xA1, 0x93, 0xF1, + 0x2B, 0x80, 0xB3, 0xCB, 0xB0, 0xE6, 0x16, 0x5B, 0xF2, 0xF8, 0x32, 0x4B, + 0x16, 0x8C, 0x05, 0x92, 0x21, 0xDB, 0xD9, 0x4E, 0xC3, 0xC8, 0x1A, 0xA9, + 0xCA, 0x19, 0xFF, 0x12, 0x34, 0x75, 0x4F, 0x27, 0x60, 0x24, 0x52, 0x7D, + 0x8A, 0x54, 0x63, 0x90, 0x6A, 0x62, 0xBD, 0x31, 0x75, 0xFE, 0x4D, 0x37, + 0xC9, 0x84, 0x11, 0x38, 0xBA, 0x54, 0xFB, 0xDE, 0x1C, 0x21, 0xD5, 0xA1, + 0x1A, 0xFE, 0x45, 0xF8, 0xC0, 0x2D, 0x01, 0xDC, 0x66, 0x0F, 0xD6, 0xEF, + 0x6C, 0xB3, 0xE2, 0xCB, 0xF7, 0x3E, 0x11, 0x52, 0xFD, 0x58, 0x1B, 0xEF, + 0xB4, 0x41, 0x15, 0xD0, 0x23, 0x19, 0x83, 0x61, 0x65, 0xAA, 0x35, 0xD0, + 0xEA, 0x10, 0x89, 0xD4, 0x42, 0x9D, 0x8C, 0xCF, 0xC2, 0x1C, 0x44, 0xB7, + 0x84, 0xAD, 0x80, 0x77, 0x74, 0x32, 0x7E, 0x0B, 0x90, 0xC2, 0xD4, 0x37, + 0xE8, 0x09, 0xF4, 0xC7, 0x78, 0x32, 0x15, 0xCD, 0xC5, 0xD2, 0x7A, 0x5C, + 0x1D, 0x91, 0x67, 0x97, 0xBE, 0xBF, 0xFD, 0xB3, 0xD6, 0x2D, 0xF6, 0x4F, + 0x94, 0xDD, 0x74, 0x31, 0x62, 0x7B, 0x56, 0x21, 0xC4, 0x94, 0x1C, 0xE3, + 0x7B, 0xBF, 0x4E, 0xC6, 0xFB, 0x86, 0x14, 0xD4, 0x7D, 0x81, 0x7B, 0x9A, + 0xB9, 0x67, 0x72, 0x8E, 0xF7, 0xCF, 0x67, 0x0D, 0x99, 0xA0, 0x93, 0xF1, + 0x7B, 0x31, 0x89, 0x5D, 0xBF, 0xB7, 0x3B, 0xF7, 0x7D, 0xD1, 0xE9, 0x73, + 0x81, 0x1F, 0x97, 0x69, 0xD9, 0xCD, 0xCD, 0xB5, 0x39, 0xAB, 0x81, 0xAB, + 0xEB, 0x4C, 0xFA, 0x99, 0xC1, 0x21, 0xDA, 0x1A, 0x54, 0x67, 0xB4, 0xA8, + 0xAB, 0xCB, 0xF2, 0xFD, 0xC0, 0xDB, 0xDA, 0x38, 0x97, 0x04, 0x5D, 0x77, + 0xBB, 0xDA, 0x3F, 0xD9, 0xF8, 0x05, 0x70, 0x5F, 0x28, 0x0D, 0x64, 0x2D, + 0x12, 0x81, 0x43, 0x31, 0x35, 0x01, 0xA2, 0x68, 0x22, 0xB7, 0x23, 0xD5, + 0xC5, 0x6D, 0x59, 0x88, 0x69, 0xDF, 0x9B, 0xDB, 0xD9, 0x2C, 0x9A, 0xDF, + 0x00, 0xCB, 0xCA, 0xD4, 0x8D, 0x39, 0x80, 0x87, 0xEF, 0x9D, 0xDB, 0x51, + 0xC8, 0x23, 0x0B, 0x7F, 0x0F, 0x71, 0xED, 0x96, 0x98, 0x52, 0xAA, 0x93, + 0x80, 0x4F, 0x31, 0x69, 0x7A, 0xAE, 0x2F, 0x26, 0x79, 0x58, 0x5C, 0x1C, + 0xF2, 0x5B, 0x12, 0x11, 0xC9, 0x63, 0x15, 0xA6, 0x7A, 0x61, 0x29, 0x71, + 0x11, 0xE1, 0xEB, 0xCD, 0x9F, 0xA5, 0x93, 0xF1, 0x5F, 0x15, 0x61, 0xBE, + 0x3A, 0x63, 0x82, 0x66, 0x5F, 0xB1, 0xF3, 0xF5, 0x1E, 0xA6, 0x46, 0xFB, + 0x8F, 0x2B, 0x6D, 0x11, 0xD6, 0xF9, 0x5E, 0x5A, 0x18, 0x0F, 0xB3, 0xE9, + 0x21, 0x6F, 0xBD, 0x02, 0xA9, 0x8E, 0x2E, 0x93, 0xEC, 0xFA, 0xDE, 0x6E, + 0xA6, 0x0A, 0xC1, 0x4F, 0x90, 0x6A, 0xDB, 0x48, 0x04, 0x52, 0x24, 0x12, + 0xB9, 0xA3, 0xAD, 0x93, 0xC8, 0x2A, 0xDF, 0x4B, 0xE3, 0x7B, 0x77, 0x60, + 0x6A, 0x72, 0x8C, 0xA1, 0x34, 0x41, 0x63, 0xB9, 0xD0, 0x00, 0x3C, 0x20, + 0x60, 0x77, 0x7C, 0x6F, 0x14, 0x1D, 0x13, 0xF7, 0x5A, 0x13, 0x4C, 0x65, + 0x6C, 0x28, 0x92, 0xF1, 0x8D, 0x08, 0xEE, 0x5A, 0x5C, 0x28, 0x6E, 0x10, + 0x89, 0xD4, 0x17, 0x25, 0xD6, 0xF2, 0x4C, 0xC1, 0xB4, 0xF0, 0x87, 0xEA, + 0x77, 0xEA, 0x64, 0x3C, 0x87, 0xA6, 0x21, 0x9E, 0xB7, 0x64, 0xD0, 0xEE, + 0xA0, 0x7D, 0x6F, 0x01, 0xC6, 0xC9, 0x28, 0x8C, 0xE3, 0x4A, 0x0C, 0xF8, + 0x2B, 0x52, 0xED, 0x5C, 0xA6, 0x6E, 0x17, 0x5A, 0x49, 0x56, 0x00, 0x27, + 0x47, 0x26, 0x90, 0x0C, 0x93, 0x15, 0x81, 0x44, 0x86, 0xB5, 0xF9, 0x15, + 0xE4, 0x7B, 0xB3, 0xF0, 0xBD, 0x13, 0x30, 0xAE, 0x8A, 0xE3, 0x29, 0x6E, + 0x42, 0xBD, 0xB5, 0x36, 0x3C, 0x98, 0xCC, 0xAC, 0x7B, 0xE2, 0x7B, 0xE7, + 0x6B, 0xDF, 0x9B, 0x4B, 0x07, 0x85, 0x48, 0xA4, 0xA6, 0x11, 0xDE, 0xFE, + 0xDC, 0x1C, 0x66, 0x14, 0x78, 0xFF, 0x39, 0x98, 0x00, 0xBB, 0x52, 0x43, + 0x81, 0xB8, 0xB1, 0x95, 0xC6, 0xF8, 0x2B, 0x4C, 0xD1, 0xB2, 0x30, 0x44, + 0xDD, 0x19, 0x18, 0xA5, 0x93, 0xF1, 0x1F, 0xAC, 0xDD, 0xD6, 0x6B, 0x0D, + 0xC0, 0xF9, 0x14, 0x2F, 0xC5, 0xCB, 0xCC, 0x0A, 0x93, 0x01, 0x1F, 0x63, + 0x4A, 0xEE, 0x86, 0xD9, 0x44, 0xF6, 0xC0, 0x1C, 0xAA, 0x77, 0x6D, 0xED, + 0xEE, 0x76, 0x11, 0x3C, 0x4D, 0xB0, 0x0A, 0x9A, 0xCD, 0xA1, 0x30, 0x02, + 0xC9, 0x52, 0x87, 0x0E, 0x05, 0x3E, 0x8E, 0x48, 0x22, 0x77, 0xDA, 0x68, + 0xF7, 0xB6, 0x0F, 0xDF, 0x7B, 0xCB, 0x96, 0x8C, 0xDD, 0x0D, 0x18, 0x81, + 0xF1, 0x69, 0x2F, 0x06, 0xA6, 0x02, 0xFF, 0x07, 0x6C, 0x8F, 0xEF, 0x9D, + 0x8A, 0xEF, 0xB5, 0xCB, 0x9D, 0x5C, 0x84, 0xE5, 0x73, 0x2D, 0x6B, 0xD7, + 0x7A, 0x8E, 0x8A, 0xD1, 0xC0, 0xD3, 0x05, 0xB6, 0x71, 0x40, 0xA9, 0x37, + 0xBA, 0xC0, 0xED, 0x20, 0x4E, 0xB1, 0xC2, 0xB8, 0xB5, 0x88, 0xFA, 0x55, + 0x8C, 0x39, 0x29, 0x0C, 0x7A, 0x03, 0x4F, 0x35, 0xCD, 0xDC, 0x2B, 0x12, + 0xA9, 0x57, 0x80, 0x0B, 0x8B, 0xB0, 0xC1, 0xFA, 0x82, 0x4A, 0x2C, 0x1D, + 0x61, 0xAC, 0x01, 0x61, 0x63, 0x8E, 0xF6, 0xD4, 0x50, 0xBB, 0x49, 0x2B, + 0x67, 0xEE, 0x5D, 0x79, 0x8E, 0x97, 0x06, 0x4E, 0xB1, 0x63, 0x19, 0x15, + 0x7B, 0x22, 0xD5, 0x9E, 0x05, 0x11, 0x88, 0x1D, 0xB8, 0xEF, 0x31, 0xE9, + 0x37, 0xA2, 0x92, 0xC8, 0x5D, 0xED, 0x86, 0x44, 0xCC, 0x78, 0x7C, 0x8E, + 0xEF, 0x5D, 0xDE, 0xD9, 0x44, 0x19, 0xF7, 0x05, 0x2E, 0xC7, 0x1C, 0x7A, + 0x7E, 0x15, 0x50, 0x50, 0xCC, 0xB2, 0x02, 0xED, 0x37, 0xC0, 0x0F, 0x7B, + 0x0A, 0xB1, 0x1B, 0xBE, 0x77, 0x1D, 0xBE, 0xF7, 0x15, 0x0E, 0x4D, 0x76, + 0xB5, 0xE2, 0x54, 0xE0, 0x06, 0x20, 0xAA, 0x47, 0xD2, 0x18, 0xE0, 0x97, + 0x14, 0x6A, 0x7E, 0x14, 0xB1, 0xA1, 0xC0, 0xCF, 0x31, 0x45, 0xC9, 0x8A, + 0xAD, 0x81, 0xBE, 0x8B, 0xA9, 0x3F, 0x7E, 0x69, 0x11, 0x03, 0x07, 0xC3, + 0x90, 0xC8, 0x3D, 0xC0, 0x23, 0x21, 0x6F, 0xEB, 0x0F, 0x3A, 0x99, 0xA3, + 0xAD, 0x3F, 0x63, 0x4C, 0x63, 0xF3, 0x0A, 0xD8, 0x4C, 0x1D, 0x56, 0xC0, + 0xFD, 0x25, 0x45, 0xB5, 0xC9, 0x62, 0xFE, 0x7C, 0xC8, 0xDB, 0x4E, 0x9A, + 0xAB, 0x75, 0xEB, 0xA7, 0xA5, 0xF1, 0xBD, 0x6F, 0x30, 0x45, 0xCD, 0xDE, + 0x2E, 0xA0, 0x95, 0x5F, 0x14, 0x4E, 0x20, 0x6B, 0x93, 0xC8, 0x94, 0x28, + 0x6B, 0xD4, 0x92, 0xC8, 0x85, 0xED, 0x49, 0xC0, 0xD9, 0x33, 0x92, 0xF7, + 0xF0, 0xBD, 0x11, 0xF8, 0xDE, 0x91, 0xF8, 0x5E, 0x6F, 0x61, 0xD4, 0xD6, + 0x3E, 0x98, 0xA8, 0xFE, 0xA3, 0x30, 0x7E, 0xF7, 0x47, 0x02, 0x07, 0x01, + 0x7B, 0x08, 0xE8, 0x86, 0xEF, 0x6D, 0x8F, 0xEF, 0x1D, 0x87, 0xEF, 0xDD, + 0x81, 0xEF, 0x7D, 0xD2, 0x5E, 0x53, 0xB1, 0x17, 0x8B, 0x44, 0x44, 0x22, + 0x75, 0x15, 0xA6, 0xF6, 0xC7, 0x93, 0x04, 0x2F, 0xC7, 0x3C, 0x15, 0x38, + 0x0D, 0x51, 0x35, 0xD4, 0xDA, 0xFB, 0x0B, 0xEB, 0xC7, 0xE5, 0xAF, 0xA6, + 0x45, 0x22, 0xF5, 0xAC, 0x48, 0xA4, 0x06, 0x62, 0xBC, 0xC3, 0x2E, 0xC1, + 0x98, 0x33, 0xA3, 0x66, 0x93, 0x99, 0x07, 0x3C, 0x0A, 0x0C, 0x44, 0x54, + 0xFF, 0x48, 0x24, 0x52, 0x2F, 0x97, 0x79, 0xA8, 0xCF, 0xC7, 0x38, 0x21, + 0x84, 0xC1, 0x6F, 0x75, 0x32, 0x3E, 0x34, 0x07, 0x89, 0x3C, 0x83, 0x71, + 0x9D, 0x1E, 0x4E, 0x70, 0xF7, 0xDE, 0x05, 0x98, 0x94, 0x30, 0x7D, 0x45, + 0x22, 0x35, 0xBD, 0x52, 0xD7, 0x63, 0xBD, 0xEF, 0x35, 0x08, 0x23, 0x54, + 0xC3, 0x9A, 0x44, 0x6F, 0x42, 0xAA, 0x83, 0xCB, 0x40, 0x22, 0xB3, 0xAB, + 0x4C, 0xA2, 0xD6, 0x73, 0x09, 0x77, 0x46, 0xB5, 0x04, 0xE3, 0xD8, 0xF0, + 0x60, 0x46, 0x80, 0x17, 0x07, 0x52, 0x6D, 0x8A, 0xA9, 0xEA, 0xF7, 0xC3, + 0x88, 0x6A, 0xFA, 0x30, 0x7C, 0xEF, 0x5E, 0x27, 0x1A, 0xDB, 0x2F, 0x74, + 0x32, 0x7E, 0x60, 0xC0, 0x4D, 0xCB, 0x4A, 0x91, 0x48, 0xBD, 0x1B, 0xB2, + 0xED, 0xEE, 0x76, 0x23, 0xB3, 0x2F, 0x26, 0xD5, 0x44, 0xA6, 0x1A, 0xE1, + 0x0A, 0xCC, 0x81, 0xF0, 0x47, 0xC0, 0x6B, 0x88, 0xD8, 0x3B, 0xD9, 0x39, + 0x9C, 0x6C, 0x20, 0xDC, 0x96, 0x01, 0x1E, 0xF1, 0xAD, 0x48, 0xA4, 0x66, + 0x06, 0xEF, 0x4F, 0xBF, 0x2A, 0xD0, 0x3B, 0xD8, 0x4D, 0xC3, 0x76, 0x98, + 0x34, 0x25, 0x3F, 0xC0, 0x54, 0x36, 0xEC, 0x6C, 0xB5, 0x95, 0xE5, 0x96, + 0x30, 0x66, 0x63, 0x4A, 0xE3, 0x4E, 0x06, 0x31, 0x25, 0xAA, 0xA9, 0x4A, + 0x27, 0xE3, 0xFB, 0x11, 0x2C, 0x09, 0x67, 0x83, 0x48, 0xA4, 0xDE, 0x0C, + 0xD1, 0xEE, 0x26, 0x84, 0x8F, 0x99, 0x59, 0x26, 0x12, 0xA9, 0xF7, 0x9B, + 0x19, 0x9F, 0x1A, 0xD0, 0x3F, 0xC1, 0xB8, 0xF8, 0xEE, 0x8E, 0x09, 0x24, + 0x5C, 0x1F, 0x73, 0xEE, 0x32, 0x17, 0x13, 0x1C, 0xFB, 0x36, 0xF0, 0x62, + 0x36, 0xD1, 0xEB, 0x64, 0xBC, 0x47, 0x40, 0x19, 0xB3, 0x5C, 0x24, 0x52, + 0x93, 0x9B, 0x91, 0x57, 0x41, 0xB3, 0x5C, 0xAF, 0xB4, 0xD9, 0x28, 0x82, + 0xCA, 0xC1, 0x2D, 0xEC, 0x7C, 0x87, 0xC1, 0xFC, 0x6C, 0xF3, 0xB4, 0x90, + 0x6A, 0x03, 0x1D, 0x3C, 0xE5, 0xD1, 0xF7, 0xF8, 0xDE, 0xB4, 0x42, 0xBE, + 0xCB, 0x6E, 0xB5, 0xA3, 0xC4, 0x12, 0xAD, 0x7F, 0x08, 0xC4, 0x81, 0x3D, + 0x30, 0xA6, 0xC8, 0x6E, 0xF6, 0x5B, 0x5D, 0x6E, 0xBF, 0x9F, 0xCF, 0x80, + 0xC9, 0x02, 0x52, 0xDA, 0xF7, 0x56, 0x64, 0x6B, 0x00, 0xC5, 0x43, 0xE1, + 0x24, 0x72, 0x11, 0xBE, 0x77, 0x9F, 0x13, 0xB5, 0x0E, 0x0E, 0x0E, 0x0E, + 0x6D, 0xC0, 0x12, 0x50, 0xF4, 0x16, 0x0D, 0x89, 0xBC, 0x48, 0xB8, 0x94, + 0xDB, 0xD9, 0x24, 0x72, 0x21, 0xBE, 0x77, 0xBF, 0x9B, 0x1A, 0x07, 0x07, + 0x07, 0x87, 0xCA, 0x46, 0xF1, 0xF3, 0xCA, 0xF8, 0xDE, 0x77, 0xD6, 0x94, + 0xF0, 0x49, 0x44, 0x42, 0xBB, 0x17, 0xA9, 0xCE, 0x77, 0x53, 0xE3, 0xE0, + 0xE0, 0xE0, 0xD0, 0xD1, 0x34, 0x90, 0x46, 0x4D, 0x64, 0x33, 0xAB, 0x89, + 0x44, 0xA9, 0xA0, 0xA6, 0x81, 0x0B, 0xF0, 0xBD, 0x07, 0xDC, 0x14, 0x39, + 0x38, 0x38, 0x38, 0x74, 0x34, 0x02, 0x71, 0x24, 0xE2, 0xE0, 0xE0, 0xE0, + 0xE0, 0x08, 0xA4, 0x8C, 0x24, 0x92, 0xB6, 0x24, 0xF2, 0x67, 0x37, 0x55, + 0x0E, 0x0E, 0x0E, 0x0E, 0x1D, 0x8D, 0x40, 0x0C, 0x89, 0x6C, 0x6E, 0x49, + 0x64, 0x57, 0x47, 0x22, 0x0E, 0x0E, 0x0E, 0x0E, 0x8E, 0x40, 0x5A, 0x9B, + 0x44, 0xCE, 0xC7, 0xF7, 0x1E, 0xAC, 0xC8, 0x51, 0x94, 0xEA, 0x2C, 0x4C, + 0xF4, 0xF8, 0xB3, 0xC0, 0xF3, 0x31, 0x78, 0x3B, 0xED, 0x7B, 0x2B, 0xDD, + 0xF2, 0x72, 0x70, 0x70, 0x70, 0x04, 0x52, 0x5C, 0x12, 0x79, 0x89, 0x68, + 0x85, 0x7C, 0xD2, 0xC0, 0x79, 0xF8, 0x9E, 0xAC, 0x30, 0xF2, 0x38, 0x13, + 0x13, 0x99, 0x99, 0xED, 0xD1, 0xB6, 0x12, 0x78, 0x07, 0x53, 0xC5, 0xF1, + 0x03, 0x4C, 0xDE, 0x99, 0xD9, 0x02, 0xBE, 0x8E, 0xC1, 0xCA, 0x2A, 0x48, + 0xC7, 0x04, 0x54, 0x23, 0xF4, 0x52, 0x17, 0x6D, 0xEE, 0xE0, 0xE0, 0xE0, + 0x08, 0x24, 0xB0, 0xC0, 0xDD, 0xC2, 0x6A, 0x22, 0x6D, 0x9F, 0x44, 0x72, + 0x93, 0x47, 0x4B, 0x58, 0x85, 0x29, 0x1D, 0x3A, 0x15, 0x38, 0xD2, 0xBA, + 0x3D, 0x3B, 0x38, 0x38, 0x38, 0x38, 0x02, 0x69, 0x25, 0x12, 0xF9, 0x15, + 0xBE, 0x57, 0x5B, 0x66, 0xF2, 0x38, 0xC3, 0x92, 0x47, 0x94, 0x12, 0x95, + 0xD3, 0x81, 0xFE, 0x2E, 0x49, 0xA2, 0x83, 0x83, 0x83, 0x23, 0x90, 0xE8, + 0x24, 0xF2, 0x12, 0x26, 0x6F, 0x51, 0xDB, 0x22, 0x11, 0x53, 0x70, 0xFE, + 0x31, 0xA2, 0x05, 0x62, 0x4E, 0xA7, 0x69, 0x8D, 0x65, 0x07, 0x07, 0x07, + 0x87, 0x36, 0x88, 0x58, 0xD9, 0x9E, 0x6C, 0x52, 0x0A, 0xF7, 0x07, 0x3E, + 0x8F, 0xD8, 0xEF, 0x3F, 0x23, 0xD5, 0xD9, 0xAD, 0xDE, 0x6F, 0xA9, 0x4E, + 0x02, 0xFE, 0x1A, 0x71, 0xEC, 0x66, 0x38, 0xF2, 0x70, 0x68, 0x6F, 0xA8, + 0x92, 0xAA, 0x7A, 0xE3, 0x56, 0xAE, 0x6B, 0xE1, 0xD0, 0xD1, 0x35, 0x90, + 0x46, 0x81, 0xBC, 0x25, 0xC6, 0x9C, 0x15, 0x55, 0x13, 0xF1, 0xF1, 0xBD, + 0x87, 0x5A, 0xA9, 0xAF, 0x27, 0x02, 0x8F, 0x03, 0xD5, 0x11, 0xC9, 0xA3, + 0x7F, 0x87, 0x26, 0x0F, 0xA9, 0x3E, 0xC4, 0x64, 0x8B, 0x7D, 0x0A, 0xDF, + 0xBB, 0xA6, 0x03, 0xBE, 0xFF, 0x79, 0x18, 0x6F, 0x3D, 0x11, 0x83, 0x3D, + 0xD2, 0xBE, 0xB7, 0xBA, 0xCD, 0xEE, 0x3C, 0xA5, 0xEA, 0x94, 0x36, 0x35, + 0x30, 0x4E, 0x07, 0x36, 0xC7, 0x94, 0x75, 0x7D, 0x14, 0xDF, 0xBB, 0xA4, + 0x03, 0xCE, 0x6B, 0x3F, 0x8C, 0x39, 0x1B, 0xE0, 0x64, 0x7C, 0xEF, 0x3D, + 0xA7, 0x81, 0xB4, 0x9E, 0x26, 0xF2, 0x35, 0x30, 0xA0, 0x00, 0x4D, 0x44, + 0x5A, 0x37, 0x5A, 0x47, 0x1E, 0x95, 0x8F, 0x9D, 0xEC, 0x46, 0x61, 0xD3, + 0x0E, 0xFA, 0xFE, 0x3D, 0xED, 0xFB, 0xEF, 0x54, 0x11, 0x9B, 0xB7, 0x02, + 0x90, 0x36, 0xB5, 0xE9, 0xAF, 0x06, 0xB6, 0xC6, 0xA4, 0xCD, 0xDF, 0x88, + 0x68, 0xE7, 0x81, 0xED, 0x01, 0x5D, 0xED, 0xBC, 0xEE, 0x0C, 0x74, 0xE9, + 0x48, 0x2F, 0x1E, 0xAB, 0x88, 0x5E, 0x34, 0x92, 0xC8, 0xB4, 0x88, 0xEF, + 0x20, 0xAD, 0x47, 0x54, 0xA9, 0xC8, 0xC3, 0x2B, 0x80, 0x3C, 0x66, 0xE2, + 0xCC, 0x56, 0x0E, 0xED, 0xC9, 0x6C, 0x21, 0x55, 0x4F, 0xE0, 0x0C, 0xFB, + 0xDF, 0x31, 0x98, 0x7A, 0x2A, 0x7D, 0x81, 0x3B, 0xDD, 0xE8, 0x74, 0x2C, + 0xC4, 0x2A, 0xA6, 0x27, 0x85, 0x93, 0x48, 0x6D, 0x49, 0x48, 0xC4, 0x90, + 0xC7, 0xC8, 0x02, 0xC8, 0xA3, 0x3F, 0xBE, 0x37, 0xDB, 0x2D, 0x35, 0x87, + 0xF6, 0x02, 0x0D, 0x3B, 0x66, 0x7D, 0x0F, 0x0F, 0xE0, 0x7B, 0xDF, 0xD8, + 0x0A, 0x9C, 0xD3, 0xDD, 0xE8, 0x74, 0x2C, 0x54, 0x57, 0x54, 0x6F, 0x7C, + 0xEF, 0x2B, 0xA4, 0x1A, 0x80, 0x39, 0x13, 0xD9, 0x29, 0x22, 0x89, 0x68, + 0x7C, 0xEF, 0x91, 0x22, 0x91, 0xC7, 0xD0, 0x02, 0x35, 0x0F, 0x47, 0x1E, + 0xF9, 0xC7, 0x76, 0x0F, 0xE0, 0x02, 0x60, 0x6F, 0x8C, 0x39, 0xE7, 0x53, + 0x60, 0x24, 0xBE, 0xF7, 0xEF, 0xAC, 0x6B, 0xEE, 0x03, 0x7A, 0x01, 0x53, + 0xF1, 0xBD, 0xAB, 0xF2, 0xB4, 0x73, 0x13, 0xA6, 0x02, 0x5C, 0x0A, 0xDF, + 0xBB, 0xB3, 0x99, 0xE7, 0xDD, 0x85, 0xB1, 0xD5, 0xFF, 0x03, 0x78, 0xDD, + 0x3E, 0xBB, 0xAF, 0x5D, 0x37, 0x1F, 0x03, 0xF7, 0xE1, 0x7B, 0x93, 0x63, + 0x52, 0xD5, 0xA4, 0xE1, 0x54, 0x4C, 0x9D, 0xF3, 0x2D, 0x80, 0x65, 0xC0, + 0xCB, 0x02, 0xEE, 0xD6, 0xBE, 0x37, 0x7F, 0xAD, 0x05, 0x67, 0xAE, 0x3D, + 0x1A, 0x18, 0x84, 0xC9, 0xB0, 0xB0, 0x1E, 0xA6, 0x94, 0xED, 0x1B, 0xC0, + 0x83, 0xA1, 0xB4, 0x4E, 0xA9, 0xB6, 0x07, 0x6E, 0xC4, 0x98, 0x81, 0xE6, + 0x08, 0xF8, 0x6D, 0xA6, 0xF2, 0x9B, 0x90, 0xAA, 0x9B, 0x36, 0x3B, 0xFE, + 0x41, 0x18, 0xF3, 0xDF, 0x62, 0xE0, 0x55, 0xFB, 0x8C, 0x6F, 0x9B, 0xB4, + 0x33, 0x18, 0x38, 0x13, 0x58, 0x19, 0x83, 0xB3, 0xD3, 0x70, 0x1A, 0xA6, + 0x8F, 0x73, 0xF1, 0xBD, 0xB3, 0x00, 0x6A, 0xA4, 0xAA, 0xAA, 0x83, 0xE3, + 0x80, 0x63, 0x30, 0x65, 0x78, 0x63, 0x98, 0x12, 0xB3, 0x93, 0x80, 0x67, + 0xF0, 0xBD, 0x0F, 0x72, 0x75, 0x71, 0xA3, 0xDA, 0x51, 0x62, 0x81, 0xD6, + 0xF7, 0xD8, 0x71, 0xC9, 0x60, 0x98, 0xFD, 0x4E, 0x00, 0xAE, 0xCB, 0xB8, + 0xA6, 0x57, 0x49, 0x55, 0xD3, 0x00, 0x9E, 0x1D, 0xC7, 0x6D, 0x80, 0x06, + 0xE0, 0x43, 0xCC, 0xF9, 0xD7, 0xCB, 0x39, 0xDE, 0x5F, 0x62, 0xCA, 0x3E, + 0x3F, 0x6D, 0xFB, 0x71, 0x1E, 0xF0, 0x23, 0xE0, 0x2A, 0x7C, 0xEF, 0xD5, + 0x66, 0xC6, 0xED, 0x12, 0x4C, 0x69, 0xD6, 0xA9, 0x02, 0xEE, 0xD2, 0x70, + 0x21, 0xA6, 0xDE, 0xF7, 0xFA, 0xC0, 0x2C, 0xE0, 0x11, 0x7C, 0x6F, 0x62, + 0xCF, 0xDA, 0x51, 0x62, 0xBE, 0xD6, 0x3F, 0xC3, 0x94, 0x9D, 0xDD, 0xD1, + 0xF6, 0x67, 0x12, 0x70, 0x4F, 0x53, 0xE2, 0xB3, 0x15, 0xFA, 0x0E, 0xB6, + 0xE3, 0xD6, 0x07, 0x53, 0x45, 0x72, 0x05, 0xF0, 0x3E, 0xF0, 0x30, 0xBE, + 0x37, 0x39, 0x84, 0xB6, 0xD6, 0x4D, 0xC3, 0x2D, 0x18, 0xF3, 0x65, 0x1D, + 0x70, 0x99, 0xDD, 0x2C, 0x53, 0x25, 0x55, 0x75, 0x83, 0x29, 0x6D, 0x7D, + 0xAC, 0x1D, 0xA3, 0xD5, 0x98, 0x00, 0xE4, 0x87, 0xF0, 0xBD, 0x8F, 0x9A, + 0xBC, 0xE7, 0xAE, 0x98, 0xD2, 0xBE, 0x60, 0xCA, 0x26, 0xEF, 0x63, 0xDF, + 0x65, 0xCB, 0x1A, 0xE8, 0x5F, 0xE7, 0x7B, 0x69, 0x7B, 0xDD, 0xC1, 0xC0, + 0xC9, 0x98, 0xA2, 0x7E, 0x5D, 0x80, 0x85, 0x98, 0x60, 0xE6, 0x89, 0xEB, + 0x09, 0xF1, 0xE2, 0x8A, 0x22, 0x07, 0x2E, 0x8B, 0x0A, 0x15, 0x2E, 0x5B, + 0x61, 0x5C, 0x7C, 0x77, 0x8C, 0x70, 0x77, 0x1A, 0x38, 0xBB, 0x60, 0x12, + 0x91, 0xEA, 0x04, 0x4C, 0xAD, 0xED, 0x28, 0xE4, 0x31, 0xCB, 0x92, 0xC7, + 0x7F, 0x1D, 0x53, 0xAC, 0x35, 0xA6, 0x2B, 0xEC, 0xA2, 0xFE, 0x1C, 0x53, + 0xE2, 0xB5, 0x53, 0x8E, 0xAB, 0x1E, 0xAF, 0x82, 0xB3, 0x1A, 0x7C, 0xAF, + 0x0E, 0xA9, 0xAE, 0x07, 0xAE, 0xB4, 0x1F, 0xFB, 0x2E, 0xF8, 0xDE, 0x8C, + 0x26, 0xED, 0xED, 0x6E, 0x85, 0xBF, 0x00, 0x06, 0xE3, 0x7B, 0x13, 0x9B, + 0x79, 0xF6, 0x54, 0x8C, 0x8D, 0xFA, 0x23, 0xBB, 0x39, 0x69, 0x6A, 0xAB, + 0xAE, 0xC3, 0xD4, 0xFF, 0x3E, 0x0F, 0xD8, 0x2F, 0x47, 0x0B, 0xFF, 0x05, + 0x7E, 0x62, 0xBD, 0x07, 0x89, 0x49, 0xD5, 0x39, 0x0D, 0x93, 0xC9, 0x9F, + 0x24, 0x74, 0x31, 0x26, 0x50, 0xF4, 0xB5, 0xAC, 0x3E, 0x5C, 0x01, 0xDC, + 0x60, 0x77, 0x3B, 0x5D, 0xD2, 0xBE, 0xB7, 0xCA, 0xFE, 0x7C, 0x4F, 0x4B, + 0x6C, 0x5B, 0x58, 0x41, 0x3E, 0x70, 0x8D, 0x00, 0x91, 0x6A, 0x1F, 0xE0, + 0x19, 0xCC, 0x59, 0x43, 0xAE, 0x67, 0xFC, 0x02, 0xDF, 0x9B, 0x90, 0xF5, + 0x8C, 0x8B, 0xAD, 0x29, 0x69, 0xB9, 0x25, 0xB2, 0x81, 0xF6, 0x37, 0x6F, + 0xE2, 0x7B, 0x07, 0xC6, 0xA4, 0xEA, 0x92, 0x36, 0x29, 0x77, 0x0E, 0xCB, + 0xD3, 0xEF, 0xFF, 0xC5, 0x60, 0xDB, 0x35, 0x7D, 0xCB, 0x42, 0xD7, 0xDA, + 0x51, 0x62, 0x99, 0xD6, 0x2B, 0xC8, 0x5F, 0x36, 0xB7, 0x0F, 0xBE, 0xF7, + 0x21, 0x52, 0x6D, 0x6B, 0xFB, 0x9C, 0xAF, 0x2C, 0xEB, 0x53, 0x02, 0xCE, + 0xD4, 0xD9, 0xE9, 0x7E, 0xA4, 0xFA, 0x16, 0xD8, 0x0C, 0x78, 0xD9, 0x12, + 0x47, 0x57, 0xFB, 0x9B, 0x9F, 0xE3, 0x7B, 0xCF, 0x36, 0x33, 0xAF, 0x4F, + 0x02, 0x27, 0xD9, 0xF9, 0xE9, 0x6C, 0xDB, 0x68, 0x8A, 0xEB, 0xED, 0x9C, + 0x9F, 0x98, 0xE3, 0x77, 0x4B, 0x81, 0x41, 0xF8, 0xDE, 0x7F, 0xB2, 0xDA, + 0x54, 0x18, 0xC1, 0x9E, 0x0B, 0x0D, 0x98, 0xDC, 0x7C, 0x0F, 0x66, 0x5D, + 0x7F, 0x04, 0x90, 0x59, 0x7B, 0x3F, 0xCD, 0xB4, 0x25, 0xA4, 0xDA, 0x44, + 0xC3, 0x73, 0xF6, 0x7D, 0x56, 0x03, 0x27, 0xE2, 0x7B, 0xCF, 0xD8, 0x7B, + 0x36, 0x05, 0xC6, 0x61, 0x4A, 0xFA, 0xE6, 0x7A, 0x46, 0x02, 0xDF, 0xBB, + 0x39, 0xEB, 0x19, 0x71, 0x20, 0xB3, 0x96, 0x9E, 0xB5, 0xE4, 0x06, 0xB0, + 0xA2, 0x1A, 0xBA, 0xD5, 0xFB, 0x5E, 0x03, 0x52, 0xDD, 0x6E, 0xC9, 0x25, + 0x5F, 0xBF, 0x7F, 0x88, 0xEF, 0x4D, 0x6D, 0x9F, 0x26, 0xAC, 0xA6, 0x9A, + 0x88, 0x71, 0xF1, 0x9D, 0x1E, 0xF1, 0x9D, 0x6A, 0x6D, 0xA0, 0x5F, 0x21, + 0xE4, 0xF1, 0x84, 0x23, 0x8F, 0x92, 0x61, 0x67, 0x2B, 0xF4, 0x25, 0x70, + 0x0E, 0x90, 0xC0, 0x38, 0x1A, 0x00, 0x9C, 0xDA, 0x60, 0x85, 0x2C, 0x70, + 0xBF, 0xFD, 0xF0, 0xAA, 0xAC, 0x70, 0x6F, 0x8A, 0x8B, 0x6C, 0x3B, 0x53, + 0x3B, 0xC1, 0xF3, 0x01, 0x9F, 0xBD, 0xA7, 0xB1, 0xC2, 0x70, 0x3F, 0xC6, + 0x23, 0xEA, 0x71, 0xFB, 0xFF, 0x1A, 0x8C, 0x27, 0xCD, 0x7E, 0x98, 0x1A, + 0xE5, 0xD7, 0x02, 0xBF, 0x07, 0x32, 0xB5, 0xC3, 0xB7, 0xB1, 0x1A, 0x82, + 0xD9, 0xA5, 0x18, 0x01, 0xFB, 0x1F, 0x4C, 0x4D, 0xF3, 0xBB, 0x30, 0xDE, + 0x48, 0x27, 0x03, 0xF7, 0x01, 0xF5, 0x76, 0xD7, 0xFA, 0x70, 0x8D, 0x54, + 0xB1, 0x16, 0xD6, 0xDA, 0x81, 0x56, 0x68, 0x66, 0xC8, 0xE3, 0xD0, 0x0C, + 0x79, 0x08, 0xA9, 0x7A, 0x61, 0x04, 0xD0, 0xD6, 0xC0, 0xF7, 0xC0, 0xA5, + 0x76, 0x77, 0x7D, 0xA6, 0x25, 0xCE, 0xEE, 0xC0, 0x28, 0xBB, 0x3B, 0x6D, + 0x8A, 0xF5, 0x2D, 0x79, 0xCC, 0x01, 0x1E, 0xC1, 0x9C, 0x55, 0x90, 0x36, + 0xE3, 0x7D, 0x58, 0xD6, 0xF8, 0x1E, 0x69, 0xFF, 0x3F, 0xCC, 0xF6, 0xE3, + 0x96, 0x5C, 0xE4, 0x01, 0xB0, 0xBE, 0xD9, 0x6E, 0x26, 0x80, 0xBB, 0xB3, + 0x09, 0x1F, 0xB8, 0x0C, 0xB8, 0x4C, 0xC0, 0xB7, 0x42, 0xAA, 0xF5, 0x6C, + 0x9F, 0xFB, 0xD8, 0x71, 0x1D, 0x0D, 0xFC, 0xCA, 0xF6, 0x7D, 0x92, 0xBD, + 0xE7, 0x24, 0x6D, 0x0E, 0xE1, 0x73, 0xE1, 0x10, 0x4B, 0x1E, 0xFF, 0xC1, + 0x5C, 0xF3, 0x45, 0xC0, 0x79, 0xDD, 0x06, 0x53, 0x73, 0x5E, 0xD9, 0x67, + 0x65, 0x48, 0x14, 0x8C, 0xB7, 0xD8, 0x89, 0x96, 0x70, 0x6F, 0xB3, 0xBF, + 0x7F, 0xC6, 0xFE, 0x6E, 0x03, 0xE0, 0x81, 0x0D, 0xD6, 0x76, 0x43, 0x7E, + 0x09, 0x93, 0x8E, 0xE8, 0x31, 0xDB, 0xF7, 0xA1, 0x76, 0x3D, 0x2C, 0xB2, + 0x6B, 0xF1, 0x2E, 0x1B, 0xC7, 0xD6, 0xDC, 0xBC, 0xF6, 0xD6, 0xF0, 0x8A, + 0x25, 0x8F, 0xBA, 0x6C, 0xF2, 0xB0, 0x6B, 0x62, 0xAC, 0x25, 0x8F, 0xD9, + 0x83, 0x5D, 0x9A, 0x00, 0x00, 0x0A, 0xDD, 0x49, 0x44, 0x41, 0x54, 0x55, + 0xC0, 0x70, 0x3B, 0x57, 0xC7, 0x03, 0x2F, 0xD8, 0x67, 0x8C, 0x40, 0xAA, + 0xE3, 0xF2, 0xB4, 0x7E, 0xB4, 0xD5, 0x88, 0x14, 0xF0, 0xE7, 0x2A, 0xD0, + 0x48, 0x75, 0x40, 0x16, 0x79, 0x4C, 0x00, 0x4E, 0xC0, 0x1C, 0x09, 0x9C, + 0x05, 0x8C, 0x02, 0x9E, 0x28, 0x36, 0x79, 0x54, 0x9E, 0x09, 0x2B, 0xB7, + 0x39, 0xEB, 0x25, 0x60, 0x87, 0x90, 0x77, 0x57, 0x65, 0x99, 0xB3, 0xFE, + 0x1A, 0x92, 0x3C, 0x8E, 0xB7, 0xE4, 0x51, 0x13, 0xA1, 0xD7, 0x5F, 0x38, + 0xF2, 0x08, 0x04, 0x0D, 0x1C, 0x83, 0xEF, 0x3D, 0x97, 0xA5, 0xEA, 0xDF, + 0xA7, 0xCD, 0x0E, 0x6B, 0x2F, 0xE0, 0x62, 0x21, 0xD5, 0x4D, 0xDA, 0xF7, + 0xBE, 0x46, 0xAA, 0x31, 0x56, 0x30, 0x9F, 0x29, 0xA4, 0xBA, 0x46, 0xFB, + 0xDE, 0x32, 0x7B, 0x7D, 0x0F, 0x0D, 0xBF, 0xB4, 0xB7, 0xDF, 0xBD, 0x3A, + 0xA3, 0xC2, 0x07, 0xC3, 0xB1, 0xF8, 0xDE, 0xF3, 0x59, 0x73, 0x3E, 0xC7, + 0x92, 0x09, 0xC0, 0x37, 0x02, 0xFA, 0x6A, 0xDF, 0x9B, 0x6B, 0x35, 0x8D, + 0x3B, 0xAD, 0xA6, 0xB1, 0x3B, 0x70, 0xD4, 0x46, 0xB5, 0xA3, 0xC4, 0x82, + 0x46, 0x33, 0xC0, 0x95, 0x02, 0x2E, 0xD3, 0xBE, 0xB7, 0x20, 0x7B, 0x77, + 0x8D, 0x54, 0xDF, 0x03, 0x7F, 0x04, 0x76, 0xA8, 0x33, 0x84, 0xF5, 0x41, + 0x9E, 0xB5, 0x76, 0xB8, 0x15, 0xB0, 0xDD, 0xB2, 0xC8, 0xE3, 0xC3, 0xAC, + 0x41, 0xBA, 0xD4, 0xEE, 0xA8, 0x57, 0x00, 0x07, 0xE3, 0x7B, 0x9F, 0xD9, + 0x5F, 0xBD, 0x2A, 0xA4, 0x1A, 0xAB, 0xE1, 0x5D, 0xAB, 0xA5, 0x5F, 0x4E, + 0xE3, 0xA1, 0x76, 0x36, 0x26, 0x0B, 0x18, 0xA0, 0x7D, 0x6F, 0x61, 0xD6, + 0xCF, 0xF6, 0xB6, 0x7F, 0x7F, 0xD7, 0x5D, 0x88, 0x0B, 0x17, 0x37, 0xBE, + 0xCB, 0xBF, 0x80, 0x7B, 0x3A, 0x4B, 0x15, 0x5B, 0x95, 0x67, 0xD0, 0xE6, + 0x9C, 0x3D, 0x54, 0x03, 0xB7, 0x23, 0xD5, 0xFE, 0x96, 0x70, 0x00, 0xFE, + 0x8E, 0xEF, 0xA9, 0xCC, 0xA4, 0x22, 0xD5, 0x45, 0x59, 0x5A, 0xD9, 0xEF, + 0xF0, 0xBD, 0xDB, 0xD6, 0x7C, 0x94, 0x52, 0xDD, 0xDD, 0x60, 0x88, 0xEC, + 0x68, 0xE0, 0x4C, 0xA4, 0xBA, 0x0D, 0xDF, 0x9B, 0x92, 0xE3, 0x51, 0xC3, + 0xF0, 0xBD, 0x7B, 0x22, 0xAC, 0xAB, 0xAB, 0xF1, 0xBD, 0x1B, 0xB3, 0xC6, + 0xF7, 0x6D, 0xCC, 0xF9, 0x25, 0x96, 0xD4, 0xFB, 0x67, 0xB9, 0xD8, 0xDE, + 0x8E, 0x54, 0x4F, 0x59, 0x62, 0xD9, 0x73, 0xA9, 0xD6, 0xDB, 0x67, 0x36, + 0x31, 0x31, 0xF8, 0x4B, 0x1A, 0x5E, 0x68, 0xA2, 0xF1, 0x8E, 0x46, 0xAA, + 0x49, 0xC0, 0xDF, 0xAD, 0xA6, 0x73, 0x04, 0xF0, 0x50, 0x9E, 0x79, 0xDD, + 0xC9, 0x12, 0xC1, 0xB6, 0xF6, 0xB9, 0x27, 0xAF, 0xD1, 0x3C, 0x0C, 0x9B, + 0x1C, 0x03, 0xFC, 0xC4, 0xFE, 0xF7, 0x14, 0x7C, 0x6F, 0x4C, 0xE6, 0x77, + 0x5D, 0x6A, 0xD5, 0x33, 0x2B, 0x35, 0xCF, 0x02, 0x43, 0xAC, 0xC9, 0x6A, + 0x6C, 0x8E, 0x27, 0xCC, 0xB5, 0x9A, 0xCE, 0xE7, 0x58, 0x06, 0x42, 0xAA, + 0x7D, 0xB3, 0x7E, 0x7F, 0x41, 0x13, 0xD3, 0xF9, 0xC3, 0x9D, 0xA4, 0x8A, + 0x95, 0xC2, 0x67, 0xBC, 0xBA, 0xA2, 0xC5, 0x8C, 0xEF, 0x7D, 0x89, 0x54, + 0xFD, 0x0B, 0x20, 0x91, 0xBF, 0x58, 0x12, 0x79, 0x34, 0x04, 0x79, 0x3C, + 0xE9, 0xC8, 0xA3, 0xE4, 0x78, 0x31, 0x9B, 0x3C, 0x00, 0xB4, 0xEF, 0x2D, + 0x46, 0xAA, 0xEB, 0xEC, 0x6E, 0xA9, 0xB3, 0x36, 0x1A, 0xE8, 0x28, 0xBB, + 0x93, 0x3C, 0x19, 0xE8, 0xA9, 0x8D, 0xCD, 0x57, 0x5A, 0x61, 0x75, 0xA6, + 0xDD, 0x3D, 0x2E, 0x12, 0xF0, 0x68, 0x08, 0xC3, 0xEE, 0x94, 0xB5, 0xC8, + 0x23, 0x23, 0xF4, 0x1B, 0x09, 0xE4, 0xAF, 0x19, 0xF2, 0xB0, 0x9A, 0xC6, + 0x6A, 0xA4, 0x7A, 0xDA, 0x12, 0x48, 0xCF, 0x85, 0x5A, 0x6F, 0x68, 0xED, + 0xCA, 0xE0, 0x7B, 0xDF, 0x59, 0xA1, 0xB9, 0x85, 0x15, 0xF4, 0x19, 0x6D, + 0x23, 0x3B, 0x45, 0x4D, 0xEF, 0x5C, 0x04, 0x92, 0x36, 0x3B, 0xC4, 0xBF, + 0x58, 0x61, 0xB4, 0xB8, 0x29, 0x79, 0x58, 0x0C, 0xB6, 0x7F, 0xFF, 0x07, + 0xA8, 0xB6, 0xE7, 0x46, 0x6B, 0x18, 0xD8, 0xEE, 0xF4, 0x2F, 0x06, 0x7E, + 0xD6, 0xB5, 0x76, 0x94, 0x58, 0xB6, 0xAE, 0x7D, 0xFB, 0xEA, 0x26, 0xE4, + 0x41, 0xD6, 0x8E, 0x7E, 0xD3, 0xC5, 0x5A, 0x4F, 0x44, 0xAA, 0x67, 0x2C, + 0xD1, 0x7C, 0xAA, 0x7D, 0x6F, 0xF1, 0xAA, 0x70, 0x44, 0x9C, 0x6F, 0x77, + 0x0C, 0xF0, 0x4D, 0x35, 0xDC, 0x59, 0x9F, 0x6D, 0x43, 0xF1, 0xBD, 0x7A, + 0xA4, 0x4A, 0xD8, 0x6B, 0x84, 0x7D, 0xBF, 0xA6, 0x04, 0x32, 0x75, 0x63, + 0x21, 0xEE, 0x9D, 0x17, 0xFE, 0xB9, 0xF5, 0xF6, 0x0C, 0x64, 0x0D, 0x04, + 0x8C, 0xD5, 0x46, 0x83, 0xED, 0x64, 0xD7, 0xDC, 0x7B, 0x39, 0xE6, 0x3D, + 0x63, 0xD6, 0x5A, 0x43, 0x20, 0x36, 0x3E, 0x67, 0x86, 0x90, 0x6A, 0x23, + 0xDD, 0xE8, 0xA6, 0x0C, 0x30, 0xDF, 0x0E, 0xBD, 0xB0, 0xF3, 0x9A, 0x0B, + 0xFB, 0x60, 0xDC, 0x9B, 0x33, 0xAE, 0xEA, 0x67, 0x64, 0x13, 0x84, 0xC5, + 0x91, 0xF6, 0xEF, 0x6F, 0x81, 0xCF, 0xB3, 0xE7, 0x75, 0xA5, 0x5E, 0x63, + 0xA2, 0x1A, 0x02, 0xEC, 0x8E, 0x54, 0x5B, 0xE7, 0x38, 0x47, 0xBD, 0x27, + 0x43, 0x1E, 0x59, 0x98, 0x99, 0xF5, 0xEF, 0x71, 0x48, 0x35, 0x12, 0x73, + 0x9E, 0xF2, 0x09, 0xBE, 0xF7, 0xFD, 0xEA, 0xC2, 0xE7, 0xB5, 0x0D, 0x12, + 0x48, 0x71, 0x48, 0xE4, 0x21, 0xA4, 0xA2, 0x45, 0x12, 0x31, 0xEA, 0x62, + 0xA1, 0xE4, 0xF1, 0x05, 0x0E, 0x41, 0xF0, 0x49, 0x5E, 0xE1, 0xDE, 0x88, + 0x2D, 0xED, 0xFC, 0xBF, 0x85, 0x54, 0x6F, 0x02, 0x07, 0x00, 0x17, 0xF5, + 0xAC, 0x1D, 0x55, 0xBB, 0x54, 0x6B, 0xB1, 0xDA, 0x1C, 0x96, 0x02, 0x3C, + 0xA4, 0x7D, 0x6F, 0x49, 0x88, 0x67, 0xCF, 0xCC, 0xF1, 0xB3, 0xC5, 0x4D, + 0x4C, 0x90, 0x4D, 0x91, 0xAD, 0x61, 0xD4, 0x64, 0xAD, 0x99, 0x83, 0xAC, + 0xF9, 0xAA, 0x4F, 0x33, 0xCF, 0xCB, 0xB7, 0x9E, 0x1E, 0xCD, 0x22, 0x9C, + 0x6E, 0x76, 0xB7, 0xDA, 0x94, 0x40, 0xB6, 0xB4, 0x7F, 0x0F, 0xC4, 0x9C, + 0xDD, 0xE4, 0x43, 0xCF, 0x15, 0x5A, 0xD7, 0x58, 0x73, 0x5F, 0x36, 0xDE, + 0x6D, 0x7A, 0xA1, 0x80, 0xFB, 0xB4, 0x39, 0x40, 0xDF, 0x07, 0x73, 0x28, + 0x3F, 0xC8, 0x12, 0x52, 0x03, 0x52, 0xA5, 0x30, 0x87, 0xD6, 0xA9, 0x02, + 0xE6, 0x36, 0x63, 0xDA, 0xF9, 0xA8, 0xDE, 0xF7, 0x1A, 0x72, 0x08, 0x9C, + 0xA9, 0xF5, 0x46, 0xA3, 0x5A, 0xCF, 0xBE, 0x73, 0x53, 0x7C, 0x30, 0x2F, + 0xDA, 0x41, 0xEF, 0x1C, 0xED, 0x7B, 0x4B, 0x9B, 0x0C, 0xFC, 0xEA, 0xD5, + 0x8D, 0x04, 0x92, 0x6B, 0x5E, 0x17, 0x35, 0x91, 0x15, 0x99, 0x79, 0xDD, + 0x01, 0x33, 0x4E, 0x87, 0x92, 0xDF, 0xCC, 0x9F, 0x4F, 0x76, 0xDE, 0xD5, + 0xE4, 0x9E, 0x5C, 0x01, 0xD2, 0x99, 0x79, 0xDD, 0x3C, 0xC7, 0x9C, 0x37, + 0xC5, 0xA6, 0xD6, 0xA4, 0xDA, 0xEC, 0xBC, 0xD6, 0xC0, 0x0B, 0x75, 0x66, + 0xC3, 0x35, 0xD4, 0x6A, 0x99, 0x7B, 0xAF, 0xD9, 0x6B, 0x48, 0xF5, 0x31, + 0x30, 0x02, 0xDF, 0x1B, 0xD9, 0xF1, 0x08, 0xA4, 0x91, 0x44, 0x32, 0xE6, + 0xAC, 0xED, 0x23, 0x92, 0x88, 0xC6, 0xF7, 0x1E, 0x6B, 0x86, 0x3C, 0x9E, + 0x72, 0xE4, 0xD1, 0x6A, 0xE8, 0x91, 0xE7, 0xE7, 0x1B, 0x65, 0xFD, 0x7B, + 0x59, 0x93, 0x8F, 0xF2, 0x00, 0x60, 0xAF, 0xF9, 0x5A, 0xF7, 0xB3, 0x02, + 0x77, 0x07, 0xB3, 0x91, 0x27, 0xAC, 0xA9, 0xA3, 0xA1, 0x85, 0xDF, 0x07, + 0xDB, 0xA9, 0x49, 0xD5, 0xDB, 0x6A, 0x00, 0x5D, 0xAD, 0x70, 0xFA, 0x9B, + 0xDD, 0xA1, 0x02, 0x6C, 0x65, 0x35, 0x83, 0xE6, 0x10, 0xB3, 0xC2, 0x63, + 0x3B, 0xFB, 0x3E, 0x8F, 0x22, 0xD5, 0x8F, 0x9B, 0xEC, 0x2C, 0x97, 0x02, + 0x1B, 0x63, 0x3C, 0xD4, 0xC6, 0x35, 0xD7, 0xE7, 0x18, 0x34, 0xA4, 0xD7, + 0x25, 0x8B, 0xBA, 0xA6, 0x92, 0x58, 0xFB, 0xDE, 0xFC, 0x2A, 0xA9, 0xF6, + 0x6F, 0x80, 0xA3, 0x30, 0x36, 0xF2, 0x3E, 0x18, 0x0F, 0xB2, 0x4D, 0x30, + 0xE7, 0x2B, 0xFF, 0x42, 0xAA, 0xBD, 0x72, 0xEC, 0x70, 0x83, 0x22, 0x33, + 0x6F, 0xBD, 0xF2, 0x0C, 0x7E, 0x37, 0x1A, 0x0F, 0xE1, 0x17, 0xE5, 0xB8, + 0x64, 0x55, 0xC4, 0xE7, 0xD6, 0x15, 0x63, 0x5E, 0xAD, 0x67, 0xD4, 0x73, + 0x56, 0xF0, 0x2F, 0xC0, 0x9C, 0xF1, 0x7C, 0x65, 0x35, 0x8F, 0x2A, 0xCC, + 0xF9, 0x5C, 0xAC, 0x85, 0x79, 0xFD, 0x0E, 0x58, 0x62, 0xCD, 0x8B, 0x57, + 0x21, 0xD5, 0x24, 0x7C, 0x6F, 0x7C, 0xD6, 0x35, 0x99, 0x0D, 0xCF, 0x42, + 0xA0, 0xA5, 0x92, 0xDD, 0xDF, 0xE4, 0xF8, 0xD9, 0x3A, 0xD6, 0xA8, 0x3A, + 0xDF, 0x4B, 0xF7, 0xA8, 0x1D, 0x75, 0xE2, 0x42, 0xAD, 0xEF, 0xB5, 0x9A, + 0x5D, 0x5F, 0xFB, 0x0E, 0xBD, 0x31, 0x66, 0xD4, 0xC7, 0x91, 0x6A, 0x65, + 0x0E, 0x6D, 0xA8, 0x03, 0x10, 0x88, 0x21, 0x91, 0xD9, 0x59, 0x9A, 0x48, + 0x14, 0x12, 0x79, 0xD8, 0x6A, 0x22, 0x8F, 0x35, 0x11, 0x04, 0xC7, 0x3A, + 0xCD, 0xA3, 0xD5, 0x31, 0x44, 0x48, 0xB5, 0x49, 0xB6, 0xA9, 0xC8, 0xE2, + 0xF4, 0xAC, 0x7F, 0xBF, 0x9D, 0xF5, 0x45, 0x8E, 0x4E, 0xC3, 0xCD, 0x76, + 0xE7, 0x36, 0xCC, 0x0A, 0x5C, 0x30, 0xF6, 0xF7, 0x99, 0x65, 0x7A, 0x87, + 0xC1, 0x34, 0x7A, 0x0A, 0x0D, 0xC2, 0xF7, 0xA6, 0x65, 0xAD, 0xA9, 0x9F, + 0x04, 0x20, 0x90, 0xD7, 0x05, 0x0C, 0xB1, 0xA6, 0xBA, 0x31, 0xC0, 0x86, + 0xC0, 0x18, 0x21, 0xD5, 0x81, 0x59, 0x3B, 0xE9, 0x49, 0x98, 0xC3, 0xE1, + 0x5E, 0x02, 0x6E, 0xD1, 0xBE, 0xB7, 0x96, 0x65, 0xA7, 0x5A, 0xAA, 0xAA, + 0x2E, 0x42, 0xA4, 0x97, 0x9E, 0x33, 0x54, 0xD7, 0x87, 0x61, 0x50, 0xDF, + 0xAB, 0xC3, 0xD8, 0xD6, 0xC7, 0xC2, 0x1A, 0xEF, 0xAA, 0xA3, 0x30, 0x07, + 0xCB, 0x9D, 0xED, 0xBB, 0x45, 0x25, 0x90, 0x37, 0x81, 0x1F, 0x03, 0x7B, + 0x5B, 0x22, 0x5A, 0x6B, 0x87, 0xAD, 0xCD, 0x1C, 0xC7, 0x9A, 0xCE, 0x71, + 0xA5, 0xA0, 0xC1, 0x10, 0x6A, 0x46, 0x6B, 0xB8, 0x10, 0xDF, 0x7B, 0x72, + 0x0D, 0x21, 0x4B, 0xD5, 0x55, 0x1B, 0x8F, 0xAE, 0xE6, 0x30, 0x1B, 0xE3, + 0x94, 0x50, 0x65, 0xC7, 0xA2, 0x3B, 0xF0, 0x18, 0x52, 0xED, 0x97, 0x75, + 0xA6, 0x32, 0x09, 0x73, 0x60, 0xBE, 0x21, 0x30, 0x16, 0xDF, 0x9B, 0x94, + 0x93, 0xC8, 0x7C, 0x2F, 0xCC, 0xB4, 0xB2, 0xD0, 0x68, 0x6E, 0xAF, 0xD8, + 0x3F, 0x99, 0xB5, 0xB8, 0x27, 0xC6, 0x39, 0xA2, 0x27, 0xC6, 0x5D, 0xB8, + 0x83, 0x12, 0x48, 0xF1, 0x48, 0x44, 0xE3, 0x7B, 0x8F, 0xDB, 0xC1, 0x1D, + 0x6C, 0xC9, 0xA3, 0x53, 0x84, 0xDE, 0xFC, 0x17, 0x13, 0x61, 0xEE, 0xC8, + 0x23, 0x3C, 0x7A, 0x6A, 0x98, 0x88, 0x54, 0xE7, 0x75, 0x11, 0x4C, 0x5E, + 0xA5, 0xE9, 0xA1, 0xCD, 0x19, 0x84, 0xBF, 0x46, 0x08, 0x65, 0xC5, 0x22, + 0xA4, 0x8D, 0x4B, 0xEF, 0xFD, 0x98, 0x8F, 0xF7, 0xB8, 0x26, 0x9A, 0x49, + 0xB9, 0x90, 0xBD, 0xA3, 0xDD, 0x0A, 0x1B, 0x00, 0x2B, 0xA4, 0xDA, 0x40, + 0xC3, 0x9F, 0x5A, 0xBA, 0x59, 0xC0, 0x91, 0xDA, 0xF7, 0x16, 0x01, 0xCF, + 0xD8, 0xB3, 0x9F, 0x6B, 0x80, 0x3D, 0xB4, 0x39, 0xE3, 0x39, 0x39, 0xEB, + 0xFD, 0x8E, 0x07, 0x36, 0xD1, 0x46, 0x33, 0x18, 0x56, 0x03, 0x6F, 0x54, + 0x09, 0xF4, 0x4A, 0xCD, 0x5E, 0xF5, 0x30, 0x62, 0xA9, 0xD6, 0xAF, 0x63, + 0x3C, 0x84, 0x82, 0xC1, 0x1C, 0xB6, 0x5E, 0x01, 0xDC, 0x13, 0x83, 0xD7, + 0xD3, 0xBE, 0x57, 0xB7, 0xEC, 0x9C, 0xA1, 0xDA, 0xBA, 0x58, 0xE7, 0xD2, + 0xFE, 0xC2, 0xE2, 0x7E, 0x8C, 0x2B, 0x74, 0x27, 0xE0, 0x59, 0xA4, 0xF2, + 0xAB, 0xE1, 0xC5, 0x06, 0xE8, 0xA2, 0x4D, 0x4C, 0xCA, 0x70, 0x7B, 0xDD, + 0xB4, 0x18, 0x4C, 0x48, 0x57, 0xDE, 0xDA, 0x6C, 0x3A, 0xAF, 0xC6, 0x44, + 0x64, 0x62, 0x67, 0x6E, 0xA4, 0xE5, 0x74, 0x2D, 0x67, 0xAF, 0xD1, 0xDE, + 0xA4, 0xFA, 0x25, 0x26, 0xAE, 0xA5, 0x07, 0x30, 0xD6, 0x6E, 0x0E, 0x96, + 0x0B, 0x78, 0x58, 0x1B, 0x6F, 0xB6, 0x0D, 0x31, 0xE7, 0x15, 0xC3, 0x62, + 0x30, 0xBE, 0x8B, 0x10, 0xAB, 0x97, 0x6B, 0xBD, 0x0D, 0xF0, 0x87, 0x06, + 0xD8, 0xB4, 0x46, 0xAA, 0xA1, 0x75, 0x39, 0xCC, 0x80, 0x39, 0xD7, 0x93, + 0x71, 0x2A, 0x79, 0x02, 0xA8, 0x15, 0xF0, 0x42, 0xD6, 0x26, 0x64, 0x59, + 0x91, 0xE6, 0x35, 0xAF, 0xBA, 0xD5, 0xB6, 0x60, 0x0E, 0x94, 0xFA, 0x93, + 0xDB, 0x96, 0x1D, 0x84, 0x44, 0x1E, 0x41, 0xAA, 0x53, 0x2D, 0x79, 0x8C, + 0x21, 0xBF, 0x4F, 0x7B, 0x4B, 0xE4, 0xD1, 0x1F, 0xDF, 0x9B, 0xE5, 0xB8, + 0x20, 0x12, 0x56, 0x60, 0xDC, 0x65, 0xDF, 0x59, 0xA9, 0x59, 0xA2, 0x8D, + 0x57, 0xC9, 0x55, 0x46, 0xAE, 0x32, 0x17, 0xE3, 0x7A, 0xD8, 0x54, 0xE0, + 0x3E, 0x68, 0xEF, 0x8B, 0xD9, 0x3F, 0x1F, 0x75, 0x15, 0xE2, 0xA5, 0x32, + 0xBE, 0xC3, 0xB3, 0x59, 0x26, 0x98, 0x09, 0x48, 0x35, 0x01, 0xA9, 0x46, + 0xEB, 0x4C, 0x00, 0x69, 0xCB, 0x04, 0xB2, 0x46, 0x60, 0x77, 0x36, 0x04, + 0x90, 0x31, 0x51, 0x9D, 0x64, 0x83, 0xE3, 0xB0, 0x31, 0x24, 0x57, 0x5B, + 0xF3, 0xC9, 0xDE, 0xC0, 0x6B, 0x75, 0xB0, 0x74, 0xA5, 0x66, 0x09, 0xC6, + 0x33, 0xEC, 0x70, 0xE0, 0x0A, 0xA4, 0xDA, 0x3A, 0x44, 0xBF, 0xFF, 0x68, + 0x49, 0xE9, 0xA5, 0x34, 0x2C, 0x42, 0xAA, 0x99, 0x36, 0x06, 0xE3, 0x05, + 0x3B, 0xFE, 0x5F, 0x8B, 0xDC, 0x9E, 0x3F, 0x41, 0xBF, 0xCF, 0xCF, 0x30, + 0xE7, 0x53, 0x69, 0xAB, 0x3D, 0xBD, 0x50, 0x0F, 0xCB, 0xB4, 0x19, 0xAB, + 0xFB, 0x30, 0xF1, 0x37, 0xF3, 0x81, 0x93, 0xD2, 0x46, 0x13, 0xAA, 0x28, + 0x74, 0x32, 0x0E, 0x0F, 0x99, 0xF3, 0xA6, 0xE1, 0x48, 0xF5, 0x32, 0x52, + 0x3D, 0x59, 0x67, 0x34, 0xB2, 0x8B, 0x03, 0x98, 0xC2, 0x96, 0x67, 0x8D, + 0xC5, 0xB3, 0x34, 0x6E, 0x26, 0xF6, 0xD2, 0x66, 0x0D, 0xA3, 0x7D, 0xEF, + 0x7B, 0x4C, 0xB0, 0xEA, 0x2A, 0xCC, 0x39, 0xC8, 0xE8, 0x34, 0x2C, 0x59, + 0xAE, 0xF5, 0x22, 0x6B, 0x0E, 0x3D, 0x0F, 0x38, 0xB6, 0x0E, 0x7E, 0x16, + 0xB4, 0xDF, 0x1A, 0x7E, 0x8D, 0xB9, 0x7E, 0x8C, 0x86, 0x05, 0x48, 0xF5, + 0x5F, 0xA4, 0xFA, 0xAF, 0xDD, 0xD8, 0xF4, 0xB4, 0xCF, 0x2A, 0x7A, 0x49, + 0xF0, 0x58, 0x9B, 0x14, 0x3F, 0x8D, 0x24, 0x12, 0x45, 0x80, 0x1B, 0x12, + 0x31, 0xE4, 0x11, 0x25, 0xF1, 0xD9, 0x6C, 0x47, 0x1E, 0x05, 0x63, 0xB8, + 0x15, 0x26, 0xAB, 0x30, 0xF1, 0x0A, 0x02, 0x63, 0xC3, 0x36, 0xBE, 0xF1, + 0xBE, 0xF7, 0xE9, 0x3A, 0x1F, 0x88, 0xEF, 0xCD, 0xB1, 0xDA, 0x62, 0x06, + 0x77, 0x2F, 0x2B, 0x67, 0x39, 0x60, 0x53, 0x49, 0xF2, 0x08, 0xCC, 0x39, + 0xC6, 0x7A, 0xD6, 0xEC, 0x73, 0xBC, 0x15, 0x20, 0xE7, 0x86, 0x69, 0x6A, + 0x95, 0xEF, 0xA5, 0x85, 0xD9, 0x9D, 0x67, 0x9C, 0x0B, 0x6E, 0xB6, 0x11, + 0xC5, 0xE0, 0x7B, 0x37, 0x60, 0xBC, 0x96, 0x26, 0x59, 0x22, 0xE9, 0x62, + 0xC7, 0x2C, 0x8D, 0x71, 0x7B, 0x1E, 0x10, 0x32, 0xDB, 0xC1, 0x3D, 0x18, + 0x1B, 0xFF, 0x4A, 0xDB, 0xEF, 0xED, 0x30, 0x1E, 0x64, 0xAB, 0xED, 0xF8, + 0xF7, 0x6B, 0x6A, 0x2A, 0x8B, 0x30, 0x36, 0xB5, 0xD6, 0x8C, 0xF3, 0xA6, + 0xED, 0x73, 0x67, 0xFB, 0xDD, 0xAD, 0xC6, 0x1C, 0xF4, 0xFE, 0xA8, 0x52, + 0x33, 0xD6, 0x5A, 0x6F, 0xA5, 0x21, 0x96, 0x50, 0xC1, 0x9C, 0x0B, 0x9D, + 0x64, 0x85, 0xF0, 0x25, 0x18, 0xCF, 0xA9, 0xC0, 0x58, 0x5F, 0x88, 0xEB, + 0xAC, 0x16, 0x02, 0x70, 0x0A, 0x52, 0x0D, 0xB3, 0x63, 0x34, 0x1E, 0x73, + 0xAE, 0xF7, 0x1C, 0xC6, 0xD5, 0xB7, 0x06, 0x63, 0x9A, 0xD5, 0x98, 0x33, + 0xAF, 0x93, 0x9B, 0x9C, 0x9B, 0xB4, 0x84, 0xB1, 0x98, 0xB8, 0x95, 0x05, + 0xD6, 0xB2, 0xB4, 0x35, 0x8D, 0xC1, 0xA7, 0x29, 0xBB, 0x4E, 0xDE, 0x2D, + 0xF6, 0x78, 0x55, 0xB7, 0x59, 0x11, 0xB4, 0xB6, 0x39, 0x6B, 0xBB, 0x08, + 0x24, 0x12, 0x25, 0x73, 0xA8, 0x23, 0x8F, 0x02, 0x20, 0x1A, 0x17, 0xF4, + 0x32, 0xED, 0x7B, 0xCB, 0x85, 0x54, 0x7F, 0xB0, 0x79, 0x95, 0x10, 0x30, + 0x53, 0xFB, 0xDE, 0xE2, 0x16, 0x9A, 0xC8, 0x9C, 0x99, 0xCC, 0x13, 0x30, + 0x52, 0x87, 0x7B, 0xF6, 0x81, 0x19, 0x21, 0xA6, 0xD7, 0xFD, 0x08, 0xA6, + 0x35, 0x98, 0x20, 0x34, 0x80, 0x25, 0x7A, 0xDD, 0x7B, 0xEF, 0xC7, 0x78, + 0x4D, 0xD1, 0x09, 0xE6, 0xAD, 0x6A, 0x5C, 0x83, 0x6F, 0x02, 0x7D, 0xAC, + 0x06, 0xB0, 0x29, 0xB0, 0xA0, 0x06, 0x66, 0xC5, 0x40, 0xAF, 0x36, 0xF1, + 0x02, 0x08, 0x58, 0x9C, 0x6E, 0x6C, 0xE7, 0x0E, 0xAC, 0x1B, 0xF2, 0x46, + 0x42, 0xAC, 0x9E, 0xB7, 0x36, 0x41, 0x2E, 0x16, 0x52, 0x65, 0x47, 0x60, + 0xAF, 0xD2, 0x8D, 0xCF, 0x19, 0x0F, 0x8C, 0xB7, 0x91, 0xCD, 0xBD, 0x6D, + 0x5B, 0xB3, 0x73, 0x09, 0x7A, 0x61, 0xDA, 0x7F, 0x12, 0xA0, 0xB3, 0x60, + 0xC1, 0xCA, 0x75, 0xBF, 0x9B, 0x17, 0x80, 0x17, 0x6C, 0x24, 0xFD, 0xD6, + 0x18, 0xC7, 0x85, 0x65, 0x02, 0xBE, 0xC8, 0xC4, 0xD8, 0x04, 0xF8, 0x78, + 0xDE, 0x4B, 0xDB, 0xF1, 0xCA, 0x7E, 0xBF, 0x26, 0xCF, 0x79, 0x11, 0x38, + 0x50, 0x48, 0xD5, 0xCB, 0xBA, 0xC2, 0xD6, 0x09, 0x98, 0x91, 0xEF, 0x19, + 0x02, 0xF6, 0xB0, 0x1B, 0xDA, 0x55, 0x21, 0xE7, 0xF5, 0x57, 0x19, 0xCD, + 0x40, 0xE7, 0x20, 0x03, 0x21, 0xD5, 0x76, 0x76, 0x83, 0xB2, 0x52, 0xAF, + 0xBB, 0x7B, 0x4E, 0xE9, 0xC6, 0xF7, 0x58, 0x94, 0x6E, 0xEC, 0xFB, 0x97, + 0xC0, 0x20, 0xA4, 0xFA, 0x81, 0x15, 0xC2, 0x2B, 0x62, 0x30, 0x3D, 0xED, + 0x7B, 0xAB, 0x84, 0x54, 0x8F, 0xDB, 0x7E, 0x2E, 0xD7, 0x8D, 0xED, 0xBC, + 0x98, 0x69, 0x27, 0x06, 0x0B, 0xB3, 0x6D, 0x4E, 0xCB, 0xCF, 0x19, 0xAA, + 0x63, 0x52, 0x9D, 0xA4, 0x8D, 0xB9, 0x0A, 0xA0, 0x21, 0x6B, 0x5E, 0xDF, + 0xC7, 0x9C, 0x07, 0x76, 0xD3, 0x46, 0x86, 0x75, 0x02, 0xBE, 0xCD, 0xA4, + 0x3A, 0x69, 0x32, 0xE6, 0x6F, 0xA5, 0x73, 0xF5, 0xB5, 0xB1, 0xAD, 0x8F, + 0x80, 0xD3, 0xAA, 0xA5, 0xAA, 0xAA, 0x37, 0xA6, 0xB7, 0x5E, 0x76, 0xCC, + 0x67, 0x37, 0x89, 0x53, 0x2A, 0xF6, 0x37, 0xDD, 0xC6, 0x21, 0xD5, 0x36, + 0x11, 0x49, 0x24, 0x2A, 0x79, 0xCC, 0xC4, 0xA1, 0xB5, 0xE6, 0xF6, 0x38, + 0x2B, 0xDC, 0x5E, 0xD1, 0x10, 0xB7, 0x66, 0x9E, 0xF5, 0x81, 0x1B, 0xF1, + 0xBD, 0x2B, 0xDD, 0x00, 0x39, 0x38, 0x94, 0x7D, 0x53, 0xD8, 0x2E, 0x04, + 0xCD, 0xB6, 0x96, 0x44, 0xB6, 0x2D, 0xD1, 0x13, 0xBE, 0xB4, 0xE4, 0x31, + 0xC3, 0x2D, 0x99, 0x56, 0x5A, 0x98, 0x52, 0xAD, 0xAF, 0x61, 0x2A, 0x66, + 0x37, 0x55, 0x9F, 0xA5, 0x2D, 0xCF, 0x14, 0xB0, 0x4F, 0x00, 0x6D, 0xC5, + 0xC1, 0xC1, 0xA1, 0xC4, 0x88, 0xB5, 0x8B, 0xB7, 0x30, 0x9E, 0x50, 0xFD, + 0x09, 0x9E, 0x37, 0xC7, 0x91, 0x47, 0xE5, 0xA3, 0x1A, 0x93, 0x5E, 0x63, + 0x99, 0xFD, 0x77, 0x03, 0xC6, 0x2E, 0x3D, 0xD0, 0x91, 0x87, 0x83, 0x83, + 0xD3, 0x40, 0xDA, 0x82, 0x26, 0xE2, 0xC8, 0xA3, 0xCC, 0xB0, 0x69, 0xC1, + 0x7B, 0x09, 0x58, 0x14, 0xD4, 0x46, 0xEF, 0xE0, 0xE0, 0xE0, 0x08, 0x24, + 0x2A, 0x89, 0x6C, 0x67, 0x49, 0x64, 0x9B, 0x22, 0x90, 0xC7, 0x00, 0x57, + 0x24, 0xC7, 0xC1, 0xC1, 0xC1, 0xA1, 0xA3, 0x10, 0x48, 0x71, 0x48, 0xE4, + 0x2B, 0xAB, 0x79, 0x38, 0xF2, 0x70, 0x70, 0x70, 0x70, 0xE8, 0x50, 0x04, + 0x52, 0x18, 0x89, 0x38, 0xF2, 0x70, 0x70, 0x70, 0x70, 0xE8, 0xD0, 0x04, + 0x12, 0x8D, 0x44, 0x1C, 0x79, 0x38, 0x38, 0x38, 0x38, 0x38, 0x02, 0x59, + 0x43, 0x22, 0xDB, 0x5B, 0x12, 0x69, 0x29, 0xDD, 0xC3, 0xD7, 0x96, 0x3C, + 0xA6, 0xB9, 0x65, 0xE1, 0xE0, 0xE0, 0xE0, 0xE0, 0x08, 0x24, 0x28, 0x89, + 0x38, 0xF2, 0x70, 0x70, 0x70, 0x70, 0x70, 0x04, 0x12, 0x9A, 0x44, 0x1C, + 0x79, 0x38, 0x38, 0x38, 0x38, 0x44, 0x40, 0xAC, 0xC3, 0xBC, 0xA9, 0x49, + 0x41, 0x32, 0x00, 0xE3, 0x9E, 0x9B, 0x4D, 0x1E, 0x03, 0x1C, 0x79, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x0D, 0x24, 0x88, 0x26, 0xB2, 0x83, 0xD5, 0x44, + 0x62, 0x96, 0x3C, 0x3E, 0x77, 0xCB, 0xC0, 0xC1, 0xC1, 0xC1, 0xC1, 0x21, + 0x28, 0x89, 0xEC, 0x88, 0x54, 0x3B, 0xBB, 0x81, 0x70, 0x70, 0x70, 0x70, + 0x88, 0x8E, 0xFF, 0x07, 0x02, 0x8E, 0xE9, 0xCF, 0xD4, 0x3B, 0x89, 0x6F, + 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 +}; + +const unsigned int logo_web_png_len = 11136; diff --git a/SW/esplan_v2_renke_weather_station/logo_web.h b/SW/esplan_v2_renke_weather_station/logo_web.h new file mode 100755 index 0000000..891f5cf --- /dev/null +++ b/SW/esplan_v2_renke_weather_station/logo_web.h @@ -0,0 +1,7 @@ +#pragma once +#include +#include +#include + +extern const uint8_t logo_web_png[]; +extern const size_t logo_web_png_len; diff --git a/SW/test_ESPLAN_V2/test_ESPLAN_V2.ino b/SW/test_ESPLAN_V2/test_ESPLAN_V2.ino index b3abf0c..f5497f9 100755 --- a/SW/test_ESPLAN_V2/test_ESPLAN_V2.ino +++ b/SW/test_ESPLAN_V2/test_ESPLAN_V2.ino @@ -1,27 +1,21 @@ -/* Test for LaskaKit ESPlan V2 ESP32 LAN8720A RS485 PoE - * ESP32 + LAN8720 + WebServer + SD + RS485 + SHT40 + BMP280 + NeoPixel - * - * Funkce: - * - web rozhrani pres Ethernet - * - stav ETH, SD, SHT40, BMP280, RS485 - * - ovladani NeoPixel LED - * - odesilani textu pres RS485 - * - prijem dat z RS485 - * - prochazeni adresaru na SD - * - upload / stahnout / smazat souboru na SD - * - vytvareni a mazani adresaru na SD - * - automaticka reakce na vyndani / vlozeni SD karty - * - po vlozeni se SD karta ihned znovu nacte - * - SD panel se na webu obnovi jen pri zmene stavu SD karty - * - periodicky debug do seriove konzole - * - volba jazyka CZ / ENG +/* + ESP32 + LAN8720 + WebServer + SD + RS485 + SHT40 + BMP280 + NeoPixel - * Board: kaKit ESPlan V2 https://www.laskakit.cz/laskakit-esplan-esp32-lan8720a-max485-poe/ - * - * - * Email:podpora@laskakit.cz - * Web:laskakit.cz - */ + Funkce: + - web rozhrani pres Ethernet + - stav ETH, SD, SHT40, BMP280, RS485 + - ovladani NeoPixel LED + - odesilani textu pres RS485 + - prijem dat z RS485 + - prochazeni adresaru na SD + - upload / stahnout / smazat souboru na SD + - vytvareni a mazani adresaru na SD + - automaticka reakce na vyndani / vlozeni SD karty + - po vlozeni se SD karta ihned znovu nacte + - SD panel se na webu obnovi jen pri zmene stavu SD karty + - periodicky debug do seriove konzole + - volba jazyka CZ / ENG +*/ #include #include @@ -2129,11 +2123,6 @@ void setup() { addLogEntry(LOG_HEAP_FREE, String(ESP.getFreeHeap()), String(getFreeHeapPercent(), 1)); initI2CDevices(); - - lastSdDetectRaw = isSdCardPresent(); - lastSdDetectStable = lastSdDetectRaw; - lastSdDetectChangeMs = millis(); - initSDCard(); initRS485(); initEthernet();