main
Konstantin Láska 2026-06-08 09:20:35 +02:00
parent 71a2c4fe37
commit b350b06595
11 changed files with 3091 additions and 28 deletions

View File

@ -0,0 +1,70 @@
#include <Arduino.h>
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(){
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <Arduino.h>
#include <ETH.h>
#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
};

View File

@ -0,0 +1,262 @@
#pragma once
#include <Arduino.h>
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;
}

View File

@ -0,0 +1,531 @@
#include "WeatherStation.h"
#include <string.h>
#include <stdlib.h>
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<uint8_t>(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<uint16_t>(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<int16_t>(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<uint8_t>(_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<uint16_t>(frame[frameLen - 2]) | (static_cast<uint16_t>(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<uint8_t>(crc & 0xFF);
frame[frameLen++] = static_cast<uint8_t>((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<uint8_t>((startReg >> 8) & 0xFF);
req[3] = static_cast<uint8_t>(startReg & 0xFF);
req[4] = static_cast<uint8_t>((count >> 8) & 0xFF);
req[5] = static_cast<uint8_t>(count & 0xFF);
const uint16_t crc = crc16(req, 6);
req[6] = static_cast<uint8_t>(crc & 0xFF);
req[7] = static_cast<uint8_t>((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<uint16_t>(resp[respLen - 2]) | (static_cast<uint16_t>(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<uint8_t>(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<size_t>(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<uint16_t>(resp[3 + i * 2]) << 8) | static_cast<uint16_t>(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<uint8_t>((reg >> 8) & 0xFF);
req[3] = static_cast<uint8_t>(reg & 0xFF);
req[4] = static_cast<uint8_t>((value >> 8) & 0xFF);
req[5] = static_cast<uint8_t>(value & 0xFF);
const uint16_t crc = crc16(req, 6);
req[6] = static_cast<uint8_t>(crc & 0xFF);
req[7] = static_cast<uint8_t>((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<uint16_t>(resp[respLen - 2]) | (static_cast<uint16_t>(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<uint8_t>(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<int>(_values.raw[1]);
_values.windDir8 = static_cast<int>(_values.raw[2]);
_values.windDirDeg = static_cast<int>(_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<int>(_values.raw[7]);
_values.air2 = static_cast<int>(_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<int>(_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<uint8_t>(crc & 0xFF);
req[reqLen++] = static_cast<uint8_t>((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");
}

View File

@ -0,0 +1,112 @@
#pragma once
#include <Arduino.h>
#include <HardwareSerial.h>
#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);
};

View File

@ -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("&amp;"); break;
case '<': out += F("&lt;"); break;
case '>': out += F("&gt;"); break;
case '"': out += F("&quot;"); break;
case '\'': out += F("&#39;"); 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<double>(v), static_cast<unsigned int>(decimals));
}
static String jsonFloat(float v, uint8_t decimals = 1) {
if (isnan(v) || !isfinite(v)) return F("null");
return String(static_cast<double>(v), static_cast<unsigned int>(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("<span class='");
s += present ? F("ok'>") : F("bad'>");
s += present ? t.present : t.absent;
s += F("</span>");
return s;
}
String buildStyle(const Texts&, UiLang) {
return F(
"<style>"
"body{font-family:Arial,sans-serif;background:#f4f7fb;color:#222;margin:0;padding:20px;}"
".wrap{max-width:1800px;margin:0 auto;}"
".grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;align-items:stretch;}"
".card{background:#fff;border-radius:14px;padding:16px;box-shadow:0 2px 12px rgba(0,0,0,.08);min-width:0;height:100%;box-sizing:border-box;display:flex;flex-direction:column;}"
".card-wide{grid-column:1 / -1;}"
".top{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:20px;}"
".title{display:flex;align-items:center;gap:18px;flex-wrap:wrap}"
".brand-logo{display:block;max-width:180px;width:100%;height:auto}"
".nav{display:flex;gap:10px;flex-wrap:wrap;margin:10px 0 18px 0}"
".nav a{display:inline-block;padding:10px 14px;border-radius:10px;background:#e8eef8;color:#1f6feb;text-decoration:none;font-weight:bold}"
".nav a.active{background:#1f6feb;color:#fff}"
"table{width:100%;border-collapse:collapse;table-layout:fixed}"
"th,td{padding:8px;border-bottom:1px solid #ddd;text-align:left;font-size:14px;vertical-align:top;word-break:break-word}"
".ok{color:#0a7a2f;font-weight:bold}.bad{color:#b00020;font-weight:bold}"
".sub{color:#637186;font-size:13px}"
".mono{font-family:monospace;white-space:pre-wrap;background:#eef2f7;padding:10px;border-radius:10px;overflow:auto}"
".mono-frames{max-height:140px;font-size:12px;line-height:1.3}"
".mono-raw{max-height:420px;font-size:12px;line-height:1.3}"
".cfg-table td:first-child{width:22%;font-weight:bold}"
".cfg-table td:nth-child(2){width:28%}"
".cfg-table td:nth-child(3){width:50%}"
".cfg-inline-form{margin:0}"
".cfg-inline-form input,.cfg-inline-form select,.cfg-inline-form textarea{margin:0}"
"label{display:block;margin:8px 0 4px;font-weight:bold}"
"input[type=text],input[type=number],select,textarea{width:100%;padding:10px;border:1px solid #ccc;border-radius:8px;box-sizing:border-box}"
"textarea{resize:vertical;min-height:88px;font-family:monospace}"
"button{padding:10px 14px;border:0;border-radius:10px;background:#1f6feb;color:#fff;cursor:pointer;margin-top:8px}"
"button:hover{opacity:.92}"
".row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}"
".msg{padding:10px 12px;border-radius:10px;margin-bottom:12px}"
".note{font-size:13px;color:#5e6a7a}"
"@media (max-width:1400px){.grid{grid-template-columns:repeat(2,1fr);}}"
"@media (max-width:800px){.grid{grid-template-columns:1fr}.row2{grid-template-columns:1fr}.brand-logo{max-width:240px;}}"
"</style>");
}
static void appendTop(String& html, UiLang lang, const Texts& t, const char* active) {
html += F("<div class='top'><div class='title'><img src='/logo.png' alt='logo' class='brand-logo'><div><h1>");
html += t.appTitle;
html += F("</h1></div></div>");
html += F("<form method='GET' action='");
html += (strcmp(active, "main") == 0) ? F("/") : F("/config");
html += F("' style='display:flex;gap:8px;align-items:center;margin:0;'>");
html += F("<label style='margin:0;font-weight:bold;'>");
html += t.language;
html += F("</label><select name='lang' onchange='this.form.submit()'>");
html += F("<option value='cz'");
if (lang == UiLang::CZ) html += F(" selected");
html += F(">CZ</option><option value='en'");
if (lang == UiLang::EN) html += F(" selected");
html += F(">ENG</option></select></form></div>");
html += F("<div class='nav'>");
html += F("<a href='/?lang="); html += langCode(lang); html += F("' class='");
html += (strcmp(active, "main") == 0) ? F("active") : F("");
html += F("'>"); html += t.tabMain; html += F("</a>");
html += F("<a href='/config?lang="); html += langCode(lang); html += F("' class='");
html += (strcmp(active, "config") == 0) ? F("active") : F("");
html += F("'>"); html += t.tabConfig; html += F("</a>");
html += F("</div>");
}
static void appendRow(String& html, const String& id, const String& label, const String& value) {
html += F("<tr><td>");
html += label;
html += F("</td><td id='");
html += id;
html += F("'>");
html += value;
html += F("</td></tr>");
}
static String buildConfigHistoryBox(const Texts& t, const WeatherStation& ws) {
String html;
html.reserve(1800);
html += F("<div class='card card-wide' style='margin-bottom:16px;'><h2>");
html += t.modbusLog;
html += F("</h2>");
if (ws.configLogCount() == 0) {
html += F("<div class='note'>");
html += t.noResult;
html += F("</div></div>");
return html;
}
const CommLog& c = ws.configLogAt(0);
html += F("<table>");
appendRow(html, F("cfg-log-state"), t.modbusLastStatus, c.lastOk ? String(F("<span class='ok'>")) + t.statusOkShort + F("</span>") : String(F("<span class='bad'>")) + t.statusErrorShort + F("</span>"));
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("</table>");
html += F("<div class='sub' style='margin-top:10px'>");
html += t.modbusTx;
html += F("</div><div class='mono'>");
html += htmlEscape(c.lastRequestHex);
html += F("</div>");
html += F("<div class='sub' style='margin-top:10px'>");
html += t.modbusRx;
html += F("</div><div class='mono'>");
html += htmlEscape(c.lastResponseHex);
html += F("</div>");
html += F("</div>");
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("<div class='grid'>");
html += F("<div class='card'><h2>");
html += t.sensorStatus;
html += F("</h2><table>");
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("</table></div>");
html += F("<div class='card'><h2>");
html += t.values;
html += F("</h2><table>");
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("</table></div>");
html += F("<div class='card'><h2>");
html += t.modbusLog;
html += F("</h2><table>");
appendRow(html, F("mb-state"), t.modbusLastStatus, c.lastOk ? String(F("<span class='ok'>")) + t.statusOkShort + F("</span>") : String(F("<span class='bad'>")) + t.statusErrorShort + F("</span>"));
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("</table></div>");
html += F("<div class='card'><h2>");
html += t.modbusFrames;
html += F("</h2>");
html += F("<div class='sub'>");
html += t.modbusTx;
html += F("</div><div class='mono mono-frames' id='mb-tx'>");
html += htmlEscape(c.lastRequestHex);
html += F("</div>");
html += F("<div class='sub' style='margin-top:10px'>");
html += t.modbusRx;
html += F("</div><div class='mono mono-frames' id='mb-rx'>");
html += htmlEscape(c.lastResponseHex);
html += F("</div>");
html += F("<div class='sub' style='margin-top:12px'>");
html += t.rawRegisters;
html += F("</div><div class='mono mono-raw' id='raw-regs'>");
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("</div></div>");
html += F("</div>");
}
static String buildMainScript(UiLang lang, const Texts& t) {
String s;
s.reserve(9000);
s += F("<script>");
s += F("const L={present:\""); s += t.present;
s += F("\",absent:\""); s += t.absent;
s += F("\",seconds:\""); s += t.seconds;
s += F("\",ok:\""); s += t.statusOkShort;
s += F("\",err:\""); s += t.statusErrorShort;
s += F("\"};");
s += F("function badge(v){return \"<span class='\"+(v?\"ok\":\"bad\")+\"'>\"+(v?L.present:L.absent)+\"</span>\";}");
s += F("function setHtml(id,v){const e=document.getElementById(id);if(e)e.innerHTML=v;}");
s += F("function setText(id,v){const e=document.getElementById(id);if(e)e.textContent=v;}");
s += F("function num(v,d){if(v===null||v===undefined||v==='-'||Number.isNaN(Number(v)))return '-';return Number(v).toFixed(d);}");
s += F("async function tick(){");
s += F("try{");
s += F("const url='/data.json?lang="); s += langCode(lang); s += F("&_=' + Date.now();");
s += F("const r=await fetch(url,{cache:'no-store'});");
s += F("if(!r.ok)return;");
s += F("const d=await r.json();");
s += F("setHtml('st-wind',badge(d.pwind));");
s += F("setHtml('st-th',badge(d.pth));");
s += F("setHtml('st-noise',badge(d.pnoise));");
s += F("setHtml('st-air',badge(d.pair));");
s += F("setHtml('st-pressure',badge(d.ppress));");
s += F("setHtml('st-light',badge(d.plight));");
s += F("setHtml('st-rain',badge(d.prain));");
s += F("setHtml('st-compass',badge(d.pcomp));");
s += F("setHtml('st-solar',badge(d.psolar));");
s += F("setText('v-wind-short',num(d.wind,1));");
s += F("setText('v-beaufort-short',(d.beaufort<0)?'-':String(d.beaufort));");
s += F("setText('v-dir8-short',(d.dir8<0)?'-':String(d.dir8));");
s += F("setText('v-dirdeg-short',(d.dirdeg<0)?'-':String(d.dirdeg));");
s += F("setText('v-hum-short',num(d.hum,1));");
s += F("setText('v-temp-short',num(d.temp,1));");
s += F("setText('v-noise-short',num(d.noise,1));");
s += F("setText('v-air1-short',(d.air1<0?'-':String(d.air1)));");
s += F("setText('v-air2-short',(d.air2<0?'-':String(d.air2)));");
s += F("setText('v-pressure-short',num(d.press,1));");
s += F("setText('v-luxh-short',String(d.lightHigh));");
s += F("setText('v-luxl-short',String(d.lightLow));");
s += F("setText('v-lux-short',String(d.light));");
s += F("setText('v-rain-short',num(d.rain,1));");
s += F("setText('v-compass-short',num(d.compass,2));");
s += F("setText('v-solar-short',(d.solar<0)?'-':String(d.solar));");
s += F("setText('data-age',String(d.age)+' '+L.seconds);");
s += F("setHtml('mb-state',d.mbOk?\"<span class='ok'>\"+L.ok+\"</span>\":\"<span class='bad'>\"+L.err+\"</span>\");");
s += F("setText('mb-action',d.mbAction||'-');");
s += F("setText('mb-dur',String(d.mbDuration)+' ms');");
s += F("setText('mb-stats',String(d.mbOkCount)+' / '+String(d.mbErrCount));");
s += F("setText('mb-err',d.mbError||'-');");
s += F("setText('mb-tx',d.mbTx||'');");
s += F("setText('mb-rx',d.mbRx||'');");
s += F("if(Array.isArray(d.raw)){setText('raw-regs',d.raw.map((v,i)=>'R'+(500+i)+' = '+v).join('\\n'));}");
s += F("}catch(e){}");
s += F("}");
s += F("window.addEventListener('load',function(){tick();setInterval(tick,2000);});");
s += F("</script>");
return s;
}
static String buildManualCommandScript() {
String s;
s.reserve(2400);
s += F("<script>");
s += F("function wsCrc16(bytes){let crc=0xFFFF;for(let i=0;i<bytes.length;i++){crc^=bytes[i];for(let j=0;j<8;j++){if(crc&1){crc=(crc>>1)^0xA001;}else{crc>>=1;}}}return crc&0xFFFF;}");
s += F("function wsHexByte(v){return Number(v).toString(16).toUpperCase().padStart(2,'0');}");
s += F("function wsHexWordFromInput(id,defv){let s=(document.getElementById(id).value||defv).trim();s=s.replace(/^0x/i,'');let v=parseInt(s,16);if(Number.isNaN(v))v=parseInt(defv,16);if(Number.isNaN(v))v=0;return Math.max(0,Math.min(65535,v));}");
s += F("function wsUpdateManualPreview(){");
s += F("const a=Math.max(1,Math.min(247,parseInt(document.getElementById('manual-addr').value||'1',10)));");
s += F("const r=wsHexWordFromInput('manual-reg','6000');");
s += F("const v=wsHexWordFromInput('manual-val','0000');");
s += F("const frame=[a,0x06,(r>>8)&0xFF,r&0xFF,(v>>8)&0xFF,v&0xFF];");
s += F("const crc=wsCrc16(frame);frame.push(crc&0xFF);frame.push((crc>>8)&0xFF);");
s += F("const el=document.getElementById('manual-preview');if(el){el.textContent=frame.map(wsHexByte).join(' ');}}");
s += F("window.addEventListener('load',function(){['manual-addr','manual-reg','manual-val'].forEach(function(id){const e=document.getElementById(id);if(e){e.addEventListener('input',wsUpdateManualPreview);e.addEventListener('change',wsUpdateManualPreview);}});wsUpdateManualPreview();});");
s += F("</script>");
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("<!DOCTYPE html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>");
html += t.appTitle;
html += F("</title>");
html += buildStyle(t, lang);
html += F("</head><body><div class='wrap'>");
appendTop(html, lang, t, "main");
appendMainCards(html, t, ws, ethUp, ip, mac);
html += buildMainScript(lang, t);
html += F("</div></body></html>");
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("<!DOCTYPE html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>");
html += t.tabConfig;
html += F("</title>");
html += buildStyle(t, lang);
html += F("</head><body><div class='wrap'>");
appendTop(html, lang, t, "config");
html += F("<div class='grid'>");
html += buildConfigHistoryBox(t, ws);
html += F("<div class='card card-wide'><h2>");
html += t.manualCommand;
html += F("</h2><div class='note'>");
html += t.manualNote;
html += F("</div>");
html += F("<form method='POST' action='/cmd-manual?lang=");
html += langCode(lang);
html += F("'>");
html += F("<div class='row2'>");
html += F("<div><label>");
html += t.manualAddress;
html += F("</label><input type='number' id='manual-addr' name='address' min='1' max='247' value='");
html += String(ws.config().address);
html += F("'></div>");
html += F("<div><label>");
html += t.manualRegisterHex;
html += F("</label><input type='text' id='manual-reg' name='reg' value='6000' placeholder='6000'></div>");
html += F("<div><label>");
html += t.manualValueHex;
html += F("</label><input type='text' id='manual-val' name='value' value='0000' placeholder='0000'></div>");
html += F("<div><label>");
html += t.manualPreview;
html += F("</label><div class='mono' id='manual-preview'>-</div></div>");
html += F("</div><button type='submit'>");
html += t.manualSend;
html += F("</button></form>");
html += F("<hr style='margin:16px 0;border:none;border-top:1px solid #ddd'>");
html += F("<h2>");
html += t.rawHexTitle;
html += F("</h2><div class='note'>");
html += t.rawHexNote;
html += F("</div>");
html += F("<form method='POST' action='/cmd-rawhex?lang=");
html += langCode(lang);
html += F("'>");
html += F("<label>");
html += t.rawHexInput;
html += F("</label><textarea name='rawhex' placeholder='010660000001560A'></textarea>");
html += F("<button type='submit'>");
html += t.rawHexSend;
html += F("</button></form></div>");
html += F("<div class='card card-wide'><h2>");
html += t.configTitle;
html += F("</h2>");
html += F("<table class='cfg-table'>");
html += F("<tr><th>");
html += t.tableItem;
html += F("</th><th>");
html += t.tableSetting;
html += F("</th><th>");
html += t.tableNote;
html += F("</th></tr>");
html += F("<tr><td>");
html += t.address;
html += F("</td><td>");
html += F("<form class='cfg-inline-form' method='POST' action='/config-save?lang=");
html += langCode(lang);
html += F("'>");
html += F("<input type='hidden' name='baudrate' value='");
html += String(ws.config().baudrate);
html += F("'>");
html += F("<input type='hidden' name='windOffset' value='");
html += hex16(ws.config().windOffset);
html += F("'>");
html += F("<input type='hidden' name='rainSensitivity' value='");
html += hex16(ws.config().rainSensitivity);
html += F("'>");
html += F("<input type='number' name='address' min='1' max='247' value='");
html += String(ws.config().address);
html += F("'> <button type='submit'>");
html += t.save;
html += F("</button></form></td><td>");
html += t.noteAddress;
html += F("</td></tr>");
html += F("<tr><td>");
html += t.baudrate;
html += F("</td><td>");
html += F("<form class='cfg-inline-form' method='POST' action='/config-save?lang=");
html += langCode(lang);
html += F("'>");
html += F("<input type='hidden' name='address' value='");
html += String(ws.config().address);
html += F("'>");
html += F("<input type='hidden' name='windOffset' value='");
html += hex16(ws.config().windOffset);
html += F("'>");
html += F("<input type='hidden' name='rainSensitivity' value='");
html += hex16(ws.config().rainSensitivity);
html += F("'>");
html += F("<select name='baudrate'>");
const uint32_t baudrates[] = {1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200};
for (uint8_t i = 0; i < sizeof(baudrates) / sizeof(baudrates[0]); i++) {
html += F("<option value='");
html += String(baudrates[i]);
html += F("'");
if (baudrates[i] == ws.config().baudrate) html += F(" selected");
html += F(">");
html += String(baudrates[i]);
html += F("</option>");
}
html += F("</select> <button type='submit'>");
html += t.save;
html += F("</button></form></td><td>");
html += t.noteBaudrate;
html += F("</td></tr>");
html += F("<tr><td>");
html += t.windOffset;
html += F("</td><td>");
html += F("<form class='cfg-inline-form' method='POST' action='/config-save?lang=");
html += langCode(lang);
html += F("'>");
html += F("<input type='hidden' name='address' value='");
html += String(ws.config().address);
html += F("'>");
html += F("<input type='hidden' name='baudrate' value='");
html += String(ws.config().baudrate);
html += F("'>");
html += F("<input type='hidden' name='rainSensitivity' value='");
html += hex16(ws.config().rainSensitivity);
html += F("'>");
html += F("<input type='text' name='windOffset' value='");
html += hex16(ws.config().windOffset);
html += F("' placeholder='0000'> <button type='submit'>");
html += t.save;
html += F("</button></form></td><td>");
html += t.noteWindOffset;
html += F("</td></tr>");
html += F("<tr><td>");
html += t.rainSensitivity;
html += F("</td><td>");
html += F("<form class='cfg-inline-form' method='POST' action='/config-save?lang=");
html += langCode(lang);
html += F("'>");
html += F("<input type='hidden' name='address' value='");
html += String(ws.config().address);
html += F("'>");
html += F("<input type='hidden' name='baudrate' value='");
html += String(ws.config().baudrate);
html += F("'>");
html += F("<input type='hidden' name='windOffset' value='");
html += hex16(ws.config().windOffset);
html += F("'>");
html += F("<input type='text' name='rainSensitivity' value='");
html += hex16(ws.config().rainSensitivity);
html += F("' placeholder='0011'> <button type='submit'>");
html += t.save;
html += F("</button></form></td><td>");
html += t.noteRainSensitivity;
html += F("</td></tr>");
html += F("<tr><td>");
html += t.commandWindZero;
html += F("</td><td><form class='cfg-inline-form' method='POST' action='/cmd-wind-zero?lang=");
html += langCode(lang);
html += F("'><button type='submit'>");
html += t.execute;
html += F("</button></form></td><td>");
html += t.noteWindZero;
html += F("</td></tr>");
html += F("<tr><td>");
html += t.commandRainZero;
html += F("</td><td><form class='cfg-inline-form' method='POST' action='/cmd-rain-zero?lang=");
html += langCode(lang);
html += F("'><button type='submit'>");
html += t.execute;
html += F("</button></form></td><td>");
html += t.noteRainZero;
html += F("</td></tr>");
html += F("</table></div>");
html += F("</div>");
html += buildManualCommandScript();
html += F("</div></body></html>");
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;
}

View File

@ -0,0 +1,13 @@
#pragma once
#include <Arduino.h>
#include <WebServer.h>
#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);

View File

@ -0,0 +1,455 @@
#include <Arduino.h>
#include <WiFi.h>
#include <ETH.h>
#include <Preferences.h>
#include <WebServer.h>
#include <stdlib.h>
#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<AirQualityMode>(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<uint8_t>(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("<div><strong>");
msg += htmlEscape(title);
msg += F(":</strong> ");
msg += ok ? String(F("<span class='ok'>")) + t.statusOkShort + F("</span>") : String(F("<span class='bad'>")) + t.statusErrorShort + F("</span>");
if (detail.length()) {
msg += F("<br>");
msg += htmlEscape(detail);
}
msg += F("</div>");
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("<div><strong>");
msg += htmlEscape(title);
msg += F(":</strong> ");
msg += c.lastOk ? String(F("<span class='ok'>")) + t.statusOkShort + F("</span>") : String(F("<span class='bad'>")) + t.statusErrorShort + F("</span>");
msg += F("<br>");
msg += htmlEscape(t.modbusAction);
msg += F(": ");
msg += htmlEscape(c.lastAction.length() ? c.lastAction : String("-"));
msg += F("<br>");
msg += htmlEscape(t.modbusDuration);
msg += F(": ");
msg += String(c.lastDurationMs);
msg += F(" ms");
msg += F("<br>");
msg += htmlEscape(t.modbusError);
msg += F(": ");
msg += (c.lastError.length() ? htmlEscape(c.lastError) : F("-"));
msg += F("</div>");
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<uint16_t>(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<uint32_t>(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<uint8_t>(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<PGM_P>(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<uint8_t>(addr)) {
station.config().address = static_cast<uint8_t>(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("<div style='margin-top:8px'>");
msg += htmlEscape(firstValidationError);
msg += F("</div>");
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<uint8_t>(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);
}
}
}

View File

@ -0,0 +1,936 @@
#include "logo_web.h"
#include <pgmspace.h>
#include <stddef.h>
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;

View File

@ -0,0 +1,7 @@
#pragma once
#include <Arduino.h>
#include <pgmspace.h>
#include <stddef.h>
extern const uint8_t logo_web_png[];
extern const size_t logo_web_png_len;

View File

@ -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 <Arduino.h>
#include <WiFi.h>
@ -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();