From aece7ca1b09a804e370c5dbc0dbc4dd62b208911 Mon Sep 17 00:00:00 2001 From: Manuel Weiser Date: Wed, 6 Jun 2018 15:28:17 +0200 Subject: [PATCH] Initial --- .gitignore | 4 + .pioenvs Alias | Bin 0 -> 980 bytes .travis.yml | 67 + Firmware | 1 + astyle.conf | 18 + build.sh | 72 + debug.sh | 75 + esp8266.flash.1m0.ld | 18 + espurna/alexa.ino | 95 + espurna/api.ino | 197 + espurna/broker.ino | 32 + espurna/button.ino | 258 + espurna/config/all.h | 36 + espurna/config/arduino.h | 133 + espurna/config/build.h | 5 + espurna/config/defaults.h | 423 ++ espurna/config/general.h | 1041 ++++ espurna/config/hardware.h | 2023 ++++++++ espurna/config/prototypes.h | 118 + espurna/config/sensors.h | 796 ++++ espurna/config/version.h | 6 + espurna/data/index.html.gz | Bin 0 -> 65168 bytes espurna/data/server.cer | Bin 0 -> 587 bytes espurna/data/server.key | Bin 0 -> 611 bytes espurna/debug.ino | 268 ++ espurna/domoticz.ino | 161 + espurna/espurna.ino | 177 + espurna/filters/BaseFilter.h | 25 + espurna/filters/MaxFilter.h | 40 + espurna/filters/MedianFilter.h | 92 + espurna/filters/MovingAverageFilter.h | 51 + espurna/gpio.ino | 39 + espurna/homeassistant.ino | 297 ++ espurna/i2c.ino | 379 ++ espurna/influxdb.ino | 111 + espurna/ir.ino | 112 + espurna/led.ino | 290 ++ espurna/libs/EmbedisWrap.h | 24 + espurna/libs/StreamInjector.h | 85 + espurna/libs/WebSocketIncommingBuffer.h | 87 + espurna/libs/pwm.h | 33 + espurna/light.ino | 1054 +++++ espurna/llmnr.ino | 18 + espurna/mdns.ino | 132 + espurna/migrate.ino | 894 ++++ espurna/mqtt.ino | 843 ++++ espurna/netbios.ino | 20 + espurna/nofuss.ino | 180 + espurna/ntp.ino | 179 + espurna/ota.ino | 249 + espurna/pwm.c | 447 ++ espurna/relay.ino | 886 ++++ espurna/rf.ino | 192 + espurna/rfbridge.ino | 534 +++ espurna/scheduler.ino | 217 + espurna/sensor.ino | 1065 +++++ espurna/sensors/AM2320Sensor.h | 193 + espurna/sensors/AnalogSensor.h | 66 + espurna/sensors/BH1750Sensor.h | 135 + espurna/sensors/BMX280Sensor.h | 438 ++ espurna/sensors/BaseSensor.h | 101 + espurna/sensors/CSE7766Sensor.h | 372 ++ espurna/sensors/DHTSensor.h | 245 + espurna/sensors/DallasSensor.h | 319 ++ espurna/sensors/DigitalSensor.h | 106 + espurna/sensors/ECH1560Sensor.h | 349 ++ espurna/sensors/EmonADC121Sensor.h | 150 + espurna/sensors/EmonADS1X15Sensor.h | 347 ++ espurna/sensors/EmonAnalogSensor.h | 124 + espurna/sensors/EmonSensor.h | 242 + espurna/sensors/EventSensor.h | 211 + espurna/sensors/GUVAS12SDSensor.h | 170 + espurna/sensors/HLW8012Sensor.h | 328 ++ espurna/sensors/I2CSensor.h | 95 + espurna/sensors/MHZ19Sensor.h | 221 + espurna/sensors/PMSX003Sensor.h | 152 + espurna/sensors/PZEM004TSensor.h | 136 + espurna/sensors/SHT3XI2CSensor.h | 89 + espurna/sensors/SI7021Sensor.h | 168 + espurna/sensors/V9261FSensor.h | 259 + espurna/settings.ino | 479 ++ espurna/ssdp.ino | 83 + espurna/static/index.html.gz.h | 3262 +++++++++++++ espurna/static/server.cer.h | 33 + espurna/static/server.key.h | 34 + espurna/system.ino | 168 + espurna/telnet.ino | 187 + espurna/thinkspeak.ino | 281 ++ espurna/uartmqtt.ino | 103 + espurna/utils.ino | 527 +++ espurna/web.ino | 341 ++ espurna/wifi.ino | 454 ++ espurna/ws.ino | 448 ++ extra_scripts.py | 68 + gulpfile.js | 134 + html/custom.css | 320 ++ html/custom.js | 1363 ++++++ html/favicon.ico | Bin 0 -> 1150 bytes html/images/loading.gif | Bin 0 -> 433 bytes html/index.html | 1425 ++++++ html/vendor/checkboxes.css | 120 + html/vendor/checkboxes.js | 366 ++ html/vendor/images/border-off.png | Bin 0 -> 308 bytes html/vendor/images/border-on.png | Bin 0 -> 302 bytes html/vendor/images/handle-center.png | Bin 0 -> 190 bytes html/vendor/images/handle-left.png | Bin 0 -> 264 bytes html/vendor/images/handle-right.png | Bin 0 -> 264 bytes html/vendor/images/label-off.png | Bin 0 -> 211 bytes html/vendor/images/label-on.png | Bin 0 -> 214 bytes html/vendor/jquery-3.2.1.min.js | 4 + html/vendor/jquery.wheelcolorpicker-3.0.3.css | 160 + .../jquery.wheelcolorpicker-3.0.3.min.js | 13 + html/vendor/pure-1.0.0.min.css | 11 + .../pure-grids-responsive-1.0.0.min.css | 7 + html/vendor/side-menu.css | 248 + lib/readme.txt | 36 + memanalyzer.py | 256 + ota.py | 295 ++ package-lock.json | 4199 +++++++++++++++++ package.json | 22 + platformio Kopie.ini | 1947 ++++++++ platformio.ini | 1948 ++++++++ platformio.ini_short | 66 + requirements.txt | 6 + 124 files changed, 39029 insertions(+) create mode 100644 .gitignore create mode 100644 .pioenvs Alias create mode 100644 .travis.yml create mode 120000 Firmware create mode 100755 astyle.conf create mode 100755 build.sh create mode 100755 debug.sh create mode 100755 esp8266.flash.1m0.ld create mode 100755 espurna/alexa.ino create mode 100755 espurna/api.ino create mode 100755 espurna/broker.ino create mode 100755 espurna/button.ino create mode 100755 espurna/config/all.h create mode 100755 espurna/config/arduino.h create mode 100755 espurna/config/build.h create mode 100755 espurna/config/defaults.h create mode 100755 espurna/config/general.h create mode 100755 espurna/config/hardware.h create mode 100755 espurna/config/prototypes.h create mode 100755 espurna/config/sensors.h create mode 100755 espurna/config/version.h create mode 100755 espurna/data/index.html.gz create mode 100755 espurna/data/server.cer create mode 100755 espurna/data/server.key create mode 100755 espurna/debug.ino create mode 100755 espurna/domoticz.ino create mode 100755 espurna/espurna.ino create mode 100755 espurna/filters/BaseFilter.h create mode 100755 espurna/filters/MaxFilter.h create mode 100755 espurna/filters/MedianFilter.h create mode 100755 espurna/filters/MovingAverageFilter.h create mode 100755 espurna/gpio.ino create mode 100755 espurna/homeassistant.ino create mode 100755 espurna/i2c.ino create mode 100755 espurna/influxdb.ino create mode 100755 espurna/ir.ino create mode 100755 espurna/led.ino create mode 100755 espurna/libs/EmbedisWrap.h create mode 100755 espurna/libs/StreamInjector.h create mode 100755 espurna/libs/WebSocketIncommingBuffer.h create mode 100755 espurna/libs/pwm.h create mode 100755 espurna/light.ino create mode 100755 espurna/llmnr.ino create mode 100755 espurna/mdns.ino create mode 100755 espurna/migrate.ino create mode 100755 espurna/mqtt.ino create mode 100755 espurna/netbios.ino create mode 100755 espurna/nofuss.ino create mode 100755 espurna/ntp.ino create mode 100755 espurna/ota.ino create mode 100755 espurna/pwm.c create mode 100755 espurna/relay.ino create mode 100755 espurna/rf.ino create mode 100755 espurna/rfbridge.ino create mode 100755 espurna/scheduler.ino create mode 100755 espurna/sensor.ino create mode 100755 espurna/sensors/AM2320Sensor.h create mode 100755 espurna/sensors/AnalogSensor.h create mode 100755 espurna/sensors/BH1750Sensor.h create mode 100755 espurna/sensors/BMX280Sensor.h create mode 100755 espurna/sensors/BaseSensor.h create mode 100755 espurna/sensors/CSE7766Sensor.h create mode 100755 espurna/sensors/DHTSensor.h create mode 100755 espurna/sensors/DallasSensor.h create mode 100755 espurna/sensors/DigitalSensor.h create mode 100755 espurna/sensors/ECH1560Sensor.h create mode 100755 espurna/sensors/EmonADC121Sensor.h create mode 100755 espurna/sensors/EmonADS1X15Sensor.h create mode 100755 espurna/sensors/EmonAnalogSensor.h create mode 100755 espurna/sensors/EmonSensor.h create mode 100755 espurna/sensors/EventSensor.h create mode 100755 espurna/sensors/GUVAS12SDSensor.h create mode 100755 espurna/sensors/HLW8012Sensor.h create mode 100755 espurna/sensors/I2CSensor.h create mode 100755 espurna/sensors/MHZ19Sensor.h create mode 100755 espurna/sensors/PMSX003Sensor.h create mode 100755 espurna/sensors/PZEM004TSensor.h create mode 100755 espurna/sensors/SHT3XI2CSensor.h create mode 100755 espurna/sensors/SI7021Sensor.h create mode 100755 espurna/sensors/V9261FSensor.h create mode 100755 espurna/settings.ino create mode 100755 espurna/ssdp.ino create mode 100755 espurna/static/index.html.gz.h create mode 100755 espurna/static/server.cer.h create mode 100755 espurna/static/server.key.h create mode 100755 espurna/system.ino create mode 100755 espurna/telnet.ino create mode 100755 espurna/thinkspeak.ino create mode 100755 espurna/uartmqtt.ino create mode 100755 espurna/utils.ino create mode 100755 espurna/web.ino create mode 100755 espurna/wifi.ino create mode 100755 espurna/ws.ino create mode 100755 extra_scripts.py create mode 100755 gulpfile.js create mode 100755 html/custom.css create mode 100755 html/custom.js create mode 100755 html/favicon.ico create mode 100755 html/images/loading.gif create mode 100755 html/index.html create mode 100755 html/vendor/checkboxes.css create mode 100755 html/vendor/checkboxes.js create mode 100755 html/vendor/images/border-off.png create mode 100755 html/vendor/images/border-on.png create mode 100755 html/vendor/images/handle-center.png create mode 100755 html/vendor/images/handle-left.png create mode 100755 html/vendor/images/handle-right.png create mode 100755 html/vendor/images/label-off.png create mode 100755 html/vendor/images/label-on.png create mode 100755 html/vendor/jquery-3.2.1.min.js create mode 100755 html/vendor/jquery.wheelcolorpicker-3.0.3.css create mode 100755 html/vendor/jquery.wheelcolorpicker-3.0.3.min.js create mode 100755 html/vendor/pure-1.0.0.min.css create mode 100755 html/vendor/pure-grids-responsive-1.0.0.min.css create mode 100755 html/vendor/side-menu.css create mode 100644 lib/readme.txt create mode 100755 memanalyzer.py create mode 100755 ota.py create mode 100755 package-lock.json create mode 100755 package.json create mode 100755 platformio Kopie.ini create mode 100644 platformio.ini create mode 100644 platformio.ini_short create mode 100755 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5dac9f5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.pioenvs +.piolibdeps +.clang_complete +.gcc-flags.json diff --git a/.pioenvs Alias b/.pioenvs Alias new file mode 100644 index 0000000000000000000000000000000000000000..eff3545c69026f85b9a6d7dcb8b29062e16c44b3 GIT binary patch literal 980 zcmZva&r2IY6vt;Z+9)1kQmPc8i!Ij6n$@Oel_FK6SWwzS6%T@}$u7nwJ7G6b3LY%@ z2lUWmDc)2N@gnU(XpTL4^iuFBc&Hu(PmSNVzBO*?u-~1}`!R3d&didO9|T6xbp6=? zlfE{RHxi?_Cmq?d`%U{eQXM6`K?rpNuL!u2@&HYk6h(+x&vHF*GuuVesaOReTG-CW zY^7*9Wy)z~-@AfYwxwHq^Hzvx;B)1Ql`VU5wk@wzaUFA!da-S-B-iS>eWkp#oK>%j za`9nmT7`5d1I3Wq*(cGkeYl+q#lQb!Ki^pi#iQ4^cF@xg zM1Mgi&>3_MMX_I<5cNcRI9eU4Tmm~G1^bJ~A5R#9nVbqX983KDM~S=tqH~}T#ICr$ zf&;Mg1#38&Otv9TXEkGH^G;cM%j#4n*cTxVI`$F2>*?q3^QW+%@tU-eN~H&lNo{Cs zuwQ$jPmXJ2>4B6sq`w@{Cyca_=^qdJ|3qt`KZ)>6KKgzVGo2CrYtG3p0sVZM|2N!R z!4CH1A=dw`)oN6W#v_Ps>lfd`eFXP%Fa};=ehr>xeh=Pcp1sF&;C1#ls6xErk2Zn# z*dGHQF}H&+nB(AM<^=eZSp|1-9UUChSE{&+oYM|Q$RF{l5L@h5j_dg`Fr6)pj*Htn KP23Hh;`%oZI%!Y< literal 0 HcmV?d00001 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9443843 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,67 @@ +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < http://docs.platformio.org/page/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < http://docs.platformio.org/page/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < http://docs.platformio.org/page/userguide/cmd_ci.html > +# +# +# Please choice one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to by used as a library with examples +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# - platformio update +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/Firmware b/Firmware new file mode 120000 index 0000000..e90612e --- /dev/null +++ b/Firmware @@ -0,0 +1 @@ +.pioenvs \ No newline at end of file diff --git a/astyle.conf b/astyle.conf new file mode 100755 index 0000000..8b4ca92 --- /dev/null +++ b/astyle.conf @@ -0,0 +1,18 @@ +style=attach +indent=spaces=4 +indent-classes +indent-switches +indent-namespaces +indent-continuation=4 +#indent-preproc-define +#indent-preproc-cond +#indent-preproc-block +indent-col1-comments +pad-oper +pad-comma +pad-header +align-pointer=type +align-reference=type +add-brackets +convert-tabs +suffix=none diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..6e6dcaf --- /dev/null +++ b/build.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Welcome +echo "--------------------------------------------------------------" +echo "ESPURNA FIRMWARE BUILDER" + +# Available environments +travis=$(grep env: platformio.ini | grep travis | sed 's/\[env://' | sed 's/\]/ /' | sort) +available=$(grep env: platformio.ini | grep -v ota | grep -v ssl | grep -v travis | sed 's/\[env://' | sed 's/\]/ /' | sort) + +# Parameters +environments=$@ +if [ "$environments" == "list" ]; then + echo "--------------------------------------------------------------" + echo "Available environments:" + for environment in $available; do + echo "* $environment" + done + exit +fi + +# Environments to build +if [ $# -eq 0 ]; then + + environments=$available + + # Hook to build travis test envs + if [[ "${TRAVIS_BRANCH}" != "" ]]; then + if [[ ${TRAVIS_BRANCH} != "master" ]]; then + environments=$travis + fi + fi + +fi + +# Get current version +version=$(grep APP_VERSION espurna/config/version.h | awk '{print $3}' | sed 's/"//g') +echo "Building for version $version" + +# Create output folder +mkdir -p firmware + +if [ ! -e node_modules/gulp/bin/gulp.js ]; then + echo "--------------------------------------------------------------" + echo "Installing dependencies..." + npm install --only=dev +fi + +echo "--------------------------------------------------------------" +echo "Get revision..." +revision=$(git rev-parse HEAD) +revision=${revision:0:7} +cp espurna/config/version.h espurna/config/version.h.original +sed -i -e "s/APP_REVISION \".*\"/APP_REVISION \"$revision\"/g" espurna/config/version.h + +# Recreate web interface +echo "--------------------------------------------------------------" +echo "Building web interface..." +node node_modules/gulp/bin/gulp.js || exit + +# Build all the required firmware images +echo "--------------------------------------------------------------" +echo "Building firmware images..." +mkdir -p ../firmware/espurna-$version +for environment in $environments; do + echo "* espurna-$version-$environment.bin" + platformio run --silent --environment $environment || break + mv .pioenvs/$environment/firmware.bin ../firmware/espurna-$version/espurna-$version-$environment.bin +done +echo "--------------------------------------------------------------" + +mv espurna/config/version.h.original espurna/config/version.h diff --git a/debug.sh b/debug.sh new file mode 100755 index 0000000..63e0fcc --- /dev/null +++ b/debug.sh @@ -0,0 +1,75 @@ +#!/bin/bash + +# ------------------------------------------------------------------------------ +# CONFIGURATION +# ------------------------------------------------------------------------------ + +ENVIRONMENT="nodemcu-lolin" +ADDR2LINE=$HOME/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-addr2line +DECODER=utils/EspStackTraceDecoder.jar +DECODER_ORIGIN=https://github.com/littleyoda/EspStackTraceDecoder/releases/download/untagged-83b6db3208da17a0f1fd/EspStackTraceDecoder.jar +FILE="/tmp/.trace" + +# ------------------------------------------------------------------------------ +# END CONFIGURATION - DO NOT EDIT FURTHER +# ------------------------------------------------------------------------------ + +# remove default trace file +rm -rf $FILE + +function help { + echo + echo "Syntax: $0 [-e ] [-d ]" + echo +} + +# get environment from command line +while [[ $# -gt 1 ]]; do + + key="$1" + + case $key in + -e) + ENVIRONMENT="$2" + shift + ;; + -d) + FILE="$2" + shift + ;; + esac + + shift # past argument or value + +done + +# check environment folder +if [ $ENVIRONMENT == "" ]; then + echo "No environment defined" + help + exit 1 +fi +ELF=.pioenvs/$ENVIRONMENT/firmware.elf +if [ ! -f $ELF ]; then + echo "Could not find ELF file for the selected environment: $ELF" + exit 2 +fi + +# get decode +if [ ! -f $DECODER ]; then + folder=$(dirname "$DECODER") + if [ $folder != "." ]; then + mkdir -p $folder + fi + echo "Downloading decoder..." + wget -q $DECODER_ORIGIN -O "$DECODER" +fi + +# get trace interactively +if [ ! -f $FILE ]; then + echo "Paste stack trace and end with a blank line:" + trace=$(sed '/^$/q') + echo $trace > $FILE +fi + +java -jar $DECODER $ADDR2LINE $ELF $FILE diff --git a/esp8266.flash.1m0.ld b/esp8266.flash.1m0.ld new file mode 100755 index 0000000..dbb44e9 --- /dev/null +++ b/esp8266.flash.1m0.ld @@ -0,0 +1,18 @@ +/* Flash Split for 1M chips, no SPIFFS */ +/* sketch 999KB */ +/* eeprom 20KB */ + +MEMORY +{ + dport0_0_seg : org = 0x3FF00000, len = 0x10 + dram0_0_seg : org = 0x3FFE8000, len = 0x14000 + iram1_0_seg : org = 0x40100000, len = 0x8000 + irom0_0_seg : org = 0x40201010, len = 0xf9ff0 +} + +PROVIDE ( _SPIFFS_start = 0x402FB000 ); +PROVIDE ( _SPIFFS_end = 0x402FB000 ); +PROVIDE ( _SPIFFS_page = 0 ); +PROVIDE ( _SPIFFS_block = 0 ); + +INCLUDE "esp8266.flash.common.ld" diff --git a/espurna/alexa.ino b/espurna/alexa.ino new file mode 100755 index 0000000..81815d8 --- /dev/null +++ b/espurna/alexa.ino @@ -0,0 +1,95 @@ +/* + +ALEXA MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if ALEXA_SUPPORT + +#include +fauxmoESP alexa; + +struct AlexaDevChange { + AlexaDevChange(unsigned char device_id, bool state) : device_id(device_id), state(state) {}; + unsigned char device_id = 0; + bool state = false; +}; +#include +static std::queue _alexa_dev_changes; + +// ----------------------------------------------------------------------------- +// ALEXA +// ----------------------------------------------------------------------------- + +bool _alexaWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "alexa", 5) == 0); +} + +void _alexaWebSocketOnSend(JsonObject& root) { + root["alexaVisible"] = 1; + root["alexaEnabled"] = getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1; +} + +void _alexaConfigure() { + alexa.enable(getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1); +} + +// ----------------------------------------------------------------------------- + +void alexaSetup() { + + // Backwards compatibility + moveSetting("fauxmoEnabled", "alexaEnabled"); + + // Load & cache settings + _alexaConfigure(); + + #if WEB_SUPPORT + + // Websockets + wsOnSendRegister(_alexaWebSocketOnSend); + wsOnAfterParseRegister(_alexaConfigure); + wsOnReceiveRegister(_alexaWebSocketOnReceive); + + #endif + + unsigned int relays = relayCount(); + String hostname = getSetting("hostname"); + if (relays == 1) { + alexa.addDevice(hostname.c_str()); + } else { + for (unsigned int i=0; i + +*/ + +#if WEB_SUPPORT + +#include +#include +#include +#include + +typedef struct { + char * key; + api_get_callback_f getFn = NULL; + api_put_callback_f putFn = NULL; +} web_api_t; +std::vector _apis; + +// ----------------------------------------------------------------------------- + +bool _apiWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "api", 3) == 0); +} + +void _apiWebSocketOnSend(JsonObject& root) { + root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1; + root["apiKey"] = getSetting("apiKey"); + root["apiRealTime"] = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1; +} + +// ----------------------------------------------------------------------------- +// API +// ----------------------------------------------------------------------------- + +bool _authAPI(AsyncWebServerRequest *request) { + + if (getSetting("apiEnabled", API_ENABLED).toInt() == 0) { + DEBUG_MSG_P(PSTR("[WEBSERVER] HTTP API is not enabled\n")); + request->send(403); + return false; + } + + if (!request->hasParam("apikey", (request->method() == HTTP_PUT))) { + DEBUG_MSG_P(PSTR("[WEBSERVER] Missing apikey parameter\n")); + request->send(403); + return false; + } + + AsyncWebParameter* p = request->getParam("apikey", (request->method() == HTTP_PUT)); + if (!p->value().equals(getSetting("apiKey"))) { + DEBUG_MSG_P(PSTR("[WEBSERVER] Wrong apikey parameter\n")); + request->send(403); + return false; + } + + return true; + +} + +bool _asJson(AsyncWebServerRequest *request) { + bool asJson = false; + if (request->hasHeader("Accept")) { + AsyncWebHeader* h = request->getHeader("Accept"); + asJson = h->value().equals("application/json"); + } + return asJson; +} + +ArRequestHandlerFunction _bindAPI(unsigned int apiID) { + + return [apiID](AsyncWebServerRequest *request) { + + webLog(request); + if (!_authAPI(request)) return; + + web_api_t api = _apis[apiID]; + + // Check if its a PUT + if (api.putFn != NULL) { + if (request->hasParam("value", request->method() == HTTP_PUT)) { + AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT); + (api.putFn)((p->value()).c_str()); + } + } + + // Get response from callback + char value[API_BUFFER_SIZE]; + (api.getFn)(value, API_BUFFER_SIZE); + + // The response will be a 404 NOT FOUND if the resource is not available + if (!value) { + DEBUG_MSG_P(PSTR("[API] Sending 404 response\n")); + request->send(404); + return; + } + DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), value); + + // Format response according to the Accept header + if (_asJson(request)) { + char buffer[64]; + snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value); + request->send(200, "application/json", buffer); + } else { + request->send(200, "text/plain", value); + } + + }; + +} + +void _onAPIs(AsyncWebServerRequest *request) { + + webLog(request); + if (!_authAPI(request)) return; + + bool asJson = _asJson(request); + + char buffer[40]; + + String output; + if (asJson) { + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + for (unsigned int i=0; i < _apis.size(); i++) { + snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), _apis[i].key); + root[_apis[i].key] = String(buffer); + } + root.printTo(output); + request->send(200, "application/json", output); + + } else { + for (unsigned int i=0; i < _apis.size(); i++) { + snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), _apis[i].key); + output += _apis[i].key + String(" -> ") + String(buffer) + String("\n"); + } + request->send(200, "text/plain", output); + } + +} + +void _onRPC(AsyncWebServerRequest *request) { + + webLog(request); + if (!_authAPI(request)) return; + + //bool asJson = _asJson(request); + int response = 404; + + if (request->hasParam("action")) { + + AsyncWebParameter* p = request->getParam("action"); + String action = p->value(); + DEBUG_MSG_P(PSTR("[RPC] Action: %s\n"), action.c_str()); + + if (action.equals("reboot")) { + response = 200; + deferredReset(100, CUSTOM_RESET_RPC); + } + + } + + request->send(response); + +} + +// ----------------------------------------------------------------------------- + +void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn) { + + // Store it + web_api_t api; + char buffer[40]; + snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), key); + api.key = strdup(key); + api.getFn = getFn; + api.putFn = putFn; + _apis.push_back(api); + + // Bind call + unsigned int methods = HTTP_GET; + if (putFn != NULL) methods += HTTP_PUT; + webServer()->on(buffer, methods, _bindAPI(_apis.size() - 1)); + +} + +void apiSetup() { + webServer()->on("/apis", HTTP_GET, _onAPIs); + webServer()->on("/rpc", HTTP_GET, _onRPC); + wsOnSendRegister(_apiWebSocketOnSend); + wsOnReceiveRegister(_apiWebSocketOnReceive); +} + +#endif // WEB_SUPPORT diff --git a/espurna/broker.ino b/espurna/broker.ino new file mode 100755 index 0000000..6db86e2 --- /dev/null +++ b/espurna/broker.ino @@ -0,0 +1,32 @@ +/* + +BROKER MODULE + +Copyright (C) 2017-2018 by Xose Pérez + +*/ + +#if BROKER_SUPPORT + +#include + +std::vector _broker_callbacks; + +// ----------------------------------------------------------------------------- + +void brokerRegister(void (*callback)(const char *, unsigned char, const char *)) { + _broker_callbacks.push_back(callback); +} + +void brokerPublish(const char * topic, unsigned char id, const char * message) { + //DEBUG_MSG_P(PSTR("[BROKER] Message %s[%u] => %s\n"), topic, id, message); + for (unsigned char i=0; i<_broker_callbacks.size(); i++) { + (_broker_callbacks[i])(topic, id, message); + } +} + +void brokerPublish(const char * topic, const char * message) { + brokerPublish(topic, 0, message); +} + +#endif // BROKER_SUPPORT diff --git a/espurna/button.ino b/espurna/button.ino new file mode 100755 index 0000000..dd0d3b5 --- /dev/null +++ b/espurna/button.ino @@ -0,0 +1,258 @@ +/* + +BUTTON MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +// ----------------------------------------------------------------------------- +// BUTTON +// ----------------------------------------------------------------------------- + +#include +#include + +typedef struct { + DebounceEvent * button; + unsigned long actions; + unsigned int relayID; +} button_t; + +std::vector _buttons; + +#if MQTT_SUPPORT + +void buttonMQTT(unsigned char id, uint8_t event) { + if (id >= _buttons.size()) return; + char payload[2]; + itoa(event, payload, 10); + mqttSend(MQTT_TOPIC_BUTTON, id, payload, false, false); // 1st bool = force, 2nd = retain +} + +#endif + +#if WEB_SUPPORT + +bool _buttonWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "btn", 3) == 0); +} + +#endif + +int buttonFromRelay(unsigned int relayID) { + for (unsigned int i=0; i < _buttons.size(); i++) { + if (_buttons[i].relayID == relayID) return i; + } + return -1; +} + +bool buttonState(unsigned char id) { + if (id >= _buttons.size()) return false; + return _buttons[id].button->pressed(); +} + +unsigned char buttonAction(unsigned char id, unsigned char event) { + if (id >= _buttons.size()) return BUTTON_MODE_NONE; + unsigned long actions = _buttons[id].actions; + if (event == BUTTON_EVENT_PRESSED) return (actions) & 0x0F; + if (event == BUTTON_EVENT_CLICK) return (actions >> 4) & 0x0F; + if (event == BUTTON_EVENT_DBLCLICK) return (actions >> 8) & 0x0F; + if (event == BUTTON_EVENT_LNGCLICK) return (actions >> 12) & 0x0F; + if (event == BUTTON_EVENT_LNGLNGCLICK) return (actions >> 16) & 0x0F; + return BUTTON_MODE_NONE; +} + +unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick) { + unsigned int value; + value = pressed; + value += click << 4; + value += dblclick << 8; + value += lngclick << 12; + value += lnglngclick << 16; + return value; +} + +uint8_t mapEvent(uint8_t event, uint8_t count, uint16_t length) { + if (event == EVENT_PRESSED) return BUTTON_EVENT_PRESSED; + if (event == EVENT_CHANGED) return BUTTON_EVENT_CLICK; + if (event == EVENT_RELEASED) { + if (count == 1) { + if (length > BUTTON_LNGLNGCLICK_DELAY) return BUTTON_EVENT_LNGLNGCLICK; + if (length > BUTTON_LNGCLICK_DELAY) return BUTTON_EVENT_LNGCLICK; + return BUTTON_EVENT_CLICK; + } + if (count == 2) return BUTTON_EVENT_DBLCLICK; + } +} + +void buttonEvent(unsigned int id, unsigned char event) { + + DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %u\n"), id, event); + if (event == 0) return; + + #if MQTT_SUPPORT + buttonMQTT(id, event); + #endif + + unsigned char action = buttonAction(id, event); + + if (action == BUTTON_MODE_TOGGLE) { + if (_buttons[id].relayID > 0) { + relayToggle(_buttons[id].relayID - 1); + } + } + if (action == BUTTON_MODE_ON) { + if (_buttons[id].relayID > 0) { + relayStatus(_buttons[id].relayID - 1, true); + } + } + if (action == BUTTON_MODE_OFF) { + if (_buttons[id].relayID > 0) { + relayStatus(_buttons[id].relayID - 1, false); + } + } + if (action == BUTTON_MODE_AP) createAP(); + if (action == BUTTON_MODE_RESET) { + deferredReset(100, CUSTOM_RESET_HARDWARE); + } + if (action == BUTTON_MODE_FACTORY) { + DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n")); + resetSettings(); + deferredReset(100, CUSTOM_RESET_FACTORY); + } + +} + +void buttonSetup() { + + #ifdef ITEAD_SONOFF_DUAL + + unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE); + _buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 1}); + _buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 2}); + _buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, BUTTON3_RELAY}); + + #else + + unsigned long btnDelay = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt(); + + #if BUTTON1_PIN != GPIO_NONE + { + unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK); + _buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY}); + } + #endif + #if BUTTON2_PIN != GPIO_NONE + { + unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK); + _buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY}); + } + #endif + #if BUTTON3_PIN != GPIO_NONE + { + unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK); + _buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY}); + } + #endif + #if BUTTON4_PIN != GPIO_NONE + { + unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK); + _buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY}); + } + #endif + #if BUTTON5_PIN != GPIO_NONE + { + unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK); + _buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY}); + } + #endif + #if BUTTON6_PIN != GPIO_NONE + { + unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK); + _buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY}); + } + #endif + #if BUTTON7_PIN != GPIO_NONE + { + unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK); + _buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY}); + } + #endif + #if BUTTON8_PIN != GPIO_NONE + { + unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK); + _buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY}); + } + #endif + + #endif + + DEBUG_MSG_P(PSTR("[BUTTON] Number of buttons: %u\n"), _buttons.size()); + + // Websocket Callbacks + #if WEB_SUPPORT + wsOnReceiveRegister(_buttonWebSocketOnReceive); + #endif + + // Register loop + espurnaRegisterLoop(buttonLoop); + +} + +void buttonLoop() { + + #ifdef ITEAD_SONOFF_DUAL + + if (Serial.available() >= 4) { + if (Serial.read() == 0xA0) { + if (Serial.read() == 0x04) { + unsigned char value = Serial.read(); + if (Serial.read() == 0xA1) { + + // RELAYs and BUTTONs are synchonized in the SIL F330 + // The on-board BUTTON2 should toggle RELAY0 value + // Since we are not passing back RELAY2 value + // (in the relayStatus method) it will only be present + // here if it has actually been pressed + if ((value & 4) == 4) { + buttonEvent(2, BUTTON_EVENT_CLICK); + return; + } + + // Otherwise check if any of the other two BUTTONs + // (in the header) has been pressed, but we should + // ensure that we only toggle one of them to avoid + // the synchronization going mad + // This loop is generic for any PSB-04 module + for (unsigned int i=0; i 0; + + // Check if the status for that relay has changed + if (relayStatus(i) != status) { + buttonEvent(i, BUTTON_EVENT_CLICK); + break; + } + + } + + } + } + } + } + + #else + + for (unsigned int i=0; i < _buttons.size(); i++) { + if (unsigned char event = _buttons[i].button->loop()) { + unsigned char count = _buttons[i].button->getEventCount(); + unsigned long length = _buttons[i].button->getEventLength(); + unsigned char mapped = mapEvent(event, count, length); + buttonEvent(i, mapped); + } + } + + #endif + +} diff --git a/espurna/config/all.h b/espurna/config/all.h new file mode 100755 index 0000000..dfa31e3 --- /dev/null +++ b/espurna/config/all.h @@ -0,0 +1,36 @@ +/* + + If you want to modify the stock configuration but you don't want to touch + the repo files you can define USE_CUSTOM_H in your build settings. + + Arduino IDE: + define it in your boards.txt for the board of your choice. + For instance, for the "Generic ESP8266 Module" with prefix "generic" just add: + + generic.build.extra_flags=-DESP8266 -DUSE_CUSTOM_H + + PlatformIO: + add the setting to your environment or just define global PLATFORMIO_BUILD_FLAGS + + export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" + + Check https://github.com/xoseperez/espurna/issues/104 + for an example on how to use this file. + +*/ + +#ifdef USE_CUSTOM_H +#include "custom.h" +#endif + +#include "version.h" +#include "arduino.h" +#include "hardware.h" +#include "defaults.h" +#include "general.h" +#include "prototypes.h" +#include "sensors.h" + +#ifdef USE_CORE_VERSION_H +#include "core_version.h" +#endif diff --git a/espurna/config/arduino.h b/espurna/config/arduino.h new file mode 100755 index 0000000..920d183 --- /dev/null +++ b/espurna/config/arduino.h @@ -0,0 +1,133 @@ +//-------------------------------------------------------------------------------- +// These settings are normally provided by PlatformIO +// Uncomment the appropiate line(s) to build from the Arduino IDE +//-------------------------------------------------------------------------------- + +//-------------------------------------------------------------------------------- +// Hardware +//-------------------------------------------------------------------------------- + +//#define NODEMCU_LOLIN +//#define WEMOS_D1_MINI_RELAYSHIELD +//#define TINKERMAN_ESPURNA_H06 +//#define TINKERMAN_ESPURNA_H08 +//#define ITEAD_SONOFF_BASIC +//#define ITEAD_SONOFF_RF +//#define ITEAD_SONOFF_TH +//#define ITEAD_SONOFF_SV +//#define ITEAD_SLAMPHER +//#define ITEAD_S20 +//#define ITEAD_SONOFF_TOUCH +//#define ITEAD_SONOFF_POW +//#define ITEAD_SONOFF_DUAL +//#define ITEAD_SONOFF_DUAL_R2 +//#define ITEAD_SONOFF_4CH +//#define ITEAD_SONOFF_4CH_PRO +//#define ITEAD_1CH_INCHING +//#define ITEAD_MOTOR +//#define ITEAD_SONOFF_BNSZ01 +//#define ITEAD_SONOFF_RFBRIDGE +//#define ITEAD_SONOFF_B1 +//#define ITEAD_SONOFF_LED +//#define ITEAD_SONOFF_T1_1CH +//#define ITEAD_SONOFF_T1_2CH +//#define ITEAD_SONOFF_T1_3CH +//#define ITEAD_SONOFF_S31 +//#define YJZK_SWITCH_2CH +//#define ELECTRODRAGON_WIFI_IOT +//#define WORKCHOICE_ECOPLUG +//#define AITHINKER_AI_LIGHT +//#define MAGICHOME_LED_CONTROLLER +//#define MAGICHOME_LED_CONTROLLER_20 +//#define HUACANXING_H801 +//#define HUACANXING_H802 +//#define JANGOE_WIFI_RELAY_NC +//#define JANGOE_WIFI_RELAY_NO +//#define JORGEGARCIA_WIFI_RELAYS +//#define OPENENERGYMONITOR_MQTT_RELAY +//#define WION_50055 +//#define EXS_WIFI_RELAY_V31 +//#define GENERIC_V9261F +//#define GENERIC_ECH1560 +//#define MANCAVEMADE_ESPLIVE +//#define INTERMITTECH_QUINLED +//#define ARILUX_AL_LC06 +//#define ARILUX_E27 +//#define XENON_SM_PW702U +//#define AUTHOMETION_LYT8266 +//#define KMC_70011 +//#define GENERIC_8CH +//#define ARILUX_AL_LC01 +//#define ARILUX_AL_LC11 +//#define ARILUX_AL_LC02 +//#define WEMOS_D1_TARPUNA_SHIELD +//#define GIZWITS_WITTY_CLOUD +//#define EUROMATE_WIFI_STECKER_SCHUKO +//#define TONBUX_POWERSTRIP02 +//#define LINGAN_SWA1 +//#define HEYGO_HY02 +//#define MAXCIO_WUS002S +//#define YIDIAN_XSSSA05 +//#define TONBUX_XSSSA06 +//#define GREEN_ESP8266RELAY +//#define IKE_ESPIKE +//#define ARNIEX_SWIFITCH +//#define GENERIC_ESP01SRELAY40 +//#define GENERIC_ESP01SRGBLED10 +//#define HELTEC_TOUCHRELAY + +//-------------------------------------------------------------------------------- +// Features (values below are non-default values) +//-------------------------------------------------------------------------------- + +//#define ALEXA_SUPPORT 0 +#define BROKER_SUPPORT 0 +//#define DEBUG_SERIAL_SUPPORT 0 +//#define DEBUG_TELNET_SUPPORT 0 +//#define DEBUG_UDP_SUPPORT 1 +#define DOMOTICZ_SUPPORT 0 +#define HOMEASSISTANT_SUPPORT 0 +//#define I2C_SUPPORT 1 +//#define INFLUXDB_SUPPORT 1 +//#define IR_SUPPORT 1 +//#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0 +//#define MDNS_SERVER_SUPPORT 0 +//#define MDNS_CLIENT_SUPPORT 1 +//#define MQTT_SUPPORT 0 +//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0 +//#define NOFUSS_SUPPORT 1 +//#define NTP_SUPPORT 0 +//#define RF_SUPPORT 1 +#define SCHEDULER_SUPPORT 0 +//#define SPIFFS_SUPPORT 1 +//#define SSDP_SUPPORT 1 +//#define TELNET_SUPPORT 0 +//#define TERMINAL_SUPPORT 0 +#define THINGSPEAK_SUPPORT 0 +//#define UART_MQTT_SUPPORT 1 +//#define WEB_SUPPORT 0 + +//-------------------------------------------------------------------------------- +// Sensors (values below are non-default values) +//-------------------------------------------------------------------------------- + +//#define AM2320_SUPPORT 1 +//#define ANALOG_SUPPORT 1 +//#define BH1750_SUPPORT 1 +//#define BMX280_SUPPORT 1 +//#define DALLAS_SUPPORT 1 +//#define DHT_SUPPORT 1 +//#define DIGITAL_SUPPORT 1 +//#define ECH1560_SUPPORT 1 +//#define EMON_ADC121_SUPPORT 1 +//#define EMON_ADS1X15_SUPPORT 1 +//#define EMON_ANALOG_SUPPORT 1 +//#define EVENTS_SUPPORT 1 +//#define GUVAS12SD_SUPPORT 1 +//#define HLW8012_SUPPORT 1 +//#define MHZ19_SUPPORT 1 +//#define PMSX003_SUPPORT 1 +//#define PZEM004T_SUPPORT 1 +//#define SHT3X_I2C_SUPPORT 1 +//#define SI7021_SUPPORT 1 +//#define V9261F_SUPPORT 1 diff --git a/espurna/config/build.h b/espurna/config/build.h new file mode 100755 index 0000000..ef31240 --- /dev/null +++ b/espurna/config/build.h @@ -0,0 +1,5 @@ +// DO NOT EDIT THIS FILE MANUALLY +// This file is modified by PlatformIO +// This file should not be pushed when modified, untrack changes with: +// git update-index --assume-unchanged code/espurna/config/build.h +#define APP_BUILD_FLAGS "" diff --git a/espurna/config/defaults.h b/espurna/config/defaults.h new file mode 100755 index 0000000..86e2599 --- /dev/null +++ b/espurna/config/defaults.h @@ -0,0 +1,423 @@ +// ----------------------------------------------------------------------------- +// Hardware default values +// ----------------------------------------------------------------------------- + +#define GPIO_NONE 0x99 + +// ----------------------------------------------------------------------------- +// Buttons +// ----------------------------------------------------------------------------- + +#ifndef BUTTON1_PIN +#define BUTTON1_PIN GPIO_NONE +#endif +#ifndef BUTTON2_PIN +#define BUTTON2_PIN GPIO_NONE +#endif +#ifndef BUTTON3_PIN +#define BUTTON3_PIN GPIO_NONE +#endif +#ifndef BUTTON4_PIN +#define BUTTON4_PIN GPIO_NONE +#endif +#ifndef BUTTON5_PIN +#define BUTTON5_PIN GPIO_NONE +#endif +#ifndef BUTTON6_PIN +#define BUTTON6_PIN GPIO_NONE +#endif +#ifndef BUTTON7_PIN +#define BUTTON7_PIN GPIO_NONE +#endif +#ifndef BUTTON8_PIN +#define BUTTON8_PIN GPIO_NONE +#endif + +#ifndef BUTTON1_PRESS +#define BUTTON1_PRESS BUTTON_MODE_NONE +#endif +#ifndef BUTTON2_PRESS +#define BUTTON2_PRESS BUTTON_MODE_NONE +#endif +#ifndef BUTTON3_PRESS +#define BUTTON3_PRESS BUTTON_MODE_NONE +#endif +#ifndef BUTTON4_PRESS +#define BUTTON4_PRESS BUTTON_MODE_NONE +#endif +#ifndef BUTTON5_PRESS +#define BUTTON5_PRESS BUTTON_MODE_NONE +#endif +#ifndef BUTTON6_PRESS +#define BUTTON6_PRESS BUTTON_MODE_NONE +#endif +#ifndef BUTTON7_PRESS +#define BUTTON7_PRESS BUTTON_MODE_NONE +#endif +#ifndef BUTTON8_PRESS +#define BUTTON8_PRESS BUTTON_MODE_NONE +#endif + +#ifndef BUTTON1_CLICK +#define BUTTON1_CLICK BUTTON_MODE_TOGGLE +#endif +#ifndef BUTTON2_CLICK +#define BUTTON2_CLICK BUTTON_MODE_TOGGLE +#endif +#ifndef BUTTON3_CLICK +#define BUTTON3_CLICK BUTTON_MODE_TOGGLE +#endif +#ifndef BUTTON4_CLICK +#define BUTTON4_CLICK BUTTON_MODE_TOGGLE +#endif +#ifndef BUTTON5_CLICK +#define BUTTON5_CLICK BUTTON_MODE_TOGGLE +#endif +#ifndef BUTTON6_CLICK +#define BUTTON6_CLICK BUTTON_MODE_TOGGLE +#endif +#ifndef BUTTON7_CLICK +#define BUTTON7_CLICK BUTTON_MODE_TOGGLE +#endif +#ifndef BUTTON8_CLICK +#define BUTTON8_CLICK BUTTON_MODE_TOGGLE +#endif + +#ifndef BUTTON1_DBLCLICK +#define BUTTON1_DBLCLICK BUTTON_MODE_AP +#endif +#ifndef BUTTON2_DBLCLICK +#define BUTTON2_DBLCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON3_DBLCLICK +#define BUTTON3_DBLCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON4_DBLCLICK +#define BUTTON4_DBLCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON5_DBLCLICK +#define BUTTON5_DBLCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON6_DBLCLICK +#define BUTTON6_DBLCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON7_DBLCLICK +#define BUTTON7_DBLCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON8_DBLCLICK +#define BUTTON8_DBLCLICK BUTTON_MODE_NONE +#endif + +#ifndef BUTTON1_LNGCLICK +#define BUTTON1_LNGCLICK BUTTON_MODE_RESET +#endif +#ifndef BUTTON2_LNGCLICK +#define BUTTON2_LNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON3_LNGCLICK +#define BUTTON3_LNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON4_LNGCLICK +#define BUTTON4_LNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON5_LNGCLICK +#define BUTTON5_LNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON6_LNGCLICK +#define BUTTON6_LNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON7_LNGCLICK +#define BUTTON7_LNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON8_LNGCLICK +#define BUTTON8_LNGCLICK BUTTON_MODE_NONE +#endif + +#ifndef BUTTON1_LNGLNGCLICK +#define BUTTON1_LNGLNGCLICK BUTTON_MODE_FACTORY +#endif +#ifndef BUTTON2_LNGLNGCLICK +#define BUTTON2_LNGLNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON3_LNGLNGCLICK +#define BUTTON3_LNGLNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON4_LNGLNGCLICK +#define BUTTON4_LNGLNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON5_LNGLNGCLICK +#define BUTTON5_LNGLNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON6_LNGLNGCLICK +#define BUTTON6_LNGLNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON7_LNGLNGCLICK +#define BUTTON7_LNGLNGCLICK BUTTON_MODE_NONE +#endif +#ifndef BUTTON8_LNGLNGCLICK +#define BUTTON8_LNGLNGCLICK BUTTON_MODE_NONE +#endif + +#ifndef BUTTON1_RELAY +#define BUTTON1_RELAY 0 +#endif +#ifndef BUTTON2_RELAY +#define BUTTON2_RELAY 0 +#endif +#ifndef BUTTON3_RELAY +#define BUTTON3_RELAY 0 +#endif +#ifndef BUTTON4_RELAY +#define BUTTON4_RELAY 0 +#endif +#ifndef BUTTON5_RELAY +#define BUTTON5_RELAY 0 +#endif +#ifndef BUTTON6_RELAY +#define BUTTON6_RELAY 0 +#endif +#ifndef BUTTON7_RELAY +#define BUTTON7_RELAY 0 +#endif +#ifndef BUTTON8_RELAY +#define BUTTON8_RELAY 0 +#endif + +// ----------------------------------------------------------------------------- +// Relays +// ----------------------------------------------------------------------------- + +#ifndef DUMMY_RELAY_COUNT +#define DUMMY_RELAY_COUNT 0 +#endif + +#ifndef RELAY1_PIN +#define RELAY1_PIN GPIO_NONE +#endif +#ifndef RELAY2_PIN +#define RELAY2_PIN GPIO_NONE +#endif +#ifndef RELAY3_PIN +#define RELAY3_PIN GPIO_NONE +#endif +#ifndef RELAY4_PIN +#define RELAY4_PIN GPIO_NONE +#endif +#ifndef RELAY5_PIN +#define RELAY5_PIN GPIO_NONE +#endif +#ifndef RELAY6_PIN +#define RELAY6_PIN GPIO_NONE +#endif +#ifndef RELAY7_PIN +#define RELAY7_PIN GPIO_NONE +#endif +#ifndef RELAY8_PIN +#define RELAY8_PIN GPIO_NONE +#endif + +#ifndef RELAY1_TYPE +#define RELAY1_TYPE RELAY_TYPE_NORMAL +#endif +#ifndef RELAY2_TYPE +#define RELAY2_TYPE RELAY_TYPE_NORMAL +#endif +#ifndef RELAY3_TYPE +#define RELAY3_TYPE RELAY_TYPE_NORMAL +#endif +#ifndef RELAY4_TYPE +#define RELAY4_TYPE RELAY_TYPE_NORMAL +#endif +#ifndef RELAY5_TYPE +#define RELAY5_TYPE RELAY_TYPE_NORMAL +#endif +#ifndef RELAY6_TYPE +#define RELAY6_TYPE RELAY_TYPE_NORMAL +#endif +#ifndef RELAY7_TYPE +#define RELAY7_TYPE RELAY_TYPE_NORMAL +#endif +#ifndef RELAY8_TYPE +#define RELAY8_TYPE RELAY_TYPE_NORMAL +#endif + +#ifndef RELAY1_RESET_PIN +#define RELAY1_RESET_PIN GPIO_NONE +#endif +#ifndef RELAY2_RESET_PIN +#define RELAY2_RESET_PIN GPIO_NONE +#endif +#ifndef RELAY3_RESET_PIN +#define RELAY3_RESET_PIN GPIO_NONE +#endif +#ifndef RELAY4_RESET_PIN +#define RELAY4_RESET_PIN GPIO_NONE +#endif +#ifndef RELAY5_RESET_PIN +#define RELAY5_RESET_PIN GPIO_NONE +#endif +#ifndef RELAY6_RESET_PIN +#define RELAY6_RESET_PIN GPIO_NONE +#endif +#ifndef RELAY7_RESET_PIN +#define RELAY7_RESET_PIN GPIO_NONE +#endif +#ifndef RELAY8_RESET_PIN +#define RELAY8_RESET_PIN GPIO_NONE +#endif + +#ifndef RELAY1_DELAY_ON +#define RELAY1_DELAY_ON 0 +#endif +#ifndef RELAY2_DELAY_ON +#define RELAY2_DELAY_ON 0 +#endif +#ifndef RELAY3_DELAY_ON +#define RELAY3_DELAY_ON 0 +#endif +#ifndef RELAY4_DELAY_ON +#define RELAY4_DELAY_ON 0 +#endif +#ifndef RELAY5_DELAY_ON +#define RELAY5_DELAY_ON 0 +#endif +#ifndef RELAY6_DELAY_ON +#define RELAY6_DELAY_ON 0 +#endif +#ifndef RELAY7_DELAY_ON +#define RELAY7_DELAY_ON 0 +#endif +#ifndef RELAY8_DELAY_ON +#define RELAY8_DELAY_ON 0 +#endif + +#ifndef RELAY1_DELAY_OFF +#define RELAY1_DELAY_OFF 0 +#endif +#ifndef RELAY2_DELAY_OFF +#define RELAY2_DELAY_OFF 0 +#endif +#ifndef RELAY3_DELAY_OFF +#define RELAY3_DELAY_OFF 0 +#endif +#ifndef RELAY4_DELAY_OFF +#define RELAY4_DELAY_OFF 0 +#endif +#ifndef RELAY5_DELAY_OFF +#define RELAY5_DELAY_OFF 0 +#endif +#ifndef RELAY6_DELAY_OFF +#define RELAY6_DELAY_OFF 0 +#endif +#ifndef RELAY7_DELAY_OFF +#define RELAY7_DELAY_OFF 0 +#endif +#ifndef RELAY8_DELAY_OFF +#define RELAY8_DELAY_OFF 0 +#endif + +// ----------------------------------------------------------------------------- +// LEDs +// ----------------------------------------------------------------------------- + +#ifndef LED1_PIN +#define LED1_PIN GPIO_NONE +#endif +#ifndef LED2_PIN +#define LED2_PIN GPIO_NONE +#endif +#ifndef LED3_PIN +#define LED3_PIN GPIO_NONE +#endif +#ifndef LED4_PIN +#define LED4_PIN GPIO_NONE +#endif +#ifndef LED5_PIN +#define LED5_PIN GPIO_NONE +#endif +#ifndef LED6_PIN +#define LED6_PIN GPIO_NONE +#endif +#ifndef LED7_PIN +#define LED7_PIN GPIO_NONE +#endif +#ifndef LED8_PIN +#define LED8_PIN GPIO_NONE +#endif + +#ifndef LED1_MODE +#define LED1_MODE LED_MODE_WIFI +#endif +#ifndef LED2_MODE +#define LED2_MODE LED_MODE_MQTT +#endif +#ifndef LED3_MODE +#define LED3_MODE LED_MODE_MQTT +#endif +#ifndef LED4_MODE +#define LED4_MODE LED_MODE_MQTT +#endif +#ifndef LED5_MODE +#define LED5_MODE LED_MODE_MQTT +#endif +#ifndef LED6_MODE +#define LED6_MODE LED_MODE_MQTT +#endif +#ifndef LED7_MODE +#define LED7_MODE LED_MODE_MQTT +#endif +#ifndef LED8_MODE +#define LED8_MODE LED_MODE_MQTT +#endif + +#ifndef LED1_RELAY +#define LED1_RELAY 1 +#endif +#ifndef LED2_RELAY +#define LED2_RELAY 2 +#endif +#ifndef LED3_RELAY +#define LED3_RELAY 3 +#endif +#ifndef LED4_RELAY +#define LED4_RELAY 4 +#endif +#ifndef LED5_RELAY +#define LED5_RELAY 5 +#endif +#ifndef LED6_RELAY +#define LED6_RELAY 6 +#endif +#ifndef LED7_RELAY +#define LED7_RELAY 7 +#endif +#ifndef LED8_RELAY +#define LED8_RELAY 8 +#endif + +// ----------------------------------------------------------------------------- +// General +// ----------------------------------------------------------------------------- + +// Default hostname will be ESPURNA_XXXXXX, where XXXXXX is last 3 octets of chipID +#ifndef HOSTNAME +#define HOSTNAME "" +#endif + +// Needed for ESP8285 boards under Windows using PlatformIO (?) +#ifndef BUTTON_PUSHBUTTON +#define BUTTON_PUSHBUTTON 0 +#define BUTTON_SWITCH 1 +#define BUTTON_DEFAULT_HIGH 2 +#define BUTTON_SET_PULLUP 4 +#endif + +// Relay providers +#ifndef RELAY_PROVIDER +#define RELAY_PROVIDER RELAY_PROVIDER_RELAY +#endif + +// Light provider +#ifndef LIGHT_PROVIDER +#define LIGHT_PROVIDER LIGHT_PROVIDER_NONE +#endif diff --git a/espurna/config/general.h b/espurna/config/general.h new file mode 100755 index 0000000..e131a5e --- /dev/null +++ b/espurna/config/general.h @@ -0,0 +1,1041 @@ +//------------------------------------------------------------------------------ +// Do not change this file unless you know what you are doing +// Configuration settings are in the settings.h file +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// GENERAL +//------------------------------------------------------------------------------ + +#define DEVICE_NAME MANUFACTURER "_" DEVICE // Concatenate both to get a unique device name +#define ADMIN_PASS "***REMOVED***" // Default password (WEB, OTA, WIFI) +#define USE_PASSWORD 1 // Insecurity caution! Disabling this will disable password querying completely. +#define LOOP_DELAY_TIME 10 // Delay for this millis in the main loop [0-250] + +#define ARRAYINIT(type, name, ...) \ + type name[] = {__VA_ARGS__}; + +//------------------------------------------------------------------------------ +// TELNET +//------------------------------------------------------------------------------ + +#ifndef TELNET_SUPPORT +#define TELNET_SUPPORT 1 // Enable telnet support by default (3.34Kb) +#endif + +#ifndef TELNET_STA +#define TELNET_STA 0 // By default, disallow connections via STA interface +#endif + +#define TELNET_PORT 23 // Port to listen to telnet clients +#define TELNET_MAX_CLIENTS 1 // Max number of concurrent telnet clients + +//------------------------------------------------------------------------------ +// DEBUG +//------------------------------------------------------------------------------ + +// Serial debug log + +#ifndef DEBUG_SERIAL_SUPPORT +#define DEBUG_SERIAL_SUPPORT 1 // Enable serial debug log +#endif + +#ifndef DEBUG_PORT +#define DEBUG_PORT Serial // Default debugging port +#endif + +#ifndef SERIAL_BAUDRATE +#define SERIAL_BAUDRATE 115200 // Default baudrate +#endif + +#ifndef DEBUG_ADD_TIMESTAMP +#define DEBUG_ADD_TIMESTAMP 1 // Add timestamp to debug messages + // (in millis overflowing every 1000 seconds) +#endif + +// Second serial port (used for RX) + +#ifndef SERIAL_RX_ENABLED +#define SERIAL_RX_ENABLED 0 // Secondary serial port for RX +#endif + +#ifndef SERIAL_RX_PORT +#define SERIAL_RX_PORT Serial // This setting is usually defined + // in the hardware.h file for those + // boards that require it +#endif + +#ifndef SERIAL_RX_BAUDRATE +#define SERIAL_RX_BAUDRATE 115200 // Default baudrate +#endif + +//------------------------------------------------------------------------------ + +// UDP debug log +// To receive the message son the destination computer use nc: +// nc -ul 8113 + +#ifndef DEBUG_UDP_SUPPORT +#define DEBUG_UDP_SUPPORT 0 // Enable UDP debug log +#endif +#define DEBUG_UDP_IP IPAddress(192, 168, 1, 100) +#define DEBUG_UDP_PORT 8113 + +//------------------------------------------------------------------------------ + +#ifndef DEBUG_TELNET_SUPPORT +#define DEBUG_TELNET_SUPPORT TELNET_SUPPORT // Enable telnet debug log if telnet is enabled too +#endif + +#if DEBUG_TELNET_SUPPORT +#undef TELNET_SUPPORT +#define TELNET_SUPPORT 1 +#endif + +//------------------------------------------------------------------------------ + +#ifndef DEBUG_WEB_SUPPORT +#define DEBUG_WEB_SUPPORT WEB_SUPPORT // Enable web debug log if web is enabled too +#endif + +#if DEBUG_WEB_SUPPORT +#undef WEB_SUPPORT +#define WEB_SUPPORT 1 // Chicken and egg :) +#endif + +#define DEBUG_WEB_ENABLED 1 // Enable debug output by default + +//------------------------------------------------------------------------------ + +// General debug options and macros +#define DEBUG_SUPPORT DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT || DEBUG_TELNET_SUPPORT + +#if DEBUG_SUPPORT + #define DEBUG_MSG(...) debugSend(__VA_ARGS__) + #define DEBUG_MSG_P(...) debugSend_P(__VA_ARGS__) +#endif + +#ifndef DEBUG_MSG + #define DEBUG_MSG(...) + #define DEBUG_MSG_P(...) +#endif + +//------------------------------------------------------------------------------ +// TERMINAL +//------------------------------------------------------------------------------ + +#ifndef TERMINAL_SUPPORT +#define TERMINAL_SUPPORT 1 // Enable terminal commands (0.97Kb) +#endif + +#define TERMINAL_BUFFER_SIZE 128 // Max size for commands commands + +//------------------------------------------------------------------------------ +// SYSTEM CHECK +//------------------------------------------------------------------------------ + +#ifndef SYSTEM_CHECK_ENABLED +#define SYSTEM_CHECK_ENABLED 1 // Enable crash check by default +#endif + +#define SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis +#ifndef SYSTEM_CHECK_MAX +#define SYSTEM_CHECK_MAX 5 // After this many crashes on boot + // the system is flagged as unstable +#endif + +//------------------------------------------------------------------------------ +// EEPROM +//------------------------------------------------------------------------------ + +#define EEPROM_SIZE 4096 // EEPROM size in bytes +#define EEPROM_RELAY_STATUS 0 // Address for the relay status (1 byte) +#define EEPROM_ENERGY_COUNT 1 // Address for the energy counter (4 bytes) +#define EEPROM_CUSTOM_RESET 5 // Address for the reset reason (1 byte) +#define EEPROM_CRASH_COUNTER 6 // Address for the crash counter (1 byte) +#define EEPROM_MESSAGE_ID 7 // Address for the MQTT message id (4 bytes) +#define EEPROM_DATA_END 11 // End of custom EEPROM data block + +//------------------------------------------------------------------------------ +// HEARTBEAT +//------------------------------------------------------------------------------ + +#ifndef HEARTBEAT_ENABLED +#define HEARTBEAT_ENABLED 1 +#endif + +#define HEARTBEAT_INTERVAL 300000 // Interval between heartbeat messages (in ms) +#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value + +// Topics that will be reported in heartbeat +#define HEARTBEAT_REPORT_STATUS 1 +#define HEARTBEAT_REPORT_IP 1 +#define HEARTBEAT_REPORT_MAC 1 +#define HEARTBEAT_REPORT_RSSI 1 +#define HEARTBEAT_REPORT_UPTIME 1 +#define HEARTBEAT_REPORT_DATETIME 1 +#define HEARTBEAT_REPORT_FREEHEAP 1 +#define HEARTBEAT_REPORT_VCC 1 +#define HEARTBEAT_REPORT_RELAY 1 +#define HEARTBEAT_REPORT_LIGHT 1 +#define HEARTBEAT_REPORT_HOSTNAME 1 +#define HEARTBEAT_REPORT_APP 1 +#define HEARTBEAT_REPORT_VERSION 1 +#define HEARTBEAT_REPORT_BOARD 1 +#define HEARTBEAT_REPORT_INTERVAL 0 + +//------------------------------------------------------------------------------ +// Load average +//------------------------------------------------------------------------------ +#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) +#define LOADAVG_REPORT 1 // Should we report Load average over MQTT? + +//------------------------------------------------------------------------------ +// RESET +//------------------------------------------------------------------------------ + +#define CUSTOM_RESET_HARDWARE 1 // Reset from hardware button +#define CUSTOM_RESET_WEB 2 // Reset from web interface +#define CUSTOM_RESET_TERMINAL 3 // Reset from terminal +#define CUSTOM_RESET_MQTT 4 // Reset via MQTT +#define CUSTOM_RESET_RPC 5 // Reset via RPC (HTTP) +#define CUSTOM_RESET_OTA 6 // Reset after successful OTA update +#define CUSTOM_RESET_HTTP 7 // Reset via HTTP GET +#define CUSTOM_RESET_NOFUSS 8 // Reset after successful NOFUSS update +#define CUSTOM_RESET_UPGRADE 9 // Reset after update from web interface +#define CUSTOM_RESET_FACTORY 10 // Factory reset from terminal + +#define CUSTOM_RESET_MAX 10 + +PROGMEM const char custom_reset_hardware[] = "Hardware button"; +PROGMEM const char custom_reset_web[] = "Reboot from web interface"; +PROGMEM const char custom_reset_terminal[] = "Reboot from terminal"; +PROGMEM const char custom_reset_mqtt[] = "Reboot from MQTT"; +PROGMEM const char custom_reset_rpc[] = "Reboot from RPC"; +PROGMEM const char custom_reset_ota[] = "Reboot after successful OTA update"; +PROGMEM const char custom_reset_http[] = "Reboot from HTTP"; +PROGMEM const char custom_reset_nofuss[] = "Reboot after successful NoFUSS update"; +PROGMEM const char custom_reset_upgrade[] = "Reboot after successful web update"; +PROGMEM const char custom_reset_factory[] = "Factory reset"; +PROGMEM const char* const custom_reset_string[] = { + custom_reset_hardware, custom_reset_web, custom_reset_terminal, + custom_reset_mqtt, custom_reset_rpc, custom_reset_ota, + custom_reset_http, custom_reset_nofuss, custom_reset_upgrade, + custom_reset_factory +}; + +//------------------------------------------------------------------------------ +// BUTTON +//------------------------------------------------------------------------------ + +#ifndef BUTTON_DEBOUNCE_DELAY +#define BUTTON_DEBOUNCE_DELAY 50 // Debounce delay (ms) +#endif + +#ifndef BUTTON_DBLCLICK_DELAY +#define BUTTON_DBLCLICK_DELAY 500 // Time in ms to wait for a second (or third...) click +#endif + +#ifndef BUTTON_LNGCLICK_DELAY +#define BUTTON_LNGCLICK_DELAY 1000 // Time in ms holding the button down to get a long click +#endif + +#ifndef BUTTON_LNGLNGCLICK_DELAY +#define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click +#endif + +#define BUTTON_EVENT_NONE 0 +#define BUTTON_EVENT_PRESSED 1 +#define BUTTON_EVENT_RELEASED 2 +#define BUTTON_EVENT_CLICK 2 +#define BUTTON_EVENT_DBLCLICK 3 +#define BUTTON_EVENT_LNGCLICK 4 +#define BUTTON_EVENT_LNGLNGCLICK 5 + +#define BUTTON_MODE_NONE 0 +#define BUTTON_MODE_TOGGLE 1 +#define BUTTON_MODE_ON 2 +#define BUTTON_MODE_OFF 3 +#define BUTTON_MODE_AP 4 +#define BUTTON_MODE_RESET 5 +#define BUTTON_MODE_PULSE 6 +#define BUTTON_MODE_FACTORY 7 + +//------------------------------------------------------------------------------ +// RELAY +//------------------------------------------------------------------------------ + +#define RELAY_BOOT_OFF 0 +#define RELAY_BOOT_ON 1 +#define RELAY_BOOT_SAME 2 +#define RELAY_BOOT_TOGGLE 3 + +#define RELAY_TYPE_NORMAL 0 +#define RELAY_TYPE_INVERSE 1 +#define RELAY_TYPE_LATCHED 2 +#define RELAY_TYPE_LATCHED_INVERSE 3 + +#define RELAY_SYNC_ANY 0 +#define RELAY_SYNC_NONE_OR_ONE 1 +#define RELAY_SYNC_ONE 2 +#define RELAY_SYNC_SAME 3 + +#define RELAY_PULSE_NONE 0 +#define RELAY_PULSE_OFF 1 +#define RELAY_PULSE_ON 2 + +#define RELAY_PROVIDER_RELAY 0 +#define RELAY_PROVIDER_DUAL 1 +#define RELAY_PROVIDER_LIGHT 2 +#define RELAY_PROVIDER_RFBRIDGE 3 +#define RELAY_PROVIDER_STM 4 + +// Default boot mode: 0 means OFF, 1 ON and 2 whatever was before +#define RELAY_BOOT_MODE RELAY_BOOT_OFF + +// 0 means ANY, 1 zero or one and 2 one and only one +#define RELAY_SYNC RELAY_SYNC_ANY + +// Default pulse mode: 0 means no pulses, 1 means normally off, 2 normally on +#define RELAY_PULSE_MODE RELAY_PULSE_NONE + +// Default pulse time in seconds +#define RELAY_PULSE_TIME 1.0 + +// Relay requests flood protection window - in seconds +#define RELAY_FLOOD_WINDOW 3 + +// Allowed actual relay changes inside requests flood protection window +#define RELAY_FLOOD_CHANGES 5 + +// Pulse with in milliseconds for a latched relay +#define RELAY_LATCHING_PULSE 10 + +// Do not save relay state after these many milliseconds +#define RELAY_SAVE_DELAY 1000 + +//------------------------------------------------------------------------------ +// LED +//------------------------------------------------------------------------------ + +#define LED_MODE_MQTT 0 // LED will be managed from MQTT (OFF by default) +#define LED_MODE_WIFI 1 // LED will blink according to the WIFI status +#define LED_MODE_FOLLOW 2 // LED will follow state of linked relay (check RELAY#_LED) +#define LED_MODE_FOLLOW_INVERSE 3 // LED will follow the opposite state of linked relay (check RELAY#_LED) +#define LED_MODE_FINDME 4 // LED will be ON if all relays are OFF +#define LED_MODE_FINDME_WIFI 5 // A mixture between WIFI and FINDME +#define LED_MODE_ON 6 // LED always ON +#define LED_MODE_OFF 7 // LED always OFF +#define LED_MODE_RELAY 8 // If any relay is ON, LED will be ON, otherwise OFF +#define LED_MODE_RELAY_WIFI 9 // A mixture between WIFI and RELAY, the reverse of MIXED + +// ----------------------------------------------------------------------------- +// WIFI +// ----------------------------------------------------------------------------- + +#define WIFI_CONNECT_TIMEOUT 60000 // Connecting timeout for WIFI in ms +#define WIFI_RECONNECT_INTERVAL 180000 // If could not connect to WIFI, retry after this time in ms +#define WIFI_MAX_NETWORKS 5 // Max number of WIFI connection configurations +#define WIFI_AP_MODE AP_MODE_ALONE +#define WIFI_SLEEP_MODE WIFI_NONE_SLEEP // WIFI_NONE_SLEEP, WIFI_LIGHT_SLEEP or WIFI_MODEM_SLEEP +#define WIFI_SCAN_NETWORKS 1 // Perform a network scan before connecting + +// Optional hardcoded configuration (up to 2 networks) +#ifndef WIFI1_SSID +#define WIFI1_SSID "iApfel" +#endif +#ifndef WIFI1_PASS +#define WIFI1_PASS "***REMOVED***" +#endif +#ifndef WIFI1_IP +#define WIFI1_IP "" +#endif +#ifndef WIFI1_GW +#define WIFI1_GW "" +#endif +#ifndef WIFI1_MASK +#define WIFI1_MASK "" +#endif +#ifndef WIFI1_DNS +#define WIFI1_DNS "" +#endif +#ifndef WIFI2_SSID +#define WIFI2_SSID "" +#endif +#ifndef WIFI2_PASS +#define WIFI2_PASS "" +#endif +#ifndef WIFI2_IP +#define WIFI2_IP "" +#endif +#ifndef WIFI2_GW +#define WIFI2_GW "" +#endif +#ifndef WIFI2_MASK +#define WIFI2_MASK "" +#endif +#ifndef WIFI2_DNS +#define WIFI2_DNS "" +#endif + +#define WIFI_RSSI_1M -30 // Calibrate it with your router reading the RSSI at 1m +#define WIFI_PROPAGATION_CONST 4 // This is typically something between 2.7 to 4.3 (free space is 2) + +// ----------------------------------------------------------------------------- +// WEB +// ----------------------------------------------------------------------------- + +#ifndef WEB_SUPPORT +#define WEB_SUPPORT 1 // Enable web support (http, api, 121.65Kb) +#endif + +#ifndef WEB_EMBEDDED +#define WEB_EMBEDDED 1 // Build the firmware with the web interface embedded in +#endif + +// This is not working at the moment!! +// Requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core 2.4.0 +#define WEB_SSL_ENABLED 0 // Use HTTPS web interface + +#define WEB_MODE_NORMAL 0 +#define WEB_MODE_PASSWORD 1 + +#define WEB_USERNAME "ManuelW" // HTTP username +#define WEB_FORCE_PASS_CHANGE 0 // Force the user to change the password if default one +#define WEB_PORT 80 // HTTP port + +// ----------------------------------------------------------------------------- +// WEBSOCKETS +// ----------------------------------------------------------------------------- + +// This will only be enabled if WEB_SUPPORT is 1 (this is the default value) + +#define WS_AUTHENTICATION 1 // WS authentication ON by default (see #507) +#define WS_BUFFER_SIZE 5 // Max number of secured websocket connections +#define WS_TIMEOUT 1800000 // Timeout for secured websocket +#define WS_UPDATE_INTERVAL 30000 // Update clients every 30 seconds + +// ----------------------------------------------------------------------------- +// API +// ----------------------------------------------------------------------------- + +// This will only be enabled if WEB_SUPPORT is 1 (this is the default value) + +#define API_ENABLED 0 // Do not enable API by default +#define API_BUFFER_SIZE 15 // Size of the buffer for HTTP GET API responses +#define API_REAL_TIME_VALUES 0 // Show filtered/median values by default (0 => median, 1 => real time) + +// ----------------------------------------------------------------------------- +// UI +// ----------------------------------------------------------------------------- + +#define UI_TAG_INPUT 0 +#define UI_TAG_CHECKBOX 1 +#define UI_TAG_SELECT 2 + +// ----------------------------------------------------------------------------- +// MDNS / LLMNR / NETBIOS / SSDP +// ----------------------------------------------------------------------------- + +#ifndef MDNS_SERVER_SUPPORT +#define MDNS_SERVER_SUPPORT 1 // Publish services using mDNS by default (1.48Kb) +#endif + +#ifndef MDNS_CLIENT_SUPPORT +#define MDNS_CLIENT_SUPPORT 0 // Resolve mDNS names (3.44Kb) +#endif + +#ifndef LLMNR_SUPPORT +#define LLMNR_SUPPORT 0 // Publish device using LLMNR protocol by default (1.95Kb) - requires 2.4.0 +#endif + +#ifndef NETBIOS_SUPPORT +#define NETBIOS_SUPPORT 0 // Publish device using NetBIOS protocol by default (1.26Kb) - requires 2.4.0 +#endif + +#ifndef SSDP_SUPPORT +#define SSDP_SUPPORT 0 // Publish device using SSDP protocol by default (4.59Kb) + // Not compatible with ALEXA_SUPPORT at the moment +#endif + +#ifndef SSDP_DEVICE_TYPE +#define SSDP_DEVICE_TYPE "upnp:rootdevice" +//#define SSDP_DEVICE_TYPE "urn:schemas-upnp-org:device:BinaryLight:1" +#endif + +#if WEB_SUPPORT == 0 +#undef SSDP_SUPPORT +#define SSDP_SUPPORT 0 // SSDP support requires web support +#endif + +// ----------------------------------------------------------------------------- +// SPIFFS +// ----------------------------------------------------------------------------- + +#ifndef SPIFFS_SUPPORT +#define SPIFFS_SUPPORT 0 // Do not add support for SPIFFS by default +#endif + +// ----------------------------------------------------------------------------- +// OTA +// ----------------------------------------------------------------------------- + +#define OTA_PORT 8266 // OTA port +#define OTA_GITHUB_FP "D7:9F:07:61:10:B3:92:93:E3:49:AC:89:84:5B:03:80:C1:9E:2F:8B" + +// ----------------------------------------------------------------------------- +// NOFUSS +// ----------------------------------------------------------------------------- + +#ifndef NOFUSS_SUPPORT +#define NOFUSS_SUPPORT 0 // Do not enable support for NoFuss by default (12.65Kb) +#endif + +#define NOFUSS_ENABLED 0 // Do not perform NoFUSS updates by default +#define NOFUSS_SERVER "" // Default NoFuss Server +#define NOFUSS_INTERVAL 3600000 // Check for updates every hour + +// ----------------------------------------------------------------------------- +// UART <-> MQTT +// ----------------------------------------------------------------------------- + +#ifndef UART_MQTT_SUPPORT +#define UART_MQTT_SUPPORT 0 // No support by default +#endif + +#define UART_MQTT_USE_SOFT 0 // Use SoftwareSerial +#define UART_MQTT_HW_PORT Serial // Hardware serial port (if UART_MQTT_USE_SOFT == 0) +#define UART_MQTT_RX_PIN 4 // RX PIN (if UART_MQTT_USE_SOFT == 1) +#define UART_MQTT_TX_PIN 5 // TX PIN (if UART_MQTT_USE_SOFT == 1) +#define UART_MQTT_BAUDRATE 115200 // Serial speed +#define UART_MQTT_BUFFER_SIZE 100 // UART buffer size + +#if UART_MQTT_SUPPORT +#define MQTT_SUPPORT 1 +#undef TERMINAL_SUPPORT +#define TERMINAL_SUPPORT 0 +#undef DEBUG_SERIAL_SUPPORT +#define DEBUG_SERIAL_SUPPORT 0 +#endif + +// ----------------------------------------------------------------------------- +// MQTT +// ----------------------------------------------------------------------------- + +#ifndef MQTT_SUPPORT +#define MQTT_SUPPORT 1 // MQTT support (22.38Kb async, 12.48Kb sync) +#endif + + +#ifndef MQTT_USE_ASYNC +#define MQTT_USE_ASYNC 1 // Use AysncMQTTClient (1) or PubSubClient (0) +#endif + +// MQTT OVER SSL +// Using MQTT over SSL works pretty well but generates problems with the web interface. +// It could be a good idea to use it in conjuntion with WEB_SUPPORT=0. +// Requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core 2.4.0. +// +// You can use SSL with MQTT_USE_ASYNC=1 (AsyncMqttClient library) +// but you might experience hiccups on the web interface, so my recommendation is: +// WEB_SUPPORT=0 +// +// If you use SSL with MQTT_USE_ASYNC=0 (PubSubClient library) +// you will have to disable all the modules that use ESPAsyncTCP, that is: +// ALEXA_SUPPORT=0, INFLUXDB_SUPPORT=0, TELNET_SUPPORT=0, THINGSPEAK_SUPPORT=0 and WEB_SUPPORT=0 +// +// You will need the fingerprint for your MQTT server, example for CloudMQTT: +// $ echo -n | openssl s_client -connect m11.cloudmqtt.com:24055 > cloudmqtt.pem +// $ openssl x509 -noout -in cloudmqtt.pem -fingerprint -sha1 + +#define MQTT_SSL_ENABLED 0 // By default MQTT over SSL will not be enabled +#define MQTT_SSL_FINGERPRINT "" // SSL fingerprint of the server + +#define MQTT_ENABLED 0 // Do not enable MQTT connection by default +#define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SERVER_SUPPORT=1 will perform an autodiscover and + // autoconnect to the first MQTT broker found if none defined +#define MQTT_SERVER "" // Default MQTT broker address +#define MQTT_USER "" // Default MQTT broker usename +#define MQTT_PASS "" // Default MQTT broker password +#define MQTT_PORT 1883 // MQTT broker port +#define MQTT_TOPIC "{hostname}" // Default MQTT base topic +#define MQTT_RETAIN true // MQTT retain flag +#define MQTT_QOS 0 // MQTT QoS value for all messages +#define MQTT_KEEPALIVE 30 // MQTT keepalive value + +#define MQTT_RECONNECT_DELAY_MIN 5000 // Try to reconnect in 5 seconds upon disconnection +#define MQTT_RECONNECT_DELAY_STEP 5000 // Increase the reconnect delay in 5 seconds after each failed attempt +#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most + +#define MQTT_SKIP_RETAINED 1 // Skip retained messages on connection +#define MQTT_SKIP_TIME 1000 // Skip messages for 1 second anter connection + +#define MQTT_USE_JSON 0 // Group messages in a JSON body +#define MQTT_USE_JSON_DELAY 100 // Wait this many ms before grouping messages +#define MQTT_QUEUE_MAX_SIZE 20 // Size of the MQTT queue when MQTT_USE_JSON is enabled + +// These are the properties that will be sent when useJson is true +#ifndef MQTT_ENQUEUE_IP +#define MQTT_ENQUEUE_IP 1 +#endif +#ifndef MQTT_ENQUEUE_MAC +#define MQTT_ENQUEUE_MAC 1 +#endif +#ifndef MQTT_ENQUEUE_HOSTNAME +#define MQTT_ENQUEUE_HOSTNAME 1 +#endif +#ifndef MQTT_ENQUEUE_DATETIME +#define MQTT_ENQUEUE_DATETIME 1 +#endif +#ifndef MQTT_ENQUEUE_MESSAGE_ID +#define MQTT_ENQUEUE_MESSAGE_ID 1 +#endif + +// These particles will be concatenated to the MQTT_TOPIC base to form the actual topic +#define MQTT_TOPIC_JSON "data" +#define MQTT_TOPIC_ACTION "action" +#define MQTT_TOPIC_RELAY "relay" +#define MQTT_TOPIC_LED "led" +#define MQTT_TOPIC_BUTTON "button" +#define MQTT_TOPIC_IP "ip" +#define MQTT_TOPIC_VERSION "version" +#define MQTT_TOPIC_UPTIME "uptime" +#define MQTT_TOPIC_DATETIME "datetime" +#define MQTT_TOPIC_FREEHEAP "freeheap" +#define MQTT_TOPIC_VCC "vcc" +#define MQTT_TOPIC_STATUS "status" +#define MQTT_TOPIC_MAC "mac" +#define MQTT_TOPIC_RSSI "rssi" +#define MQTT_TOPIC_MESSAGE_ID "id" +#define MQTT_TOPIC_APP "app" +#define MQTT_TOPIC_INTERVAL "interval" +#define MQTT_TOPIC_HOSTNAME "host" +#define MQTT_TOPIC_TIME "time" +#define MQTT_TOPIC_RFOUT "rfout" +#define MQTT_TOPIC_RFIN "rfin" +#define MQTT_TOPIC_RFLEARN "rflearn" +#define MQTT_TOPIC_RFRAW "rfraw" +#define MQTT_TOPIC_UARTIN "uartin" +#define MQTT_TOPIC_UARTOUT "uartout" +#define MQTT_TOPIC_LOADAVG "loadavg" +#define MQTT_TOPIC_BOARD "board" + +// Light module +#define MQTT_TOPIC_CHANNEL "channel" +#define MQTT_TOPIC_COLOR_RGB "rgb" +#define MQTT_TOPIC_COLOR_HSV "hsv" +#define MQTT_TOPIC_ANIM_MODE "anim_mode" +#define MQTT_TOPIC_ANIM_SPEED "anim_speed" +#define MQTT_TOPIC_BRIGHTNESS "brightness" +#define MQTT_TOPIC_MIRED "mired" +#define MQTT_TOPIC_KELVIN "kelvin" + +#define MQTT_STATUS_ONLINE "1" // Value for the device ON message +#define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will) + +#define MQTT_ACTION_RESET "reboot" // RESET MQTT topic particle + +// Internal MQTT events (do not change) +#define MQTT_CONNECT_EVENT 0 +#define MQTT_DISCONNECT_EVENT 1 +#define MQTT_MESSAGE_EVENT 2 + +#define MQTT_MESSAGE_ID_SHIFT 1000 // Store MQTT message id into EEPROM every these many + +// Custom get and set postfixes +// Use something like "/status" or "/set", with leading slash +// Since 1.9.0 the default value is "" for getter and "/set" for setter +#ifndef MQTT_GETTER +#define MQTT_GETTER "" +#endif +#ifndef MQTT_SETTER +#define MQTT_SETTER "/set" +#endif + +// ----------------------------------------------------------------------------- +// BROKER +// ----------------------------------------------------------------------------- + +#ifndef BROKER_SUPPORT +#define BROKER_SUPPORT 1 // The broker is a poor-man's pubsub manager +#endif + +// ----------------------------------------------------------------------------- +// SETTINGS +// ----------------------------------------------------------------------------- + +#ifndef SETTINGS_AUTOSAVE +#define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit +#endif + +#define SETTINGS_MAX_LIST_COUNT 10 // Maximum index for settings lists + +// ----------------------------------------------------------------------------- +// LIGHT +// ----------------------------------------------------------------------------- + +// Available light providers (do not change) +#define LIGHT_PROVIDER_NONE 0 +#define LIGHT_PROVIDER_MY92XX 1 // works with MY9291 and MY9231 +#define LIGHT_PROVIDER_DIMMER 2 + +// LIGHT_PROVIDER_DIMMER can have from 1 to 5 different channels. +// They have to be defined for each device in the hardware.h file. +// If 3 or more channels first 3 will be considered RGB. +// Usual configurations are: +// 1 channels => W +// 2 channels => WW +// 3 channels => RGB +// 4 channels => RGBW +// 5 channels => RGBWW + +#ifndef LIGHT_SAVE_ENABLED +#define LIGHT_SAVE_ENABLED 1 // Light channel values saved by default after each change +#endif + +#define LIGHT_SAVE_DELAY 5 // Persist color after 5 seconds to avoid wearing out + +#ifndef LIGHT_MAX_PWM + +#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX +#define LIGHT_MAX_PWM 255 +#endif + +#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER +#define LIGHT_MAX_PWM 10000 // 5000 * 200ns => 1 kHz +#endif + +#endif // LIGHT_MAX_PWM + +#ifndef LIGHT_LIMIT_PWM +#define LIGHT_LIMIT_PWM LIGHT_MAX_PWM // Limit PWM to this value (prevent 100% power) +#endif + +#ifndef LIGHT_MAX_VALUE +#define LIGHT_MAX_VALUE 255 // Maximum light value +#endif + +#define LIGHT_MAX_BRIGHTNESS 255 // Maximun brightness value +//#define LIGHT_MIN_MIREDS 153 // NOT USED (yet)! // Default to the Philips Hue value that HA has always assumed +//#define LIGHT_MAX_MIREDS 500 // NOT USED (yet)! // https://developers.meethue.com/documentation/core-concepts +#define LIGHT_DEFAULT_MIREDS 153 // Default value used by MQTT. This value is __NEVRER__ applied! +#define LIGHT_STEP 32 // Step size +#define LIGHT_USE_COLOR 1 // Use 3 first channels as RGB +#define LIGHT_USE_WHITE 0 // Use white channel whenever RGB have the same value +#define LIGHT_USE_GAMMA 0 // Use gamma correction for color channels +#define LIGHT_USE_CSS 1 // Use CSS style to report colors (1=> "#FF0000", 0=> "255,0,0") +#define LIGHT_USE_RGB 0 // Use RGB color selector (1=> RGB, 0=> HSV) + +#define LIGHT_USE_TRANSITIONS 1 // Transitions between colors +#define LIGHT_TRANSITION_STEP 10 // Time in millis between each transtion step +#define LIGHT_TRANSITION_TIME 500 // Time in millis from color to color + +// ----------------------------------------------------------------------------- +// DOMOTICZ +// ----------------------------------------------------------------------------- + +#ifndef DOMOTICZ_SUPPORT +#define DOMOTICZ_SUPPORT MQTT_SUPPORT // Build with domoticz (if MQTT) support (1.72Kb) +#endif + +#if DOMOTICZ_SUPPORT +#undef MQTT_SUPPORT +#define MQTT_SUPPORT 1 // If Domoticz enabled enable MQTT +#endif + +#define DOMOTICZ_ENABLED 0 // Disable domoticz by default +#define DOMOTICZ_IN_TOPIC "domoticz/in" // Default subscription topic +#define DOMOTICZ_OUT_TOPIC "domoticz/out" // Default publication topic + +// ----------------------------------------------------------------------------- +// HOME ASSISTANT +// ----------------------------------------------------------------------------- + +#ifndef HOMEASSISTANT_SUPPORT +#define HOMEASSISTANT_SUPPORT MQTT_SUPPORT // Build with home assistant support (if MQTT, 1.64Kb) +#endif + +#if HOMEASSISTANT_SUPPORT +#undef MQTT_SUPPORT +#define MQTT_SUPPORT 1 // If Home Assistant enabled enable MQTT +#endif + +#define HOMEASSISTANT_ENABLED 0 // Integration not enabled by default +#define HOMEASSISTANT_PREFIX "homeassistant" // Default MQTT prefix + +// ----------------------------------------------------------------------------- +// INFLUXDB +// ----------------------------------------------------------------------------- + +#ifndef INFLUXDB_SUPPORT +#define INFLUXDB_SUPPORT 0 // Disable InfluxDB support by default (4.38Kb) +#endif + +#define INFLUXDB_ENABLED 0 // InfluxDB disabled by default +#define INFLUXDB_HOST "" // Default server +#define INFLUXDB_PORT 8086 // Default InfluxDB port +#define INFLUXDB_DATABASE "" // Default database +#define INFLUXDB_USERNAME "" // Default username +#define INFLUXDB_PASSWORD "" // Default password + +// ----------------------------------------------------------------------------- +// THINGSPEAK +// ----------------------------------------------------------------------------- + +#ifndef THINGSPEAK_SUPPORT +#define THINGSPEAK_SUPPORT 1 // Enable Thingspeak support by default (2.56Kb) +#endif + +#define THINGSPEAK_ENABLED 0 // Thingspeak disabled by default +#define THINGSPEAK_APIKEY "" // Default API KEY + +#define THINGSPEAK_USE_ASYNC 1 // Use AsyncClient instead of WiFiClientSecure + +// THINGSPEAK OVER SSL +// Using THINGSPEAK over SSL works well but generates problems with the web interface, +// so you should compile it with WEB_SUPPORT to 0. +// When THINGSPEAK_USE_ASYNC is 1, requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core 2.4.0. +#define THINGSPEAK_USE_SSL 0 // Use secure connection +#define THINGSPEAK_FINGERPRINT "78 60 18 44 81 35 BF DF 77 84 D4 0A 22 0D 9B 4E 6C DC 57 2C" + +#define THINGSPEAK_HOST "api.thingspeak.com" +#if THINGSPEAK_USE_SSL +#define THINGSPEAK_PORT 443 +#else +#define THINGSPEAK_PORT 80 +#endif +#define THINGSPEAK_URL "/update" +#define THINGSPEAK_MIN_INTERVAL 15000 // Minimum interval between POSTs (in millis) + +#ifndef ASYNC_TCP_SSL_ENABLED +#if THINGSPEAK_USE_SSL && THINGSPEAK_USE_ASYNC +#undef THINGSPEAK_SUPPORT // Thingspeak in ASYNC mode requires ASYNC_TCP_SSL_ENABLED +#endif +#endif + +// ----------------------------------------------------------------------------- +// SCHEDULER +// ----------------------------------------------------------------------------- + +#define SCHEDULER_TYPE_SWITCH 1 +#define SCHEDULER_TYPE_DIM 2 + +#ifndef SCHEDULER_SUPPORT +#define SCHEDULER_SUPPORT 1 // Enable scheduler (1.77Kb) +#endif + +#if SCHEDULER_SUPPORT +#undef NTP_SUPPORT +#define NTP_SUPPORT 1 // Scheduler needs NTP +#endif + +#define SCHEDULER_MAX_SCHEDULES 10 // Max schedules alowed + +// ----------------------------------------------------------------------------- +// NTP +// ----------------------------------------------------------------------------- + +#ifndef NTP_SUPPORT +#define NTP_SUPPORT 1 // Build with NTP support by default (6.78Kb) +#endif + +#define NTP_SERVER "pool.ntp.org" // Default NTP server +#define NTP_TIMEOUT 2000 // Set NTP request timeout to 2 seconds (issue #452) +#define NTP_TIME_OFFSET 60 // Default timezone offset (GMT+1) +#define NTP_DAY_LIGHT true // Enable daylight time saving by default +#define NTP_SYNC_INTERVAL 60 // NTP initial check every minute +#define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes +#define NTP_START_DELAY 1000 // Delay NTP start 1 second +#define NTP_DST_REGION 0 // 0 for Europe, 1 for USA (defined in NtpClientLib) + +// ----------------------------------------------------------------------------- +// ALEXA +// ----------------------------------------------------------------------------- + +// This setting defines whether Alexa support should be built into the firmware +#ifndef ALEXA_SUPPORT +#define ALEXA_SUPPORT 1 // Enable Alexa support by default (10.84Kb) +#endif + +// This is default value for the alexaEnabled setting that defines whether +// this device should be discoberable and respond to Alexa commands. +// Both ALEXA_SUPPORT and alexaEnabled should be 1 for Alexa support to work. +#define ALEXA_ENABLED 1 + +// ----------------------------------------------------------------------------- +// RFBRIDGE +// This module is not compatible with RF_SUPPORT=1 +// ----------------------------------------------------------------------------- + +#define RF_SEND_TIMES 4 // How many times to send the message +#define RF_SEND_DELAY 500 // Interval between sendings in ms +#define RF_RECEIVE_DELAY 500 // Interval between recieving in ms (avoid debouncing) + +#define RF_RAW_SUPPORT 0 // RF raw codes require a specific firmware for the EFM8BB1 + // https://github.com/rhx/RF-Bridge-EFM8BB1 + +// ----------------------------------------------------------------------------- +// IR +// ----------------------------------------------------------------------------- + +#ifndef IR_SUPPORT +#define IR_SUPPORT 0 // Do not build with IR support by default (10.25Kb) +#endif + +#ifndef IR_PIN +#define IR_PIN 4 // IR LED +#endif + +// 24 Buttons Set of the IR Remote +#ifndef IR_BUTTON_SET +#define IR_BUTTON_SET 1 // IR button set to use (see below) +#endif + +// IR Button modes +#define IR_BUTTON_MODE_NONE 0 +#define IR_BUTTON_MODE_RGB 1 +#define IR_BUTTON_MODE_HSV 2 +#define IR_BUTTON_MODE_BRIGHTER 3 +#define IR_BUTTON_MODE_STATE 4 +#define IR_BUTTON_MODE_EFFECT 5 + +#define LIGHT_EFFECT_SOLID 0 +#define LIGHT_EFFECT_FLASH 1 +#define LIGHT_EFFECT_STROBE 2 +#define LIGHT_EFFECT_FADE 3 +#define LIGHT_EFFECT_SMOOTH 4 + +//Remote Buttons SET 1 (for the original Remote shipped with the controller) +#if IR_SUPPORT +#if IR_BUTTON_SET == 1 + +/* + +------+------+------+------+ + | UP | Down | OFF | ON | + +------+------+------+------+ + | R | G | B | W | + +------+------+------+------+ + | 1 | 2 | 3 |FLASH | + +------+------+------+------+ + | 4 | 5 | 6 |STROBE| + +------+------+------+------+ + | 7 | 8 | 9 | FADE | + +------+------+------+------+ + | 10 | 11 | 12 |SMOOTH| + +------+------+------+------+ +*/ + + #define IR_BUTTON_COUNT 24 + + const unsigned long IR_BUTTON[IR_BUTTON_COUNT][3] PROGMEM = { + + { 0xFF906F, IR_BUTTON_MODE_BRIGHTER, 1 }, + { 0xFFB847, IR_BUTTON_MODE_BRIGHTER, 0 }, + { 0xFFF807, IR_BUTTON_MODE_STATE, 0 }, + { 0xFFB04F, IR_BUTTON_MODE_STATE, 1 }, + + { 0xFF9867, IR_BUTTON_MODE_RGB, 0xFF0000 }, + { 0xFFD827, IR_BUTTON_MODE_RGB, 0x00FF00 }, + { 0xFF8877, IR_BUTTON_MODE_RGB, 0x0000FF }, + { 0xFFA857, IR_BUTTON_MODE_RGB, 0xFFFFFF }, + + { 0xFFE817, IR_BUTTON_MODE_RGB, 0xD13A01 }, + { 0xFF48B7, IR_BUTTON_MODE_RGB, 0x00E644 }, + { 0xFF6897, IR_BUTTON_MODE_RGB, 0x0040A7 }, + { 0xFFB24D, IR_BUTTON_MODE_EFFECT, LIGHT_EFFECT_FLASH }, + + { 0xFF02FD, IR_BUTTON_MODE_RGB, 0xE96F2A }, + { 0xFF32CD, IR_BUTTON_MODE_RGB, 0x00BEBF }, + { 0xFF20DF, IR_BUTTON_MODE_RGB, 0x56406F }, + { 0xFF00FF, IR_BUTTON_MODE_EFFECT, LIGHT_EFFECT_STROBE }, + + { 0xFF50AF, IR_BUTTON_MODE_RGB, 0xEE9819 }, + { 0xFF7887, IR_BUTTON_MODE_RGB, 0x00799A }, + { 0xFF708F, IR_BUTTON_MODE_RGB, 0x944E80 }, + { 0xFF58A7, IR_BUTTON_MODE_EFFECT, LIGHT_EFFECT_FADE }, + + { 0xFF38C7, IR_BUTTON_MODE_RGB, 0xFFFF00 }, + { 0xFF28D7, IR_BUTTON_MODE_RGB, 0x0060A1 }, + { 0xFFF00F, IR_BUTTON_MODE_RGB, 0xEF45AD }, + { 0xFF30CF, IR_BUTTON_MODE_EFFECT, LIGHT_EFFECT_SMOOTH } + + }; + +#endif + +//Remote Buttons SET 2 (another identical IR Remote shipped with another controller) +#if IR_BUTTON_SET == 2 + +/* + +------+------+------+------+ + | UP | Down | OFF | ON | + +------+------+------+------+ + | R | G | B | W | + +------+------+------+------+ + | 1 | 2 | 3 |FLASH | + +------+------+------+------+ + | 4 | 5 | 6 |STROBE| + +------+------+------+------+ + | 7 | 8 | 9 | FADE | + +------+------+------+------+ + | 10 | 11 | 12 |SMOOTH| + +------+------+------+------+ +*/ + + #define IR_BUTTON_COUNT 24 + + const unsigned long IR_BUTTON[IR_BUTTON_COUNT][3] PROGMEM = { + + { 0xFF00FF, IR_BUTTON_MODE_BRIGHTER, 1 }, + { 0xFF807F, IR_BUTTON_MODE_BRIGHTER, 0 }, + { 0xFF40BF, IR_BUTTON_MODE_STATE, 0 }, + { 0xFFC03F, IR_BUTTON_MODE_STATE, 1 }, + + { 0xFF20DF, IR_BUTTON_MODE_RGB, 0xFF0000 }, + { 0xFFA05F, IR_BUTTON_MODE_RGB, 0x00FF00 }, + { 0xFF609F, IR_BUTTON_MODE_RGB, 0x0000FF }, + { 0xFFE01F, IR_BUTTON_MODE_RGB, 0xFFFFFF }, + + { 0xFF10EF, IR_BUTTON_MODE_RGB, 0xD13A01 }, + { 0xFF906F, IR_BUTTON_MODE_RGB, 0x00E644 }, + { 0xFF50AF, IR_BUTTON_MODE_RGB, 0x0040A7 }, + { 0xFFD02F, IR_BUTTON_MODE_EFFECT, LIGHT_EFFECT_FLASH }, + + { 0xFF30CF, IR_BUTTON_MODE_RGB, 0xE96F2A }, + { 0xFFB04F, IR_BUTTON_MODE_RGB, 0x00BEBF }, + { 0xFF708F, IR_BUTTON_MODE_RGB, 0x56406F }, + { 0xFFF00F, IR_BUTTON_MODE_EFFECT, LIGHT_EFFECT_STROBE }, + + { 0xFF08F7, IR_BUTTON_MODE_RGB, 0xEE9819 }, + { 0xFF8877, IR_BUTTON_MODE_RGB, 0x00799A }, + { 0xFF48B7, IR_BUTTON_MODE_RGB, 0x944E80 }, + { 0xFFC837, IR_BUTTON_MODE_EFFECT, LIGHT_EFFECT_FADE }, + + { 0xFF28D7, IR_BUTTON_MODE_RGB, 0xFFFF00 }, + { 0xFFA857, IR_BUTTON_MODE_RGB, 0x0060A1 }, + { 0xFF6897, IR_BUTTON_MODE_RGB, 0xEF45AD }, + { 0xFFE817, IR_BUTTON_MODE_EFFECT, LIGHT_EFFECT_SMOOTH } + + }; + +#endif + +#endif // IR_SUPPORT + +//-------------------------------------------------------------------------------- +// Custom RF module +// Check http://tinkerman.cat/adding-rf-to-a-non-rf-itead-sonoff/ +// Enable support by passing RF_SUPPORT=1 build flag +// This module is not compatible with RFBRIDGE +//-------------------------------------------------------------------------------- + +#ifndef RF_SUPPORT +#define RF_SUPPORT 0 +#endif + +#ifndef RF_PIN +#define RF_PIN 14 +#endif + +#define RF_DEBOUNCE 500 +#define RF_LEARN_TIMEOUT 60000 diff --git a/espurna/config/hardware.h b/espurna/config/hardware.h new file mode 100755 index 0000000..208ecae --- /dev/null +++ b/espurna/config/hardware.h @@ -0,0 +1,2023 @@ +// ----------------------------------------------------------------------------- +// Configuration HELP +// ----------------------------------------------------------------------------- +// +// MANUFACTURER: Name of the manufacturer of the board ("string") +// DEVICE: Name of the device ("string") +// BUTTON#_PIN: GPIO for the n-th button (1-based, up to 4 buttons) +// BUTTON#_RELAY: Relay number that will be bind to the n-th button (1-based) +// BUTTON#_MODE: A mask of options (BUTTON_PUSHBUTTON and BUTTON_SWITCH cannot be together) +// - BUTTON_PUSHBUTTON: button event is fired when released +// - BUTTON_SWITCH: button event is fired when pressed or released +// - BUTTON_DEFAULT_HIGH: there is a pull up in place +// - BUTTON_SET_PULLUP: set pullup by software +// RELAY#_PIN: GPIO for the n-th relay (1-based, up to 8 relays) +// RELAY#_TYPE: Relay can be RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE +// LED#_PIN: GPIO for the n-th LED (1-based, up to 8 LEDs) +// LED#_PIN_INVERSE: LED has inversed logic (lit when pulled down) +// LED#_MODE: Check general.h for LED_MODE_% +// LED#_RELAY: Linked relay (1-based) +// +// Besides, other hardware specific information should be stated here + +// ----------------------------------------------------------------------------- +// ESPurna Core +// ----------------------------------------------------------------------------- + +#if defined(ESPURNA_CORE) + + // This is a special device targeted to generate a light-weight binary image + // meant to be able to do two-step-updates: + // https://github.com/xoseperez/espurna/wiki/TwoStepUpdates + + // Info + #define MANUFACTURER "ESPRESSIF" + #define DEVICE "ESPURNA_CORE" + + // Disable non-core modules + #define ALEXA_SUPPORT 0 + #define BROKER_SUPPORT 0 + #define DOMOTICZ_SUPPORT 0 + #define HOMEASSISTANT_SUPPORT 0 + #define I2C_SUPPORT 0 + #define MQTT_SUPPORT 0 + #define NTP_SUPPORT 0 + #define SCHEDULER_SUPPORT 0 + #define SENSOR_SUPPORT 0 + #define THINGSPEAK_SUPPORT 0 + #define WEB_SUPPORT 0 + +// ----------------------------------------------------------------------------- +// Development boards +// ----------------------------------------------------------------------------- + +#elif defined(NODEMCU_LOLIN) + + // Info + #define MANUFACTURER "NODEMCU" + #define DEVICE "LOLIN" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 1 + +#elif defined(WEMOS_D1_MINI_RELAYSHIELD) + + // Info + #define MANUFACTURER "WEMOS" + #define DEVICE "D1_MINI_RELAYSHIELD" + + // Buttons + // No buttons on the D1 MINI alone, but defining it without adding a button doen't create problems + #define BUTTON1_PIN 0 // Connect a pushbutton between D3 and GND, + // it's the same as using a Wemos one button shield + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + #define BUTTON2_PIN 15 + #define BUTTON2_RELAY 1 + #define BUTTON2_MODE BUTTON_PUSHBUTTON + #define BUTTON2_PRESS BUTTON_MODE_ON + #define BUTTON2_CLICK BUTTON_MODE_OFF + #define BUTTON2_DBLCLICK BUTTON_MODE_OFF + #define BUTTON2_LNGCLICK BUTTON_MODE_OFF + #define BUTTON2_LNGLNGCLICK BUTTON_MODE_OFF + + // Relays + #define RELAY1_PIN 4 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_PIN 12 + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 1 + + // When Wemos relay shield is connected GPIO5 (D1) is used for relay, + // so I2C must be remapped to other pins + //#define I2C_SDA_PIN 12 // D6 + //#define I2C_SCL_PIN 14 // D5 + +#elif defined(WEMOS_D1_TARPUNA_SHIELD) + + // Info + #define MANUFACTURER "WEMOS" + #define DEVICE "D1_TARPUNA_SHIELD" + +// ----------------------------------------------------------------------------- +// ESPurna +// ----------------------------------------------------------------------------- + +#elif defined(TINKERMAN_ESPURNA_H06) + + // Info + #define MANUFACTURER "TINKERMAN" + #define DEVICE "ESPURNA_H06" + + // Buttons + #define BUTTON1_PIN 4 + #define BUTTON1_RELAY 1 + + // Normal pushbutton + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_INVERSE + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 1 + + // HLW8012 + #ifndef HLW8012_SUPPORT + #define HLW8012_SUPPORT 1 + #endif + #define HLW8012_SEL_PIN 2 + #define HLW8012_CF1_PIN 13 + #define HLW8012_CF_PIN 14 + +#elif defined(TINKERMAN_ESPURNA_H08) + + // Info + #define MANUFACTURER "TINKERMAN" + #define DEVICE "ESPURNA_H08" + + // Buttons + #define BUTTON1_PIN 4 + #define BUTTON1_RELAY 1 + + // Normal pushbutton + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 0 + + // HLW8012 + #ifndef HLW8012_SUPPORT + #define HLW8012_SUPPORT 1 + #endif + #define HLW8012_SEL_PIN 5 + #define HLW8012_CF1_PIN 13 + #define HLW8012_CF_PIN 14 + +#elif defined(TINKERMAN_ESPURNA_SWITCH) + + // Info + #define MANUFACTURER "TINKERMAN" + #define DEVICE "ESPURNA_SWITCH" + + // Buttons + #define BUTTON1_PIN 4 + #define BUTTON1_RELAY 1 + + // Touch button + #define BUTTON1_MODE BUTTON_PUSHBUTTON + #define BUTTON1_PRESS BUTTON_MODE_TOGGLE + #define BUTTON1_CLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 0 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_INVERSE + +// ----------------------------------------------------------------------------- +// Itead Studio boards +// ----------------------------------------------------------------------------- + +#elif defined(ITEAD_SONOFF_BASIC) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_BASIC" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + #define BUTTON2_PIN 14 + #define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH + #define BUTTON2_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_RF) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_RF" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + #define BUTTON2_PIN 14 + #define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH + #define BUTTON2_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_TH) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_TH" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + + // Jack is connected to GPIO14 (and with a small hack to GPIO4) + #ifndef DALLAS_SUPPORT + #define DALLAS_SUPPORT 1 + #endif + #define DALLAS_PIN 14 + + #ifndef DHT_SUPPORT + #define DHT_SUPPORT 1 + #endif + #define DHT_PIN 14 + + //#define I2C_SDA_PIN 4 + //#define I2C_SCL_PIN 14 + +#elif defined(ITEAD_SONOFF_SV) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_SV" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SLAMPHER) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SLAMPHER" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_S20) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "S20" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_TOUCH) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_TOUCH" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_PRESS BUTTON_MODE_TOGGLE + #define BUTTON1_CLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_POW) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_POW" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 15 + #define LED1_PIN_INVERSE 0 + + // HLW8012 + #ifndef HLW8012_SUPPORT + #define HLW8012_SUPPORT 1 + #endif + #define HLW8012_SEL_PIN 5 + #define HLW8012_CF1_PIN 13 + #define HLW8012_CF_PIN 14 + +#elif defined(ITEAD_SONOFF_DUAL) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_DUAL" + #define SERIAL_BAUDRATE 19230 + #define RELAY_PROVIDER RELAY_PROVIDER_DUAL + #define DUMMY_RELAY_COUNT 2 + #define DEBUG_SERIAL_SUPPORT 0 + #define TERMINAL_SUPPORT 0 + + // Buttons + #define BUTTON3_RELAY 1 + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_DUAL_R2) + + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_DUAL_R2" + + // Buttons + #define BUTTON1_PIN 0 // Button 0 on header + #define BUTTON2_PIN 9 // Button 1 on header + #define BUTTON3_PIN 10 // Physical button + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + #define BUTTON3_RELAY 1 + #define BUTTON1_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH + #define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH + #define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // Relays + #define RELAY1_PIN 12 + #define RELAY2_PIN 5 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_4CH) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_4CH" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON2_PIN 9 + #define BUTTON3_PIN 10 + #define BUTTON4_PIN 14 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON4_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + #define BUTTON3_RELAY 3 + #define BUTTON4_RELAY 4 + + // Relays + #define RELAY1_PIN 12 + #define RELAY2_PIN 5 + #define RELAY3_PIN 4 + #define RELAY4_PIN 15 + + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + #define RELAY3_TYPE RELAY_TYPE_NORMAL + #define RELAY4_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_4CH_PRO) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_4CH_PRO" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON2_PIN 9 + #define BUTTON3_PIN 10 + #define BUTTON4_PIN 14 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON4_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + #define BUTTON3_RELAY 3 + #define BUTTON4_RELAY 4 + + // Sonoff 4CH Pro uses a secondary STM32 microcontroller to handle + // buttons and relays, but it also forwards button presses to the ESP8285. + // This allows ESPurna to handle button presses -almost- the same way + // as with other devices except: + // * Double click seems to break/disable the button on the STM32 side + // * With S6 switch to 1 (self-locking and inching modes) everything's OK + // * With S6 switch to 0 (interlock mode) if there is a relay ON + // and you click on another relay button, the STM32 sends a "press" + // event for the button of the first relay (to turn it OFF) but it + // does not send a "release" event. It's like it's holding the + // button down since you can see it is still LOW. + // Whatever reason the result is that it may actually perform a + // long click or long-long click. + // The configuration below make the button toggle the relay on press events + // and disables any possibly harmful combination with S6 set to 0. + // If you are sure you will only use S6 to 1 you can comment the + // BUTTON1_LNGCLICK and BUTTON1_LNGLNGCLICK options below to recover the + // reset mode and factory reset functionalities, or link other actions like + // AP mode in the commented line below. + + #define BUTTON1_PRESS BUTTON_MODE_TOGGLE + #define BUTTON1_CLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + //#define BUTTON1_LNGCLICK BUTTON_MODE_AP + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE + #define BUTTON2_PRESS BUTTON_MODE_TOGGLE + #define BUTTON2_CLICK BUTTON_MODE_NONE + #define BUTTON3_PRESS BUTTON_MODE_TOGGLE + #define BUTTON3_CLICK BUTTON_MODE_NONE + #define BUTTON4_PRESS BUTTON_MODE_TOGGLE + #define BUTTON4_CLICK BUTTON_MODE_NONE + + // Relays + #define RELAY1_PIN 12 + #define RELAY2_PIN 5 + #define RELAY3_PIN 4 + #define RELAY4_PIN 15 + + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + #define RELAY3_TYPE RELAY_TYPE_NORMAL + #define RELAY4_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_1CH_INCHING) + + // The inching functionality is managed by a misterious IC in the board. + // You cannot control the inching button and functionality from the ESP8266 + // Besides, enabling the inching functionality using the hardware button + // will result in the relay switching on and off continuously. + // Fortunately the unkown IC keeps memory of the hardware inching status + // so you can just disable it and forget. The inching LED must be lit. + // You can still use the pulse options from the web interface + // without problem. + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "1CH_INCHING" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_MOTOR) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "MOTOR" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_RELAY 1 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_BNSZ01) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "BNSZ01" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + + // Light + #define LIGHT_CHANNELS 1 + #define LIGHT_CH1_PIN 12 + #define LIGHT_CH1_INVERSE 0 + +#elif defined(ITEAD_SONOFF_RFBRIDGE) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_RFBRIDGE" + #define SERIAL_BAUDRATE 19200 + #define RELAY_PROVIDER RELAY_PROVIDER_RFBRIDGE + + #ifndef DUMMY_RELAY_COUNT + #define DUMMY_RELAY_COUNT 8 + #endif + + // Remove UART noise on serial line + #define TERMINAL_SUPPORT 0 + #define DEBUG_SERIAL_SUPPORT 0 + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_B1) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_B1" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX + #define DUMMY_RELAY_COUNT 1 + + // Light + #define LIGHT_CHANNELS 5 + #define MY92XX_MODEL MY92XX_MODEL_MY9231 + #define MY92XX_CHIPS 2 + #define MY92XX_DI_PIN 12 + #define MY92XX_DCKI_PIN 14 + #define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT + #define MY92XX_MAPPING 4, 3, 5, 0, 1 + +#elif defined(ITEAD_SONOFF_LED) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_LED" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + + // Light + #define LIGHT_CHANNELS 2 + #define LIGHT_CH1_PIN 12 // Cold white + #define LIGHT_CH2_PIN 14 // Warm white + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + +#elif defined(ITEAD_SONOFF_T1_1CH) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_T1_1CH" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_PRESS BUTTON_MODE_TOGGLE + #define BUTTON1_CLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_T1_2CH) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_T1_2CH" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON2_PIN 9 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_PRESS BUTTON_MODE_TOGGLE + #define BUTTON1_CLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET + + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_PRESS BUTTON_MODE_TOGGLE + #define BUTTON2_CLICK BUTTON_MODE_NONE + #define BUTTON2_DBLCLICK BUTTON_MODE_NONE + #define BUTTON2_LNGCLICK BUTTON_MODE_NONE + #define BUTTON2_LNGLNGCLICK BUTTON_MODE_RESET + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + + // Relays + #define RELAY1_PIN 12 + #define RELAY2_PIN 5 + + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_T1_3CH) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_T1_3CH" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON2_PIN 9 + #define BUTTON3_PIN 10 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_PRESS BUTTON_MODE_TOGGLE + #define BUTTON1_CLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET + + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_PRESS BUTTON_MODE_TOGGLE + #define BUTTON2_CLICK BUTTON_MODE_NONE + #define BUTTON2_DBLCLICK BUTTON_MODE_NONE + #define BUTTON2_LNGCLICK BUTTON_MODE_NONE + #define BUTTON2_LNGLNGCLICK BUTTON_MODE_RESET + + #define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON3_PRESS BUTTON_MODE_TOGGLE + #define BUTTON3_CLICK BUTTON_MODE_NONE + #define BUTTON3_DBLCLICK BUTTON_MODE_NONE + #define BUTTON3_LNGCLICK BUTTON_MODE_NONE + #define BUTTON3_LNGLNGCLICK BUTTON_MODE_RESET + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + #define BUTTON3_RELAY 3 + + // Relays + #define RELAY1_PIN 12 + #define RELAY2_PIN 5 + #define RELAY3_PIN 4 + + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + #define RELAY3_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + +#elif defined(ITEAD_SONOFF_S31) + + // Info + #define MANUFACTURER "ITEAD" + #define DEVICE "SONOFF_S31" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 1 + + // Disable UART noise + #define TERMINAL_SUPPORT 0 + #define DEBUG_SERIAL_SUPPORT 0 + + // CSE7766 + #define CSE7766_SUPPORT 1 + #define CSE7766_PIN 1 + +// ----------------------------------------------------------------------------- +// YJZK +// ----------------------------------------------------------------------------- + +#elif defined(YJZK_SWITCH_2CH) + + // Info + #define MANUFACTURER "YJZK" + #define DEVICE "SWITCH_2CH" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON2_PIN 9 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + + // Relays + #define RELAY1_PIN 12 + #define RELAY2_PIN 5 + + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 0 + + +// ----------------------------------------------------------------------------- +// Electrodragon boards +// ----------------------------------------------------------------------------- + +#elif defined(ELECTRODRAGON_WIFI_IOT) + + // Info + #define MANUFACTURER "ELECTRODRAGON" + #define DEVICE "WIFI_IOT" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON2_PIN 2 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + + // Relays + #define RELAY1_PIN 12 + #define RELAY2_PIN 13 + + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 16 + #define LED1_PIN_INVERSE 0 + +// ----------------------------------------------------------------------------- +// WorkChoice ecoPlug +// ----------------------------------------------------------------------------- + +#elif defined(WORKCHOICE_ECOPLUG) + + // Info + #define MANUFACTURER "WORKCHOICE" + #define DEVICE "ECOPLUG" + + // Buttons + #define BUTTON1_PIN 13 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 15 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 0 + +// ----------------------------------------------------------------------------- +// AI Thinker +// ----------------------------------------------------------------------------- + +#elif defined(AITHINKER_AI_LIGHT) + + // Info + #define MANUFACTURER "AITHINKER" + #define DEVICE "AI_LIGHT" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX + #define DUMMY_RELAY_COUNT 1 + + // Light + #define LIGHT_CHANNELS 4 + #define MY92XX_MODEL MY92XX_MODEL_MY9291 + #define MY92XX_CHIPS 1 + #define MY92XX_DI_PIN 13 + #define MY92XX_DCKI_PIN 15 + #define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT + #define MY92XX_MAPPING 0, 1, 2, 3 + +// ----------------------------------------------------------------------------- +// LED Controller +// ----------------------------------------------------------------------------- + +#elif defined(MAGICHOME_LED_CONTROLLER) + + // Info + #define MANUFACTURER "MAGICHOME" + #define DEVICE "LED_CONTROLLER" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 1 + + // Light + #define LIGHT_CHANNELS 4 + #define LIGHT_CH1_PIN 14 // RED + #define LIGHT_CH2_PIN 5 // GREEN + #define LIGHT_CH3_PIN 12 // BLUE + #define LIGHT_CH4_PIN 13 // WHITE + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + + // IR + #define IR_SUPPORT 1 + #define IR_PIN 4 + #define IR_BUTTON_SET 1 + +#elif defined(MAGICHOME_LED_CONTROLLER_20) + + // Info + #define MANUFACTURER "MAGICHOME" + #define DEVICE "LED_CONTROLLER_20" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 1 + + // Light + #define LIGHT_CHANNELS 4 + #define LIGHT_CH1_PIN 5 // RED + #define LIGHT_CH2_PIN 12 // GREEN + #define LIGHT_CH3_PIN 13 // BLUE + #define LIGHT_CH4_PIN 15 // WHITE + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + + // IR + #define IR_SUPPORT 1 + #define IR_PIN 4 + #define IR_BUTTON_SET 1 + +// ----------------------------------------------------------------------------- +// HUACANXING H801 & H802 +// ----------------------------------------------------------------------------- + +#elif defined(HUACANXING_H801) + + // Info + #define MANUFACTURER "HUACANXING" + #define DEVICE "H801" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + #define DEBUG_PORT Serial1 + #define SERIAL_RX_ENABLED 1 + + // LEDs + #define LED1_PIN 5 + #define LED1_PIN_INVERSE 1 + + // Light + #define LIGHT_CHANNELS 5 + #define LIGHT_CH1_PIN 15 // RED + #define LIGHT_CH2_PIN 13 // GREEN + #define LIGHT_CH3_PIN 12 // BLUE + #define LIGHT_CH4_PIN 14 // WHITE1 + #define LIGHT_CH5_PIN 4 // WHITE2 + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + #define LIGHT_CH5_INVERSE 0 + +#elif defined(HUACANXING_H802) + + // Info + #define MANUFACTURER "HUACANXING" + #define DEVICE "H802" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + #define DEBUG_PORT Serial1 + #define SERIAL_RX_ENABLED 1 + + // Light + #define LIGHT_CHANNELS 4 + #define LIGHT_CH1_PIN 12 // RED + #define LIGHT_CH2_PIN 14 // GREEN + #define LIGHT_CH3_PIN 13 // BLUE + #define LIGHT_CH4_PIN 15 // WHITE + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + +// ----------------------------------------------------------------------------- +// Jan Goedeke Wifi Relay +// https://github.com/JanGoe/esp8266-wifi-relay +// ----------------------------------------------------------------------------- + +#elif defined(JANGOE_WIFI_RELAY_NC) + + // Info + #define MANUFACTURER "JANGOE" + #define DEVICE "WIFI_RELAY_NC" + + // Buttons + #define BUTTON1_PIN 12 + #define BUTTON2_PIN 13 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + + // Relays + #define RELAY1_PIN 2 + #define RELAY2_PIN 14 + + #define RELAY1_TYPE RELAY_TYPE_INVERSE + #define RELAY2_TYPE RELAY_TYPE_INVERSE + +#elif defined(JANGOE_WIFI_RELAY_NO) + + // Info + #define MANUFACTURER "JANGOE" + #define DEVICE "WIFI_RELAY_NO" + + // Buttons + #define BUTTON1_PIN 12 + #define BUTTON2_PIN 13 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + + // Relays + #define RELAY1_PIN 2 + #define RELAY2_PIN 14 + + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + +// ----------------------------------------------------------------------------- +// Jorge García Wifi+Relays Board Kit +// https://www.tindie.com/products/jorgegarciadev/wifi--relays-board-kit +// https://github.com/jorgegarciadev/wifikit +// ----------------------------------------------------------------------------- + +#elif defined(JORGEGARCIA_WIFI_RELAYS) + + // Info + #define MANUFACTURER "JORGEGARCIA" + #define DEVICE "WIFI_RELAYS" + + // Relays + #define RELAY1_PIN 0 + #define RELAY2_PIN 2 + + #define RELAY1_TYPE RELAY_TYPE_INVERSE + #define RELAY2_TYPE RELAY_TYPE_INVERSE + +// ----------------------------------------------------------------------------- +// WiFi MQTT Relay / Thermostat +// ----------------------------------------------------------------------------- + +#elif defined(OPENENERGYMONITOR_MQTT_RELAY) + + // Info + #define MANUFACTURER "OPENENERGYMONITOR" + #define DEVICE "MQTT_RELAY" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_RELAY 1 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 16 + #define LED1_PIN_INVERSE 0 + +// ----------------------------------------------------------------------------- +// WiOn 50055 Indoor Wi-Fi Wall Outlet & Tap +// https://rover.ebay.com/rover/1/711-53200-19255-0/1?icep_id=114&ipn=icep&toolid=20004&campid=5338044841&mpre=http%3A%2F%2Fwww.ebay.com%2Fitm%2FWiOn-50050-Indoor-Wi-Fi-Outlet-Wireless-Switch-Programmable-Timer-%2F263112281551 +// https://rover.ebay.com/rover/1/711-53200-19255-0/1?icep_id=114&ipn=icep&toolid=20004&campid=5338044841&mpre=http%3A%2F%2Fwww.ebay.com%2Fitm%2FWiOn-50055-Indoor-Wi-Fi-Wall-Tap-Monitor-Energy-Usage-Wireless-Smart-Switch-%2F263020837777 +// ----------------------------------------------------------------------------- + +#elif defined(WION_50055) + + // Currently untested, does not support energy monitoring + + // Info + #define MANUFACTURER "WION" + #define DEVICE "50055" + + // Buttons + #define BUTTON1_PIN 13 + #define BUTTON1_RELAY 1 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // Relays + #define RELAY1_PIN 15 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 0 + +// ----------------------------------------------------------------------------- +// EX-Store Wifi Relay v3.1 +// https://ex-store.de/ESP8266-WiFi-Relay-V31 +// ----------------------------------------------------------------------------- + +#elif defined(EXS_WIFI_RELAY_V31) + + // Untested + + // Info + #define MANUFACTURER "EXS" + #define DEVICE "WIFI_RELAY_V31" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_RELAY 1 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + // Relays + #define RELAY1_PIN 13 + #define RELAY1_TYPE RELAY_TYPE_LATCHED + #define RELAY1_RESET_PIN 12 + +// ----------------------------------------------------------------------------- +// V9261F +// ----------------------------------------------------------------------------- + +#elif defined(GENERIC_V9261F) + + // Info + #define MANUFACTURER "GENERIC" + #define DEVICE "V9261F" + #define ALEXA_SUPPORT 0 + + // V9261F + #define V9261F_SUPPORT 1 + #define V9261F_PIN 2 + #define V9261F_PIN_INVERSE 1 + +// ----------------------------------------------------------------------------- +// ECH1560 +// ----------------------------------------------------------------------------- + +#elif defined(GENERIC_ECH1560) + + // Info + #define MANUFACTURER "GENERIC" + #define DEVICE "ECH1560" + #define ALEXA_SUPPORT 0 + + // ECH1560 + #define ECH1560_SUPPORT 1 + #define ECH1560_CLK_PIN 4 + #define ECH1560_MISO_PIN 5 + #define ECH1560_INVERTED 0 + +// ----------------------------------------------------------------------------- +// ESPLive +// https://github.com/ManCaveMade/ESP-Live +// ----------------------------------------------------------------------------- + +#elif defined(MANCAVEMADE_ESPLIVE) + + // Info + #define MANUFACTURER "MANCAVEMADE" + #define DEVICE "ESPLIVE" + + // Buttons + #define BUTTON1_PIN 4 + #define BUTTON2_PIN 5 + + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON1_RELAY 1 + #define BUTTON2_RELAY 2 + + // Relays + #define RELAY1_PIN 12 + #define RELAY2_PIN 13 + + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + // DB18B20 + #ifndef DALLAS_SUPPORT + #define DALLAS_SUPPORT 1 + #endif + #define DALLAS_PIN 2 + #define DALLAS_UPDATE_INTERVAL 5000 + #define TEMPERATURE_MIN_CHANGE 1.0 + +// ----------------------------------------------------------------------------- +// QuinLED +// http://blog.quindorian.org/2017/02/esp8266-led-lighting-quinled-v2-6-pcb.html +// ----------------------------------------------------------------------------- + +#elif defined(INTERMITTECH_QUINLED) + + // Info + #define MANUFACTURER "INTERMITTECH" + #define DEVICE "QUINLED" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // LEDs + #define LED1_PIN 5 + #define LED1_PIN_INVERSE 1 + + // Light + #define LIGHT_CHANNELS 2 + #define LIGHT_CH1_PIN 0 + #define LIGHT_CH2_PIN 2 + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + +// ----------------------------------------------------------------------------- +// Arilux AL-LC06 +// ----------------------------------------------------------------------------- + +#elif defined(ARILUX_AL_LC01) + + // Info + #define MANUFACTURER "ARILUX" + #define DEVICE "AL_LC01" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // Light + #define LIGHT_CHANNELS 4 + #define LIGHT_CH1_PIN 5 // RED + #define LIGHT_CH2_PIN 12 // GREEN + #define LIGHT_CH3_PIN 13 // BLUE + #define LIGHT_CH4_PIN 14 // WHITE1 + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + +#elif defined(ARILUX_AL_LC02) + + // Info + #define MANUFACTURER "ARILUX" + #define DEVICE "AL_LC02" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // Light + #define LIGHT_CHANNELS 4 + #define LIGHT_CH1_PIN 12 // RED + #define LIGHT_CH2_PIN 5 // GREEN + #define LIGHT_CH3_PIN 13 // BLUE + #define LIGHT_CH4_PIN 15 // WHITE1 + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + +#elif defined(ARILUX_AL_LC06) + + // Info + #define MANUFACTURER "ARILUX" + #define DEVICE "AL_LC06" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // Light + #define LIGHT_CHANNELS 5 + #define LIGHT_CH1_PIN 14 // RED + #define LIGHT_CH2_PIN 12 // GREEN + #define LIGHT_CH3_PIN 13 // BLUE + #define LIGHT_CH4_PIN 15 // WHITE1 + #define LIGHT_CH5_PIN 5 // WHITE2 + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + #define LIGHT_CH5_INVERSE 0 + +#elif defined(ARILUX_AL_LC11) + + // Info + #define MANUFACTURER "ARILUX" + #define DEVICE "AL_LC11" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // Light + #define LIGHT_CHANNELS 5 + #define LIGHT_CH1_PIN 5 // RED + #define LIGHT_CH2_PIN 4 // GREEN + #define LIGHT_CH3_PIN 14 // BLUE + #define LIGHT_CH4_PIN 13 // WHITE1 + #define LIGHT_CH5_PIN 12 // WHITE1 + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + #define LIGHT_CH5_INVERSE 0 + +#elif defined(ARILUX_E27) + + // Info + #define MANUFACTURER "ARILUX" + #define DEVICE "E27" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX + #define DUMMY_RELAY_COUNT 1 + + // Light + #define LIGHT_CHANNELS 4 + #define MY92XX_MODEL MY92XX_MODEL_MY9291 + #define MY92XX_CHIPS 1 + #define MY92XX_DI_PIN 13 + #define MY92XX_DCKI_PIN 15 + #define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT + #define MY92XX_MAPPING 0, 1, 2, 3 + +// ----------------------------------------------------------------------------- +// XENON SM-PW701U +// ----------------------------------------------------------------------------- + +#elif defined(XENON_SM_PW702U) + + // Info + #define MANUFACTURER "XENON" + #define DEVICE "SM_PW702U" + + // Buttons + #define BUTTON1_PIN 13 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 4 + #define LED1_PIN_INVERSE 1 + +// ----------------------------------------------------------------------------- +// AUTHOMETION LYT8266 +// https://authometion.com/shop/en/home/13-lyt8266.html +// ----------------------------------------------------------------------------- + +#elif defined(AUTHOMETION_LYT8266) + + // Info + #define MANUFACTURER "AUTHOMETION" + #define DEVICE "LYT8266" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // Light + #define LIGHT_CHANNELS 4 + #define LIGHT_CH1_PIN 13 // RED + #define LIGHT_CH2_PIN 12 // GREEN + #define LIGHT_CH3_PIN 14 // BLUE + #define LIGHT_CH4_PIN 2 // WHITE + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + #define LIGHT_CH4_INVERSE 0 + + #define LIGHT_ENABLE_PIN 15 + +#elif defined(GIZWITS_WITTY_CLOUD) + + // Info + #define MANUFACTURER "GIZWITS" + #define DEVICE "WITTY_CLOUD" + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + + // Buttons + #define BUTTON1_PIN 4 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_PRESS BUTTON_MODE_TOGGLE + #define BUTTON1_CLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_RESET + + #define ANALOG_SUPPORT 1 + + // LEDs + #define LED1_PIN 2 // BLUE build-in + #define LED1_PIN_INVERSE 1 + + // Light + #define LIGHT_CHANNELS 3 + #define LIGHT_CH1_PIN 15 // RED + #define LIGHT_CH2_PIN 12 // GREEN + #define LIGHT_CH3_PIN 13 // BLUE + #define LIGHT_CH1_INVERSE 0 + #define LIGHT_CH2_INVERSE 0 + #define LIGHT_CH3_INVERSE 0 + +// ----------------------------------------------------------------------------- +// KMC 70011 +// https://www.amazon.com/KMC-Monitoring-Required-Control-Compatible/dp/B07313TH7B +// ----------------------------------------------------------------------------- + +#elif defined(KMC_70011) + + // Info + #define MANUFACTURER "KMC" + #define DEVICE "70011" + + // Buttons + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 14 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 13 + #define LED1_PIN_INVERSE 0 + + // HLW8012 + #ifndef HLW8012_SUPPORT + #define HLW8012_SUPPORT 1 + #endif + #define HLW8012_SEL_PIN 12 + #define HLW8012_CF1_PIN 5 + #define HLW8012_CF_PIN 4 + + #define HLW8012_VOLTAGE_R_UP ( 2 * 1000000 ) // Upstream voltage resistor + +// ----------------------------------------------------------------------------- +// Euromate (?) Wifi Stecker Shuko +// https://www.obi.de/hausfunksteuerung/wifi-stecker-schuko/p/2291706 +// Thanks to @Geitde +// ----------------------------------------------------------------------------- + +#elif defined(EUROMATE_WIFI_STECKER_SCHUKO) + + // Info + #define MANUFACTURER "EUROMATE" + #define DEVICE "WIFI_STECKER_SCHUKO" + + // Buttons + #define BUTTON1_PIN 14 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // The relay in the device is not a bistable (latched) relay. + // The device is reported to have a flip-flop circuit to drive the relay + // So @Geitde hack is still the only possible + + // Hack: drive GPIO12 low and use GPIO5 as normal relay pin: + #define RELAY1_PIN 5 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define LED2_PIN 12 /* DUMMY: exploit default off state for GPIO12=low */ + #define LED2_PIN_INVERSE 0 + + // LEDs + #define LED1_PIN 4 + #define LED1_PIN_INVERSE 0 + +// ----------------------------------------------------------------------------- +// Generic 8CH +// ----------------------------------------------------------------------------- + +#elif defined(GENERIC_8CH) + + // Info + #define MANUFACTURER "GENERIC" + #define DEVICE "8CH" + + // Relays + #define RELAY1_PIN 0 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_PIN 2 + #define RELAY2_TYPE RELAY_TYPE_NORMAL + #define RELAY3_PIN 4 + #define RELAY3_TYPE RELAY_TYPE_NORMAL + #define RELAY4_PIN 5 + #define RELAY4_TYPE RELAY_TYPE_NORMAL + #define RELAY5_PIN 12 + #define RELAY5_TYPE RELAY_TYPE_NORMAL + #define RELAY6_PIN 13 + #define RELAY6_TYPE RELAY_TYPE_NORMAL + #define RELAY7_PIN 14 + #define RELAY7_TYPE RELAY_TYPE_NORMAL + #define RELAY8_PIN 15 + #define RELAY8_TYPE RELAY_TYPE_NORMAL + +// ----------------------------------------------------------------------------- +// STM RELAY +// ----------------------------------------------------------------------------- + +#elif defined(STM_RELAY) + + // Info + #define MANUFACTURER "STM_RELAY" + #define DEVICE "2CH" + + // Relays + #define DUMMY_RELAY_COUNT 2 + #define RELAY_PROVIDER RELAY_PROVIDER_STM + + // Remove UART noise on serial line + #define TERMINAL_SUPPORT 0 + #define DEBUG_SERIAL_SUPPORT 0 + +// ----------------------------------------------------------------------------- +// Tonbux Powerstrip02 +// ----------------------------------------------------------------------------- + +#elif defined(TONBUX_POWERSTRIP02) + + // Info + #define MANUFACTURER "TONBUX" + #define DEVICE "POWERSTRIP02" + + // Buttons + #define BUTTON1_PIN 5 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 0 + + // Relays + #define RELAY1_PIN 4 + #define RELAY1_TYPE RELAY_TYPE_INVERSE + #define RELAY2_PIN 13 + #define RELAY2_TYPE RELAY_TYPE_INVERSE + #define RELAY3_PIN 12 + #define RELAY3_TYPE RELAY_TYPE_INVERSE + #define RELAY4_PIN 14 + #define RELAY4_TYPE RELAY_TYPE_INVERSE + // Not a relay. USB ports on/off + #define RELAY5_PIN 16 + #define RELAY5_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 0 // 1 blue led + #define LED1_PIN_INVERSE 1 + #define LED2_PIN 3 // 3 red leds + #define LED2_PIN_INVERSE 1 + +// ----------------------------------------------------------------------------- +// Lingan SWA1 +// ----------------------------------------------------------------------------- + +#elif defined(LINGAN_SWA1) + + // Info + #define MANUFACTURER "LINGAN" + #define DEVICE "SWA1" + + // Buttons + #define BUTTON1_PIN 13 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 5 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 4 + #define LED1_PIN_INVERSE 1 + +// ----------------------------------------------------------------------------- +// HEYGO HY02 +// ----------------------------------------------------------------------------- + +#elif defined(HEYGO_HY02) + + // Info + #define MANUFACTURER "HEYGO" + #define DEVICE "HY02" + + // Buttons + #define BUTTON1_PIN 13 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 4 + #define LED1_PIN_INVERSE 0 + +// ----------------------------------------------------------------------------- +// Maxcio W-US002S +// ----------------------------------------------------------------------------- + +#elif defined(MAXCIO_WUS002S) + + // Info + #define MANUFACTURER "MAXCIO" + #define DEVICE "WUS002S" + + // Buttons + #define BUTTON1_PIN 2 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 13 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 3 + #define LED1_PIN_INVERSE 0 + + // HLW8012 + #ifndef HLW8012_SUPPORT + #define HLW8012_SUPPORT 1 + #endif + #define HLW8012_SEL_PIN 12 + #define HLW8012_CF1_PIN 5 + #define HLW8012_CF_PIN 4 + + #define HLW8012_CURRENT_R 0.002 // Current resistor + #define HLW8012_VOLTAGE_R_UP ( 2 * 1000000 ) // Upstream voltage resistor + +// ----------------------------------------------------------------------------- +// YiDian XS-SSA05 +// ----------------------------------------------------------------------------- + +#elif defined(YIDIAN_XSSSA05) + + // Info + #define MANUFACTURER "YIDIAN" + #define DEVICE "XSSSA05" + + // Buttons + #define BUTTON1_PIN 13 + #define BUTTON1_MODE BUTTON_PUSHBUTTON + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 4 + #define LED1_PIN_INVERSE 0 + + // HLW8012 + #ifndef HLW8012_SUPPORT + #define HLW8012_SUPPORT 1 + #endif + #define HLW8012_SEL_PIN 3 + #define HLW8012_CF1_PIN 14 + #define HLW8012_CF_PIN 5 + + #define HLW8012_CURRENT_R 0.001 // Current resistor + #define HLW8012_VOLTAGE_R_UP ( 2 * 1200000 ) // Upstream voltage resistor + +// ----------------------------------------------------------------------------- +// TONBUX XS-SSA06 +// ----------------------------------------------------------------------------- + +#elif defined(TONBUX_XSSSA06) + + // Info + #define MANUFACTURER "TONBUX" + #define DEVICE "XSSSA06" + + // Buttons + #define BUTTON1_PIN 13 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 15 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 0 // R - 8 rgb led ring + #define LED1_PIN_INVERSE 0 + #define LED2_PIN 5 // G + #define LED2_PIN_INVERSE 0 + #define LED3_PIN 2 // B + #define LED3_PIN_INVERSE 0 + +// ----------------------------------------------------------------------------- +// GREEN ESP8266 RELAY MODULE +// https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180323113846&SearchText=Green+ESP8266 +// ----------------------------------------------------------------------------- + +#elif defined(GREEN_ESP8266RELAY) + + // Info + #define MANUFACTURER "GREEN" + #define DEVICE "ESP8266RELAY" + + // Buttons + // Not a button but input via Optocoupler + #define BUTTON1_PIN 5 + #define BUTTON1_MODE BUTTON_PUSHBUTTON + #define BUTTON1_RELAY 1 + + // Relays + #define RELAY1_PIN 4 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 1 + +// ----------------------------------------------------------------------------- +// Henrique Gravina ESPIKE +// https://github.com/Henriquegravina/Espike +// ----------------------------------------------------------------------------- + +#elif defined(IKE_ESPIKE) + + #define MANUFACTURER "IKE" + #define DEVICE "ESPIKE" + + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + + #define BUTTON1_PIN 13 + #define BUTTON1_RELAY 1 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON2_PIN 12 + #define BUTTON2_RELAY 2 + #define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define BUTTON3_PIN 14 + #define BUTTON3_RELAY 2 + #define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + + #define RELAY1_PIN 4 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + #define RELAY2_PIN 5 + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + #define RELAY3_PIN 16 + #define RELAY3_TYPE RELAY_TYPE_NORMAL + + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 1 + +// ----------------------------------------------------------------------------- +// SWIFITCH +// https://github.com/ArnieX/swifitch +// ----------------------------------------------------------------------------- + +#elif defined(ARNIEX_SWIFITCH) + + // Info + #define MANUFACTURER "ARNIEX" + #define DEVICE "SWIFITCH" + + // Buttons + #define BUTTON1_PIN 4 // D2 + #define BUTTON1_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + #define BUTTON1_PRESS BUTTON_MODE_NONE + #define BUTTON1_CLICK BUTTON_MODE_TOGGLE + #define BUTTON1_DBLCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGCLICK BUTTON_MODE_NONE + #define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE + + // Relays + #define RELAY1_PIN 5 // D1 + #define RELAY1_TYPE RELAY_TYPE_INVERSE + + // LEDs + #define LED1_PIN 12 // D6 + #define LED1_PIN_INVERSE 1 + +// ----------------------------------------------------------------------------- +// ESP-01S RELAY v4.0 +// https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180404024035&SearchText=esp-01s+relay +// ----------------------------------------------------------------------------- + +#elif defined(GENERIC_ESP01SRELAY40) + + // Info + #define MANUFACTURER "GENERIC" + #define DEVICE "ESP01S_RELAY_40" + + // Relays + #define RELAY1_PIN 0 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // LEDs + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 0 + +// ----------------------------------------------------------------------------- +// ESP-01S RGB LED v1.0 (some sold with ws2818) +// https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180404023816&SearchText=esp-01s+led+controller +// ----------------------------------------------------------------------------- + +#elif defined(GENERIC_ESP01SRGBLED10) + + // Info + #define MANUFACTURER "GENERIC" + #define DEVICE "ESP01S_RGBLED_10" + + // This board is sold as RGB LED module BUT it has on board 3 pin ph2.0 connector (VCC, GPIO2, GND) + // so, if you wish, you may connect LED, BUTTON, RELAY, SENSOR etc. + + // Buttons + //#define BUTTON1_PIN 2 + + // Relays + //#define RELAY1_PIN 2 + + // LEDs + #define LED1_PIN 2 + +// ----------------------------------------------------------------------------- +// Heltec Touch Relay +// https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180408043114&SearchText=esp8266+touch+relay +// ----------------------------------------------------------------------------- + +#elif defined(HELTEC_TOUCHRELAY) + + // Info + #define MANUFACTURER "HELTEC" + #define DEVICE "TOUCH_RELAY" + + // Buttons + #define BUTTON1_PIN 14 + #define BUTTON1_RELAY 1 + #define BUTTON1_MODE BUTTON_PUSHBUTTON + + // Relays + #define RELAY1_PIN 12 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + +// ----------------------------------------------------------------------------- +// TEST boards (do not use!!) +// ----------------------------------------------------------------------------- + +#elif defined(TRAVIS01) + + // Info + #define MANUFACTURER "TravisCI" + #define DEVICE "Virtual board 01" + + // Some buttons - pin 0 + #define BUTTON1_PIN 0 + #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH + #define BUTTON1_RELAY 1 + + // Some relays - pin 1 + #define RELAY1_PIN 1 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + + // Some LEDs - pin 2 + #define LED1_PIN 2 + #define LED1_PIN_INVERSE 1 + + // A bit of I2C - pins 3,4 + #define I2C_SDA_PIN 3 + #define I2C_SCL_PIN 4 + + // And, as they say in "From Dusk till Dawn": + // This is a sensor blow out! + // Alright, we got white sensor, black sensor, spanish sensor, yellow sensor. We got hot sensor, cold sensor. + // We got wet sensor. We got smelly sensor. We got hairy sensor, bloody sensor. We got snapping sensor. + // We got silk sensor, velvet sensor, naugahyde sensor. We even got horse sensor, dog sensor, chicken sensor. + // C'mon, you want sensor, come on in sensor lovers! + // If we don’t got it, you don't want it! + #define BH1750_SUPPORT 1 + #define BMX280_SUPPORT 1 + #define SHT3X_I2C_SUPPORT 1 + #define EMON_ADC121_SUPPORT 1 + #define EMON_ADS1X15_SUPPORT 1 + #define SHT3X_I2C_SUPPORT 1 + #define SI7021_SUPPORT 1 + + + // A bit of lights - pin 5 + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER + #define DUMMY_RELAY_COUNT 1 + #define LIGHT_CHANNELS 1 + #define LIGHT_CH1_PIN 5 + #define LIGHT_CH1_INVERSE 0 + + // A bit of HLW8012 - pins 6,7,8 + #ifndef HLW8012_SUPPORT + #define HLW8012_SUPPORT 1 + #endif + #define HLW8012_SEL_PIN 6 + #define HLW8012_CF1_PIN 7 + #define HLW8012_CF_PIN 8 + + // A bit of Dallas - pin 9 + #ifndef DALLAS_SUPPORT + #define DALLAS_SUPPORT 1 + #endif + #define DALLAS_PIN 9 + + // A bit of ECH1560 - pins 10,11, 12 + #ifndef ECH1560_SUPPORT + #define ECH1560_SUPPORT 1 + #endif + #define ECH1560_CLK_PIN 10 + #define ECH1560_MISO_PIN 11 + #define ECH1560_INVERTED 12 + +#elif defined(TRAVIS02) + + // Relay provider dual + #define MANUFACTURER "TravisCI" + #define DEVICE "Virtual board 02" + + // A bit of DHT - pin 1 + #ifndef DHT_SUPPORT + #define DHT_SUPPORT 1 + #endif + #define DHT_PIN 1 + + // Relay type dual - pins 2,3 + #define RELAY_PROVIDER RELAY_PROVIDER_DUAL + #define RELAY1_PIN 2 + #define RELAY2_PIN 3 + #define RELAY1_TYPE RELAY_TYPE_NORMAL + #define RELAY2_TYPE RELAY_TYPE_NORMAL + + // IR - pin 4 + #define IR_SUPPORT 1 + #define IR_PIN 4 + #define IR_BUTTON_SET 1 + +#elif defined(TRAVIS03) + + // Relay provider light/my92XX + #define MANUFACTURER "TravisCI" + #define DEVICE "Virtual board 03" + + // MY9231 Light - pins 1,2 + #define RELAY_PROVIDER RELAY_PROVIDER_LIGHT + #define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX + #define DUMMY_RELAY_COUNT 1 + #define LIGHT_CHANNELS 5 + #define MY92XX_MODEL MY92XX_MODEL_MY9231 + #define MY92XX_CHIPS 2 + #define MY92XX_DI_PIN 1 + #define MY92XX_DCKI_PIN 2 + #define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT + #define MY92XX_MAPPING 4, 3, 5, 0, 1 + +#endif + +// ----------------------------------------------------------------------------- +// Check definitions +// ----------------------------------------------------------------------------- + +#if not defined(MANUFACTURER) || not defined(DEVICE) + #error "UNSUPPORTED HARDWARE!!" +#endif diff --git a/espurna/config/prototypes.h b/espurna/config/prototypes.h new file mode 100755 index 0000000..eb01071 --- /dev/null +++ b/espurna/config/prototypes.h @@ -0,0 +1,118 @@ +#include +#include +#include +#include + +extern "C" { + #include "user_interface.h" +} + +// ----------------------------------------------------------------------------- +// WebServer +// ----------------------------------------------------------------------------- +#include +AsyncWebServer * webServer(); + +// ----------------------------------------------------------------------------- +// API +// ----------------------------------------------------------------------------- +typedef std::function api_get_callback_f; +typedef std::function api_put_callback_f; +void apiRegister(const char * key, api_get_callback_f getFn, api_put_callback_f putFn = NULL); + +// ----------------------------------------------------------------------------- +// WebSockets +// ----------------------------------------------------------------------------- +typedef std::function ws_on_send_callback_f; +void wsOnSendRegister(ws_on_send_callback_f callback); +void wsSend(ws_on_send_callback_f sender); + +typedef std::function ws_on_action_callback_f; +void wsOnActionRegister(ws_on_action_callback_f callback); + +typedef std::function ws_on_after_parse_callback_f; +void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback); + +typedef std::function ws_on_receive_callback_f; +void wsOnReceiveRegister(ws_on_receive_callback_f callback); + +// ----------------------------------------------------------------------------- +// WIFI +// ----------------------------------------------------------------------------- +#include "JustWifi.h" +typedef std::function wifi_callback_f; +void wifiRegister(wifi_callback_f callback); + +// ----------------------------------------------------------------------------- +// MQTT +// ----------------------------------------------------------------------------- +typedef std::function mqtt_callback_f; +void mqttRegister(mqtt_callback_f callback); +String mqttMagnitude(char * topic); + +// ----------------------------------------------------------------------------- +// Broker +// ----------------------------------------------------------------------------- +void brokerRegister(void (*)(const char *, unsigned char, const char *)); + +// ----------------------------------------------------------------------------- +// Settings +// ----------------------------------------------------------------------------- +#include +template bool setSetting(const String& key, T value); +template bool setSetting(const String& key, unsigned int index, T value); +template String getSetting(const String& key, T defaultValue); +template String getSetting(const String& key, unsigned int index, T defaultValue); +bool settingsGetJson(JsonObject& data); +bool settingsRestoreJson(JsonObject& data); +void settingsRegisterCommand(const String& name, void (*call)(Embedis*)); +void settingsInject(void *data, size_t len); + +// ----------------------------------------------------------------------------- +// I2C +// ----------------------------------------------------------------------------- +void i2cScan(); +void i2cClearBus(); +bool i2cGetLock(unsigned char address); +bool i2cReleaseLock(unsigned char address); +unsigned char i2cFindAndLock(size_t size, unsigned char * addresses); + +uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len); +uint8_t i2c_write_uint8(uint8_t address, uint8_t value); +uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value); +uint8_t i2c_write_uint8(uint8_t address, uint8_t reg, uint8_t value1, uint8_t value2); +uint8_t i2c_write_uint16(uint8_t address, uint16_t value); +uint8_t i2c_write_uint16(uint8_t address, uint8_t reg, uint16_t value); +uint8_t i2c_read_uint8(uint8_t address); +uint8_t i2c_read_uint8(uint8_t address, uint8_t reg); +uint16_t i2c_read_uint16(uint8_t address); +uint16_t i2c_read_uint16(uint8_t address, uint8_t reg); +uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg); +int16_t i2c_read_int16(uint8_t address, uint8_t reg); +int16_t i2c_read_int16_le(uint8_t address, uint8_t reg); +void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len); + +// ----------------------------------------------------------------------------- +// GPIO +// ----------------------------------------------------------------------------- +bool gpioValid(unsigned char gpio); +bool gpioGetLock(unsigned char gpio); +bool gpioReleaseLock(unsigned char gpio); + +// ----------------------------------------------------------------------------- +// Debug +// ----------------------------------------------------------------------------- +void debugSend(const char * format, ...); +void debugSend_P(PGM_P format, ...); + +// ----------------------------------------------------------------------------- +// Domoticz +// ----------------------------------------------------------------------------- +template void domoticzSend(const char * key, T value); +template void domoticzSend(const char * key, T nvalue, const char * svalue); + +// ----------------------------------------------------------------------------- +// Utils +// ----------------------------------------------------------------------------- +char * ltrim(char * s); +void nice_delay(unsigned long ms); diff --git a/espurna/config/sensors.h b/espurna/config/sensors.h new file mode 100755 index 0000000..1d58b48 --- /dev/null +++ b/espurna/config/sensors.h @@ -0,0 +1,796 @@ +// ============================================================================= +// SENSORS - General data +// ============================================================================= + +#define SENSOR_DEBUG 0 // Debug sensors + +#define SENSOR_READ_INTERVAL 6 // Read data from sensors every 6 seconds +#define SENSOR_READ_MIN_INTERVAL 6 // Minimum read interval +#define SENSOR_READ_MAX_INTERVAL 3600 // Maximum read interval +#define SENSOR_INIT_INTERVAL 10000 // Try to re-init non-ready sensors every 10s + +#define SENSOR_REPORT_EVERY 10 // Report every this many readings +#define SENSOR_REPORT_MIN_EVERY 1 // Minimum every value +#define SENSOR_REPORT_MAX_EVERY 12 // Maximum + +#define SENSOR_USE_INDEX 0 // Use the index in topic (i.e. temperature/0) + // even if just one sensor (0 for backwards compatibility) + +#ifndef SENSOR_POWER_CHECK_STATUS +#define SENSOR_POWER_CHECK_STATUS 1 // If set to 1 the reported power/current/energy will be 0 if the relay[0] is OFF +#endif + +#ifndef SENSOR_TEMPERATURE_CORRECTION +#define SENSOR_TEMPERATURE_CORRECTION 0.0 // Offset correction +#endif + +#ifndef TEMPERATURE_MIN_CHANGE +#define TEMPERATURE_MIN_CHANGE 0.0 // Minimum temperature change to report +#endif + +#ifndef SENSOR_HUMIDITY_CORRECTION +#define SENSOR_HUMIDITY_CORRECTION 0.0 // Offset correction +#endif + +#ifndef HUMIDITY_MIN_CHANGE +#define HUMIDITY_MIN_CHANGE 0 // Minimum humidity change to report +#endif + +// American Society of Heating, Refrigerating and Air-Conditioning Engineers suggests a range of 45% - 55% humidity to manage health effects and illnesses. +// Comfortable: 30% - 60% +// Recommended: 45% - 55% +// High : 55% - 80% +#define HUMIDITY_NORMAL 0 // > %30 +#define HUMIDITY_COMFORTABLE 1 // > %45 +#define HUMIDITY_DRY 2 // < %30 +#define HUMIDITY_WET 3 // > %70 + +// United States Environmental Protection Agency - UV Index Scale +// One UV Index unit is equivalent to 25 milliWatts per square meter. +#define UV_INDEX_LOW 0 // 0 to 2 means low danger from the sun's UV rays for the average person. +#define UV_INDEX_MODERATE 1 // 3 to 5 means moderate risk of harm from unprotected sun exposure. +#define UV_INDEX_HIGH 2 // 6 to 7 means high risk of harm from unprotected sun exposure. Protection against skin and eye damage is needed. +#define UV_INDEX_VERY_HIGH 3 // 8 to 10 means very high risk of harm from unprotected sun exposure. + // Take extra precautions because unprotected skin and eyes will be damaged and can burn quickly. +#define UV_INDEX_EXTREME 4 // 11 or more means extreme risk of harm from unprotected sun exposure. + // Take all precautions because unprotected skin and eyes can burn in minutes. + +#define SENSOR_PUBLISH_ADDRESSES 0 // Publish sensor addresses +#define SENSOR_ADDRESS_TOPIC "address" // Topic to publish sensor addresses + +//------------------------------------------------------------------------------ +// UNITS +//------------------------------------------------------------------------------ + +#define POWER_WATTS 0 +#define POWER_KILOWATTS 1 + +#define ENERGY_JOULES 0 +#define ENERGY_KWH 1 + +#define TMP_CELSIUS 0 +#define TMP_FAHRENHEIT 1 + +#ifndef SENSOR_TEMPERATURE_UNITS +#define SENSOR_TEMPERATURE_UNITS TMP_CELSIUS // Temperature units (TMP_CELSIUS | TMP_FAHRENHEIT) +#endif + +#ifndef SENSOR_ENERGY_UNITS +#define SENSOR_ENERGY_UNITS ENERGY_JOULES // Energy units (ENERGY_JOULES | ENERGY_KWH) +#endif + +#ifndef SENSOR_POWER_UNITS +#define SENSOR_POWER_UNITS POWER_WATTS // Power units (POWER_WATTS | POWER_KILOWATTS) +#endif + +//-------------------------------------------------------------------------------- +// Sensor ID +// These should remain over time, do not modify them, only add new ones at the end +//-------------------------------------------------------------------------------- + +#define SENSOR_DHTXX_ID 0x01 +#define SENSOR_DALLAS_ID 0x02 +#define SENSOR_EMON_ANALOG_ID 0x03 +#define SENSOR_EMON_ADC121_ID 0x04 +#define SENSOR_EMON_ADS1X15_ID 0x05 +#define SENSOR_HLW8012_ID 0x06 +#define SENSOR_V9261F_ID 0x07 +#define SENSOR_ECH1560_ID 0x08 +#define SENSOR_ANALOG_ID 0x09 +#define SENSOR_DIGITAL_ID 0x10 +#define SENSOR_EVENTS_ID 0x11 +#define SENSOR_PMSX003_ID 0x12 +#define SENSOR_BMX280_ID 0x13 +#define SENSOR_MHZ19_ID 0x14 +#define SENSOR_SI7021_ID 0x15 +#define SENSOR_SHT3X_I2C_ID 0x16 +#define SENSOR_BH1750_ID 0x17 +#define SENSOR_PZEM004T_ID 0x18 +#define SENSOR_AM2320_ID 0x19 +#define SENSOR_GUVAS12SD_ID 0x20 +#define SENSOR_CSE7766_ID 0x21 + +//-------------------------------------------------------------------------------- +// Magnitudes +//-------------------------------------------------------------------------------- + +#define MAGNITUDE_NONE 0 +#define MAGNITUDE_TEMPERATURE 1 +#define MAGNITUDE_HUMIDITY 2 +#define MAGNITUDE_PRESSURE 3 +#define MAGNITUDE_CURRENT 4 +#define MAGNITUDE_VOLTAGE 5 +#define MAGNITUDE_POWER_ACTIVE 6 +#define MAGNITUDE_POWER_APPARENT 7 +#define MAGNITUDE_POWER_REACTIVE 8 +#define MAGNITUDE_POWER_FACTOR 9 +#define MAGNITUDE_ENERGY 10 +#define MAGNITUDE_ENERGY_DELTA 11 +#define MAGNITUDE_ANALOG 12 +#define MAGNITUDE_DIGITAL 13 +#define MAGNITUDE_EVENTS 14 +#define MAGNITUDE_PM1dot0 15 +#define MAGNITUDE_PM2dot5 16 +#define MAGNITUDE_PM10 17 +#define MAGNITUDE_CO2 18 +#define MAGNITUDE_LUX 19 +#define MAGNITUDE_UV 20 + +#define MAGNITUDE_MAX 21 + +// ============================================================================= +// Specific data for each sensor +// ============================================================================= + +//------------------------------------------------------------------------------ +// Analog sensor +// Enable support by passing ANALOG_SUPPORT=1 build flag +//-------------------------------------------------------------------------------- + +#ifndef ANALOG_SUPPORT +#define ANALOG_SUPPORT 0 +#endif + +#if ANALOG_SUPPORT +#undef ADC_VCC_ENABLED +#define ADC_VCC_ENABLED 0 +#endif + +//------------------------------------------------------------------------------ +// BH1750 +// Enable support by passing BH1750_SUPPORT=1 build flag +// http://www.elechouse.com/elechouse/images/product/Digital%20light%20Sensor/bh1750fvi-e.pdf +//------------------------------------------------------------------------------ + +#ifndef BH1750_SUPPORT +#define BH1750_SUPPORT 0 +#endif + +#ifndef BH1750_ADDRESS +#define BH1750_ADDRESS 0x00 // 0x00 means auto +#endif + +#define BH1750_MODE BH1750_CONTINUOUS_HIGH_RES_MODE + +#if BH1750_SUPPORT +#undef I2C_SUPPORT +#define I2C_SUPPORT 1 +#endif + +//------------------------------------------------------------------------------ +// BME280/BMP280 +// Enable support by passing BMX280_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef BMX280_SUPPORT +#define BMX280_SUPPORT 0 +#endif + +#ifndef BMX280_ADDRESS +#define BMX280_ADDRESS 0x00 // 0x00 means auto +#endif + +#define BMX280_MODE 1 // 0 for sleep mode, 1 or 2 for forced mode, 3 for normal mode +#define BMX280_STANDBY 0 // 0 for 0.5ms, 1 for 62.5ms, 2 for 125ms + // 3 for 250ms, 4 for 500ms, 5 for 1000ms + // 6 for 10ms, 7 for 20ms +#define BMX280_FILTER 0 // 0 for OFF, 1 for 2 values, 2 for 4 values, 3 for 8 values and 4 for 16 values +#define BMX280_TEMPERATURE 1 // Oversampling for temperature (set to 0 to disable magnitude) +#define BMX280_HUMIDITY 1 // Oversampling for humidity (set to 0 to disable magnitude, only for BME280) +#define BMX280_PRESSURE 1 // Oversampling for pressure (set to 0 to disable magnitude) + +#if BMX280_SUPPORT +#undef I2C_SUPPORT +#define I2C_SUPPORT 1 +#endif + +//------------------------------------------------------------------------------ +// Dallas OneWire temperature sensors +// Enable support by passing DALLAS_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef DALLAS_SUPPORT +#define DALLAS_SUPPORT 0 +#endif + +#ifndef DALLAS_PIN +#define DALLAS_PIN 14 +#endif + +#define DALLAS_RESOLUTION 9 // Not used atm +#define DALLAS_READ_INTERVAL 2000 // Force sensor read & cache every 2 seconds + +//------------------------------------------------------------------------------ +// DHTXX temperature/humidity sensor +// Enable support by passing DHT_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef DHT_SUPPORT +#define DHT_SUPPORT 0 +#endif + +#ifndef DHT_PIN +#define DHT_PIN 13 +#endif + +#ifndef DHT_TYPE +#define DHT_TYPE DHT_CHIP_DHT22 +#endif + +//------------------------------------------------------------------------------ +// CSE7766 based power sensor +// Enable support by passing CSE7766_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef CSE7766_SUPPORT +#define CSE7766_SUPPORT 0 +#endif + +#ifndef CSE7766_PIN +#define CSE7766_PIN 1 // TX pin from the CSE7766 +#endif + +#ifndef CSE7766_PIN_INVERSE +#define CSE7766_PIN_INVERSE 0 // Signal is inverted +#endif + +#define CSE7766_SYNC_INTERVAL 300 // Safe time between transmissions (ms) +#define CSE7766_BAUDRATE 4800 // UART baudrate + +#define CSE7766_V1R 1.0 // 1mR current resistor +#define CSE7766_V2R 1.0 // 1M voltage resistor + + +//------------------------------------------------------------------------------ +// Digital sensor +// Enable support by passing DIGITAL_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef DIGITAL_SUPPORT +#define DIGITAL_SUPPORT 0 +#endif + +#ifndef DIGITAL_PIN +#define DIGITAL_PIN 2 +#endif + +#ifndef DIGITAL_PIN_MODE +#define DIGITAL_PIN_MODE INPUT_PULLUP +#endif + +#ifndef DIGITAL_DEFAULT_STATE +#define DIGITAL_DEFAULT_STATE 1 +#endif + +//------------------------------------------------------------------------------ +// ECH1560 based power sensor +// Enable support by passing ECH1560_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef ECH1560_SUPPORT +#define ECH1560_SUPPORT 0 +#endif + +#ifndef ECH1560_CLK_PIN +#define ECH1560_CLK_PIN 4 // CLK pin for the ECH1560 +#endif + +#ifndef ECH1560_MISO_PIN +#define ECH1560_MISO_PIN 5 // MISO pin for the ECH1560 +#endif + +#ifndef ECH1560_INVERTED +#define ECH1560_INVERTED 0 // Signal is inverted +#endif + +//------------------------------------------------------------------------------ +// Energy Monitor general settings +//------------------------------------------------------------------------------ + +#define EMON_MAX_SAMPLES 1000 // Max number of samples to get +#define EMON_MAX_TIME 250 // Max time in ms to sample +#define EMON_FILTER_SPEED 512 // Mobile average filter speed +#define EMON_MAINS_VOLTAGE 230 // Mains voltage +#define EMON_REFERENCE_VOLTAGE 3.3 // Reference voltage of the ADC +#define EMON_CURRENT_RATIO 30 // Current ratio in the clamp (30V/1A) +#define EMON_REPORT_CURRENT 0 // Report current +#define EMON_REPORT_POWER 1 // Report power +#define EMON_REPORT_ENERGY 1 // Report energy + +//------------------------------------------------------------------------------ +// Energy Monitor based on ADC121 +// Enable support by passing EMON_ADC121_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef EMON_ADC121_SUPPORT +#define EMON_ADC121_SUPPORT 0 // Do not build support by default +#endif + +#define EMON_ADC121_I2C_ADDRESS 0x00 // 0x00 means auto + +#if EMON_ADC121_SUPPORT +#undef I2C_SUPPORT +#define I2C_SUPPORT 1 +#endif + +//------------------------------------------------------------------------------ +// Energy Monitor based on ADS1X15 +// Enable support by passing EMON_ADS1X15_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef EMON_ADS1X15_SUPPORT +#define EMON_ADS1X15_SUPPORT 0 // Do not build support by default +#endif + +#define EMON_ADS1X15_I2C_ADDRESS 0x00 // 0x00 means auto +#define EMON_ADS1X15_TYPE ADS1X15_CHIP_ADS1115 +#define EMON_ADS1X15_GAIN ADS1X15_REG_CONFIG_PGA_4_096V +#define EMON_ADS1X15_MASK 0x0F // A0=1 A1=2 A2=4 A3=8 + +#if EMON_ADS1X15_SUPPORT +#undef I2C_SUPPORT +#define I2C_SUPPORT 1 +#endif + +//------------------------------------------------------------------------------ +// Energy Monitor based on interval analog GPIO +// Enable support by passing EMON_ANALOG_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef EMON_ANALOG_SUPPORT +#define EMON_ANALOG_SUPPORT 0 // Do not build support by default +#endif + +#if EMON_ANALOG_SUPPORT +#undef ADC_VCC_ENABLED +#define ADC_VCC_ENABLED 0 +#endif + +//------------------------------------------------------------------------------ +// Counter sensor +// Enable support by passing EVENTS_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef EVENTS_SUPPORT +#define EVENTS_SUPPORT 0 // Do not build with counter support by default +#endif + +#ifndef EVENTS_PIN +#define EVENTS_PIN 2 // GPIO to monitor +#endif + +#ifndef EVENTS_PIN_MODE +#define EVENTS_PIN_MODE INPUT // INPUT, INPUT_PULLUP +#endif + +#ifndef EVENTS_INTERRUPT_MODE +#define EVENTS_INTERRUPT_MODE RISING // RISING, FALLING, BOTH +#endif + +#define EVENTS_DEBOUNCE 50 // Do not register events within less than 10 millis + +//------------------------------------------------------------------------------ +// HLW8012 Energy monitor IC +// Enable support by passing HLW8012_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef HLW8012_SUPPORT +#define HLW8012_SUPPORT 0 +#endif + +#ifndef HLW8012_SEL_PIN +#define HLW8012_SEL_PIN 5 +#endif + +#ifndef HLW8012_CF1_PIN +#define HLW8012_CF1_PIN 13 +#endif + +#ifndef HLW8012_CF_PIN +#define HLW8012_CF_PIN 14 +#endif + +#ifndef HLW8012_SEL_CURRENT +#define HLW8012_SEL_CURRENT HIGH // SEL pin to HIGH to measure current +#endif + +#ifndef HLW8012_CURRENT_R +#define HLW8012_CURRENT_R 0.001 // Current resistor +#endif + +#ifndef HLW8012_VOLTAGE_R_UP +#define HLW8012_VOLTAGE_R_UP ( 5 * 470000 ) // Upstream voltage resistor +#endif + +#ifndef HLW8012_VOLTAGE_R_DOWN +#define HLW8012_VOLTAGE_R_DOWN ( 1000 ) // Downstream voltage resistor +#endif + +#define HLW8012_USE_INTERRUPTS 1 // Use interrupts to trap HLW8012 signals + +//------------------------------------------------------------------------------ +// MHZ19 CO2 sensor +// Enable support by passing MHZ19_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef MHZ19_SUPPORT +#define MHZ19_SUPPORT 0 +#endif + +#define MHZ19_RX_PIN 13 +#define MHZ19_TX_PIN 15 + +//------------------------------------------------------------------------------ +// Particle Monitor based on Plantower PMSX003 +// Enable support by passing PMSX003_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef PMSX003_SUPPORT +#define PMSX003_SUPPORT 0 +#endif + +#define PMS_RX_PIN 13 +#define PMS_TX_PIN 15 + +//------------------------------------------------------------------------------ +// PZEM004T based power monitor +// Enable support by passing PZEM004T_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef PZEM004T_SUPPORT +#define PZEM004T_SUPPORT 0 +#endif + +#ifndef PZEM004T_USE_SOFT +#define PZEM004T_USE_SOFT 1 // Use software serial +#endif + +#ifndef PZEM004T_RX_PIN +#define PZEM004T_RX_PIN 13 // Software serial RX GPIO (if PZEM004T_USE_SOFT == 1) +#endif + +#ifndef PZEM004T_TX_PIN +#define PZEM004T_TX_PIN 15 // Software serial TX GPIO (if PZEM004T_USE_SOFT == 1) +#endif + +#ifndef PZEM004T_HW_PORT +#define PZEM004T_HW_PORT Serial1 // Hardware serial port (if PZEM004T_USE_SOFT == 0) +#endif + + +//------------------------------------------------------------------------------ +// SHT3X I2C (Wemos) temperature & humidity sensor +// Enable support by passing SHT3X_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef SHT3X_I2C_SUPPORT +#define SHT3X_I2C_SUPPORT 0 +#endif + +#ifndef SHT3X_I2C_ADDRESS +#define SHT3X_I2C_ADDRESS 0x00 // 0x00 means auto +#endif + +#if SHT3X_I2C_SUPPORT +#undef I2C_SUPPORT +#define I2C_SUPPORT 1 +#endif + +//------------------------------------------------------------------------------ +// SI7021 temperature & humidity sensor +// Enable support by passing SI7021_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef SI7021_SUPPORT +#define SI7021_SUPPORT 0 +#endif + +#ifndef SI7021_ADDRESS +#define SI7021_ADDRESS 0x00 // 0x00 means auto +#endif + +#if SI7021_SUPPORT +#undef I2C_SUPPORT +#define I2C_SUPPORT 1 +#endif + +//------------------------------------------------------------------------------ +// V9261F based power sensor +// Enable support by passing SI7021_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef V9261F_SUPPORT +#define V9261F_SUPPORT 0 +#endif + +#ifndef V9261F_PIN +#define V9261F_PIN 2 // TX pin from the V9261F +#endif + +#ifndef V9261F_PIN_INVERSE +#define V9261F_PIN_INVERSE 1 // Signal is inverted +#endif + +#define V9261F_SYNC_INTERVAL 600 // Sync signal length (ms) +#define V9261F_BAUDRATE 4800 // UART baudrate + +// Default ratios +#define V9261F_CURRENT_FACTOR 79371434.0 +#define V9261F_VOLTAGE_FACTOR 4160651.0 +#define V9261F_POWER_FACTOR 153699.0 +#define V9261F_RPOWER_FACTOR V9261F_CURRENT_FACTOR + +//------------------------------------------------------------------------------ +// AM2320 Humidity & Temperature sensor over I2C +// Enable support by passing AM2320_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef AM2320_SUPPORT +#define AM2320_SUPPORT 0 +#endif + +#ifndef AM2320_ADDRESS +#define AM2320_ADDRESS 0x00 // 0x00 means auto +#endif + +#if AM2320_SUPPORT +#undef I2C_SUPPORT +#define I2C_SUPPORT 1 +#endif + +//------------------------------------------------------------------------------ +// GUVAS12SD UV Sensor (analog) +// Enable support by passing GUVAS12SD_SUPPORT=1 build flag +//------------------------------------------------------------------------------ + +#ifndef GUVAS12SD_SUPPORT +#define GUVAS12SD_SUPPORT 0 +#endif + +#ifndef GUVAS12SD_PIN +#define GUVAS12SD_PIN 14 +#endif + +// ============================================================================= +// Sensor helpers configuration +// ============================================================================= + +#ifndef SENSOR_SUPPORT +#if ANALOG_SUPPORT || BH1750_SUPPORT || BMX280_SUPPORT || DALLAS_SUPPORT \ + || DHT_SUPPORT || DIGITAL_SUPPORT || ECH1560_SUPPORT \ + || EMON_ADC121_SUPPORT || EMON_ADS1X15_SUPPORT \ + || EMON_ANALOG_SUPPORT || EVENTS_SUPPORT || HLW8012_SUPPORT \ + || MHZ19_SUPPORT || PMSX003_SUPPORT || SHT3X_I2C_SUPPORT \ + || SI7021_SUPPORT || V9261F_SUPPORT || AM2320_SUPPORT \ + || GUVAS12SD_SUPPORT || CSE7766_SUPPORT +#define SENSOR_SUPPORT 1 +#else +#define SENSOR_SUPPORT 0 +#endif +#endif + +// ----------------------------------------------------------------------------- +// I2C +// ----------------------------------------------------------------------------- + +#ifndef I2C_SUPPORT +#define I2C_SUPPORT 0 // I2C enabled (1.98Kb) +#endif + +#define I2C_USE_BRZO 0 // Use brzo_i2c library or standard Wire + +#ifndef I2C_SDA_PIN +#define I2C_SDA_PIN SDA // SDA GPIO (Sonoff => 4) +#endif + +#ifndef I2C_SCL_PIN +#define I2C_SCL_PIN SCL // SCL GPIO (Sonoff => 14) +#endif + +#define I2C_CLOCK_STRETCH_TIME 200 // BRZO clock stretch time +#define I2C_SCL_FREQUENCY 1000 // BRZO SCL frequency +#define I2C_CLEAR_BUS 0 // Clear I2C bus on boot +#define I2C_PERFORM_SCAN 1 // Perform a bus scan on boot + +//-------------------------------------------------------------------------------- +// Internal power monitor +// Enable support by passing ADC_VCC_ENABLED=1 build flag +// Do not enable this if using the analog GPIO for any other thing +//-------------------------------------------------------------------------------- + +#ifndef ADC_VCC_ENABLED +#define ADC_VCC_ENABLED 1 +#endif + +#if ADC_VCC_ENABLED + ADC_MODE(ADC_VCC); +#endif + +//-------------------------------------------------------------------------------- +// Class loading +//-------------------------------------------------------------------------------- + +#if SENSOR_SUPPORT + +PROGMEM const unsigned char magnitude_decimals[] = { + 0, + 1, 0, 2, + 3, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + 0, 0, 0, + 0, 0 +}; + +PROGMEM const char magnitude_unknown_topic[] = "unknown"; +PROGMEM const char magnitude_temperature_topic[] = "temperature"; +PROGMEM const char magnitude_humidity_topic[] = "humidity"; +PROGMEM const char magnitude_pressure_topic[] = "pressure"; +PROGMEM const char magnitude_current_topic[] = "current"; +PROGMEM const char magnitude_voltage_topic[] = "voltage"; +PROGMEM const char magnitude_active_power_topic[] = "power"; +PROGMEM const char magnitude_apparent_power_topic[] = "apparent"; +PROGMEM const char magnitude_reactive_power_topic[] = "reactive"; +PROGMEM const char magnitude_power_factor_topic[] = "factor"; +PROGMEM const char magnitude_energy_topic[] = "energy"; +PROGMEM const char magnitude_energy_delta_topic[] = "energy_delta"; +PROGMEM const char magnitude_analog_topic[] = "analog"; +PROGMEM const char magnitude_digital_topic[] = "digital"; +PROGMEM const char magnitude_events_topic[] = "events"; +PROGMEM const char magnitude_pm1dot0_topic[] = "pm1dot0"; +PROGMEM const char magnitude_pm2dot5_topic[] = "pm2dot5"; +PROGMEM const char magnitude_pm10_topic[] = "pm10"; +PROGMEM const char magnitude_co2_topic[] = "co2"; +PROGMEM const char magnitude_lux_topic[] = "lux"; +PROGMEM const char magnitude_uv_topic[] = "uv"; + +PROGMEM const char* const magnitude_topics[] = { + magnitude_unknown_topic, magnitude_temperature_topic, magnitude_humidity_topic, + magnitude_pressure_topic, magnitude_current_topic, magnitude_voltage_topic, + magnitude_active_power_topic, magnitude_apparent_power_topic, magnitude_reactive_power_topic, + magnitude_power_factor_topic, magnitude_energy_topic, magnitude_energy_delta_topic, + magnitude_analog_topic, magnitude_digital_topic, magnitude_events_topic, + magnitude_pm1dot0_topic, magnitude_pm2dot5_topic, magnitude_pm10_topic, + magnitude_co2_topic, magnitude_lux_topic, magnitude_uv_topic +}; + +PROGMEM const char magnitude_empty[] = ""; +PROGMEM const char magnitude_celsius[] = "C"; +PROGMEM const char magnitude_fahrenheit[] = "F"; +PROGMEM const char magnitude_percentage[] = "%"; +PROGMEM const char magnitude_hectopascals[] = "hPa"; +PROGMEM const char magnitude_amperes[] = "A"; +PROGMEM const char magnitude_volts[] = "V"; +PROGMEM const char magnitude_watts[] = "W"; +PROGMEM const char magnitude_kw[] = "kW"; +PROGMEM const char magnitude_joules[] = "J"; +PROGMEM const char magnitude_kwh[] = "kWh"; +PROGMEM const char magnitude_ugm3[] = "µg/m3"; +PROGMEM const char magnitude_ppm[] = "ppm"; +PROGMEM const char magnitude_lux[] = "lux"; +PROGMEM const char magnitude_uv[] = "uv"; + +PROGMEM const char* const magnitude_units[] = { + magnitude_empty, magnitude_celsius, magnitude_percentage, + magnitude_hectopascals, magnitude_amperes, magnitude_volts, + magnitude_watts, magnitude_watts, magnitude_watts, + magnitude_percentage, magnitude_joules, magnitude_joules, + magnitude_empty, magnitude_empty, magnitude_empty, + magnitude_ugm3, magnitude_ugm3, magnitude_ugm3, + magnitude_ppm, magnitude_lux, magnitude_uv +}; + +#include "../sensors/BaseSensor.h" + +#if ANALOG_SUPPORT + #include "../sensors/AnalogSensor.h" +#endif + +#if BH1750_SUPPORT + #include "../sensors/BH1750Sensor.h" +#endif + +#if BMX280_SUPPORT + #include "../sensors/BMX280Sensor.h" +#endif + +#if CSE7766_SUPPORT + #include + #include "../sensors/CSE7766Sensor.h" +#endif + +#if DALLAS_SUPPORT + #include + #include "../sensors/DallasSensor.h" +#endif + +#if DHT_SUPPORT + #include "../sensors/DHTSensor.h" +#endif + +#if DIGITAL_SUPPORT + #include "../sensors/DigitalSensor.h" +#endif + +#if ECH1560_SUPPORT + #include "../sensors/ECH1560Sensor.h" +#endif + +#if EMON_ADC121_SUPPORT + #include "../sensors/EmonADC121Sensor.h" +#endif + +#if EMON_ADS1X15_SUPPORT + #include "../sensors/EmonADS1X15Sensor.h" +#endif + +#if EMON_ANALOG_SUPPORT + #include "../sensors/EmonAnalogSensor.h" +#endif + +#if EVENTS_SUPPORT + #include "../sensors/EventSensor.h" +#endif + +#if HLW8012_SUPPORT + #include + #include "../sensors/HLW8012Sensor.h" +#endif + +#if MHZ19_SUPPORT + #include + #include "../sensors/MHZ19Sensor.h" +#endif + +#if PMSX003_SUPPORT + #include + #include + #include "../sensors/PMSX003Sensor.h" +#endif + +#if PZEM004T_SUPPORT + #include + #include "../sensors/PZEM004TSensor.h" +#endif + +#if SI7021_SUPPORT + #include "../sensors/SI7021Sensor.h" +#endif + +#if SHT3X_I2C_SUPPORT + #include "../sensors/SHT3XI2CSensor.h" +#endif + +#if V9261F_SUPPORT + #include + #include "../sensors/V9261FSensor.h" +#endif + +#if AM2320_SUPPORT + #include "../sensors/AM2320Sensor.h" +#endif + +#if GUVAS12SD_SUPPORT + #include "../sensors/GUVAS12SDSensor.h" +#endif + +#endif // SENSOR_SUPPORT diff --git a/espurna/config/version.h b/espurna/config/version.h new file mode 100755 index 0000000..f818fe3 --- /dev/null +++ b/espurna/config/version.h @@ -0,0 +1,6 @@ +#define APP_NAME "ESPURNA" +#define APP_VERSION "1.12.6a" +#define APP_REVISION "" +#define APP_AUTHOR "xose.perez@gmail.com" +#define APP_WEBSITE "http://tinkerman.cat" +#define CFG_VERSION 3 diff --git a/espurna/data/index.html.gz b/espurna/data/index.html.gz new file mode 100755 index 0000000000000000000000000000000000000000..8b35a44ed6c09326023b2818e63939668493e31d GIT binary patch literal 65168 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 literal 0 HcmV?d00001 diff --git a/espurna/data/server.cer b/espurna/data/server.cer new file mode 100755 index 0000000000000000000000000000000000000000..b5e5f248c4a5e86231876e4a9d0188624355266d GIT binary patch literal 587 zcmXqLVsbZVV$^5iWSGpfaN~?!WzGh?Y@Awc9&O)w85vnw84P3$r3@t4m_u2(cm!RG z3yM;Ui!;*{f-8$lQge$9E++@_GkM4DONU`s!Yh*a3uA%Yr>0vuTxoGhl`X= zOm)@|y?rGhX-}oBV&|g6>yCF;GafqH|Lx56CmB)ZicHLm42;Om1BMZE6C)!7Q{4 zVCeqqWd!S26n2qiLoZd^)KFr)c^x5%qQkDoiF*OU#gF#P*W_qgHz5K80RRC4fq?*x z404IsAG!|uZhSjSEN@k>Opd#k7h|ttCdOIJSPO%wGa?lf^COVvG_XDD&e? zyvGxataxxV>tpH@0=8+~?+Q)L-YEeBK>*|f|LN$~bw50ah7!i*rhGxNWaSKQgYqgm z{hCLUY2^{1Hv@E?C}m=~;yrD$M#BP=%o;o()sx1v9dHi3c<};30Lr%&dusO-%he4r zCXmZGM{6`I6pLC{CISQo#O-Kr1!n69lpq7y(si&Q zF#kQUO^(d{A^ck;ARC_@8*h>ZYI(wDiee6p=K$&v!C*c49 literal 0 HcmV?d00001 diff --git a/espurna/debug.ino b/espurna/debug.ino new file mode 100755 index 0000000..554e3fd --- /dev/null +++ b/espurna/debug.ino @@ -0,0 +1,268 @@ +/* + +DEBUG MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if DEBUG_SUPPORT + +#include +#include +#include + +extern "C" { + #include "user_interface.h" +} + +#if DEBUG_UDP_SUPPORT +#include +WiFiUDP _udp_debug; +#endif + +void _debugSend(char * message) { + + #if DEBUG_ADD_TIMESTAMP + static bool add_timestamp = true; + char timestamp[10] = {0}; + if (add_timestamp) snprintf_P(timestamp, sizeof(timestamp), PSTR("[%06lu] "), millis() % 1000000); + add_timestamp = (message[strlen(message)-1] == 10) || (message[strlen(message)-1] == 13); + #endif + + #if DEBUG_SERIAL_SUPPORT + #if DEBUG_ADD_TIMESTAMP + DEBUG_PORT.printf(timestamp); + #endif + DEBUG_PORT.printf(message); + #endif + + #if DEBUG_UDP_SUPPORT + #if SYSTEM_CHECK_ENABLED + if (systemCheck()) { + #endif + _udp_debug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT); + #if DEBUG_ADD_TIMESTAMP + _udp_debug.write(timestamp); + #endif + _udp_debug.write(message); + _udp_debug.endPacket(); + delay(1); // https://github.com/xoseperez/espurna/issues/438 + #if SYSTEM_CHECK_ENABLED + } + #endif + #endif + + #if DEBUG_TELNET_SUPPORT + #if DEBUG_ADD_TIMESTAMP + _telnetWrite(timestamp, strlen(timestamp)); + #endif + _telnetWrite(message, strlen(message)); + #endif + + #if DEBUG_WEB_SUPPORT + if (wsConnected() && (getFreeHeap() > 10000)) { + String m = String(message); + m.replace("\"", """); + m.replace("{", "{"); + m.replace("}", "}"); + char buffer[m.length() + 24]; + #if DEBUG_ADD_TIMESTAMP + snprintf_P(buffer, sizeof(buffer), PSTR("{\"weblog\": \"%s%s\"}"), timestamp, m.c_str()); + #else + snprintf_P(buffer, sizeof(buffer), PSTR("{\"weblog\": \"%s\"}"), m.c_str()); + #endif + wsSend(buffer); + } + #endif + +} + +// ----------------------------------------------------------------------------- + +void debugSend(const char * format, ...) { + + va_list args; + va_start(args, format); + char test[1]; + int len = ets_vsnprintf(test, 1, format, args) + 1; + char * buffer = new char[len]; + ets_vsnprintf(buffer, len, format, args); + va_end(args); + + _debugSend(buffer); + + delete[] buffer; + +} + +void debugSend_P(PGM_P format_P, ...) { + + char format[strlen_P(format_P)+1]; + memcpy_P(format, format_P, sizeof(format)); + + va_list args; + va_start(args, format_P); + char test[1]; + int len = ets_vsnprintf(test, 1, format, args) + 1; + char * buffer = new char[len]; + ets_vsnprintf(buffer, len, format, args); + va_end(args); + + _debugSend(buffer); + + delete[] buffer; + +} + +#if DEBUG_WEB_SUPPORT + +void debugSetup() { + + wsOnSendRegister([](JsonObject& root) { + root["dbgVisible"] = 1; + }); + + wsOnActionRegister([](uint32_t client_id, const char * action, JsonObject& data) { + if (strcmp(action, "dbgcmd") == 0) { + const char* command = data.get("command"); + char buffer[strlen(command) + 2]; + snprintf(buffer, sizeof(buffer), "%s\n", command); + settingsInject((void*) buffer, strlen(buffer)); + } + }); + +} + +#endif // DEBUG_WEB_SUPPORT + +// ----------------------------------------------------------------------------- +// Save crash info +// Taken from krzychb EspSaveCrash +// https://github.com/krzychb/EspSaveCrash +// ----------------------------------------------------------------------------- + +#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data + +/** + * Structure of the single crash data set + * + * 1. Crash time + * 2. Restart reason + * 3. Exception cause + * 4. epc1 + * 5. epc2 + * 6. epc3 + * 7. excvaddr + * 8. depc + * 9. adress of stack start + * 10. adress of stack end + * 11. stack trace bytes + * ... + */ +#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes +#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte +#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte +#define SAVE_CRASH_EPC1 0x06 // 4 bytes +#define SAVE_CRASH_EPC2 0x0A // 4 bytes +#define SAVE_CRASH_EPC3 0x0E // 4 bytes +#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes +#define SAVE_CRASH_DEPC 0x16 // 4 bytes +#define SAVE_CRASH_STACK_START 0x1A // 4 bytes +#define SAVE_CRASH_STACK_END 0x1E // 4 bytes +#define SAVE_CRASH_STACK_TRACE 0x22 // variable + +/** + * Save crash information in EEPROM + * This function is called automatically if ESP8266 suffers an exception + * It should be kept quick / consise to be able to execute before hardware wdt may kick in + */ +extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end ) { + + // This method assumes EEPROM has already been initialized + // which is the first thing ESPurna does + + // write crash time to EEPROM + uint32_t crash_time = millis(); + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); + + // write reset info to EEPROM + EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason); + EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause); + + // write epc1, epc2, epc3, excvaddr and depc to EEPROM + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1); + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2); + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3); + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr); + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc); + + // write stack start and end address to EEPROM + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start); + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end); + + // write stack trace to EEPROM + int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE; + for (uint32_t i = stack_start; i < stack_end; i++) { + byte* byteValue = (byte*) i; + EEPROM.write(current_address++, *byteValue); + } + + EEPROM.commit(); + +} + +/** + * Clears crash info + */ +void debugClearCrashInfo() { + uint32_t crash_time = 0xFFFFFFFF; + EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); + EEPROM.commit(); +} + +/** + * Print out crash information that has been previusly saved in EEPROM + */ +void debugDumpCrashInfo() { + + uint32_t crash_time; + EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time); + if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) { + DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n")); + return; + } + + DEBUG_MSG_P(PSTR("[DEBUG] Latest crash was at %lu ms after boot\n"), crash_time); + DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %u\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON)); + DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %u\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE)); + + uint32_t epc1, epc2, epc3, excvaddr, depc; + EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1); + EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2); + EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3); + EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr); + EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc); + DEBUG_MSG_P(PSTR("[DEBUG] epc1=0x%08x epc2=0x%08x epc3=0x%08x\n"), epc1, epc2, epc3); + DEBUG_MSG_P(PSTR("[DEBUG] excvaddr=0x%08x depc=0x%08x\n"), excvaddr, depc); + + uint32_t stack_start, stack_end; + EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start); + EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end); + DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] ")); + int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE; + int16_t stack_len = stack_end - stack_start; + uint32_t stack_trace; + for (int16_t i = 0; i < stack_len; i += 0x10) { + DEBUG_MSG_P(PSTR("%08x: "), stack_start + i); + for (byte j = 0; j < 4; j++) { + EEPROM.get(current_address, stack_trace); + DEBUG_MSG_P(PSTR("%08x "), stack_trace); + current_address += 4; + } + DEBUG_MSG_P(PSTR("\n[DEBUG] ")); + } + DEBUG_MSG_P(PSTR("<< + +*/ + +#if DOMOTICZ_SUPPORT + +#include + +bool _dcz_enabled = false; + +//------------------------------------------------------------------------------ +// Private methods +//------------------------------------------------------------------------------ + +unsigned char _domoticzRelay(unsigned int idx) { + for (unsigned char relayID=0; relayID= 0) { + unsigned char value = root["nvalue"]; + DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx); + relayStatus(relayID, value == 1); + } + + } + + } + +}; + +#if WEB_SUPPORT + +bool _domoticzWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "dcz", 3) == 0); +} + +void _domoticzWebSocketOnSend(JsonObject& root) { + + root["dczVisible"] = 1; + root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1; + root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC); + root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC); + + JsonArray& relays = root.createNestedArray("dczRelays"); + for (unsigned char i=0; i void domoticzSend(const char * key, T nvalue, const char * svalue) { + if (!_dcz_enabled) return; + unsigned int idx = getSetting(key).toInt(); + if (idx > 0) { + char payload[128]; + snprintf(payload, sizeof(payload), "{\"idx\": %u, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue); + mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload); + } +} + +template void domoticzSend(const char * key, T nvalue) { + domoticzSend(key, nvalue, ""); +} + +void domoticzSendRelay(unsigned char relayID) { + if (!_dcz_enabled) return; + char buffer[15]; + snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID); + domoticzSend(buffer, relayStatus(relayID) ? "1" : "0"); +} + +unsigned int domoticzIdx(unsigned char relayID) { + char buffer[15]; + snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID); + return getSetting(buffer).toInt(); +} + +void domoticzSetup() { + _domoticzConfigure(); + #if WEB_SUPPORT + wsOnSendRegister(_domoticzWebSocketOnSend); + wsOnAfterParseRegister(_domoticzConfigure); + wsOnReceiveRegister(_domoticzWebSocketOnReceive); + #endif + mqttRegister(_domoticzMqtt); +} + +bool domoticzEnabled() { + return _dcz_enabled; +} + +#endif diff --git a/espurna/espurna.ino b/espurna/espurna.ino new file mode 100755 index 0000000..6f6c843 --- /dev/null +++ b/espurna/espurna.ino @@ -0,0 +1,177 @@ +/* + +ESPurna + +Copyright (C) 2016-2018 by Xose Pérez + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +#include "config/all.h" +#include + +std::vector _loop_callbacks; + +// ----------------------------------------------------------------------------- +// REGISTER +// ----------------------------------------------------------------------------- + +void espurnaRegisterLoop(void (*callback)()) { + _loop_callbacks.push_back(callback); +} + +// ----------------------------------------------------------------------------- +// BOOTING +// ----------------------------------------------------------------------------- + +void setup() { + + // ------------------------------------------------------------------------- + // Basic modules, will always run + // ------------------------------------------------------------------------- + + // Init EEPROM, Serial, SPIFFS and system check + systemSetup(); + + // Init persistance and terminal features + settingsSetup(); + + // Hostname & board name initialization + if (getSetting("hostname").length() == 0) { + setDefaultHostname(); + } + setBoardName(); + + // Show welcome message and system configuration + info(); + + wifiSetup(); + otaSetup(); + #if TELNET_SUPPORT + telnetSetup(); + #endif + + // ------------------------------------------------------------------------- + // Check if system is stable + // ------------------------------------------------------------------------- + + #if SYSTEM_CHECK_ENABLED + if (!systemCheck()) return; + #endif + + // ------------------------------------------------------------------------- + // Next modules will be only loaded if system is flagged as stable + // ------------------------------------------------------------------------- + + // Init webserver required before any module that uses API + #if WEB_SUPPORT + webSetup(); + wsSetup(); + apiSetup(); + #if DEBUG_WEB_SUPPORT + debugSetup(); + #endif + #endif + + // lightSetup must be called before relaySetup + #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE + lightSetup(); + #endif + + relaySetup(); + buttonSetup(); + ledSetup(); + #if MQTT_SUPPORT + mqttSetup(); + #endif + #if MDNS_SERVER_SUPPORT + mdnsServerSetup(); + #endif + #if MDNS_CLIENT_SUPPORT + mdnsClientSetup(); + #endif + #if LLMNR_SUPPORT + llmnrSetup(); + #endif + #if NETBIOS_SUPPORT + netbiosSetup(); + #endif + #if SSDP_SUPPORT + ssdpSetup(); + #endif + #if NTP_SUPPORT + ntpSetup(); + #endif + #if I2C_SUPPORT + i2cSetup(); + #endif + #ifdef ITEAD_SONOFF_RFBRIDGE + rfbSetup(); + #endif + #if ALEXA_SUPPORT + alexaSetup(); + #endif + #if NOFUSS_SUPPORT + nofussSetup(); + #endif + #if INFLUXDB_SUPPORT + idbSetup(); + #endif + #if THINGSPEAK_SUPPORT + tspkSetup(); + #endif + #if RF_SUPPORT + rfSetup(); + #endif + #if IR_SUPPORT + irSetup(); + #endif + #if DOMOTICZ_SUPPORT + domoticzSetup(); + #endif + #if HOMEASSISTANT_SUPPORT + haSetup(); + #endif + #if SENSOR_SUPPORT + sensorSetup(); + #endif + #if SCHEDULER_SUPPORT + schSetup(); + #endif + #if UART_MQTT_SUPPORT + uartmqttSetup(); + #endif + + + // 3rd party code hook + #if USE_EXTRA + extraSetup(); + #endif + + // Prepare configuration for version 2.0 + migrate(); + + saveSettings(); + +} + +void loop() { + + // Call registered loop callbacks + for (unsigned char i = 0; i < _loop_callbacks.size(); i++) { + (_loop_callbacks[i])(); + } + +} diff --git a/espurna/filters/BaseFilter.h b/espurna/filters/BaseFilter.h new file mode 100755 index 0000000..3ad2852 --- /dev/null +++ b/espurna/filters/BaseFilter.h @@ -0,0 +1,25 @@ +// ----------------------------------------------------------------------------- +// Base Filter (other filters inherit from this) +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT + +#pragma once + +class BaseFilter { + + public: + virtual void add(double value); + virtual unsigned char count(); + virtual void reset(); + virtual double result(); + virtual void resize(unsigned char size); + unsigned char size() { return _size; }; + + protected: + unsigned char _size; + +}; + +#endif // SENSOR_SUPPORT diff --git a/espurna/filters/MaxFilter.h b/espurna/filters/MaxFilter.h new file mode 100755 index 0000000..9668fc9 --- /dev/null +++ b/espurna/filters/MaxFilter.h @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------------- +// Max Filter +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT + +#pragma once + +#include "BaseFilter.h" + +class MaxFilter : public BaseFilter { + + public: + + void add(double value) { + if (value > _max) _max = value; + } + + unsigned char count() { + return 1; + } + + void reset() { + _max = 0; + } + + double result() { + return _max; + } + + void resize(unsigned char size) {} + + protected: + + double _max = 0; + +}; + +#endif // SENSOR_SUPPORT diff --git a/espurna/filters/MedianFilter.h b/espurna/filters/MedianFilter.h new file mode 100755 index 0000000..1d34a30 --- /dev/null +++ b/espurna/filters/MedianFilter.h @@ -0,0 +1,92 @@ +// ----------------------------------------------------------------------------- +// Median Filter +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT + +#pragma once + +#include "BaseFilter.h" + +class MedianFilter : public BaseFilter { + + public: + + ~MedianFilter() { + if (_data) delete _data; + } + + void add(double value) { + if (_pointer <= _size) { + _data[_pointer] = value; + _pointer++; + } + } + + unsigned char count() { + return _pointer; + } + + void reset() { + if (_pointer > 0) { + _data[0] = _data[_pointer-1]; + _pointer = 1; + } else { + _pointer = 0; + } + } + + double result() { + + double sum = 0; + + if (_pointer > 2) { + + for (unsigned char i = 1; i <= _pointer - 2; i++) { + + // For each position, + // we find the median with the previous and next value + // and use that for the sum + + double previous = _data[i-1]; + double current = _data[i]; + double next = _data[i+1]; + + if (previous > current) std::swap(previous, current); + if (current > next) std::swap(current, next); + if (previous > current) std::swap(previous, current); + + sum += current; + + } + + sum /= (_pointer - 2); + + } else if (_pointer > 0) { + + sum = _data[0]; + + } + + return sum; + + } + + void resize(unsigned char size) { + if (_size == size) return; + _size = size; + if (_data) delete _data; + _data = new double[_size+1]; + for (unsigned char i=0; i<=_size; i++) _data[i] = 0; + _pointer = 0; + } + + protected: + + unsigned char _pointer = 0; + double * _data = NULL; + +}; + +#endif // SENSOR_SUPPORT diff --git a/espurna/filters/MovingAverageFilter.h b/espurna/filters/MovingAverageFilter.h new file mode 100755 index 0000000..21ba66d --- /dev/null +++ b/espurna/filters/MovingAverageFilter.h @@ -0,0 +1,51 @@ +// ----------------------------------------------------------------------------- +// Moving Average Filter +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT + +#pragma once + +#include +#include "BaseFilter.h" + +class MovingAverageFilter : public BaseFilter { + + public: + + void add(double value) { + _sum = _sum + value - _data[_pointer]; + _data[_pointer] = value; + _pointer = (_pointer + 1) % _size; + } + + unsigned char count() { + return _pointer; + } + + void reset() {} + + double result() { + return _sum; + } + + void resize(unsigned char size) { + if (_size == size) return; + _size = size; + if (_data) delete _data; + _data = new double[_size]; + for (unsigned char i=0; i<_size; i++) _data[i] = 0; + _pointer = 0; + _sum = 0; + } + + protected: + + unsigned char _pointer = 0; + double _sum = 0; + double * _data = NULL; + +}; + +#endif // SENSOR_SUPPORT diff --git a/espurna/gpio.ino b/espurna/gpio.ino new file mode 100755 index 0000000..c69f437 --- /dev/null +++ b/espurna/gpio.ino @@ -0,0 +1,39 @@ +/* + +GPIO MODULE + +Copyright (C) 2017-2018 by Xose Pérez + +*/ + +unsigned int _gpio_locked = 0; + +bool gpioValid(unsigned char gpio) { + if (gpio <= 5) return true; + if (12 <= gpio && gpio <= 15) return true; + return false; +} + +bool gpioGetLock(unsigned char gpio) { + if (gpioValid(gpio)) { + unsigned int mask = 1 << gpio; + if ((_gpio_locked & mask) == 0) { + _gpio_locked |= mask; + DEBUG_MSG_P(PSTR("[GPIO] GPIO%u locked\n"), gpio); + return true; + } + } + DEBUG_MSG_P(PSTR("[GPIO] Failed getting lock for GPIO%u\n"), gpio); + return false; +} + +bool gpioReleaseLock(unsigned char gpio) { + if (gpioValid(gpio)) { + unsigned int mask = 1 << gpio; + _gpio_locked &= ~mask; + DEBUG_MSG_P(PSTR("[GPIO] GPIO%u lock released\n"), gpio); + return true; + } + DEBUG_MSG_P(PSTR("[GPIO] Failed releasing lock for GPIO%u\n"), gpio); + return false; +} diff --git a/espurna/homeassistant.ino b/espurna/homeassistant.ino new file mode 100755 index 0000000..6c4852e --- /dev/null +++ b/espurna/homeassistant.ino @@ -0,0 +1,297 @@ +/* + +HOME ASSISTANT MODULE + +Copyright (C) 2017-2018 by Xose Pérez + +*/ + +#if HOMEASSISTANT_SUPPORT + +#include + +bool _haEnabled = false; +bool _haSendFlag = false; + +// ----------------------------------------------------------------------------- +// SENSORS +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT + +void _haSendMagnitude(unsigned char i, JsonObject& config) { + + unsigned char type = magnitudeType(i); + config["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type); + config.set("platform", "mqtt"); + config.set("device_class", "sensor"); + config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false); + config["unit_of_measurement"] = String("\"") + magnitudeUnits(type) + String("\""); + +} + +void _haSendMagnitudes() { + + for (unsigned char i=0; i 1) { + name += String(" #") + String(i); + } + + config.set("name", name); + config.set("platform", "mqtt"); + + if (relayCount()) { + config["state_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, false); + config["command_topic"] = mqttTopic(MQTT_TOPIC_RELAY, i, true); + config["payload_on"] = String("1"); + config["payload_off"] = String("0"); + config["availability_topic"] = mqttTopic(MQTT_TOPIC_STATUS, false); + config["payload_available"] = String("1"); + config["payload_not_available"] = String("0"); + } + + #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE + + if (i == 0) { + + if (lightHasColor()) { + config["brightness_state_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, false); + config["brightness_command_topic"] = mqttTopic(MQTT_TOPIC_BRIGHTNESS, true); + config["rgb_state_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, false); + config["rgb_command_topic"] = mqttTopic(MQTT_TOPIC_COLOR_RGB, true); + config["color_temp_command_topic"] = mqttTopic(MQTT_TOPIC_MIRED, true); + } + + if (lightChannels() > 3) { + config["white_value_state_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, false); + config["white_value_command_topic"] = mqttTopic(MQTT_TOPIC_CHANNEL, 3, true); + } + + } + + #endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE + +} + +void _haSendSwitches() { + + #if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER)) + String type = String("light"); + #else + String type = String("switch"); + #endif + + for (unsigned char i=0; i() + String("\n"); + } + output += "\n"; + + } + + #if SENSOR_SUPPORT + + for (unsigned char i=0; i() + String("\n"); + } + output += "\n"; + + } + + #endif + + return output; + +} + +void _haSend() { + + // Pending message to send? + if (!_haSendFlag) return; + + // Are we connected? + if (!mqttConnected()) return; + + DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n")); + + // Send messages + _haSendSwitches(); + #if SENSOR_SUPPORT + _haSendMagnitudes(); + #endif + + _haSendFlag = false; + +} + +void _haConfigure() { + bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1; + _haSendFlag = (enabled != _haEnabled); + _haEnabled = enabled; + _haSend(); +} + +#if WEB_SUPPORT + +bool _haWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "ha", 2) == 0); +} + +void _haWebSocketOnSend(JsonObject& root) { + root["haVisible"] = 1; + root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX); + root["haEnabled"] = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1; +} + +void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { + if (strcmp(action, "haconfig") == 0) { + String output = _haGetConfig(); + output.replace(" ", " "); + output.replace("\n", "
"); + output = String("{\"haConfig\": \"") + output + String("\"}"); + wsSend(client_id, output.c_str()); + } +} + +#endif + +#if TERMINAL_SUPPORT + +void _haInitCommands() { + settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) { + DEBUG_MSG(_haGetConfig().c_str()); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) { + setSetting("haEnabled", "1"); + _haConfigure(); + wsSend(_haWebSocketOnSend); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) { + setSetting("haEnabled", "0"); + _haConfigure(); + wsSend(_haWebSocketOnSend); + DEBUG_MSG_P(PSTR("+OK\n")); + }); +} + +#endif + +// ----------------------------------------------------------------------------- + +void haSetup() { + + _haConfigure(); + + #if WEB_SUPPORT + wsOnSendRegister(_haWebSocketOnSend); + wsOnAfterParseRegister(_haConfigure); + wsOnActionRegister(_haWebSocketOnAction); + wsOnReceiveRegister(_haWebSocketOnReceive); + #endif + + // On MQTT connect check if we have something to send + mqttRegister([](unsigned int type, const char * topic, const char * payload) { + if (type == MQTT_CONNECT_EVENT) _haSend(); + }); + + #if TERMINAL_SUPPORT + _haInitCommands(); + #endif + + +} + +#endif // HOMEASSISTANT_SUPPORT diff --git a/espurna/i2c.ino b/espurna/i2c.ino new file mode 100755 index 0000000..8300929 --- /dev/null +++ b/espurna/i2c.ino @@ -0,0 +1,379 @@ +/* + +I2C MODULE + +Copyright (C) 2017-2018 by Xose Pérez + +*/ + +#if I2C_SUPPORT + +unsigned int _i2c_locked[16] = {0}; + +#if I2C_USE_BRZO +#include "brzo_i2c.h" +unsigned long _i2c_scl_frequency = 0; +#else +#include "Wire.h" +#endif + +// ----------------------------------------------------------------------------- +// Private +// ----------------------------------------------------------------------------- + +int _i2cClearbus(int sda, int scl) { + + #if defined(TWCR) && defined(TWEN) + // Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly + TWCR &= ~(_BV(TWEN)); + #endif + + // Make SDA (data) and SCL (clock) pins inputs with pullup + pinMode(sda, INPUT_PULLUP); + pinMode(scl, INPUT_PULLUP); + + nice_delay(2500); + // Wait 2.5 secs. This is strictly only necessary on the first power + // up of the DS3231 module to allow it to initialize properly, + // but is also assists in reliable programming of FioV3 boards as it gives the + // IDE a chance to start uploaded the program + // before existing sketch confuses the IDE by sending Serial data. + + // If it is held low the device cannot become the I2C master + // I2C bus error. Could not clear SCL clock line held low + boolean scl_low = (digitalRead(scl) == LOW); + if (scl_low) return 1; + + + boolean sda_low = (digitalRead(sda) == LOW); + int clockCount = 20; // > 2x9 clock + + // While SDA is low for at most 20 cycles + while (sda_low && (clockCount > 0)) { + + clockCount--; + + // Note: I2C bus is open collector so do NOT drive SCL or SDA high + pinMode(scl, INPUT); // release SCL pullup so that when made output it will be LOW + pinMode(scl, OUTPUT); // then clock SCL Low + delayMicroseconds(10); // for >5uS + pinMode(scl, INPUT); // release SCL LOW + pinMode(scl, INPUT_PULLUP); // turn on pullup resistors again + // do not force high as slave may be holding it low for clock stretching + + delayMicroseconds(10); // The >5uS is so that even the slowest I2C devices are handled + + // loop waiting for SCL to become high only wait 2sec + scl_low = (digitalRead(scl) == LOW); + int counter = 20; + while (scl_low && (counter > 0)) { + counter--; + nice_delay(100); + scl_low = (digitalRead(scl) == LOW); + } + + // If still low after 2 sec error + // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec + if (scl_low) return 2; + + sda_low = (digitalRead(sda) == LOW); // and check SDA input again and loop + + } + + // If still low + // I2C bus error. Could not clear. SDA data line held low + if (sda_low) return 3; + + // Pull SDA line low for "start" or "repeated start" + pinMode(sda, INPUT); // remove pullup + pinMode(sda, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control + + // When there is only one I2C master a "start" or "repeat start" has the same function as a "stop" and clears the bus + // A Repeat Start is a Start occurring after a Start with no intervening Stop. + + delayMicroseconds(10); // wait >5uS + pinMode(sda, INPUT); // remove output low + pinMode(sda, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control. + + delayMicroseconds(10); // wait >5uS + pinMode(sda, INPUT); // and reset pins as tri-state inputs which is the default state on reset + pinMode(scl, INPUT); + + // Everything OK + return 0; + +} + +// --------------------------------------------------------------------- +// I2C API +// --------------------------------------------------------------------- + +#if I2C_USE_BRZO + +void i2c_wakeup(uint8_t address) { + brzo_i2c_start_transaction(_address, _i2c_scl_frequency); + brzo_i2c_end_transaction(); +} + +uint8_t i2c_write_uint8(uint8_t address, uint8_t value) { + uint8_t buffer[1] = {value}; + brzo_i2c_start_transaction(_address, _i2c_scl_frequency); + brzo_i2c_write_uint8(buffer, 1, false); + return brzo_i2c_end_transaction(); +} + +uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) { + brzo_i2c_start_transaction(_address, _i2c_scl_frequency); + brzo_i2c_write_uint8(buffer, len, false); + return brzo_i2c_end_transaction(); +} + +uint8_t i2c_read_uint8(uint8_t address) { + uint8_t buffer[1] = {reg}; + brzo_i2c_start_transaction(_address, _i2c_scl_frequency); + brzo_i2c_read(buffer, 1, false); + brzo_i2c_end_transaction(); + return buffer[0]; +}; + +uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) { + uint8_t buffer[1] = {reg}; + brzo_i2c_start_transaction(_address, _i2c_scl_frequency); + brzo_i2c_write_uint8(buffer, 1, false); + brzo_i2c_read(buffer, 1, false); + brzo_i2c_end_transaction(); + return buffer[0]; +}; + +uint16_t i2c_read_uint16(uint8_t address) { + uint8_t buffer[2] = {reg, 0}; + brzo_i2c_start_transaction(_address, _i2c_scl_frequency); + brzo_i2c_read(buffer, 2, false); + brzo_i2c_end_transaction(); + return (buffer[0] * 256) | buffer[1]; +}; + +uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) { + uint8_t buffer[2] = {reg, 0}; + brzo_i2c_start_transaction(_address, _i2c_scl_frequency); + brzo_i2c_write_uint8(buffer, 1, false); + brzo_i2c_read(buffer, 2, false); + brzo_i2c_end_transaction(); + return (buffer[0] * 256) | buffer[1]; +}; + +void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) { + brzo_i2c_start_transaction(address, _i2c_scl_frequency); + brzo_i2c_read(buffer, len, false); + brzo_i2c_end_transaction(); +} + +#else // not I2C_USE_BRZO + +void i2c_wakeup(uint8_t address) { + Wire.beginTransmission((uint8_t) address); + Wire.endTransmission(); +} + +uint8_t i2c_write_uint8(uint8_t address, uint8_t value) { + Wire.beginTransmission((uint8_t) address); + Wire.write((uint8_t) value); + return Wire.endTransmission(); +} + +uint8_t i2c_write_buffer(uint8_t address, uint8_t * buffer, size_t len) { + Wire.beginTransmission((uint8_t) address); + Wire.write(buffer, len); + return Wire.endTransmission(); +} + +uint8_t i2c_read_uint8(uint8_t address) { + uint8_t value; + Wire.beginTransmission((uint8_t) address); + Wire.requestFrom((uint8_t) address, (uint8_t) 1); + value = Wire.read(); + Wire.endTransmission(); + return value; +}; + +uint8_t i2c_read_uint8(uint8_t address, uint8_t reg) { + uint8_t value; + Wire.beginTransmission((uint8_t) address); + Wire.write((uint8_t) reg); + Wire.endTransmission(); + Wire.requestFrom((uint8_t) address, (uint8_t) 1); + value = Wire.read(); + Wire.endTransmission(); + return value; +}; + +uint16_t i2c_read_uint16(uint8_t address) { + uint16_t value; + Wire.beginTransmission((uint8_t) address); + Wire.requestFrom((uint8_t) address, (uint8_t) 2); + value = (Wire.read() * 256) | Wire.read(); + Wire.endTransmission(); + return value; +}; + +uint16_t i2c_read_uint16(uint8_t address, uint8_t reg) { + uint16_t value; + Wire.beginTransmission((uint8_t) address); + Wire.write((uint8_t) reg); + Wire.endTransmission(); + Wire.requestFrom((uint8_t) address, (uint8_t) 2); + value = (Wire.read() * 256) | Wire.read(); + Wire.endTransmission(); + return value; +}; + +void i2c_read_buffer(uint8_t address, uint8_t * buffer, size_t len) { + Wire.beginTransmission((uint8_t) address); + Wire.requestFrom(address, (uint8_t) len); + for (int i=0; i> 8) & 0xFF; + buffer[2] = (value >> 0) & 0xFF; + return i2c_write_buffer(address, buffer, 3); +} + +uint8_t i2c_write_uint16(uint8_t address, uint16_t value) { + uint8_t buffer[2]; + buffer[0] = (value >> 8) & 0xFF; + buffer[1] = (value >> 0) & 0xFF; + return i2c_write_buffer(address, buffer, 2); +} + +uint16_t i2c_read_uint16_le(uint8_t address, uint8_t reg) { + uint16_t temp = i2c_read_uint16(address, reg); + return (temp / 256) | (temp * 256); +}; + +int16_t i2c_read_int16(uint8_t address, uint8_t reg) { + return (int16_t) i2c_read_uint16(address, reg); +}; + +int16_t i2c_read_int16_le(uint8_t address, uint8_t reg) { + return (int16_t) i2c_read_uint16_le(address, reg); +}; + +// ----------------------------------------------------------------------------- +// Utils +// ----------------------------------------------------------------------------- + +void i2cClearBus() { + unsigned char sda = getSetting("i2cSDA", I2C_SDA_PIN).toInt(); + unsigned char scl = getSetting("i2cSCL", I2C_SCL_PIN).toInt(); + DEBUG_MSG_P(PSTR("[I2C] Clear bus (response: %d)\n"), _i2cClearbus(sda, scl)); +} + +bool i2cCheck(unsigned char address) { + #if I2C_USE_BRZO + brzo_i2c_start_transaction(address, _i2c_scl_frequency); + brzo_i2c_ACK_polling(1000); + return brzo_i2c_end_transaction(); + #else + Wire.beginTransmission(address); + return Wire.endTransmission(); + #endif +} + +bool i2cGetLock(unsigned char address) { + unsigned char index = address / 8; + unsigned char mask = 1 << (address % 8); + if (_i2c_locked[index] & mask) return false; + _i2c_locked[index] = _i2c_locked[index] | mask; + DEBUG_MSG_P(PSTR("[I2C] Address 0x%02X locked\n"), address); + return true; +} + +bool i2cReleaseLock(unsigned char address) { + unsigned char index = address / 8; + unsigned char mask = 1 << (address % 8); + if (_i2c_locked[index] & mask) { + _i2c_locked[index] = _i2c_locked[index] & ~mask; + return true; + } + return false; +} + +unsigned char i2cFind(size_t size, unsigned char * addresses, unsigned char &start) { + for (unsigned char i=start; i + +*/ + +#if INFLUXDB_SUPPORT + +#include "ESPAsyncTCP.h" +#include "SyncClient.h" + +bool _idb_enabled = false; +SyncClient _idb_client; + +// ----------------------------------------------------------------------------- + +bool _idbWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "idb", 3) == 0); +} + +void _idbWebSocketOnSend(JsonObject& root) { + root["idbVisible"] = 1; + root["idbEnabled"] = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1; + root["idbHost"] = getSetting("idbHost", INFLUXDB_HOST); + root["idbPort"] = getSetting("idbPort", INFLUXDB_PORT).toInt(); + root["idbDatabase"] = getSetting("idbDatabase", INFLUXDB_DATABASE); + root["idbUsername"] = getSetting("idbUsername", INFLUXDB_USERNAME); + root["idbPassword"] = getSetting("idbPassword", INFLUXDB_PASSWORD); +} + +void _idbConfigure() { + _idb_enabled = getSetting("idbEnabled", INFLUXDB_ENABLED).toInt() == 1; + if (_idb_enabled && (getSetting("idbHost", INFLUXDB_HOST).length() == 0)) { + _idb_enabled = false; + setSetting("idbEnabled", 0); + } +} + +// ----------------------------------------------------------------------------- + +bool idbSend(const char * topic, const char * payload) { + + if (!_idb_enabled) return true; + if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return true; + + String h = getSetting("idbHost", INFLUXDB_HOST); + #if MDNS_CLIENT_SUPPORT + h = mdnsResolve(h); + #endif + char * host = strdup(h.c_str()); + unsigned int port = getSetting("idbPort", INFLUXDB_PORT).toInt(); + DEBUG_MSG("[INFLUXDB] Sending to %s:%u\n", host, port); + + bool success = false; + + _idb_client.setTimeout(2); + if (_idb_client.connect((const char *) host, port)) { + + char data[128]; + snprintf(data, sizeof(data), "%s,device=%s value=%s", topic, getSetting("hostname").c_str(), String(payload).c_str()); + DEBUG_MSG("[INFLUXDB] Data: %s\n", data); + + char request[256]; + snprintf(request, sizeof(request), "POST /write?db=%s&u=%s&p=%s HTTP/1.1\r\nHost: %s:%u\r\nContent-Length: %d\r\n\r\n%s", + getSetting("idbDatabase", INFLUXDB_DATABASE).c_str(), + getSetting("idbUsername", INFLUXDB_USERNAME).c_str(), getSetting("idbPassword", INFLUXDB_PASSWORD).c_str(), + host, port, strlen(data), data); + + if (_idb_client.printf(request) > 0) { + while (_idb_client.connected() && _idb_client.available() == 0) delay(1); + while (_idb_client.available()) _idb_client.read(); + if (_idb_client.connected()) _idb_client.stop(); + success = true; + } else { + DEBUG_MSG("[INFLUXDB] Sent failed\n"); + } + + _idb_client.stop(); + while (_idb_client.connected()) yield(); + + } else { + DEBUG_MSG("[INFLUXDB] Connection failed\n"); + } + + free(host); + return success; + +} + +bool idbSend(const char * topic, unsigned char id, const char * payload) { + char measurement[64]; + snprintf(measurement, sizeof(measurement), "%s,id=%d", topic, id); + return idbSend(topic, payload); +} + +bool idbEnabled() { + return _idb_enabled; +} + +void idbSetup() { + _idbConfigure(); + #if WEB_SUPPORT + wsOnSendRegister(_idbWebSocketOnSend); + wsOnAfterParseRegister(_idbConfigure); + wsOnReceiveRegister(_idbWebSocketOnReceive); + #endif +} + +#endif diff --git a/espurna/ir.ino b/espurna/ir.ino new file mode 100755 index 0000000..241c6fa --- /dev/null +++ b/espurna/ir.ino @@ -0,0 +1,112 @@ +/* + +IR MODULE + +Copyright (C) 2016-2018 by Xose Pérez +Copyright (C) 2017-2018 by François Déchery + +*/ + +#if IR_SUPPORT + +#include +#include + +IRrecv * _ir_recv; +decode_results _ir_results; + +// ----------------------------------------------------------------------------- +// PRIVATE +// ----------------------------------------------------------------------------- + +void _irProcessCode(unsigned long code) { + + static unsigned long last_code; + boolean found = false; + + // Repeat last valid code + DEBUG_MSG_P(PSTR("[IR] Received 0x%06X\n"), code); + if (code == 0xFFFFFFFF) { + DEBUG_MSG_P(PSTR("[IR] Processing 0x%06X\n"), code); + code = last_code; + } + + for (unsigned char i = 0; i < IR_BUTTON_COUNT ; i++) { + + unsigned long button_code = pgm_read_dword(&IR_BUTTON[i][0]); + if (code == button_code) { + + unsigned long button_mode = pgm_read_dword(&IR_BUTTON[i][1]); + unsigned long button_value = pgm_read_dword(&IR_BUTTON[i][2]); + + if (button_mode == IR_BUTTON_MODE_STATE) { + relayStatus(0, button_value); + } + + #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE + + if (button_mode == IR_BUTTON_MODE_BRIGHTER) { + lightBrightnessStep(button_value ? 1 : -1); + nice_delay(150); //debounce + } + + if (button_mode == IR_BUTTON_MODE_RGB) { + lightColor(button_value); + } + + /* + #if LIGHT_PROVIDER == LIGHT_PROVIDER_FASTLED + if (button_mode == IR_BUTTON_MODE_EFFECT) { + _buttonAnimMode(button_value); + } + #endif + */ + + /* + if (button_mode == IR_BUTTON_MODE_HSV) { + lightColor(button_value); + } + */ + + lightUpdate(true, true); + + #endif + + found = true; + last_code = code; + break; + + } + + } + + if (!found) { + DEBUG_MSG_P(PSTR("[IR] Ignoring code\n")); + } + +} + + +// ----------------------------------------------------------------------------- +// PUBLIC API +// ----------------------------------------------------------------------------- + +void irSetup() { + + _ir_recv = new IRrecv(IR_PIN); + _ir_recv->enableIRIn(); + + // Register loop + espurnaRegisterLoop(irLoop); + +} + +void irLoop() { + if (_ir_recv->decode(&_ir_results)) { + unsigned long code = _ir_results.value; + _irProcessCode(code); + _ir_recv->resume(); // Receive the next value + } +} + +#endif // IR_SUPPORT diff --git a/espurna/led.ino b/espurna/led.ino new file mode 100755 index 0000000..b93013f --- /dev/null +++ b/espurna/led.ino @@ -0,0 +1,290 @@ +/* + +LED MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +// ----------------------------------------------------------------------------- +// LED +// ----------------------------------------------------------------------------- + +typedef struct { + unsigned char pin; + bool reverse; + unsigned char mode; + unsigned char relay; +} led_t; + +std::vector _leds; +bool _led_update = false; // For relay-based modes + +// ----------------------------------------------------------------------------- + +bool _ledStatus(unsigned char id) { + if (id >= _ledCount()) return false; + bool status = digitalRead(_leds[id].pin); + return _leds[id].reverse ? !status : status; +} + +bool _ledStatus(unsigned char id, bool status) { + if (id >=_ledCount()) return false; + digitalWrite(_leds[id].pin, _leds[id].reverse ? !status : status); + return status; +} + +bool _ledToggle(unsigned char id) { + if (id >= _ledCount()) return false; + return _ledStatus(id, !_ledStatus(id)); +} + +unsigned char _ledMode(unsigned char id) { + if (id >= _ledCount()) return false; + return _leds[id].mode; +} + +void _ledMode(unsigned char id, unsigned char mode) { + if (id >= _ledCount()) return; + _leds[id].mode = mode; +} + +void _ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn) { + if (id >= _ledCount()) return; + static unsigned long next = millis(); + if (next < millis()) { + next += (_ledToggle(id) ? delayOn : delayOff); + } +} + +#if WEB_SUPPORT + +bool _ledWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "led", 3) == 0); +} + +void _ledWebSocketOnSend(JsonObject& root) { + if (_ledCount() == 0) return; + root["ledVisible"] = 1; + root["ledMode0"] = _ledMode(0); +} + +#endif + +#if MQTT_SUPPORT +void _ledMQTTCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + char buffer[strlen(MQTT_TOPIC_LED) + 3]; + snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_LED); + mqttSubscribe(buffer); + } + + if (type == MQTT_MESSAGE_EVENT) { + + // Match topic + String t = mqttMagnitude((char *) topic); + if (!t.startsWith(MQTT_TOPIC_LED)) return; + + // Get led ID + unsigned int ledID = t.substring(strlen(MQTT_TOPIC_LED)+1).toInt(); + if (ledID >= _ledCount()) { + DEBUG_MSG_P(PSTR("[LED] Wrong ledID (%d)\n"), ledID); + return; + } + + // Check if LED is managed + if (_ledMode(ledID) != LED_MODE_MQTT) return; + + // get value + unsigned char value = relayParsePayload(payload); + + // Action to perform + if (value == 2) { + _ledToggle(ledID); + } else { + _ledStatus(ledID, value == 1); + } + + } + +} +#endif + +unsigned char _ledCount() { + return _leds.size(); +} + +void _ledConfigure() { + for (unsigned int i=0; i < _leds.size(); i++) { + _ledMode(i, getSetting("ledMode", i, _ledMode(i)).toInt()); + } + _led_update = true; +} + +// ----------------------------------------------------------------------------- + +void ledUpdate(bool value) { + _led_update = value; +} + +void ledSetup() { + + #if LED1_PIN != GPIO_NONE + _leds.push_back((led_t) { LED1_PIN, LED1_PIN_INVERSE, LED1_MODE, LED1_RELAY }); + #endif + #if LED2_PIN != GPIO_NONE + _leds.push_back((led_t) { LED2_PIN, LED2_PIN_INVERSE, LED2_MODE, LED2_RELAY }); + #endif + #if LED3_PIN != GPIO_NONE + _leds.push_back((led_t) { LED3_PIN, LED3_PIN_INVERSE, LED3_MODE, LED3_RELAY }); + #endif + #if LED4_PIN != GPIO_NONE + _leds.push_back((led_t) { LED4_PIN, LED4_PIN_INVERSE, LED4_MODE, LED4_RELAY }); + #endif + #if LED5_PIN != GPIO_NONE + _leds.push_back((led_t) { LED5_PIN, LED5_PIN_INVERSE, LED5_MODE, LED5_RELAY }); + #endif + #if LED6_PIN != GPIO_NONE + _leds.push_back((led_t) { LED6_PIN, LED6_PIN_INVERSE, LED6_MODE, LED6_RELAY }); + #endif + #if LED7_PIN != GPIO_NONE + _leds.push_back((led_t) { LED7_PIN, LED7_PIN_INVERSE, LED7_MODE, LED7_RELAY }); + #endif + #if LED8_PIN != GPIO_NONE + _leds.push_back((led_t) { LED8_PIN, LED8_PIN_INVERSE, LED8_MODE, LED8_RELAY }); + #endif + + for (unsigned int i=0; i < _leds.size(); i++) { + pinMode(_leds[i].pin, OUTPUT); + _ledStatus(i, false); + } + + _ledConfigure(); + + #if MQTT_SUPPORT + mqttRegister(_ledMQTTCallback); + #endif + + #if WEB_SUPPORT + wsOnSendRegister(_ledWebSocketOnSend); + wsOnAfterParseRegister(_ledConfigure); + wsOnReceiveRegister(_ledWebSocketOnReceive); + #endif + + DEBUG_MSG_P(PSTR("[LED] Number of leds: %d\n"), _leds.size()); + + // Register loop + espurnaRegisterLoop(ledLoop); + + +} + +void ledLoop() { + + for (unsigned char i=0; i<_leds.size(); i++) { + + if (_ledMode(i) == LED_MODE_WIFI) { + + if (wifiConnected()) { + if (WiFi.getMode() == WIFI_AP) { + _ledBlink(i, 900, 100); + } else { + _ledBlink(i, 4900, 100); + } + } else { + _ledBlink(i, 500, 500); + } + + } + + if (_ledMode(i) == LED_MODE_FINDME_WIFI) { + + if (wifiConnected()) { + if (relayStatus(_leds[i].relay-1)) { + if (WiFi.getMode() == WIFI_AP) { + _ledBlink(i, 900, 100); + } else { + _ledBlink(i, 4900, 100); + } + } else { + if (WiFi.getMode() == WIFI_AP) { + _ledBlink(i, 100, 900); + } else { + _ledBlink(i, 100, 4900); + } + } + } else { + _ledBlink(i, 500, 500); + } + + } + + if (_ledMode(i) == LED_MODE_RELAY_WIFI) { + + if (wifiConnected()) { + if (relayStatus(_leds[i].relay-1)) { + if (WiFi.getMode() == WIFI_AP) { + _ledBlink(i, 100, 900); + } else { + _ledBlink(i, 100, 4900); + } + } else { + if (WiFi.getMode() == WIFI_AP) { + _ledBlink(i, 900, 100); + } else { + _ledBlink(i, 4900, 100); + } + } + } else { + _ledBlink(i, 500, 500); + } + + } + + // Relay-based modes, update only if relays have been updated + if (!_led_update) continue; + + if (_ledMode(i) == LED_MODE_FOLLOW) { + _ledStatus(i, relayStatus(_leds[i].relay-1)); + } + + if (_ledMode(i) == LED_MODE_FOLLOW_INVERSE) { + _ledStatus(i, !relayStatus(_leds[i].relay-1)); + } + + if (_ledMode(i) == LED_MODE_FINDME) { + bool status = true; + for (unsigned char k=0; k + +class StreamInjector : public Stream { + + public: + + typedef std::function writeCallback; + + StreamInjector(Stream& serial, size_t buflen = 128) : _stream(serial), _buffer_size(buflen) { + _buffer = new char[buflen]; + } + + ~StreamInjector() { + delete[] _buffer; + } + + virtual void callback(writeCallback c) { + _callback = c; + } + + virtual size_t write(uint8_t ch) { + if (_callback) _callback(ch); + return _stream.write(ch); + } + + virtual int read() { + int ch = _stream.read(); + if (ch == -1) { + if (_buffer_read != _buffer_write) { + ch = _buffer[_buffer_read]; + _buffer_read = (_buffer_read + 1) % _buffer_size; + } + } + return ch; + } + + virtual int available() { + unsigned int bytes = _stream.available(); + if (_buffer_read > _buffer_write) { + bytes += (_buffer_write - _buffer_read + _buffer_size); + } else if (_buffer_read < _buffer_write) { + bytes += (_buffer_write - _buffer_read); + } + return bytes; + } + + virtual int peek() { + int ch = _stream.peek(); + if (ch == -1) { + if (_buffer_read != _buffer_write) { + ch = _buffer[_buffer_read]; + } + } + return ch; + } + + virtual void flush() { + _stream.flush(); + _buffer_read = _buffer_write; + } + + virtual void inject(char *data, size_t len) { + for (int i=0; i AwsMessageHandler; + +class WebSocketIncommingBuffer { + + public: + WebSocketIncommingBuffer(AwsMessageHandler cb, bool terminate_string = true, bool cb_on_fragments = false) : + _cb(cb), + _terminate_string(terminate_string), + _cb_on_fragments(cb_on_fragments), + _buffer(0) + {} + + ~WebSocketIncommingBuffer() { + if (_buffer) delete _buffer; + } + + void data_event(AsyncWebSocketClient *client, AwsFrameInfo *info, uint8_t *data, size_t len) { + + if ((info->final || _cb_on_fragments) + && !_terminate_string + && info->index == 0 + && info->len == len) { + + /* The whole message is in a single frame and we got all of it's + data therefore we can parse it without copying the data first.*/ + _cb(client, data, len); + + } else { + + if (info->len > MAX_WS_MSG_SIZE) return; + + /* Check if previous fragment was discarded because it was too long. */ + //if (!_cb_on_fragments && info->num > 0 && !_buffer) return; + + if (!_buffer) _buffer = new std::vector(); + + if (info->index == 0) { + //New frame => preallocate memory + if (_cb_on_fragments) { + _buffer->reserve(info->len + 1); + } else { + /* The current fragment would lead to a message which is + too long. So discard everything received so far. */ + if (info->len + _buffer->size() > MAX_WS_MSG_SIZE) { + delete _buffer; + _buffer = 0; + return; + } else { + _buffer->reserve(info->len + _buffer->size() + 1); + } + } + } + + //assert(_buffer->size() == info->index); + _buffer->insert(_buffer->end(), data, data+len); + if (info->index + len == info->len + && (info->final || _cb_on_fragments)) { + + // Frame/message complete + if (_terminate_string) _buffer->push_back(0); + _cb(client, _buffer->data(), _buffer->size()); + _buffer->clear(); + + } + } + } + + private: + + AwsMessageHandler _cb; + bool _cb_on_fragments; + bool _terminate_string; + std::vector *_buffer; + +}; diff --git a/espurna/libs/pwm.h b/espurna/libs/pwm.h new file mode 100755 index 0000000..bf5605f --- /dev/null +++ b/espurna/libs/pwm.h @@ -0,0 +1,33 @@ +#ifndef __PWM_H__ +#define __PWM_H__ + +/*pwm.h: function and macro definition of PWM API , driver level */ +/*user_light.h: user interface for light API, user level*/ +/*user_light_adj: API for color changing and lighting effects, user level*/ + + + /*NOTE!! : DO NOT CHANGE THIS FILE*/ + + /*SUPPORT UP TO 8 PWM CHANNEL*/ +//#define PWM_CHANNEL_NUM_MAX 8 + +struct pwm_param { + uint32 period; + uint32 freq; + uint32 duty[PWM_CHANNEL_NUM_MAX]; //PWM_CHANNEL<=8 +}; + + +/* pwm_init should be called only once, for now */ +void pwm_init(uint32 period, uint32 *duty,uint32 pwm_channel_num,uint32 (*pin_info_list)[3]); +void pwm_start(void); + +void pwm_set_duty(uint32 duty, uint8 channel); +uint32 pwm_get_duty(uint8 channel); +void pwm_set_period(uint32 period); +uint32 pwm_get_period(void); + +uint32 get_pwm_version(void); +void set_pwm_debug_en(uint8 print_en); + +#endif diff --git a/espurna/light.ino b/espurna/light.ino new file mode 100755 index 0000000..0150576 --- /dev/null +++ b/espurna/light.ino @@ -0,0 +1,1054 @@ +/* + +LIGHT MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE + +#include +#include +#include + +#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER +#define PWM_CHANNEL_NUM_MAX LIGHT_CHANNELS +extern "C" { + #include "libs/pwm.h" +} +#endif + +// ----------------------------------------------------------------------------- + +Ticker _light_save_ticker; +Ticker _light_transition_ticker; + +typedef struct { + unsigned char pin; + bool reverse; + bool state; + unsigned char inputValue; // value that has been inputted + unsigned char value; // normalized value including brightness + unsigned char shadow; // represented value + double current; // transition value +} channel_t; +std::vector _light_channel; + +bool _light_state = false; +bool _light_use_transitions = false; +unsigned int _light_transition_time = LIGHT_TRANSITION_TIME; +bool _light_has_color = false; +bool _light_use_white = false; +bool _light_use_gamma = false; +unsigned long _light_steps_left = 1; +unsigned char _light_brightness = LIGHT_MAX_BRIGHTNESS; +unsigned int _light_mireds = LIGHT_DEFAULT_MIREDS; + +#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX +#include +my92xx * _my92xx; +ARRAYINIT(unsigned char, _light_channel_map, MY92XX_MAPPING); +#endif + +// Gamma Correction lookup table (8 bit) +// TODO: move to PROGMEM +const unsigned char _light_gamma_table[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, + 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, + 12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, + 19, 20, 20, 21, 22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, + 29, 30, 30, 31, 32, 33, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, + 41, 42, 43, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 71, + 72, 73, 74, 75, 76, 77, 78, 80, 81, 82, 83, 84, 86, 87, 88, 89, + 91, 92, 93, 94, 96, 97, 98, 100, 101, 102, 104, 105, 106, 108, 109, 110, + 112, 113, 115, 116, 118, 119, 121, 122, 123, 125, 126, 128, 130, 131, 133, 134, + 136, 137, 139, 140, 142, 144, 145, 147, 149, 150, 152, 154, 155, 157, 159, 160, + 162, 164, 166, 167, 169, 171, 173, 175, 176, 178, 180, 182, 184, 186, 187, 189, + 191, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, + 223, 225, 227, 229, 231, 233, 235, 238, 240, 242, 244, 246, 248, 251, 253, 255 +}; + +// ----------------------------------------------------------------------------- +// UTILS +// ----------------------------------------------------------------------------- + +void _setRGBInputValue(unsigned char red, unsigned char green, unsigned char blue) { + _light_channel[0].inputValue = red; + _light_channel[1].inputValue = green; + _light_channel[2].inputValue = blue; +} + +void _generateBrightness() { + + double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS; + + // Convert RGB to RGBW + if (_light_has_color && _light_use_white) { + + unsigned char white, max_in, max_out; + double factor = 0; + + white = std::min(_light_channel[0].inputValue, std::min(_light_channel[1].inputValue, _light_channel[2].inputValue)); + max_in = std::max(_light_channel[0].inputValue, std::max(_light_channel[1].inputValue, _light_channel[2].inputValue)); + + for (unsigned int i=0; i < 3; i++) { + _light_channel[i].value = _light_channel[i].inputValue - white; + } + _light_channel[3].value = white; + + max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value)); + if (max_out > 0) { + factor = (double) (max_in / max_out); + } + + // Scale up to equal input values. So [250,150,50] -> [200,100,0,50] -> [250, 125, 0, 63] + for (unsigned int i=0; i < 4; i++) { + _light_channel[i].value = round((double) _light_channel[i].value * factor * brightness); + } + + // Don't apply brightness, it is already in the inputValue: + if (_light_channel.size() == 5) { + _light_channel[4].value = _light_channel[4].inputValue; + } + + } else { + + // Don't apply brightness, it is already in the inputValue: + for (unsigned char i=0; i < _light_channel.size(); i++) { + if (_light_has_color & (i<3)) { + _light_channel[i].value = _light_channel[i].inputValue * brightness; + } else { + _light_channel[i].value = _light_channel[i].inputValue; + } + } + + } + +} + +// ----------------------------------------------------------------------------- +// Input Values +// ----------------------------------------------------------------------------- + +void _fromLong(unsigned long value, bool brightness) { + if (brightness) { + _setRGBInputValue((value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF); + _light_brightness = (value & 0xFF) * LIGHT_MAX_BRIGHTNESS / 255; + } else { + _setRGBInputValue((value >> 16) & 0xFF, (value >> 8) & 0xFF, (value) & 0xFF); + } +} + +void _fromRGB(const char * rgb) { + char * p = (char *) rgb; + if (strlen(p) == 0) return; + + switch (p[0]) { + case '#': // HEX Value + if (_light_has_color) { + ++p; + unsigned long value = strtoul(p, NULL, 16); + // RGBA values are interpreted like RGB + brightness + _fromLong(value, strlen(p) > 7); + } + break; + case 'M': // Mired Value + if (_light_has_color) { + unsigned long mireds = atol(p + 1); + _fromMireds(mireds); + } + break; + case 'K': // Kelvin Value + if (_light_has_color) { + unsigned long kelvin = atol(p + 1); + _fromKelvin(kelvin); + } + break; + default: // assume decimal values separated by commas + char * tok; + unsigned char count = 0; + unsigned char channels = _light_channel.size(); + + tok = strtok(p, ","); + while (tok != NULL) { + _light_channel[count].inputValue = atoi(tok); + if (++count == channels) break; + tok = strtok(NULL, ","); + } + + // RGB but less than 3 values received, assume it is 0 + if (_light_has_color && (count < 3)) { + // check channel 1 and 2: + for (int i = 1; i <= 2; i++) { + if (count < (i+1)) { + _light_channel[i].inputValue = 0; + } + } + } + break; + } +} + +// HSV string is expected to be "H,S,V", where: +// 0 <= H <= 360 +// 0 <= S <= 100 +// 0 <= V <= 100 +void _fromHSV(const char * hsv) { + + char * ptr = (char *) hsv; + if (strlen(ptr) == 0) return; + if (!_light_has_color) return; + + char * tok; + unsigned char count = 0; + unsigned int value[3] = {0}; + + tok = strtok(ptr, ","); + while (tok != NULL) { + value[count] = atoi(tok); + if (++count == 3) break; + tok = strtok(NULL, ","); + } + if (count != 3) return; + + // HSV to RGB transformation ----------------------------------------------- + + //INPUT: [0,100,57] + //IS: [145,0,0] + //SHOULD: [255,0,0] + + double h = (value[0] == 360) ? 0 : (double) value[0] / 60.0; + double f = (h - floor(h)); + double s = (double) value[1] / 100.0; + + _light_brightness = round((double) value[2] * 2.55); // (255/100) + unsigned char p = round(255 * (1.0 - s)); + unsigned char q = round(255 * (1.0 - s * f)); + unsigned char t = round(255 * (1.0 - s * (1.0 - f))); + + switch (int(h)) { + case 0: + _setRGBInputValue(255, t, p); + break; + case 1: + _setRGBInputValue(q, 255, p); + break; + case 2: + _setRGBInputValue(p, 255, t); + break; + case 3: + _setRGBInputValue(p, q, 255); + break; + case 4: + _setRGBInputValue(t, p, 255); + break; + case 5: + _setRGBInputValue(255, p, q); + break; + default: + _setRGBInputValue(0, 0, 0); + break; + } +} + +// Thanks to Sacha Telgenhof for sharing this code in his AiLight library +// https://github.com/stelgenhof/AiLight +void _fromKelvin(unsigned long kelvin, bool setMireds) { + + // Check we have RGB channels + if (!_light_has_color) return; + + if (setMireds) { + _light_mireds = round(1000000UL / kelvin); + } + + // Calculate colors + unsigned int red = (kelvin <= 66) + ? LIGHT_MAX_VALUE + : 329.698727446 * pow((kelvin - 60), -0.1332047592); + unsigned int green = (kelvin <= 66) + ? 99.4708025861 * log(kelvin) - 161.1195681661 + : 288.1221695283 * pow(kelvin, -0.0755148492); + unsigned int blue = (kelvin >= 66) + ? LIGHT_MAX_VALUE + : ((kelvin <= 19) + ? 0 + : 138.5177312231 * log(kelvin - 10) - 305.0447927307); + + _setRGBInputValue( + constrain(red, 0, LIGHT_MAX_VALUE), + constrain(green, 0, LIGHT_MAX_VALUE), + constrain(blue, 0, LIGHT_MAX_VALUE) + ); +} + +void _fromKelvin(unsigned long kelvin) { + _fromKelvin(kelvin, true); +} + +// Color temperature is measured in mireds (kelvin = 1e6/mired) +void _fromMireds(unsigned long mireds) { + if (mireds == 0) mireds = 1; + _light_mireds = mireds; + unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100; + _fromKelvin(kelvin, false); +} + +// ----------------------------------------------------------------------------- +// Output Values +// ----------------------------------------------------------------------------- + +void _toRGB(char * rgb, size_t len) { + unsigned long value = 0; + + value += _light_channel[0].inputValue; + value <<= 8; + value += _light_channel[1].inputValue; + value <<= 8; + value += _light_channel[2].inputValue; + + snprintf_P(rgb, len, PSTR("#%06X"), value); +} + +void _toHSV(char * hsv, size_t len) { + double min, max, h, s, v; + double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS; + + double r = (double) (_light_channel[0].inputValue * brightness) / 255.0; + double g = (double) (_light_channel[1].inputValue * brightness) / 255.0; + double b = (double) (_light_channel[2].inputValue * brightness) / 255.0; + + min = std::min(r, std::min(g, b)); + max = std::max(r, std::max(g, b)); + + v = 100.0 * max; + if (v == 0) { + h = s = 0; + } else { + s = 100.0 * (max - min) / max; + if (s == 0) { + h = 0; + } else { + if (max == r) { + if (g >= b) { + h = 0.0 + 60.0 * (g - b) / (max - min); + } else { + h = 360.0 + 60.0 * (g - b) / (max - min); + } + } else if (max == g) { + h = 120.0 + 60.0 * (b - r) / (max - min); + } else { + h = 240.0 + 60.0 * (r - g) / (max - min); + } + } + } + + // String + snprintf_P(hsv, len, PSTR("%d,%d,%d"), round(h), round(s), round(v)); +} + +void _toLong(char * color, size_t len) { + if (!_light_has_color) return; + + snprintf_P(color, len, PSTR("%d,%d,%d"), + (int) _light_channel[0].inputValue, + (int) _light_channel[1].inputValue, + (int) _light_channel[2].inputValue + ); +} + +void _toCSV(char * buffer, size_t len, bool applyBrightness) { + char num[10]; + float b = applyBrightness ? (float) _light_brightness / LIGHT_MAX_BRIGHTNESS : 1; + for (unsigned char i=0; i<_light_channel.size(); i++) { + itoa(_light_channel[i].inputValue * b, num, 10); + if (i>0) strncat(buffer, ",", len--); + strncat(buffer, num, len); + len = len - strlen(num); + } +} + +// ----------------------------------------------------------------------------- +// PROVIDER +// ----------------------------------------------------------------------------- + +unsigned int _toPWM(unsigned long value, bool gamma, bool reverse) { + value = constrain(value, 0, LIGHT_MAX_VALUE); + if (gamma) value = _light_gamma_table[value]; + if (LIGHT_MAX_VALUE != LIGHT_LIMIT_PWM) value = map(value, 0, LIGHT_MAX_VALUE, 0, LIGHT_LIMIT_PWM); + if (reverse) value = LIGHT_LIMIT_PWM - value; + return value; +} + +// Returns a PWM value for the given channel ID +unsigned int _toPWM(unsigned char id) { + bool useGamma = _light_use_gamma && _light_has_color && (id < 3); + return _toPWM(_light_channel[id].shadow, useGamma, _light_channel[id].reverse); +} + +void _shadow() { + // Update transition ticker + _light_steps_left--; + if (_light_steps_left == 0) _light_transition_ticker.detach(); + + // Transitions + unsigned char target; + for (unsigned int i=0; i < _light_channel.size(); i++) { + target = _light_state ? _light_channel[i].value : 0; + if (_light_steps_left == 0) { + _light_channel[i].current = target; + } else { + double difference = (double) (target - _light_channel[i].current) / (_light_steps_left + 1); + _light_channel[i].current = _light_channel[i].current + difference; + } + _light_channel[i].shadow = _light_channel[i].current; + } +} + +void _lightProviderUpdate() { + + _shadow(); + + #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX + + for (unsigned char i=0; i<_light_channel.size(); i++) { + _my92xx->setChannel(_light_channel_map[i], _toPWM(i)); + } + _my92xx->setState(true); + _my92xx->update(); + + #endif + + #if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER + + for (unsigned int i=0; i < _light_channel.size(); i++) { + pwm_set_duty(_toPWM(i), i); + } + pwm_start(); + + #endif + +} + +// ----------------------------------------------------------------------------- +// PERSISTANCE +// ----------------------------------------------------------------------------- + +void _lightColorSave() { + for (unsigned int i=0; i < _light_channel.size(); i++) { + setSetting("ch", i, _light_channel[i].inputValue); + } + setSetting("brightness", _light_brightness); + saveSettings(); +} + +void _lightColorRestore() { + for (unsigned int i=0; i < _light_channel.size(); i++) { + _light_channel[i].inputValue = getSetting("ch", i, i==0 ? 255 : 0).toInt(); + } + _light_brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt(); + lightUpdate(false, false); +} + +// ----------------------------------------------------------------------------- +// MQTT +// ----------------------------------------------------------------------------- + +#if MQTT_SUPPORT +void _lightMQTTCallback(unsigned int type, const char * topic, const char * payload) { + + String mqtt_group_color = getSetting("mqttGroupColor"); + + if (type == MQTT_CONNECT_EVENT) { + + if (_light_has_color) { + mqttSubscribe(MQTT_TOPIC_BRIGHTNESS); + mqttSubscribe(MQTT_TOPIC_MIRED); + mqttSubscribe(MQTT_TOPIC_KELVIN); + mqttSubscribe(MQTT_TOPIC_COLOR_RGB); + mqttSubscribe(MQTT_TOPIC_COLOR_HSV); + } + + // Group color + if (mqtt_group_color.length() > 0) mqttSubscribeRaw(mqtt_group_color.c_str()); + + // Channels + char buffer[strlen(MQTT_TOPIC_CHANNEL) + 3]; + snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_CHANNEL); + mqttSubscribe(buffer); + + } + + if (type == MQTT_MESSAGE_EVENT) { + + // Group color + if ((mqtt_group_color.length() > 0) & (mqtt_group_color.equals(topic))) { + lightColor(payload, true); + lightUpdate(true, mqttForward(), false); + return; + } + + // Match topic + String t = mqttMagnitude((char *) topic); + + // Color temperature in mireds + if (t.equals(MQTT_TOPIC_MIRED)) { + _fromMireds(atol(payload)); + lightUpdate(true, mqttForward()); + return; + } + + // Color temperature in kelvins + if (t.equals(MQTT_TOPIC_KELVIN)) { + _fromKelvin(atol(payload)); + lightUpdate(true, mqttForward()); + return; + } + + // Color + if (t.equals(MQTT_TOPIC_COLOR_RGB)) { + lightColor(payload, true); + lightUpdate(true, mqttForward()); + return; + } + if (t.equals(MQTT_TOPIC_COLOR_HSV)) { + lightColor(payload, false); + lightUpdate(true, mqttForward()); + return; + } + + // Brightness + if (t.equals(MQTT_TOPIC_BRIGHTNESS)) { + _light_brightness = constrain(atoi(payload), 0, LIGHT_MAX_BRIGHTNESS); + lightUpdate(true, mqttForward()); + return; + } + + // Channel + if (t.startsWith(MQTT_TOPIC_CHANNEL)) { + unsigned int channelID = t.substring(strlen(MQTT_TOPIC_CHANNEL)+1).toInt(); + if (channelID >= _light_channel.size()) { + DEBUG_MSG_P(PSTR("[LIGHT] Wrong channelID (%d)\n"), channelID); + return; + } + lightChannel(channelID, atoi(payload)); + lightUpdate(true, mqttForward()); + return; + } + + } + +} + +void lightMQTT() { + char buffer[20]; + + if (_light_has_color) { + + // Color + if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) { + _toRGB(buffer, sizeof(buffer)); + } else { + _toLong(buffer, sizeof(buffer)); + } + mqttSend(MQTT_TOPIC_COLOR_RGB, buffer); + + _toHSV(buffer, sizeof(buffer)); + mqttSend(MQTT_TOPIC_COLOR_HSV, buffer); + + // Brightness + snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_brightness); + mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer); + + // Mireds + snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _light_mireds); + mqttSend(MQTT_TOPIC_MIRED, buffer); + } + + // Channels + for (unsigned int i=0; i < _light_channel.size(); i++) { + itoa(_light_channel[i].inputValue, buffer, 10); + mqttSend(MQTT_TOPIC_CHANNEL, i, buffer); + } + +} + +void lightMQTTGroup() { + String mqtt_group_color = getSetting("mqttGroupColor"); + if (mqtt_group_color.length()>0) { + char buffer[20]; + _toCSV(buffer, sizeof(buffer), true); + mqttSendRaw(mqtt_group_color.c_str(), buffer); + } +} + +#endif + +// ----------------------------------------------------------------------------- +// Broker +// ----------------------------------------------------------------------------- + +#if BROKER_SUPPORT + +void lightBroker() { + char buffer[10]; + for (unsigned int i=0; i < _light_channel.size(); i++) { + itoa(_light_channel[i].inputValue, buffer, 10); + brokerPublish(MQTT_TOPIC_CHANNEL, i, buffer); + } +} + +#endif + +// ----------------------------------------------------------------------------- +// API +// ----------------------------------------------------------------------------- + +unsigned char lightChannels() { + return _light_channel.size(); +} + +bool lightHasColor() { + return _light_has_color; +} + +void lightUpdate(bool save, bool forward, bool group_forward) { + + _generateBrightness(); + + // Configure color transition + _light_steps_left = _light_use_transitions ? _light_transition_time / LIGHT_TRANSITION_STEP : 1; + _light_transition_ticker.attach_ms(LIGHT_TRANSITION_STEP, _lightProviderUpdate); + + // Report channels to local broker + #if BROKER_SUPPORT + lightBroker(); + #endif + + // Report color & brightness to MQTT broker + #if MQTT_SUPPORT + if (forward) lightMQTT(); + if (group_forward) lightMQTTGroup(); + #endif + + // Report color to WS clients (using current brightness setting) + #if WEB_SUPPORT + wsSend(_lightWebSocketOnSend); + #endif + + #if LIGHT_SAVE_ENABLED + // Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily + if (save) _light_save_ticker.once(LIGHT_SAVE_DELAY, _lightColorSave); + #endif + +}; + +void lightUpdate(bool save, bool forward) { + lightUpdate(save, forward, true); +} + +#if LIGHT_SAVE_ENABLED == 0 +void lightSave() { + _lightColorSave(); +} +#endif + +void lightState(unsigned char i, bool state) { + _light_channel[i].state = state; +} + +bool lightState(unsigned char i) { + return _light_channel[i].state; +} + +void lightState(bool state) { + _light_state = state; +} + +bool lightState() { + return _light_state; +} + +void lightColor(const char * color, bool rgb) { + DEBUG_MSG_P(PSTR("[LIGHT] %s: %s\n"), rgb ? "RGB" : "HSV", color); + if (rgb) { + _fromRGB(color); + } else { + _fromHSV(color); + } +} + +void lightColor(const char * color) { + lightColor(color, true); +} + +void lightColor(unsigned long color) { + _fromLong(color, false); +} + +String lightColor(bool rgb) { + char str[12]; + if (rgb) { + _toRGB(str, sizeof(str)); + } else { + _toHSV(str, sizeof(str)); + } + return String(str); +} + +String lightColor() { + return lightColor(true); +} + +unsigned int lightChannel(unsigned char id) { + if (id <= _light_channel.size()) { + return _light_channel[id].inputValue; + } + return 0; +} + +void lightChannel(unsigned char id, unsigned int value) { + if (id <= _light_channel.size()) { + _light_channel[id].inputValue = constrain(value, 0, LIGHT_MAX_VALUE); + } +} + +unsigned int lightBrightness() { + return _light_brightness; +} + +void lightBrightness(int b) { + _light_brightness = constrain(b, 0, LIGHT_MAX_BRIGHTNESS); +} + +void lightBrightnessStep(int steps) { + lightBrightness(_light_brightness + steps * LIGHT_STEP); +} + +// ----------------------------------------------------------------------------- +// SETUP +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _lightWebSocketOnReceive(const char * key, JsonVariant& value) { + if (strncmp(key, "light", 5) == 0) return true; + if (strncmp(key, "use", 3) == 0) return true; + return false; +} + +void _lightWebSocketOnSend(JsonObject& root) { + root["colorVisible"] = 1; + root["mqttGroupColor"] = getSetting("mqttGroupColor"); + root["useColor"] = _light_has_color; + root["useWhite"] = _light_use_white; + root["useGamma"] = _light_use_gamma; + root["useTransitions"] = _light_use_transitions; + root["lightTime"] = _light_transition_time; + root["useCSS"] = getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1; + bool useRGB = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1; + root["useRGB"] = useRGB; + if (_light_has_color) { + if (useRGB) { + root["rgb"] = lightColor(true); + root["brightness"] = lightBrightness(); + } else { + root["hsv"] = lightColor(false); + } + } + JsonArray& channels = root.createNestedArray("channels"); + for (unsigned char id=0; id < _light_channel.size(); id++) { + channels.add(lightChannel(id)); + } +} + +void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { + if (_light_has_color) { + if (strcmp(action, "color") == 0) { + if (data.containsKey("rgb")) { + lightColor(data["rgb"], true); + lightUpdate(true, true); + } + if (data.containsKey("hsv")) { + lightColor(data["hsv"], false); + lightUpdate(true, true); + } + if (data.containsKey("brightness")) { + lightBrightness(data["brightness"]); + lightUpdate(true, true); + } + } + } + + if (strcmp(action, "channel") == 0) { + if (data.containsKey("id") && data.containsKey("value")) { + lightChannel(data["id"], data["value"]); + lightUpdate(true, true); + } + } +} + +void _lightAPISetup() { + // API entry points (protected with apikey) + if (_light_has_color) { + apiRegister(MQTT_TOPIC_COLOR_RGB, + [](char * buffer, size_t len) { + if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) { + _toRGB(buffer, len); + } else { + _toLong(buffer, len); + } + }, + [](const char * payload) { + lightColor(payload, true); + lightUpdate(true, true); + } + ); + + apiRegister(MQTT_TOPIC_COLOR_HSV, + [](char * buffer, size_t len) { + _toHSV(buffer, len); + }, + [](const char * payload) { + lightColor(payload, false); + lightUpdate(true, true); + } + ); + + apiRegister(MQTT_TOPIC_BRIGHTNESS, + [](char * buffer, size_t len) { + snprintf_P(buffer, len, PSTR("%d"), _light_brightness); + }, + [](const char * payload) { + lightBrightness(atoi(payload)); + lightUpdate(true, true); + } + ); + + apiRegister(MQTT_TOPIC_KELVIN, + [](char * buffer, size_t len) {}, + [](const char * payload) { + _fromKelvin(atol(payload)); + lightUpdate(true, true); + } + ); + + apiRegister(MQTT_TOPIC_MIRED, + [](char * buffer, size_t len) {}, + [](const char * payload) { + _fromMireds(atol(payload)); + lightUpdate(true, true); + } + ); + } + + for (unsigned int id=0; id<_light_channel.size(); id++) { + char key[15]; + snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_CHANNEL, id); + + apiRegister(key, + [id](char * buffer, size_t len) { + snprintf_P(buffer, len, PSTR("%d"), lightChannel(id)); + }, + [id](const char * payload) { + lightChannel(id, atoi(payload)); + lightUpdate(true, true); + } + ); + } +} + +#endif // WEB_SUPPORT + +#if TERMINAL_SUPPORT + +void _lightInitCommands() { + + settingsRegisterCommand(F("BRIGHTNESS"), [](Embedis* e) { + if (e->argc > 1) { + lightBrightness(String(e->argv[1]).toInt()); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness()); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("CHANNEL"), [](Embedis* e) { + if (e->argc < 2) { + DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n")); + } + int id = String(e->argv[1]).toInt(); + if (e->argc > 2) { + int value = String(e->argv[2]).toInt(); + lightChannel(id, value); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id)); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("COLOR"), [](Embedis* e) { + if (e->argc > 1) { + String color = String(e->argv[1]); + lightColor(color.c_str()); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("KELVIN"), [](Embedis* e) { + if (e->argc > 1) { + String color = String("K") + String(e->argv[1]); + lightColor(color.c_str()); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("MIRED"), [](Embedis* e) { + if (e->argc > 1) { + String color = String("M") + String(e->argv[1]); + lightColor(color.c_str()); + lightUpdate(true, true); + } + DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str()); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + +} + +#endif // TERMINAL_SUPPORT + +#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER + +unsigned long getIOMux(unsigned long gpio) { + unsigned long muxes[16] = { + PERIPHS_IO_MUX_GPIO0_U, PERIPHS_IO_MUX_U0TXD_U, PERIPHS_IO_MUX_GPIO2_U, PERIPHS_IO_MUX_U0RXD_U, + PERIPHS_IO_MUX_GPIO4_U, PERIPHS_IO_MUX_GPIO5_U, PERIPHS_IO_MUX_SD_CLK_U, PERIPHS_IO_MUX_SD_DATA0_U, + PERIPHS_IO_MUX_SD_DATA1_U, PERIPHS_IO_MUX_SD_DATA2_U, PERIPHS_IO_MUX_SD_DATA3_U, PERIPHS_IO_MUX_SD_CMD_U, + PERIPHS_IO_MUX_MTDI_U, PERIPHS_IO_MUX_MTCK_U, PERIPHS_IO_MUX_MTMS_U, PERIPHS_IO_MUX_MTDO_U + }; + return muxes[gpio]; +} + +unsigned long getIOFunc(unsigned long gpio) { + unsigned long funcs[16] = { + FUNC_GPIO0, FUNC_GPIO1, FUNC_GPIO2, FUNC_GPIO3, + FUNC_GPIO4, FUNC_GPIO5, FUNC_GPIO6, FUNC_GPIO7, + FUNC_GPIO8, FUNC_GPIO9, FUNC_GPIO10, FUNC_GPIO11, + FUNC_GPIO12, FUNC_GPIO13, FUNC_GPIO14, FUNC_GPIO15 + }; + return funcs[gpio]; +} + +#endif + +void _lightConfigure() { + + _light_has_color = getSetting("useColor", LIGHT_USE_COLOR).toInt() == 1; + if (_light_has_color && (_light_channel.size() < 3)) { + _light_has_color = false; + setSetting("useColor", _light_has_color); + } + + _light_use_white = getSetting("useWhite", LIGHT_USE_WHITE).toInt() == 1; + if (_light_use_white && (_light_channel.size() < 4)) { + _light_use_white = false; + setSetting("useWhite", _light_use_white); + } + + _light_use_gamma = getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1; + _light_use_transitions = getSetting("useTransitions", LIGHT_USE_TRANSITIONS).toInt() == 1; + _light_transition_time = getSetting("lightTime", LIGHT_TRANSITION_TIME).toInt(); + +} + +void lightSetup() { + + #ifdef LIGHT_ENABLE_PIN + pinMode(LIGHT_ENABLE_PIN, OUTPUT); + digitalWrite(LIGHT_ENABLE_PIN, HIGH); + #endif + + #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX + + _my92xx = new my92xx(MY92XX_MODEL, MY92XX_CHIPS, MY92XX_DI_PIN, MY92XX_DCKI_PIN, MY92XX_COMMAND); + for (unsigned char i=0; i + +*/ + +#if LLMNR_SUPPORT + +#include + +void llmnrSetup() { + LLMNR.begin(getSetting("hostname").c_str()); + DEBUG_MSG_P(PSTR("[LLMNR] Configured\n")); +} + +#endif // LLMNR_SUPPORT diff --git a/espurna/mdns.ino b/espurna/mdns.ino new file mode 100755 index 0000000..c82f328 --- /dev/null +++ b/espurna/mdns.ino @@ -0,0 +1,132 @@ +/* + +MDNS MODULE + +Copyright (C) 2017-2018 by Xose Pérez + +*/ + +// ----------------------------------------------------------------------------- +// mDNS Server +// ----------------------------------------------------------------------------- + +#if MDNS_SERVER_SUPPORT + +#include + +#if MQTT_SUPPORT + +void _mdnsFindMQTT() { + int count = MDNS.queryService("mqtt", "tcp"); + DEBUG_MSG_P(PSTR("[MQTT] MQTT brokers found: %d\n"), count); + for (int i=0; i +#include + +using namespace mDNSResolver; +WiFiUDP _mdns_udp; +Resolver _mdns_resolver(_mdns_udp); + +String mdnsResolve(char * name) { + + if (strlen(name) == 0) return String(); + if (WiFi.status() != WL_CONNECTED) return String(); + + _mdns_resolver.setLocalIP(WiFi.localIP()); + IPAddress ip = _mdns_resolver.search(name); + + if (ip == INADDR_NONE) return String(name); + DEBUG_MSG_P(PSTR("[MDNS] '%s' resolved to '%s'\n"), name, ip.toString().c_str()); + return ip.toString(); + +} + +String mdnsResolve(String name) { + return mdnsResolve((char *) name.c_str()); +} + +void mdnsClientSetup() { + + // Register loop + espurnaRegisterLoop(mdnsClientLoop); + +} + +void mdnsClientLoop() { + _mdns_resolver.loop(); +} + +#endif // MDNS_CLIENT_SUPPORT diff --git a/espurna/migrate.ino b/espurna/migrate.ino new file mode 100755 index 0000000..81f79c1 --- /dev/null +++ b/espurna/migrate.ino @@ -0,0 +1,894 @@ +/* + +MIGRATE MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +void _cmpMoveIndexDown(const char * key, int offset = 0) { + if (hasSetting(key, 0)) return; + for (unsigned char index = 1; index < SETTINGS_MAX_LIST_COUNT; index++) { + if (hasSetting(key, index)) { + setSetting(key, index - 1, getSetting(key, index).toInt() + offset); + } else { + delSetting(key, index - 1); + } + } +} + +// Configuration versions +// +// 1: based on Embedis, no board definitions +// 2: based on Embedis, with board definitions 1-based +// 3: based on Embedis, with board definitions 0-based + +void migrate() { + + // Get config version + unsigned int board = getSetting("board", 0).toInt(); + unsigned int config_version = getSetting("cfg", board > 0 ? 2 : 1).toInt(); + + // Update if not on latest version + if (config_version == CFG_VERSION) return; + setSetting("cfg", CFG_VERSION); + + if (config_version == 2) { + _cmpMoveIndexDown("ledGPIO"); + _cmpMoveIndexDown("ledLogic"); + _cmpMoveIndexDown("btnGPIO"); + _cmpMoveIndexDown("btnRelay", -1); + _cmpMoveIndexDown("relayGPIO"); + _cmpMoveIndexDown("relayType"); + } + + if (config_version == 1) { + + #if defined(NODEMCU_LOLIN) + + setSetting("board", 2); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(WEMOS_D1_MINI_RELAYSHIELD) + + setSetting("board", 3); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 5); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_BASIC) + + setSetting("board", 4); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_TH) + + setSetting("board", 5); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_SV) + + setSetting("board", 6); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_TOUCH) + + setSetting("board", 7); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_POW) + + setSetting("board", 8); + setSetting("ledGPIO", 0, 15); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("selGPIO", 5); + setSetting("cf1GPIO", 13); + setSetting("cfGPIO", 14); + + #elif defined(ITEAD_SONOFF_DUAL) + + setSetting("board", 9); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnRelay", 0, 0xFF); + setSetting("btnRelay", 1, 0xFF); + setSetting("btnRelay", 2, 0); + setSetting("relayProvider", RELAY_PROVIDER_DUAL); + setSetting("relays", 2); + + #elif defined(ITEAD_1CH_INCHING) + + setSetting("board", 10); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_4CH) + + setSetting("board", 11); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnGPIO", 1, 9); + setSetting("btnGPIO", 2, 10); + setSetting("btnGPIO", 3, 14); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("btnRelay", 2, 2); + setSetting("btnRelay", 3, 3); + setSetting("relayGPIO", 0, 12); + setSetting("relayGPIO", 1, 5); + setSetting("relayGPIO", 2, 4); + setSetting("relayGPIO", 3, 15); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + setSetting("relayType", 2, RELAY_TYPE_NORMAL); + setSetting("relayType", 3, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SLAMPHER) + + setSetting("board", 12); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_S20) + + setSetting("board", 13); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ELECTRODRAGON_WIFI_IOT) + + setSetting("board", 14); + setSetting("ledGPIO", 0, 16); + setSetting("ledLogic", 0, 0); + setSetting("btnGPIO", 0, 0); + setSetting("btnGPIO", 1, 2); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("relayGPIO", 0, 12); + setSetting("relayGPIO", 1, 13); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + + #elif defined(WORKCHOICE_ECOPLUG) + + setSetting("board", 15); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 0); + setSetting("btnGPIO", 0, 13); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 15); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(JANGOE_WIFI_RELAY_NC) + + setSetting("board", 16); + setSetting("btnGPIO", 0, 12); + setSetting("btnGPIO", 1, 13); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("relayGPIO", 0, 2); + setSetting("relayGPIO", 1, 14); + setSetting("relayType", 0, RELAY_TYPE_INVERSE); + setSetting("relayType", 1, RELAY_TYPE_INVERSE); + + #elif defined(JANGOE_WIFI_RELAY_NO) + + setSetting("board", 17); + setSetting("btnGPIO", 0, 12); + setSetting("btnGPIO", 1, 13); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("relayGPIO", 0, 2); + setSetting("relayGPIO", 1, 14); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + + #elif defined(OPENENERGYMONITOR_MQTT_RELAY) + + setSetting("board", 18); + setSetting("ledGPIO", 0, 16); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(JORGEGARCIA_WIFI_RELAYS) + + setSetting("board", 19); + setSetting("relayGPIO", 0, 0); + setSetting("relayGPIO", 1, 2); + setSetting("relayType", 0, RELAY_TYPE_INVERSE); + setSetting("relayType", 1, RELAY_TYPE_INVERSE); + + #elif defined(AITHINKER_AI_LIGHT) + + setSetting("board", 20); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_MY92XX); + setSetting("myModel", MY92XX_MODEL_MY9291); + setSetting("myChips", 1); + setSetting("myDIGPIO", 13); + setSetting("myDCKIGPIO", 15); + setSetting("relays", 1); + + #elif defined(MAGICHOME_LED_CONTROLLER) + + setSetting("board", 21); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 1); + setSetting("chGPIO", 0, 14); + setSetting("chGPIO", 1, 5); + setSetting("chGPIO", 2, 12); + setSetting("chGPIO", 3, 13); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("relays", 1); + + #elif defined(MAGICHOME_LED_CONTROLLER_IR) + + setSetting("board", 21); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 1); + setSetting("chGPIO", 0, 5); + setSetting("chGPIO", 1, 12); + setSetting("chGPIO", 2, 13); + setSetting("chGPIO", 3, 14); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("relays", 1); + + #elif defined(ITEAD_MOTOR) + + setSetting("board", 22); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(TINKERMAN_ESPURNA_H06) + + setSetting("board", 23); + setSetting("ledGPIO", 0, 5); + setSetting("ledLogic", 0, 0); + setSetting("btnGPIO", 0, 4); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_INVERSE); + setSetting("selGPIO", 2); + setSetting("cf1GPIO", 13); + setSetting("cfGPIO", 14); + + #elif defined(HUACANXING_H801) + + setSetting("board", 24); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("ledGPIO", 0, 5); + setSetting("ledLogic", 0, 1); + setSetting("chGPIO", 0, 15); + setSetting("chGPIO", 1, 13); + setSetting("chGPIO", 2, 12); + setSetting("chGPIO", 3, 14); + setSetting("chGPIO", 4, 4); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("chLogic", 4, 0); + setSetting("relays", 1); + + #elif defined(ITEAD_BNSZ01) + + setSetting("board", 25); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("chGPIO", 0, 12); + setSetting("chLogic", 0, 0); + setSetting("relays", 1); + + #elif defined(ITEAD_SONOFF_RFBRIDGE) + + setSetting("board", 26); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("relayProvider", RELAY_PROVIDER_RFBRIDGE); + setSetting("relays", 6); + + #elif defined(ITEAD_SONOFF_4CH_PRO) + + setSetting("board", 27); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnGPIO", 1, 9); + setSetting("btnGPIO", 2, 10); + setSetting("btnGPIO", 3, 14); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("btnRelay", 2, 2); + setSetting("btnRelay", 3, 3); + setSetting("relayGPIO", 0, 12); + setSetting("relayGPIO", 1, 5); + setSetting("relayGPIO", 2, 4); + setSetting("relayGPIO", 3, 15); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + setSetting("relayType", 2, RELAY_TYPE_NORMAL); + setSetting("relayType", 3, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_B1) + + setSetting("board", 28); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_MY92XX); + setSetting("myModel", MY92XX_MODEL_MY9231); + setSetting("myChips", 2); + setSetting("myDIGPIO", 12); + setSetting("myDCKIGPIO", 14); + setSetting("relays", 1); + + #elif defined(ITEAD_SONOFF_LED) + + setSetting("board", 29); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("chGPIO", 0, 12); + setSetting("chLogic", 0, 0); + setSetting("chGPIO", 1, 14); + setSetting("chLogic", 1, 0); + setSetting("relays", 1); + + #elif defined(ITEAD_SONOFF_T1_1CH) + + setSetting("board", 30); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 9); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 5); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_T1_2CH) + + setSetting("board", 31); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnGPIO", 1, 10); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("relayGPIO", 0, 12); + setSetting("relayGPIO", 1, 4); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_T1_3CH) + + setSetting("board", 32); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnGPIO", 1, 9); + setSetting("btnGPIO", 2, 10); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("btnRelay", 2, 2); + setSetting("relayGPIO", 0, 12); + setSetting("relayGPIO", 1, 5); + setSetting("relayGPIO", 2, 4); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + setSetting("relayType", 2, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_RF) + + setSetting("board", 33); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(WION_50055) + + setSetting("board", 34); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 0); + setSetting("btnGPIO", 0, 13); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 15); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(EXS_WIFI_RELAY_V31) + + setSetting("board", 35); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 13); + setSetting("relayResetGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(HUACANXING_H802) + + setSetting("board", 36); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("chGPIO", 0, 12); + setSetting("chGPIO", 1, 14); + setSetting("chGPIO", 2, 13); + setSetting("chGPIO", 3, 15); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("relays", 1); + + #elif defined(GENERIC_V9261F) + + setSetting("board", 37); + + #elif defined(GENERIC_ECH1560) + + setSetting("board", 38); + + #elif defined(TINKERMAN_ESPURNA_H08) + + setSetting("board", 39); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 0); + setSetting("btnGPIO", 0, 4); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("selGPIO", 5); + setSetting("cf1GPIO", 13); + setSetting("cfGPIO", 14); + + #elif defined(MANCAVEMADE_ESPLIVE) + + setSetting("board", 40); + setSetting("btnGPIO", 0, 4); + setSetting("btnGPIO", 1, 5); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("relayGPIO", 0, 12); + setSetting("relayGPIO", 1, 13); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + + #elif defined(INTERMITTECH_QUINLED) + + setSetting("board", 41); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("ledGPIO", 0, 1); + setSetting("ledLogic", 0, 1); + setSetting("chGPIO", 0, 0); + setSetting("chGPIO", 1, 2); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("relays", 1); + + #elif defined(MAGICHOME_LED_CONTROLLER_20) + + setSetting("board", 42); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("chGPIO", 0, 5); + setSetting("chGPIO", 1, 12); + setSetting("chGPIO", 2, 13); + setSetting("chGPIO", 3, 15); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("relays", 1); + + #elif defined(ARILUX_AL_LC06) + + setSetting("board", 43); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("chGPIO", 0, 12); + setSetting("chGPIO", 1, 14); + setSetting("chGPIO", 2, 13); + setSetting("chGPIO", 3, 15); + setSetting("chGPIO", 4, 5); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("chLogic", 4, 0); + setSetting("relays", 1); + + #elif defined(XENON_SM_PW702U) + + setSetting("board", 44); + setSetting("ledGPIO", 0, 4); + setSetting("ledLogic", 0, 0); + setSetting("btnGPIO", 0, 13); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(AUTHOMETION_LYT8266) + + setSetting("board", 45); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("chGPIO", 0, 13); + setSetting("chGPIO", 1, 12); + setSetting("chGPIO", 2, 14); + setSetting("chGPIO", 3, 2); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("relays", 1); + setSetting("enGPIO", 15); + + #elif defined(ARILUX_E27) + + setSetting("board", 46); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_MY92XX); + setSetting("myModel", MY92XX_MODEL_MY9291); + setSetting("myChips", 1); + setSetting("myDIGPIO", 13); + setSetting("myDCKIGPIO", 15); + setSetting("relays", 1); + + #elif defined(YJZK_SWITCH_2CH) + + setSetting("board", 47); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 0); + setSetting("ledWifi", 0); + setSetting("btnGPIO", 0, 0); + setSetting("btnGPIO", 1, 9); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("relayGPIO", 0, 12); + setSetting("relayGPIO", 1, 5); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + + #elif defined(ITEAD_SONOFF_DUAL_R2) + + setSetting("board", 48); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 0); + setSetting("btnGPIO", 1, 9); + setSetting("btnGPIO", 2, 10); + setSetting("btnRelay", 0, 0); + setSetting("btnRelay", 1, 1); + setSetting("btnRelay", 2, 0); + setSetting("relayGPIO", 0, 12); + setSetting("relayGPIO", 1, 5); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + + #elif defined(GENERIC_8CH) + + setSetting("board", 49); + setSetting("relayGPIO", 0, 0); + setSetting("relayGPIO", 1, 2); + setSetting("relayGPIO", 2, 4); + setSetting("relayGPIO", 3, 5); + setSetting("relayGPIO", 4, 12); + setSetting("relayGPIO", 5, 13); + setSetting("relayGPIO", 6, 14); + setSetting("relayGPIO", 7, 15); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + setSetting("relayType", 2, RELAY_TYPE_NORMAL); + setSetting("relayType", 3, RELAY_TYPE_NORMAL); + setSetting("relayType", 4, RELAY_TYPE_NORMAL); + setSetting("relayType", 5, RELAY_TYPE_NORMAL); + setSetting("relayType", 6, RELAY_TYPE_NORMAL); + setSetting("relayType", 7, RELAY_TYPE_NORMAL); + + #elif defined(ARILUX_AL_LC01) + + setSetting("board", 50); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("chGPIO", 0, 5); + setSetting("chGPIO", 1, 12); + setSetting("chGPIO", 2, 13); + setSetting("chGPIO", 3, 14); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("relays", 1); + + #elif defined(ARILUX_AL_LC11) + + setSetting("board", 51); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("chGPIO", 0, 5); + setSetting("chGPIO", 1, 4); + setSetting("chGPIO", 2, 14); + setSetting("chGPIO", 3, 13); + setSetting("chGPIO", 4, 12); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("chLogic", 4, 0); + setSetting("relays", 1); + + #elif defined(ARILUX_AL_LC02) + + setSetting("board", 52); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("chGPIO", 0, 12); + setSetting("chGPIO", 1, 5); + setSetting("chGPIO", 2, 13); + setSetting("chGPIO", 3, 15); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("chLogic", 3, 0); + setSetting("relays", 1); + + #elif defined(KMC_70011) + + setSetting("board", 53); + setSetting("ledGPIO", 0, 13); + setSetting("ledLogic", 0, 0); + setSetting("btnGPIO", 0, 0); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 14); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("selGPIO", 12); + setSetting("cf1GPIO", 5); + setSetting("cfGPIO", 4); + + #elif defined(GIZWITS_WITTY_CLOUD) + + setSetting("board", 54); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 4); + setSetting("relayProvider", RELAY_PROVIDER_LIGHT); + setSetting("lightProvider", LIGHT_PROVIDER_DIMMER); + setSetting("chGPIO", 0, 15); + setSetting("chGPIO", 1, 12); + setSetting("chGPIO", 2, 13); + setSetting("chLogic", 0, 0); + setSetting("chLogic", 1, 0); + setSetting("chLogic", 2, 0); + setSetting("relays", 1); + + #elif defined(EUROMATE_WIFI_STECKER_SCHUKO) + + setSetting("board", 55); + setSetting("ledGPIO", 0, 4); + setSetting("ledLogic", 0, 0); + setSetting("ledGPIO", 1, 12); + setSetting("ledLogic", 1, 0); + setSetting("btnGPIO", 0, 14); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 5); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(TONBUX_POWERSTRIP02) + + setSetting("board", 56); + setSetting("relayGPIO", 0, 4); + setSetting("relayGPIO", 1, 13); + setSetting("relayGPIO", 2, 12); + setSetting("relayGPIO", 3, 14); + setSetting("relayGPIO", 4, 16); + setSetting("relayType", 0, RELAY_TYPE_INVERSE); + setSetting("relayType", 1, RELAY_TYPE_INVERSE); + setSetting("relayType", 2, RELAY_TYPE_INVERSE); + setSetting("relayType", 3, RELAY_TYPE_INVERSE); + setSetting("relayType", 4, RELAY_TYPE_NORMAL); // Not a relay. USB ports on/off + setSetting("ledGPIO", 0, 0); // 1 blue led + setSetting("ledLogic", 0, 1); + setSetting("ledGPIO", 1, 3); // 3 red leds + setSetting("ledLogic", 1, 1); + setSetting("btnGPIO", 0, 5); + setSetting("btnRelay", 0, 1); + + #elif defined(LINGAN_SWA1) + + setSetting("board", 57); + setSetting("ledGPIO", 0, 4); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 13); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 5); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(HEYGO_HY02) + + setSetting("board", 58); + setSetting("ledGPIO", 0, 0); + setSetting("ledLogic", 0, 1); + setSetting("ledGPIO", 1, 15); + setSetting("ledLogic", 1, 0); + setSetting("btnGPIO", 0, 13); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 15); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("selGPIO", 3); + setSetting("cf1GPIO", 14); + setSetting("cfGPIO", 5); + + #elif defined(MAXCIO_WUS002S) + + setSetting("board", 59); + setSetting("ledGPIO", 0, 3); + setSetting("ledLogic", 0, 0); + setSetting("btnGPIO", 0, 2); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 13); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("selGPIO", 12); + setSetting("cf1GPIO", 5); + setSetting("cfGPIO", 4); + + #elif defined(YIDIAN_XSSSA05) + + setSetting("board", 60); + setSetting("ledGPIO", 0, 0); + setSetting("ledLogic", 0, 0); + setSetting("ledGPIO", 1, 5); + setSetting("ledLogic", 1, 0); + setSetting("ledGPIO", 2, 2); + setSetting("ledLogic", 2, 0); + setSetting("btnGPIO", 0, 13); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 15); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(TONBUX_XSSSA06) + + setSetting("board", 61); + setSetting("ledGPIO", 0, 4); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 13); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 5); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(GREEN_ESP8266RELAY) + + setSetting("board", 62); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 5); + setSetting("btnRelay", 0, 0); + setSetting("relayGPIO", 0, 4); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(IKE_ESPIKE) + + setSetting("board", 63); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 13); + setSetting("btnRelay", 0, 0); + setSetting("btnGPIO", 1, 12); + setSetting("btnRelay", 1, 1); + setSetting("btnGPIO", 2, 13); + setSetting("btnRelay", 2, 2); + setSetting("relayGPIO", 0, 4); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + setSetting("relayGPIO", 1, 5); + setSetting("relayType", 1, RELAY_TYPE_NORMAL); + setSetting("relayGPIO", 2, 16); + setSetting("relayType", 2, RELAY_TYPE_NORMAL); + + #elif defined(ARNIEX_SWIFITCH) + + setSetting("board", 64); + setSetting("ledGPIO", 0, 12); + setSetting("ledLogic", 0, 1); + setSetting("btnGPIO", 0, 4); + setSetting("btnRelay", 0, 1); + setSetting("relayGPIO", 0, 5); + setSetting("relayType", 0, RELAY_TYPE_INVERSE); + + #elif defined(GENERIC_ESP01SRELAY40) + + setSetting("board", 65); + setSetting("ledGPIO", 0, 2); + setSetting("ledLogic", 0, 0); + setSetting("relayGPIO", 0, 0); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #elif defined(GENERIC_ESP01SRGBLED10) + + setSetting("board", 66); + setSetting("ledGPIO", 0, 2); + + #elif defined(HELTEC_TOUCHRELAY) + + setSetting("board", 67); + setSetting("btnGPIO", 0, 14); + setSetting("btnRelay", 0, 1); + setSetting("relayGPIO", 0, 12); + setSetting("relayType", 0, RELAY_TYPE_NORMAL); + + #else + + // Allow users to define new settings without migration config + //#error "UNSUPPORTED HARDWARE!" + + #endif + + } + + saveSettings(); + +} diff --git a/espurna/mqtt.ino b/espurna/mqtt.ino new file mode 100755 index 0000000..b74be3f --- /dev/null +++ b/espurna/mqtt.ino @@ -0,0 +1,843 @@ +/* + +MQTT MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if MQTT_SUPPORT + +#include +#include +#include +#include +#include +#include + +#if MQTT_USE_ASYNC // Using AsyncMqttClient + +#include +AsyncMqttClient _mqtt; + +#else // Using PubSubClient + +#include +PubSubClient _mqtt; +bool _mqtt_connected = false; + +WiFiClient _mqtt_client; +#if ASYNC_TCP_SSL_ENABLED +WiFiClientSecure _mqtt_client_secure; +#endif // ASYNC_TCP_SSL_ENABLED + +#endif // MQTT_USE_ASYNC + +bool _mqtt_enabled = MQTT_ENABLED; +bool _mqtt_use_json = false; +unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; +unsigned char _mqtt_qos = MQTT_QOS; +bool _mqtt_retain = MQTT_RETAIN; +unsigned long _mqtt_keepalive = MQTT_KEEPALIVE; +String _mqtt_topic; +String _mqtt_topic_json; +String _mqtt_setter; +String _mqtt_getter; +bool _mqtt_forward; +char *_mqtt_user = 0; +char *_mqtt_pass = 0; +char *_mqtt_will; +char *_mqtt_clientid; +#if MQTT_SKIP_RETAINED +unsigned long _mqtt_connected_at = 0; +#endif + +std::vector _mqtt_callbacks; + +typedef struct { + unsigned char parent = 255; + char * topic; + char * message = NULL; +} mqtt_message_t; +std::vector _mqtt_queue; +Ticker _mqtt_flush_ticker; + +// ----------------------------------------------------------------------------- +// Private +// ----------------------------------------------------------------------------- + +void _mqttConnect() { + + // Do not connect if disabled + if (!_mqtt_enabled) return; + + // Do not connect if already connected + if (_mqtt.connected()) return; + + // Check reconnect interval + static unsigned long last = 0; + if (millis() - last < _mqtt_reconnect_delay) return; + last = millis(); + + // Increase the reconnect delay + _mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP; + if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) { + _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX; + } + + String h = getSetting("mqttServer", MQTT_SERVER); + #if MDNS_CLIENT_SUPPORT + h = mdnsResolve(h); + #endif + char * host = strdup(h.c_str()); + + unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt(); + + if (_mqtt_user) free(_mqtt_user); + if (_mqtt_pass) free(_mqtt_pass); + if (_mqtt_will) free(_mqtt_will); + if (_mqtt_clientid) free(_mqtt_clientid); + + _mqtt_user = strdup(getSetting("mqttUser", MQTT_USER).c_str()); + _mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str()); + _mqtt_will = strdup(mqttTopic(MQTT_TOPIC_STATUS, false).c_str()); + _mqtt_clientid = strdup(getSetting("mqttClientID", getIdentifier()).c_str()); + + DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port); + + #if MQTT_USE_ASYNC + + _mqtt.setServer(host, port); + _mqtt.setClientId(_mqtt_clientid); + _mqtt.setKeepAlive(_mqtt_keepalive); + _mqtt.setCleanSession(false); + _mqtt.setWill(_mqtt_will, _mqtt_qos, _mqtt_retain, "0"); + if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) { + DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user); + _mqtt.setCredentials(_mqtt_user, _mqtt_pass); + } + + #if ASYNC_TCP_SSL_ENABLED + + bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1; + _mqtt.setSecure(secure); + if (secure) { + DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n")); + unsigned char fp[20] = {0}; + if (sslFingerPrintArray(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) { + _mqtt.addServerFingerprint(fp); + } else { + DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n")); + } + } + + #endif // ASYNC_TCP_SSL_ENABLED + + DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid); + DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos); + DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0); + DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %ds\n"), _mqtt_keepalive); + DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will); + + _mqtt.connect(); + + #else // not MQTT_USE_ASYNC + + bool response = true; + + #if ASYNC_TCP_SSL_ENABLED + + bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1; + if (secure) { + DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n")); + if (_mqtt_client_secure.connect(host, port)) { + char fp[60] = {0}; + if (sslFingerPrintChar(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) { + if (_mqtt_client_secure.verify(fp, host)) { + _mqtt.setClient(_mqtt_client_secure); + } else { + DEBUG_MSG_P(PSTR("[MQTT] Invalid fingerprint\n")); + response = false; + } + _mqtt_client_secure.stop(); + yield(); + } else { + DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n")); + response = false; + } + } else { + DEBUG_MSG_P(PSTR("[MQTT] Client connection failed\n")); + response = false; + } + + } else { + _mqtt.setClient(_mqtt_client); + } + + #else // not ASYNC_TCP_SSL_ENABLED + + _mqtt.setClient(_mqtt_client); + + #endif // ASYNC_TCP_SSL_ENABLED + + if (response) { + + _mqtt.setServer(host, port); + + if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) { + DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user); + response = _mqtt.connect(_mqtt_clientid, _mqtt_user, _mqtt_pass, _mqtt_will, _mqtt_qos, _mqtt_retain, "0"); + } else { + response = _mqtt.connect(_mqtt_clientid, _mqtt_will, _mqtt_qos, _mqtt_retain, "0"); + } + + DEBUG_MSG_P(PSTR("[MQTT] Client ID: %s\n"), _mqtt_clientid); + DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), _mqtt_qos); + DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), _mqtt_retain ? 1 : 0); + DEBUG_MSG_P(PSTR("[MQTT] Keepalive time: %ds\n"), _mqtt_keepalive); + DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will); + + } + + if (response) { + _mqttOnConnect(); + } else { + DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n")); + } + + #endif // MQTT_USE_ASYNC + + free(host); + +} + +void _mqttConfigure() { + + // Get base topic + _mqtt_topic = getSetting("mqttTopic", MQTT_TOPIC); + if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1); + + // Placeholders + _mqtt_topic.replace("{identifier}", getSetting("hostname")); + _mqtt_topic.replace("{hostname}", getSetting("hostname")); + _mqtt_topic.replace("{magnitude}", "#"); + if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#"; + String mac = WiFi.macAddress(); + mac.replace(":", ""); + _mqtt_topic.replace("{mac}", mac); + + // Getters and setters + _mqtt_setter = getSetting("mqttSetter", MQTT_SETTER); + _mqtt_getter = getSetting("mqttGetter", MQTT_GETTER); + _mqtt_forward = !_mqtt_getter.equals(_mqtt_setter); + + // MQTT options + _mqtt_qos = getSetting("mqttQoS", MQTT_QOS).toInt(); + _mqtt_retain = getSetting("mqttRetain", MQTT_RETAIN).toInt() == 1; + _mqtt_keepalive = getSetting("mqttKeep", MQTT_KEEPALIVE).toInt(); + if (getSetting("mqttClientID").length() == 0) delSetting("mqttClientID"); + + // Enable + if (getSetting("mqttServer", MQTT_SERVER).length() == 0) { + mqttEnabled(false); + } else { + _mqtt_enabled = getSetting("mqttEnabled", MQTT_ENABLED).toInt() == 1; + } + _mqtt_use_json = (getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1); + mqttQueueTopic(MQTT_TOPIC_JSON); + + _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; + +} + +unsigned long _mqttNextMessageId() { + + static unsigned long id = 0; + + // just reboot, get last count from EEPROM + if (id == 0) { + + // read id from EEPROM and shift it + id = EEPROM.read(EEPROM_MESSAGE_ID); + if (id == 0xFF) { + + // There was nothing in EEPROM, + // next message is first message + id = 0; + + } else { + + id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 1); + id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 2); + id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 3); + + // Calculate next block and start from there + id = MQTT_MESSAGE_ID_SHIFT * (1 + (id / MQTT_MESSAGE_ID_SHIFT)); + + } + + } + + // Save to EEPROM every MQTT_MESSAGE_ID_SHIFT + if (id % MQTT_MESSAGE_ID_SHIFT == 0) { + EEPROM.write(EEPROM_MESSAGE_ID + 0, (id >> 24) & 0xFF); + EEPROM.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF); + EEPROM.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF); + EEPROM.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF); + EEPROM.commit(); + } + + id++; + return id; + +} + +// ----------------------------------------------------------------------------- +// WEB +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _mqttWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "mqtt", 3) == 0); +} + +void _mqttWebSocketOnSend(JsonObject& root) { + root["mqttVisible"] = 1; + root["mqttStatus"] = mqttConnected(); + root["mqttEnabled"] = mqttEnabled(); + root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER); + root["mqttPort"] = getSetting("mqttPort", MQTT_PORT); + root["mqttUser"] = getSetting("mqttUser", MQTT_USER); + root["mqttClientID"] = getSetting("mqttClientID"); + root["mqttPassword"] = getSetting("mqttPassword", MQTT_PASS); + root["mqttKeep"] = _mqtt_keepalive; + root["mqttRetain"] = _mqtt_retain; + root["mqttQoS"] = _mqtt_qos; + #if ASYNC_TCP_SSL_ENABLED + root["mqttsslVisible"] = 1; + root["mqttUseSSL"] = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1; + root["mqttFP"] = getSetting("mqttFP", MQTT_SSL_FINGERPRINT); + #endif + root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC); + root["mqttUseJson"] = getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1; +} + +#endif + +// ----------------------------------------------------------------------------- +// SETTINGS +// ----------------------------------------------------------------------------- + +#if TERMINAL_SUPPORT + +void _mqttInitCommands() { + + settingsRegisterCommand(F("MQTT.RESET"), [](Embedis* e) { + _mqttConfigure(); + mqttDisconnect(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + +} + +#endif // TERMINAL_SUPPORT + +// ----------------------------------------------------------------------------- +// MQTT Callbacks +// ----------------------------------------------------------------------------- + +void _mqttCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + + // Subscribe to internal action topics + mqttSubscribe(MQTT_TOPIC_ACTION); + + // Flag system to send heartbeat + systemSendHeartbeat(); + + } + + if (type == MQTT_MESSAGE_EVENT) { + + // Match topic + String t = mqttMagnitude((char *) topic); + + // Actions + if (t.equals(MQTT_TOPIC_ACTION)) { + if (strcmp(payload, MQTT_ACTION_RESET) == 0) { + deferredReset(100, CUSTOM_RESET_MQTT); + } + } + + } + +} + +void _mqttOnConnect() { + + DEBUG_MSG_P(PSTR("[MQTT] Connected!\n")); + _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN; + + #if MQTT_SKIP_RETAINED + _mqtt_connected_at = millis(); + #endif + + // Clean subscriptions + mqttUnsubscribeRaw("#"); + + // Send connect event to subscribers + for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) { + (_mqtt_callbacks[i])(MQTT_CONNECT_EVENT, NULL, NULL); + } + +} + +void _mqttOnDisconnect() { + + DEBUG_MSG_P(PSTR("[MQTT] Disconnected!\n")); + + // Send disconnect event to subscribers + for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) { + (_mqtt_callbacks[i])(MQTT_DISCONNECT_EVENT, NULL, NULL); + } + +} + +void _mqttOnMessage(char* topic, char* payload, unsigned int len) { + + if (len == 0) return; + + char message[len + 1]; + strlcpy(message, (char *) payload, len + 1); + + #if MQTT_SKIP_RETAINED + if (millis() - _mqtt_connected_at < MQTT_SKIP_TIME) { + DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message); + return; + } + #endif + DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message); + + // Send message event to subscribers + for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) { + (_mqtt_callbacks[i])(MQTT_MESSAGE_EVENT, topic, message); + } + +} + +// ----------------------------------------------------------------------------- +// Public API +// ----------------------------------------------------------------------------- + +/** + Returns the magnitude part of a topic + + @param topic the full MQTT topic + @return String object with the magnitude part. +*/ +String mqttMagnitude(char * topic) { + + String pattern = _mqtt_topic + _mqtt_setter; + int position = pattern.indexOf("#"); + if (position == -1) return String(); + String start = pattern.substring(0, position); + String end = pattern.substring(position + 1); + + String magnitude = String(topic); + if (magnitude.startsWith(start) && magnitude.endsWith(end)) { + magnitude.replace(start, ""); + magnitude.replace(end, ""); + } else { + magnitude = String(); + } + + return magnitude; + +} + +/** + Returns a full MQTT topic from the magnitude + + @param magnitude the magnitude part of the topic. + @param is_set whether to build a command topic (true) + or a state topic (false). + @return String full MQTT topic. +*/ +String mqttTopic(const char * magnitude, bool is_set) { + String output = _mqtt_topic; + output.replace("#", magnitude); + output += is_set ? _mqtt_setter : _mqtt_getter; + return output; +} + +/** + Returns a full MQTT topic from the magnitude + + @param magnitude the magnitude part of the topic. + @param index index of the magnitude when more than one such magnitudes. + @param is_set whether to build a command topic (true) + or a state topic (false). + @return String full MQTT topic. +*/ +String mqttTopic(const char * magnitude, unsigned int index, bool is_set) { + char buffer[strlen(magnitude)+5]; + snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), magnitude, index); + return mqttTopic(buffer, is_set); +} + +// ----------------------------------------------------------------------------- + +void mqttSendRaw(const char * topic, const char * message, bool retain) { + + if (_mqtt.connected()) { + #if MQTT_USE_ASYNC + unsigned int packetId = _mqtt.publish(topic, _mqtt_qos, retain, message); + DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId); + #else + _mqtt.publish(topic, message, retain); + DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message); + #endif + } +} + + +void mqttSendRaw(const char * topic, const char * message) { + mqttSendRaw (topic, message, _mqtt_retain); +} + +void mqttSend(const char * topic, const char * message, bool force, bool retain) { + + bool useJson = force ? false : _mqtt_use_json; + + // Equeue message + if (useJson) { + + // Set default queue topic + mqttQueueTopic(MQTT_TOPIC_JSON); + + // Enqueue new message + mqttEnqueue(topic, message); + + // Reset flush timer + _mqtt_flush_ticker.once_ms(MQTT_USE_JSON_DELAY, mqttFlush); + + // Send it right away + } else { + mqttSendRaw(mqttTopic(topic, false).c_str(), message, retain); + + } + +} + +void mqttSend(const char * topic, const char * message, bool force) { + mqttSend(topic, message, force, _mqtt_retain); +} + +void mqttSend(const char * topic, const char * message) { + mqttSend(topic, message, false); +} + +void mqttSend(const char * topic, unsigned int index, const char * message, bool force, bool retain) { + char buffer[strlen(topic)+5]; + snprintf_P(buffer, sizeof(buffer), PSTR("%s/%d"), topic, index); + mqttSend(buffer, message, force, retain); +} + +void mqttSend(const char * topic, unsigned int index, const char * message, bool force) { + mqttSend(topic, index, message, force, _mqtt_retain); +} + +void mqttSend(const char * topic, unsigned int index, const char * message) { + mqttSend(topic, index, message, false); +} + +// ----------------------------------------------------------------------------- + +unsigned char _mqttBuildTree(JsonObject& root, char parent) { + + unsigned char count = 0; + + // Add enqueued messages + for (unsigned char i=0; i<_mqtt_queue.size(); i++) { + mqtt_message_t element = _mqtt_queue[i]; + if (element.parent == parent) { + ++count; + JsonObject& elements = root.createNestedObject(element.topic); + unsigned char num = _mqttBuildTree(elements, i); + if (0 == num) { + root.set(element.topic, element.message); + } + } + } + + return count; + +} + +void mqttFlush() { + + if (!_mqtt.connected()) return; + if (_mqtt_queue.size() == 0) return; + + // Build tree recursively + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + _mqttBuildTree(root, 255); + + // Add extra propeties + #if NTP_SUPPORT && MQTT_ENQUEUE_DATETIME + if (ntpSynced()) root[MQTT_TOPIC_TIME] = ntpDateTime(); + #endif + #if MQTT_ENQUEUE_MAC + root[MQTT_TOPIC_MAC] = WiFi.macAddress(); + #endif + #if MQTT_ENQUEUE_HOSTNAME + root[MQTT_TOPIC_HOSTNAME] = getSetting("hostname"); + #endif + #if MQTT_ENQUEUE_IP + root[MQTT_TOPIC_IP] = getIP(); + #endif + #if MQTT_ENQUEUE_MESSAGE_ID + root[MQTT_TOPIC_MESSAGE_ID] = _mqttNextMessageId(); + #endif + + // Send + String output; + root.printTo(output); + mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str(), false); + + // Clear queue + for (unsigned char i = 0; i < _mqtt_queue.size(); i++) { + mqtt_message_t element = _mqtt_queue[i]; + free(element.topic); + if (element.message) { + free(element.message); + } + } + _mqtt_queue.clear(); + +} + +void mqttQueueTopic(const char * topic) { + String t = mqttTopic(topic, false); + if (!t.equals(_mqtt_topic_json)) { + mqttFlush(); + _mqtt_topic_json = t; + } +} + +int8_t mqttEnqueue(const char * topic, const char * message, unsigned char parent) { + + // Queue is not meant to send message "offline" + // We must prevent the queue does not get full while offline + if (!_mqtt.connected()) return -1; + + // Force flusing the queue if the MQTT_QUEUE_MAX_SIZE has been reached + if (_mqtt_queue.size() >= MQTT_QUEUE_MAX_SIZE) mqttFlush(); + + int8_t index = _mqtt_queue.size(); + + // Enqueue new message + mqtt_message_t element; + element.parent = parent; + element.topic = strdup(topic); + if (NULL != message) { + element.message = strdup(message); + } + _mqtt_queue.push_back(element); + + return index; + +} + +int8_t mqttEnqueue(const char * topic, const char * message) { + return mqttEnqueue(topic, message, 255); +} + +// ----------------------------------------------------------------------------- + +void mqttSubscribeRaw(const char * topic) { + if (_mqtt.connected() && (strlen(topic) > 0)) { + #if MQTT_USE_ASYNC + unsigned int packetId = _mqtt.subscribe(topic, _mqtt_qos); + DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId); + #else + _mqtt.subscribe(topic, _mqtt_qos); + DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s\n"), topic); + #endif + } +} + +void mqttSubscribe(const char * topic) { + mqttSubscribeRaw(mqttTopic(topic, true).c_str()); +} + +void mqttUnsubscribeRaw(const char * topic) { + if (_mqtt.connected() && (strlen(topic) > 0)) { + #if MQTT_USE_ASYNC + unsigned int packetId = _mqtt.unsubscribe(topic); + DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)\n"), topic, packetId); + #else + _mqtt.unsubscribe(topic); + DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s\n"), topic); + #endif + } +} + +void mqttUnsubscribe(const char * topic) { + mqttUnsubscribeRaw(mqttTopic(topic, true).c_str()); +} + +// ----------------------------------------------------------------------------- + +void mqttEnabled(bool status) { + _mqtt_enabled = status; + setSetting("mqttEnabled", status ? 1 : 0); +} + +bool mqttEnabled() { + return _mqtt_enabled; +} + +bool mqttConnected() { + return _mqtt.connected(); +} + +void mqttDisconnect() { + if (_mqtt.connected()) { + DEBUG_MSG_P(PSTR("[MQTT] Disconnecting\n")); + _mqtt.disconnect(); + } +} + +bool mqttForward() { + return _mqtt_forward; +} + +void mqttRegister(mqtt_callback_f callback) { + _mqtt_callbacks.push_back(callback); +} + +void mqttSetBroker(IPAddress ip, unsigned int port) { + setSetting("mqttServer", ip.toString()); + setSetting("mqttPort", port); + mqttEnabled(MQTT_AUTOCONNECT); +} + +void mqttSetBrokerIfNone(IPAddress ip, unsigned int port) { + if (!hasSetting("mqttServer")) mqttSetBroker(ip, port); +} + +void mqttReset() { + _mqttConfigure(); + mqttDisconnect(); +} + +// ----------------------------------------------------------------------------- +// Initialization +// ----------------------------------------------------------------------------- + +void mqttSetup() { + + DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"), + MQTT_USE_ASYNC ? "ENABLED" : "DISABLED", + ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED", + MQTT_AUTOCONNECT ? "ENABLED" : "DISABLED" + ); + + #if MQTT_USE_ASYNC + + _mqtt.onConnect([](bool sessionPresent) { + _mqttOnConnect(); + }); + _mqtt.onDisconnect([](AsyncMqttClientDisconnectReason reason) { + if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) { + DEBUG_MSG_P(PSTR("[MQTT] TCP Disconnected\n")); + } + if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) { + DEBUG_MSG_P(PSTR("[MQTT] Identifier Rejected\n")); + } + if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) { + DEBUG_MSG_P(PSTR("[MQTT] Server unavailable\n")); + } + if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) { + DEBUG_MSG_P(PSTR("[MQTT] Malformed credentials\n")); + } + if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) { + DEBUG_MSG_P(PSTR("[MQTT] Not authorized\n")); + } + #if ASYNC_TCP_SSL_ENABLED + if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT) { + DEBUG_MSG_P(PSTR("[MQTT] Bad fingerprint\n")); + } + #endif + _mqttOnDisconnect(); + }); + _mqtt.onMessage([](char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { + _mqttOnMessage(topic, payload, len); + }); + _mqtt.onSubscribe([](uint16_t packetId, uint8_t qos) { + DEBUG_MSG_P(PSTR("[MQTT] Subscribe ACK for PID %d\n"), packetId); + }); + _mqtt.onPublish([](uint16_t packetId) { + DEBUG_MSG_P(PSTR("[MQTT] Publish ACK for PID %d\n"), packetId); + }); + + #else // not MQTT_USE_ASYNC + + _mqtt.setCallback([](char* topic, byte* payload, unsigned int length) { + _mqttOnMessage(topic, (char *) payload, length); + }); + + #endif // MQTT_USE_ASYNC + + _mqttConfigure(); + mqttRegister(_mqttCallback); + + #if WEB_SUPPORT + wsOnSendRegister(_mqttWebSocketOnSend); + wsOnAfterParseRegister(_mqttConfigure); + wsOnReceiveRegister(_mqttWebSocketOnReceive); + #endif + + #if TERMINAL_SUPPORT + _mqttInitCommands(); + #endif + + // Register loop + espurnaRegisterLoop(mqttLoop); + +} + +void mqttLoop() { + + if (WiFi.status() != WL_CONNECTED) return; + + #if MQTT_USE_ASYNC + + _mqttConnect(); + + #else // not MQTT_USE_ASYNC + + if (_mqtt.connected()) { + + _mqtt.loop(); + + } else { + + if (_mqtt_connected) { + _mqttOnDisconnect(); + _mqtt_connected = false; + } + + _mqttConnect(); + + } + + #endif + +} + +#endif // MQTT_SUPPORT diff --git a/espurna/netbios.ino b/espurna/netbios.ino new file mode 100755 index 0000000..418a184 --- /dev/null +++ b/espurna/netbios.ino @@ -0,0 +1,20 @@ +/* + +NETBIOS MODULE + +Copyright (C) 2017-2018 by Xose Pérez + +*/ + +#if NETBIOS_SUPPORT + +#include + +void netbiosSetup() { + static WiFiEventHandler _netbios_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) { + NBNS.begin(getSetting("hostname").c_str()); + DEBUG_MSG_P(PSTR("[NETBIOS] Configured\n")); + }); +} + +#endif // NETBIOS_SUPPORT diff --git a/espurna/nofuss.ino b/espurna/nofuss.ino new file mode 100755 index 0000000..c924798 --- /dev/null +++ b/espurna/nofuss.ino @@ -0,0 +1,180 @@ +/* + +NOFUSS MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if NOFUSS_SUPPORT + +#include "NoFUSSClient.h" + +unsigned long _nofussLastCheck = 0; +unsigned long _nofussInterval = 0; +bool _nofussEnabled = false; + +// ----------------------------------------------------------------------------- +// NOFUSS +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _nofussWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "nofuss", 6) == 0); +} + +void _nofussWebSocketOnSend(JsonObject& root) { + root["nofussVisible"] = 1; + root["nofussEnabled"] = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1; + root["nofussServer"] = getSetting("nofussServer", NOFUSS_SERVER); +} + +#endif + +void _nofussConfigure() { + + String nofussServer = getSetting("nofussServer", NOFUSS_SERVER); + #if MDNS_CLIENT_SUPPORT + nofussServer = mdnsResolve(nofussServer); + #endif + + if (nofussServer.length() == 0) { + setSetting("nofussEnabled", 0); + _nofussEnabled = false; + } else { + _nofussEnabled = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1; + } + _nofussInterval = getSetting("nofussInterval", NOFUSS_INTERVAL).toInt(); + _nofussLastCheck = 0; + + if (!_nofussEnabled) { + + DEBUG_MSG_P(PSTR("[NOFUSS] Disabled\n")); + + } else { + + char buffer[20]; + snprintf_P(buffer, sizeof(buffer), PSTR("%s-%s"), APP_NAME, DEVICE); + + NoFUSSClient.setServer(nofussServer); + NoFUSSClient.setDevice(buffer); + NoFUSSClient.setVersion(APP_VERSION); + + DEBUG_MSG_P(PSTR("[NOFUSS] Server : %s\n"), nofussServer.c_str()); + DEBUG_MSG_P(PSTR("[NOFUSS] Dervice: %s\n"), buffer); + DEBUG_MSG_P(PSTR("[NOFUSS] Version: %s\n"), APP_VERSION); + DEBUG_MSG_P(PSTR("[NOFUSS] Enabled\n")); + + } + +} + +#if TERMINAL_SUPPORT + +void _nofussInitCommands() { + + settingsRegisterCommand(F("NOFUSS"), [](Embedis* e) { + DEBUG_MSG_P(PSTR("+OK\n")); + nofussRun(); + }); + +} + +#endif // TERMINAL_SUPPORT + +// ----------------------------------------------------------------------------- + +void nofussRun() { + NoFUSSClient.handle(); + _nofussLastCheck = millis(); +} + +void nofussSetup() { + + _nofussConfigure(); + + NoFUSSClient.onMessage([](nofuss_t code) { + + if (code == NOFUSS_START) { + DEBUG_MSG_P(PSTR("[NoFUSS] Start\n")); + } + + if (code == NOFUSS_UPTODATE) { + DEBUG_MSG_P(PSTR("[NoFUSS] Already in the last version\n")); + } + + if (code == NOFUSS_NO_RESPONSE_ERROR) { + DEBUG_MSG_P(PSTR("[NoFUSS] Wrong server response: %d %s\n"), NoFUSSClient.getErrorNumber(), (char *) NoFUSSClient.getErrorString().c_str()); + } + + if (code == NOFUSS_PARSE_ERROR) { + DEBUG_MSG_P(PSTR("[NoFUSS] Error parsing server response\n")); + } + + if (code == NOFUSS_UPDATING) { + DEBUG_MSG_P(PSTR("[NoFUSS] Updating\n")); + DEBUG_MSG_P(PSTR(" New version: %s\n"), (char *) NoFUSSClient.getNewVersion().c_str()); + DEBUG_MSG_P(PSTR(" Firmware: %s\n"), (char *) NoFUSSClient.getNewFirmware().c_str()); + DEBUG_MSG_P(PSTR(" File System: %s\n"), (char *) NoFUSSClient.getNewFileSystem().c_str()); + #if WEB_SUPPORT + wsSend_P(PSTR("{\"message\": 1}")); + #endif + } + + if (code == NOFUSS_FILESYSTEM_UPDATE_ERROR) { + DEBUG_MSG_P(PSTR("[NoFUSS] File System Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str()); + } + + if (code == NOFUSS_FILESYSTEM_UPDATED) { + DEBUG_MSG_P(PSTR("[NoFUSS] File System Updated\n")); + } + + if (code == NOFUSS_FIRMWARE_UPDATE_ERROR) { + DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Update Error: %s\n"), (char *) NoFUSSClient.getErrorString().c_str()); + } + + if (code == NOFUSS_FIRMWARE_UPDATED) { + DEBUG_MSG_P(PSTR("[NoFUSS] Firmware Updated\n")); + } + + if (code == NOFUSS_RESET) { + DEBUG_MSG_P(PSTR("[NoFUSS] Resetting board\n")); + #if WEB_SUPPORT + wsSend_P(PSTR("{\"action\": \"reload\"}")); + #endif + nice_delay(100); + } + + if (code == NOFUSS_END) { + DEBUG_MSG_P(PSTR("[NoFUSS] End\n")); + } + + }); + + #if WEB_SUPPORT + wsOnSendRegister(_nofussWebSocketOnSend); + wsOnAfterParseRegister(_nofussConfigure); + wsOnReceiveRegister(_nofussWebSocketOnReceive); + #endif + + #if TERMINAL_SUPPORT + _nofussInitCommands(); + #endif + + // Register loop + espurnaRegisterLoop(nofussLoop); + +} + +void nofussLoop() { + + if (!_nofussEnabled) return; + if (!wifiConnected()) return; + if ((_nofussLastCheck > 0) && ((millis() - _nofussLastCheck) < _nofussInterval)) return; + + nofussRun(); + +} + +#endif // NOFUSS_SUPPORT diff --git a/espurna/ntp.ino b/espurna/ntp.ino new file mode 100755 index 0000000..a21e312 --- /dev/null +++ b/espurna/ntp.ino @@ -0,0 +1,179 @@ +/* + +NTP MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if NTP_SUPPORT + +#include +#include +#include +#include + +unsigned long _ntp_start = 0; +bool _ntp_update = false; +bool _ntp_configure = false; + +// ----------------------------------------------------------------------------- +// NTP +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _ntpWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "ntp", 3) == 0); +} + +void _ntpWebSocketOnSend(JsonObject& root) { + root["ntpVisible"] = 1; + root["ntpStatus"] = (timeStatus() == timeSet); + root["ntpServer"] = getSetting("ntpServer", NTP_SERVER); + root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt(); + root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1; + root["ntpRegion"] = getSetting("ntpRegion", NTP_DST_REGION).toInt(); + if (ntpSynced()) root["now"] = now(); +} + +#endif + +void _ntpStart() { + + _ntp_start = 0; + + NTP.begin(getSetting("ntpServer", NTP_SERVER)); + NTP.setInterval(NTP_SYNC_INTERVAL, NTP_UPDATE_INTERVAL); + NTP.setNTPTimeout(NTP_TIMEOUT); + _ntpConfigure(); + +} + +void _ntpConfigure() { + + _ntp_configure = false; + + int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt(); + int sign = offset > 0 ? 1 : -1; + offset = abs(offset); + int tz_hours = sign * (offset / 60); + int tz_minutes = sign * (offset % 60); + if (NTP.getTimeZone() != tz_hours || NTP.getTimeZoneMinutes() != tz_minutes) { + NTP.setTimeZone(tz_hours, tz_minutes); + _ntp_update = true; + } + + bool daylight = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1; + if (NTP.getDayLight() != daylight) { + NTP.setDayLight(daylight); + _ntp_update = true; + } + + String server = getSetting("ntpServer", NTP_SERVER); + if (!NTP.getNtpServerName().equals(server)) { + NTP.setNtpServerName(server); + } + + uint8_t dst_region = getSetting("ntpRegion", NTP_DST_REGION).toInt(); + NTP.setDSTZone(dst_region); + +} + +void _ntpUpdate() { + + _ntp_update = false; + + #if WEB_SUPPORT + wsSend(_ntpWebSocketOnSend); + #endif + + if (strlen(ntpDateTime().c_str())) { + DEBUG_MSG_P(PSTR("[NTP] Time: %s\n"), (char *) ntpDateTime().c_str()); + } + +} + +void _ntpLoop() { + + if (0 < _ntp_start && _ntp_start < millis()) _ntpStart(); + if (_ntp_configure) _ntpConfigure(); + if (_ntp_update) _ntpUpdate(); + + now(); + + #if BROKER_SUPPORT + static unsigned char last_minute = 60; + if (ntpSynced() && (minute() != last_minute)) { + last_minute = minute(); + brokerPublish(MQTT_TOPIC_DATETIME, ntpDateTime().c_str()); + } + #endif + +} + +void _ntpBackwards() { + moveSetting("ntpServer1", "ntpServer"); + delSetting("ntpServer2"); + delSetting("ntpServer3"); + int offset = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt(); + if (-30 < offset && offset < 30) { + offset *= 60; + setSetting("ntpOffset", offset); + } +} + +// ----------------------------------------------------------------------------- + +bool ntpSynced() { + return (year() > 2017); +} + +String ntpDateTime() { + if (!ntpSynced()) return String(); + char buffer[20]; + time_t t = now(); + snprintf_P(buffer, sizeof(buffer), + PSTR("%04d-%02d-%02d %02d:%02d:%02d"), + year(t), month(t), day(t), hour(t), minute(t), second(t) + ); + return String(buffer); +} + +// ----------------------------------------------------------------------------- + +void ntpSetup() { + + _ntpBackwards(); + + NTP.onNTPSyncEvent([](NTPSyncEvent_t error) { + if (error) { + #if WEB_SUPPORT + wsSend_P(PSTR("{\"ntpStatus\": false}")); + #endif + if (error == noResponse) { + DEBUG_MSG_P(PSTR("[NTP] Error: NTP server not reachable\n")); + } else if (error == invalidAddress) { + DEBUG_MSG_P(PSTR("[NTP] Error: Invalid NTP server address\n")); + } + } else { + _ntp_update = true; + } + }); + + wifiRegister([](justwifi_messages_t code, char * parameter) { + if (code == MESSAGE_CONNECTED) _ntp_start = millis() + NTP_START_DELAY; + }); + + #if WEB_SUPPORT + wsOnSendRegister(_ntpWebSocketOnSend); + wsOnReceiveRegister(_ntpWebSocketOnReceive); + wsOnAfterParseRegister([]() { _ntp_configure = true; }); + #endif + + // Register loop + espurnaRegisterLoop(_ntpLoop); + +} + +#endif // NTP_SUPPORT diff --git a/espurna/ota.ino b/espurna/ota.ino new file mode 100755 index 0000000..2d730f4 --- /dev/null +++ b/espurna/ota.ino @@ -0,0 +1,249 @@ +/* + +OTA MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#include "ArduinoOTA.h" + +// ----------------------------------------------------------------------------- +// Arduino OTA +// ----------------------------------------------------------------------------- + +void _otaConfigure() { + ArduinoOTA.setPort(OTA_PORT); + ArduinoOTA.setHostname(getSetting("hostname").c_str()); + #if USE_PASSWORD + ArduinoOTA.setPassword(getSetting("adminPass", ADMIN_PASS).c_str()); + #endif +} + +void _otaLoop() { + ArduinoOTA.handle(); +} + +// ----------------------------------------------------------------------------- +// Terminal OTA +// ----------------------------------------------------------------------------- + +#if TERMINAL_SUPPORT + +#include +AsyncClient * _ota_client; +char * _ota_host; +char * _ota_url; +unsigned int _ota_port = 80; +unsigned long _ota_size = 0; + +const char OTA_REQUEST_TEMPLATE[] PROGMEM = + "GET %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: ESPurna\r\n" + "Connection: close\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: 0\r\n\r\n\r\n"; + + +void _otaFrom(const char * host, unsigned int port, const char * url) { + + if (_ota_host) free(_ota_host); + if (_ota_url) free(_ota_url); + _ota_host = strdup(host); + _ota_url = strdup(url); + _ota_port = port; + _ota_size = 0; + + if (_ota_client == NULL) { + _ota_client = new AsyncClient(); + } + + _ota_client->onDisconnect([](void *s, AsyncClient *c) { + + DEBUG_MSG_P(PSTR("\n")); + + if (Update.end(true)){ + DEBUG_MSG_P(PSTR("[OTA] Success: %u bytes\n"), _ota_size); + deferredReset(100, CUSTOM_RESET_OTA); + } else { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + } + + DEBUG_MSG_P(PSTR("[OTA] Disconnected\n")); + + _ota_client->free(); + delete _ota_client; + _ota_client = NULL; + free(_ota_host); + _ota_host = NULL; + free(_ota_url); + _ota_url = NULL; + + }, 0); + + _ota_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) { + _ota_client->close(true); + }, 0); + + _ota_client->onData([](void * arg, AsyncClient * c, void * data, size_t len) { + + char * p = (char *) data; + + if (_ota_size == 0) { + + Update.runAsync(true); + if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + } + + p = strstr((char *)data, "\r\n\r\n") + 4; + len = len - (p - (char *) data); + + } + + if (!Update.hasError()) { + if (Update.write((uint8_t *) p, len) != len) { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + } + } + + _ota_size += len; + DEBUG_MSG_P(PSTR("[OTA] Progress: %u bytes\r"), _ota_size); + + + }, NULL); + + _ota_client->onConnect([](void * arg, AsyncClient * client) { + + #if ASYNC_TCP_SSL_ENABLED + if (443 == _ota_port) { + uint8_t fp[20] = {0}; + sslFingerPrintArray(getSetting("otafp", OTA_GITHUB_FP).c_str(), fp); + SSL * ssl = _ota_client->getSSL(); + if (ssl_match_fingerprint(ssl, fp) != SSL_OK) { + DEBUG_MSG_P(PSTR("[OTA] Warning: certificate doesn't match\n")); + } + } + #endif + + DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url); + char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)]; + snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host); + client->write(buffer); + + }, NULL); + + + #if ASYNC_TCP_SSL_ENABLED + bool connected = _ota_client->connect(host, port, 443 == port); + #else + bool connected = _ota_client->connect(host, port); + #endif + + if (!connected) { + DEBUG_MSG_P(PSTR("[OTA] Connection failed\n")); + _ota_client->close(true); + } + +} + +void _otaFrom(String url) { + + // Port from protocol + unsigned int port = 80; + if (url.startsWith("https://")) port = 443; + url = url.substring(url.indexOf("/") + 2); + + // Get host + String host = url.substring(0, url.indexOf("/")); + + // Explicit port + int p = host.indexOf(":"); + if (p > 0) { + port = host.substring(p + 1).toInt(); + host = host.substring(0, p); + } + + // Get URL + String uri = url.substring(url.indexOf("/")); + + _otaFrom(host.c_str(), port, uri.c_str()); + +} + +void _otaInitCommands() { + + settingsRegisterCommand(F("OTA"), [](Embedis* e) { + if (e->argc < 2) { + DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n")); + } else { + DEBUG_MSG_P(PSTR("+OK\n")); + String url = String(e->argv[1]); + _otaFrom(url); + } + }); + +} + +#endif // TERMINAL_SUPPORT + +// ----------------------------------------------------------------------------- + +void otaSetup() { + + _otaConfigure(); + + #if WEB_SUPPORT + wsOnAfterParseRegister(_otaConfigure); + #endif + + #if TERMINAL_SUPPORT + _otaInitCommands(); + #endif + + // Register loop + espurnaRegisterLoop(_otaLoop); + + // ------------------------------------------------------------------------- + + ArduinoOTA.onStart([]() { + DEBUG_MSG_P(PSTR("[OTA] Start\n")); + #if WEB_SUPPORT + wsSend_P(PSTR("{\"message\": 2}")); + #endif + }); + + ArduinoOTA.onEnd([]() { + DEBUG_MSG_P(PSTR("\n")); + DEBUG_MSG_P(PSTR("[OTA] Done, restarting...\n")); + #if WEB_SUPPORT + wsSend_P(PSTR("{\"action\": \"reload\"}")); + #endif + deferredReset(100, CUSTOM_RESET_OTA); + }); + + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%\r"), (progress / (total / 100))); + }); + + ArduinoOTA.onError([](ota_error_t error) { + #if DEBUG_SUPPORT + DEBUG_MSG_P(PSTR("\n[OTA] Error #%u: "), error); + if (error == OTA_AUTH_ERROR) DEBUG_MSG_P(PSTR("Auth Failed\n")); + else if (error == OTA_BEGIN_ERROR) DEBUG_MSG_P(PSTR("Begin Failed\n")); + else if (error == OTA_CONNECT_ERROR) DEBUG_MSG_P(PSTR("Connect Failed\n")); + else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n")); + else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n")); + #endif + }); + + ArduinoOTA.begin(); + +} diff --git a/espurna/pwm.c b/espurna/pwm.c new file mode 100755 index 0000000..0e3794c --- /dev/null +++ b/espurna/pwm.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2016 Stefan Brüns + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Set the following three defines to your needs */ + +#ifndef SDK_PWM_PERIOD_COMPAT_MODE + #define SDK_PWM_PERIOD_COMPAT_MODE 0 +#endif +#ifndef PWM_MAX_CHANNELS + #define PWM_MAX_CHANNELS 8 +#endif +#define PWM_DEBUG 0 +#define PWM_USE_NMI 1 + +/* no user servicable parts beyond this point */ + +#define PWM_MAX_TICKS 0x7fffff +#if SDK_PWM_PERIOD_COMPAT_MODE +#define PWM_PERIOD_TO_TICKS(x) (x * 0.2) +#define PWM_DUTY_TO_TICKS(x) (x * 5) +#define PWM_MAX_DUTY (PWM_MAX_TICKS * 0.2) +#define PWM_MAX_PERIOD (PWM_MAX_TICKS * 5) +#else +#define PWM_PERIOD_TO_TICKS(x) (x) +#define PWM_DUTY_TO_TICKS(x) (x) +#define PWM_MAX_DUTY PWM_MAX_TICKS +#define PWM_MAX_PERIOD PWM_MAX_TICKS +#endif + +#include +#include +#include +#include + +// from SDK hw_timer.c +#define TIMER1_DIVIDE_BY_16 0x0004 +#define TIMER1_ENABLE_TIMER 0x0080 + +struct pwm_phase { + uint32_t ticks; ///< delay until next phase, in 200ns units + uint16_t on_mask; ///< GPIO mask to switch on + uint16_t off_mask; ///< GPIO mask to switch off +}; + +/* Three sets of PWM phases, the active one, the one used + * starting with the next cycle, and the one updated + * by pwm_start. After the update pwm_next_set + * is set to the last updated set. pwm_current_set is set to + * pwm_next_set from the interrupt routine during the first + * pwm phase + */ +typedef struct pwm_phase (pwm_phase_array)[PWM_MAX_CHANNELS + 2]; +static pwm_phase_array pwm_phases[3]; +static struct { + struct pwm_phase* next_set; + struct pwm_phase* current_set; + uint8_t current_phase; +} pwm_state; + +static uint32_t pwm_period; +static uint32_t pwm_period_ticks; +static uint32_t pwm_duty[PWM_MAX_CHANNELS]; +static uint16_t gpio_mask[PWM_MAX_CHANNELS]; +static uint8_t pwm_channels; + +// 3-tuples of MUX_REGISTER, MUX_VALUE and GPIO number +typedef uint32_t (pin_info_type)[3]; + +struct gpio_regs { + uint32_t out; /* 0x60000300 */ + uint32_t out_w1ts; /* 0x60000304 */ + uint32_t out_w1tc; /* 0x60000308 */ + uint32_t enable; /* 0x6000030C */ + uint32_t enable_w1ts; /* 0x60000310 */ + uint32_t enable_w1tc; /* 0x60000314 */ + uint32_t in; /* 0x60000318 */ + uint32_t status; /* 0x6000031C */ + uint32_t status_w1ts; /* 0x60000320 */ + uint32_t status_w1tc; /* 0x60000324 */ +}; +static struct gpio_regs* gpio = (struct gpio_regs*)(0x60000300); + +struct timer_regs { + uint32_t frc1_load; /* 0x60000600 */ + uint32_t frc1_count; /* 0x60000604 */ + uint32_t frc1_ctrl; /* 0x60000608 */ + uint32_t frc1_int; /* 0x6000060C */ + uint8_t pad[16]; + uint32_t frc2_load; /* 0x60000620 */ + uint32_t frc2_count; /* 0x60000624 */ + uint32_t frc2_ctrl; /* 0x60000628 */ + uint32_t frc2_int; /* 0x6000062C */ + uint32_t frc2_alarm; /* 0x60000630 */ +}; +static struct timer_regs* timer = (struct timer_regs*)(0x60000600); + +static void ICACHE_RAM_ATTR +pwm_intr_handler(void) +{ + if ((pwm_state.current_set[pwm_state.current_phase].off_mask == 0) && + (pwm_state.current_set[pwm_state.current_phase].on_mask == 0)) { + pwm_state.current_set = pwm_state.next_set; + pwm_state.current_phase = 0; + } + + do { + // force write to GPIO registers on each loop + asm volatile ("" : : : "memory"); + + gpio->out_w1ts = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].on_mask); + gpio->out_w1tc = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].off_mask); + + uint32_t ticks = pwm_state.current_set[pwm_state.current_phase].ticks; + + pwm_state.current_phase++; + + if (ticks) { + if (ticks >= 16) { + // constant interrupt overhead + ticks -= 9; + timer->frc1_int &= ~FRC1_INT_CLR_MASK; + WRITE_PERI_REG(&timer->frc1_load, ticks); + return; + } + + ticks *= 4; + do { + ticks -= 1; + // stop compiler from optimizing delay loop to noop + asm volatile ("" : : : "memory"); + } while (ticks > 0); + } + + } while (1); +} + +/** + * period: initial period (base unit 1us OR 200ns) + * duty: array of initial duty values, may be NULL, may be freed after pwm_init + * pwm_channel_num: number of channels to use + * pin_info_list: array of pin_info + */ +void ICACHE_FLASH_ATTR +pwm_init(uint32_t period, uint32_t *duty, uint32_t pwm_channel_num, + uint32_t (*pin_info_list)[3]) +{ + int i, j, n; + + pwm_channels = pwm_channel_num; + if (pwm_channels > PWM_MAX_CHANNELS) + pwm_channels = PWM_MAX_CHANNELS; + + for (i = 0; i < 3; i++) { + for (j = 0; j < (PWM_MAX_CHANNELS + 2); j++) { + pwm_phases[i][j].ticks = 0; + pwm_phases[i][j].on_mask = 0; + pwm_phases[i][j].off_mask = 0; + } + } + pwm_state.current_set = pwm_state.next_set = 0; + pwm_state.current_phase = 0; + + uint32_t all = 0; + // PIN info: MUX-Register, Mux-Setting, PIN-Nr + for (n = 0; n < pwm_channels; n++) { + pin_info_type* pin_info = &pin_info_list[n]; + PIN_FUNC_SELECT((*pin_info)[0], (*pin_info)[1]); + gpio_mask[n] = 1 << (*pin_info)[2]; + all |= 1 << (*pin_info)[2]; + if (duty) + pwm_set_duty(duty[n], n); + } + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, all); + GPIO_REG_WRITE(GPIO_ENABLE_W1TS_ADDRESS, all); + + pwm_set_period(period); + +#if PWM_USE_NMI + ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_intr_handler); +#else + ETS_FRC_TIMER1_INTR_ATTACH(pwm_intr_handler, NULL); +#endif + TM1_EDGE_INT_ENABLE(); + + timer->frc1_int &= ~FRC1_INT_CLR_MASK; + timer->frc1_ctrl = 0; + + pwm_start(); +} + +__attribute__ ((noinline)) +static uint8_t ICACHE_FLASH_ATTR +_pwm_phases_prep(struct pwm_phase* pwm) +{ + uint8_t n, phases; + + for (n = 0; n < pwm_channels + 2; n++) { + pwm[n].ticks = 0; + pwm[n].on_mask = 0; + pwm[n].off_mask = 0; + } + phases = 1; + for (n = 0; n < pwm_channels; n++) { + uint32_t ticks = PWM_DUTY_TO_TICKS(pwm_duty[n]); + if (ticks == 0) { + pwm[0].off_mask |= gpio_mask[n]; + } else if (ticks >= pwm_period_ticks) { + pwm[0].on_mask |= gpio_mask[n]; + } else { + if (ticks < (pwm_period_ticks/2)) { + pwm[phases].ticks = ticks; + pwm[0].on_mask |= gpio_mask[n]; + pwm[phases].off_mask = gpio_mask[n]; + } else { + pwm[phases].ticks = pwm_period_ticks - ticks; + pwm[phases].on_mask = gpio_mask[n]; + pwm[0].off_mask |= gpio_mask[n]; + } + phases++; + } + } + pwm[phases].ticks = pwm_period_ticks; + + // bubble sort, lowest to hightest duty + n = 2; + while (n < phases) { + if (pwm[n].ticks < pwm[n - 1].ticks) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n - 1]; + pwm[n - 1] = t; + if (n > 2) + n--; + } else { + n++; + } + } + +#if PWM_DEBUG + int t = 0; + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // shift left to align right edge; + uint8_t l = 0, r = 1; + while (r <= phases) { + uint32_t diff = pwm[r].ticks - pwm[l].ticks; + if (diff && (diff <= 16)) { + uint16_t mask = pwm[r].on_mask | pwm[r].off_mask; + pwm[l].off_mask ^= pwm[r].off_mask; + pwm[l].on_mask ^= pwm[r].on_mask; + pwm[0].off_mask ^= pwm[r].on_mask; + pwm[0].on_mask ^= pwm[r].off_mask; + pwm[r].ticks = pwm_period_ticks - diff; + pwm[r].on_mask ^= mask; + pwm[r].off_mask ^= mask; + } else { + l = r; + } + r++; + } + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // sort again + n = 2; + while (n <= phases) { + if (pwm[n].ticks < pwm[n - 1].ticks) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n - 1]; + pwm[n - 1] = t; + if (n > 2) + n--; + } else { + n++; + } + } + + // merge same duty + l = 0, r = 1; + while (r <= phases) { + if (pwm[r].ticks == pwm[l].ticks) { + pwm[l].off_mask |= pwm[r].off_mask; + pwm[l].on_mask |= pwm[r].on_mask; + pwm[r].on_mask = 0; + pwm[r].off_mask = 0; + } else { + l++; + if (l != r) { + struct pwm_phase t = pwm[l]; + pwm[l] = pwm[r]; + pwm[r] = t; + } + } + r++; + } + phases = l; + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // transform absolute end time to phase durations + for (n = 0; n < phases; n++) { + pwm[n].ticks = + pwm[n + 1].ticks - pwm[n].ticks; + // subtract common overhead + pwm[n].ticks--; + } + pwm[phases].ticks = 0; + + // do a cyclic shift if last phase is short + if (pwm[phases - 1].ticks < 16) { + for (n = 0; n < phases - 1; n++) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n + 1]; + pwm[n + 1] = t; + } + } + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d +%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } + ets_printf("\n"); +#endif + + return phases; +} + +void ICACHE_FLASH_ATTR +pwm_start(void) +{ + pwm_phase_array* pwm = &pwm_phases[0]; + + if ((*pwm == pwm_state.next_set) || + (*pwm == pwm_state.current_set)) + pwm++; + if ((*pwm == pwm_state.next_set) || + (*pwm == pwm_state.current_set)) + pwm++; + + uint8_t phases = _pwm_phases_prep(*pwm); + + // all with 0% / 100% duty - stop timer + if (phases == 1) { + if (pwm_state.next_set) { +#if PWM_DEBUG + ets_printf("PWM stop\n"); +#endif + timer->frc1_ctrl = 0; + ETS_FRC1_INTR_DISABLE(); + } + pwm_state.next_set = NULL; + + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (*pwm)[0].on_mask); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (*pwm)[0].off_mask); + + return; + } + + // start if not running + if (!pwm_state.next_set) { +#if PWM_DEBUG + ets_printf("PWM start\n"); +#endif + pwm_state.current_set = pwm_state.next_set = *pwm; + pwm_state.current_phase = phases - 1; + ETS_FRC1_INTR_ENABLE(); + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, 0); + timer->frc1_ctrl = TIMER1_DIVIDE_BY_16 | TIMER1_ENABLE_TIMER; + return; + } + + pwm_state.next_set = *pwm; +} + +void ICACHE_FLASH_ATTR +pwm_set_duty(uint32_t duty, uint8_t channel) +{ + if (channel > PWM_MAX_CHANNELS) + return; + + if (duty > PWM_MAX_DUTY) + duty = PWM_MAX_DUTY; + + pwm_duty[channel] = duty; +} + +uint32_t ICACHE_FLASH_ATTR +pwm_get_duty(uint8_t channel) +{ + if (channel > PWM_MAX_CHANNELS) + return 0; + return pwm_duty[channel]; +} + +void ICACHE_FLASH_ATTR +pwm_set_period(uint32_t period) +{ + pwm_period = period; + + if (pwm_period > PWM_MAX_PERIOD) + pwm_period = PWM_MAX_PERIOD; + + pwm_period_ticks = PWM_PERIOD_TO_TICKS(period); +} + +uint32_t ICACHE_FLASH_ATTR +pwm_get_period(void) +{ + return pwm_period; +} + +uint32_t ICACHE_FLASH_ATTR +get_pwm_version(void) +{ + return 1; +} + +void ICACHE_FLASH_ATTR +set_pwm_debug_en(uint8_t print_en) +{ + (void) print_en; +} diff --git a/espurna/relay.ino b/espurna/relay.ino new file mode 100755 index 0000000..ffc6d1f --- /dev/null +++ b/espurna/relay.ino @@ -0,0 +1,886 @@ +/* + +RELAY MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#include +#include +#include +#include +#include + +typedef struct { + + // Configuration variables + + unsigned char pin; // GPIO pin for the relay + unsigned char type; // RELAY_TYPE_NORMAL, RELAY_TYPE_INVERSE, RELAY_TYPE_LATCHED or RELAY_TYPE_LATCHED_INVERSE + unsigned char reset_pin; // GPIO to reset the relay if RELAY_TYPE_LATCHED + unsigned long delay_on; // Delay to turn relay ON + unsigned long delay_off; // Delay to turn relay OFF + unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON + unsigned long pulse_ms; // Pulse length in millis + + // Status variables + + bool current_status; // Holds the current (physical) status of the relay + bool target_status; // Holds the target status + unsigned long fw_start; // Flood window start time + unsigned char fw_count; // Number of changes within the current flood window + unsigned long change_time; // Scheduled time to change + bool report; // Whether to report to own topic + bool group_report; // Whether to report to group topic + + // Helping objects + + Ticker pulseTicker; // Holds the pulse back timer + +} relay_t; +std::vector _relays; +bool _relayRecursive = false; +Ticker _relaySaveTicker; + +// ----------------------------------------------------------------------------- +// RELAY PROVIDERS +// ----------------------------------------------------------------------------- + +void _relayProviderStatus(unsigned char id, bool status) { + + // Check relay ID + if (id >= _relays.size()) return; + + // Store new current status + _relays[id].current_status = status; + + #if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE + rfbStatus(id, status); + #endif + + #if RELAY_PROVIDER == RELAY_PROVIDER_DUAL + + // Calculate mask + unsigned char mask=0; + for (unsigned char i=0; i<_relays.size(); i++) { + if (_relays[i].current_status) mask = mask + (1 << i); + } + + // Send it to F330 + Serial.flush(); + Serial.write(0xA0); + Serial.write(0x04); + Serial.write(mask); + Serial.write(0xA1); + Serial.flush(); + + #endif + + #if RELAY_PROVIDER == RELAY_PROVIDER_STM + Serial.flush(); + Serial.write(0xA0); + Serial.write(id + 1); + Serial.write(status); + Serial.write(0xA1 + status + id); + Serial.flush(); + #endif + + #if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT + + // If the number of relays matches the number of light channels + // assume each relay controls one channel. + // If the number of relays is the number of channels plus 1 + // assume the first one controls all the channels and + // the rest one channel each. + // Otherwise every relay controls all channels. + // TODO: this won't work with a mixed of dummy and real relays + // but this option is not allowed atm (YANGNI) + if (_relays.size() == lightChannels()) { + lightState(id, status); + lightState(true); + } else if (_relays.size() == lightChannels() + 1) { + if (id == 0) { + lightState(status); + } else { + lightState(id-1, status); + } + } else { + lightState(status); + } + + lightUpdate(true, true); + + #endif + + #if RELAY_PROVIDER == RELAY_PROVIDER_RELAY + if (_relays[id].type == RELAY_TYPE_NORMAL) { + digitalWrite(_relays[id].pin, status); + } else if (_relays[id].type == RELAY_TYPE_INVERSE) { + digitalWrite(_relays[id].pin, !status); + } else if (_relays[id].type == RELAY_TYPE_LATCHED || _relays[id].type == RELAY_TYPE_LATCHED_INVERSE) { + bool pulse = RELAY_TYPE_LATCHED ? HIGH : LOW; + digitalWrite(_relays[id].pin, !pulse); + digitalWrite(_relays[id].reset_pin, !pulse); + if (status) { + digitalWrite(_relays[id].pin, pulse); + } else { + digitalWrite(_relays[id].reset_pin, pulse); + } + nice_delay(RELAY_LATCHING_PULSE); + digitalWrite(_relays[id].pin, !pulse); + digitalWrite(_relays[id].reset_pin, !pulse); + } + #endif + +} + +/** + * Walks the relay vector processing only those relays + * that have to change to the requested mode + * @bool mode Requested mode + */ +void _relayProcess(bool mode) { + + unsigned long current_time = millis(); + + for (unsigned char id = 0; id < _relays.size(); id++) { + + bool target = _relays[id].target_status; + + // Only process the relays we have to change + if (target == _relays[id].current_status) continue; + + // Only process the relays we have change to the requested mode + if (target != mode) continue; + + // Only process if the change_time has arrived + if (current_time < _relays[id].change_time) continue; + + DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, target ? "ON" : "OFF"); + + // Call the provider to perform the action + _relayProviderStatus(id, target); + + // Send to Broker + #if BROKER_SUPPORT + brokerPublish(MQTT_TOPIC_RELAY, id, target ? "1" : "0"); + #endif + + // Send MQTT + #if MQTT_SUPPORT + relayMQTT(id); + #endif + + if (!_relayRecursive) { + relayPulse(id); + _relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave); + #if WEB_SUPPORT + wsSend(_relayWebSocketUpdate); + #endif + } + + #if DOMOTICZ_SUPPORT + domoticzSendRelay(id); + #endif + + #if INFLUXDB_SUPPORT + relayInfluxDB(id); + #endif + + #if THINGSPEAK_SUPPORT + tspkEnqueueRelay(id, target); + tspkFlush(); + #endif + + // Flag relay-based LEDs to update status + ledUpdate(true); + + _relays[id].report = false; + _relays[id].group_report = false; + + } + +} + +// ----------------------------------------------------------------------------- +// RELAY +// ----------------------------------------------------------------------------- + +void relayPulse(unsigned char id) { + + byte mode = _relays[id].pulse; + if (mode == RELAY_PULSE_NONE) return; + unsigned long ms = _relays[id].pulse_ms; + if (ms == 0) return; + + bool status = relayStatus(id); + bool pulseStatus = (mode == RELAY_PULSE_ON); + + if (pulseStatus == status) { + _relays[id].pulseTicker.detach(); + } else { + _relays[id].pulseTicker.once_ms(ms, relayToggle, id); + } + +} + +bool relayStatus(unsigned char id, bool status, bool report, bool group_report) { + + if (id >= _relays.size()) return false; + + bool changed = false; + + if (_relays[id].current_status == status) { + + if (_relays[id].target_status != status) { + DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled change cancelled\n"), id); + _relays[id].target_status = status; + _relays[id].report = false; + _relays[id].group_report = false; + changed = true; + } + + // For RFBridge, keep sending the message even if the status is already the required + #if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE + rfbStatus(id, status); + #endif + + // Update the pulse counter if the relay is already in the non-normal state (#454) + relayPulse(id); + + } else { + + unsigned int current_time = millis(); + unsigned int fw_end = _relays[id].fw_start + 1000 * RELAY_FLOOD_WINDOW; + unsigned long delay = status ? _relays[id].delay_on : _relays[id].delay_off; + + _relays[id].fw_count++; + _relays[id].change_time = current_time + delay; + + // If current_time is off-limits the floodWindow... + if (current_time < _relays[id].fw_start || fw_end <= current_time) { + + // We reset the floodWindow + _relays[id].fw_start = current_time; + _relays[id].fw_count = 1; + + // If current_time is in the floodWindow and there have been too many requests... + } else if (_relays[id].fw_count >= RELAY_FLOOD_CHANGES) { + + // We schedule the changes to the end of the floodWindow + // unless it's already delayed beyond that point + if (fw_end - delay > current_time) { + _relays[id].change_time = fw_end; + } + + } + + _relays[id].target_status = status; + if (report) _relays[id].report = true; + if (group_report) _relays[id].group_report = true; + + relaySync(id); + + DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled %s in %u ms\n"), + id, status ? "ON" : "OFF", + (_relays[id].change_time - current_time)); + + changed = true; + + } + + return changed; + +} + +bool relayStatus(unsigned char id, bool status) { + return relayStatus(id, status, true, true); +} + +bool relayStatus(unsigned char id) { + + // Check relay ID + if (id >= _relays.size()) return false; + + // Get status from storage + return _relays[id].current_status; + +} + +void relaySync(unsigned char id) { + + // No sync if none or only one relay + if (_relays.size() < 2) return; + + // Do not go on if we are comming from a previous sync + if (_relayRecursive) return; + + // Flag sync mode + _relayRecursive = true; + + byte relaySync = getSetting("relaySync", RELAY_SYNC).toInt(); + bool status = _relays[id].target_status; + + // If RELAY_SYNC_SAME all relays should have the same state + if (relaySync == RELAY_SYNC_SAME) { + for (unsigned short i=0; i<_relays.size(); i++) { + if (i != id) relayStatus(i, status); + } + + // If NONE_OR_ONE or ONE and setting ON we should set OFF all the others + } else if (status) { + if (relaySync != RELAY_SYNC_ANY) { + for (unsigned short i=0; i<_relays.size(); i++) { + if (i != id) relayStatus(i, false); + } + } + + // If ONLY_ONE and setting OFF we should set ON the other one + } else { + if (relaySync == RELAY_SYNC_ONE) { + unsigned char i = (id + 1) % _relays.size(); + relayStatus(i, true); + } + } + + // Unflag sync mode + _relayRecursive = false; + +} + +void relaySave() { + unsigned char bit = 1; + unsigned char mask = 0; + for (unsigned int i=0; i < _relays.size(); i++) { + if (relayStatus(i)) mask += bit; + bit += bit; + } + EEPROM.write(EEPROM_RELAY_STATUS, mask); + DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask); + EEPROM.commit(); +} + +void relayToggle(unsigned char id, bool report, bool group_report) { + if (id >= _relays.size()) return; + relayStatus(id, !relayStatus(id), report, group_report); +} + +void relayToggle(unsigned char id) { + relayToggle(id, true, true); +} + +unsigned char relayCount() { + return _relays.size(); +} + +unsigned char relayParsePayload(const char * payload) { + + // Payload could be "OFF", "ON", "TOGGLE" + // or its number equivalents: 0, 1 or 2 + + if (payload[0] == '0') return 0; + if (payload[0] == '1') return 1; + if (payload[0] == '2') return 2; + + // trim payload + char * p = ltrim((char *)payload); + + // to lower + unsigned int l = strlen(p); + if (l>6) l=6; + for (unsigned char i=0; i 1) { + root["multirelayVisible"] = 1; + root["relaySync"] = getSetting("relaySync", RELAY_SYNC); + } + + root["relayVisible"] = 1; + +} + +void _relayWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) { + + if (strcmp(action, "relay") != 0) return; + + if (data.containsKey("status")) { + + unsigned char value = relayParsePayload(data["status"]); + + if (value == 3) { + + wsSend(_relayWebSocketUpdate); + + } else if (value < 3) { + + unsigned int relayID = 0; + if (data.containsKey("id")) { + String value = data["id"]; + relayID = value.toInt(); + } + + // Action to perform + if (value == 0) { + relayStatus(relayID, false); + } else if (value == 1) { + relayStatus(relayID, true); + } else if (value == 2) { + relayToggle(relayID); + } + + } + + } + +} + +void relaySetupWS() { + wsOnSendRegister(_relayWebSocketOnStart); + wsOnActionRegister(_relayWebSocketOnAction); + wsOnAfterParseRegister(_relayConfigure); + wsOnReceiveRegister(_relayWebSocketOnReceive); +} + +#endif // WEB_SUPPORT + +//------------------------------------------------------------------------------ +// REST API +//------------------------------------------------------------------------------ + +#if WEB_SUPPORT + +void relaySetupAPI() { + + // API entry points (protected with apikey) + for (unsigned int relayID=0; relayID= _relays.size()) return; + + // Send state topic + if (_relays[id].report) { + _relays[id].report = false; + mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? "1" : "0"); + } + + // Check group topic + if (_relays[id].group_report) { + _relays[id].group_report = false; + String t = getSetting("mqttGroup", id, ""); + if (t.length() > 0) { + bool status = relayStatus(id); + if (getSetting("mqttGroupInv", id, 0).toInt() == 1) status = !status; + mqttSendRaw(t.c_str(), status ? "1" : "0"); + } + } +} + +void relayMQTT() { + for (unsigned int id=0; id < _relays.size(); id++) { + mqttSend(MQTT_TOPIC_RELAY, id, _relays[id].current_status ? "1" : "0"); + } +} + +void relayStatusWrap(unsigned char id, unsigned char value, bool is_group_topic) { + switch (value) { + case 0: + relayStatus(id, false, mqttForward(), !is_group_topic); + break; + case 1: + relayStatus(id, true, mqttForward(), !is_group_topic); + break; + case 2: + relayToggle(id, true, true); + break; + default: + _relays[id].report = true; + relayMQTT(id); + break; + } +} + +void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + + // Send status on connect + #if not HEARTBEAT_REPORT_RELAY + relayMQTT(); + #endif + + // Subscribe to own /set topic + char buffer[strlen(MQTT_TOPIC_RELAY) + 3]; + snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RELAY); + mqttSubscribe(buffer); + + // Subscribe to group topics + for (unsigned int i=0; i < _relays.size(); i++) { + String t = getSetting("mqttGroup", i, ""); + if (t.length() > 0) mqttSubscribeRaw(t.c_str()); + } + + } + + if (type == MQTT_MESSAGE_EVENT) { + + // Check relay topic + String t = mqttMagnitude((char *) topic); + if (t.startsWith(MQTT_TOPIC_RELAY)) { + + // Get value + unsigned char value = relayParsePayload(payload); + if (value == 0xFF) return; + + // Get relay ID + unsigned int id = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt(); + if (id >= relayCount()) { + DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id); + } else { + relayStatusWrap(id, value, false); + } + + return; + + } + + // Check group topics + for (unsigned int i=0; i < _relays.size(); i++) { + + String t = getSetting("mqttGroup", i, ""); + + if ((t.length() > 0) && t.equals(topic)) { + + unsigned char value = relayParsePayload(payload); + if (value == 0xFF) return; + + if (value < 2) { + if (getSetting("mqttGroupInv", i, 0).toInt() == 1) { + value = 1 - value; + } + } + + DEBUG_MSG_P(PSTR("[RELAY] Matched group topic for relayID %d\n"), i); + relayStatusWrap(i, value, true); + + } + } + + } + + if (type == MQTT_DISCONNECT_EVENT) { + for (unsigned int i=0; i < _relays.size(); i++){ + int reaction = getSetting("relayOnDisc", i, 0).toInt(); + if (1 == reaction) { // switch relay OFF + DEBUG_MSG_P(PSTR("[RELAY] Reset relay (%d) due to MQTT disconnection\n"), i); + relayStatusWrap(i, false, false); + } else if(2 == reaction) { // switch relay ON + DEBUG_MSG_P(PSTR("[RELAY] Set relay (%d) due to MQTT disconnection\n"), i); + relayStatusWrap(i, true, false); + } + } + + } + +} + +void relaySetupMQTT() { + mqttRegister(relayMQTTCallback); +} + +#endif + +//------------------------------------------------------------------------------ +// InfluxDB +//------------------------------------------------------------------------------ + +#if INFLUXDB_SUPPORT + +void relayInfluxDB(unsigned char id) { + if (id >= _relays.size()) return; + idbSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0"); +} + +#endif + + +//------------------------------------------------------------------------------ +// Settings +//------------------------------------------------------------------------------ + +#if TERMINAL_SUPPORT + +void _relayInitCommands() { + + settingsRegisterCommand(F("RELAY"), [](Embedis* e) { + if (e->argc < 2) { + DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n")); + } + int id = String(e->argv[1]).toInt(); + if (e->argc > 2) { + int value = String(e->argv[2]).toInt(); + if (value == 2) { + relayToggle(id); + } else { + relayStatus(id, value == 1); + } + } + DEBUG_MSG_P(PSTR("Status: %s\n"), _relays[id].target_status ? "true" : "false"); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + +} + +#endif // TERMINAL_SUPPORT + +//------------------------------------------------------------------------------ +// Setup +//------------------------------------------------------------------------------ + +void _relayLoop() { + _relayProcess(false); + _relayProcess(true); +} + +void relaySetup() { + + // Dummy relays for AI Light, Magic Home LED Controller, H801, + // Sonoff Dual and Sonoff RF Bridge + #if DUMMY_RELAY_COUNT > 0 + + for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) { + _relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL}); + } + + #else + + #if RELAY1_PIN != GPIO_NONE + _relays.push_back((relay_t) { RELAY1_PIN, RELAY1_TYPE, RELAY1_RESET_PIN, RELAY1_DELAY_ON, RELAY1_DELAY_OFF }); + #endif + #if RELAY2_PIN != GPIO_NONE + _relays.push_back((relay_t) { RELAY2_PIN, RELAY2_TYPE, RELAY2_RESET_PIN, RELAY2_DELAY_ON, RELAY2_DELAY_OFF }); + #endif + #if RELAY3_PIN != GPIO_NONE + _relays.push_back((relay_t) { RELAY3_PIN, RELAY3_TYPE, RELAY3_RESET_PIN, RELAY3_DELAY_ON, RELAY3_DELAY_OFF }); + #endif + #if RELAY4_PIN != GPIO_NONE + _relays.push_back((relay_t) { RELAY4_PIN, RELAY4_TYPE, RELAY4_RESET_PIN, RELAY4_DELAY_ON, RELAY4_DELAY_OFF }); + #endif + #if RELAY5_PIN != GPIO_NONE + _relays.push_back((relay_t) { RELAY5_PIN, RELAY5_TYPE, RELAY5_RESET_PIN, RELAY5_DELAY_ON, RELAY5_DELAY_OFF }); + #endif + #if RELAY6_PIN != GPIO_NONE + _relays.push_back((relay_t) { RELAY6_PIN, RELAY6_TYPE, RELAY6_RESET_PIN, RELAY6_DELAY_ON, RELAY6_DELAY_OFF }); + #endif + #if RELAY7_PIN != GPIO_NONE + _relays.push_back((relay_t) { RELAY7_PIN, RELAY7_TYPE, RELAY7_RESET_PIN, RELAY7_DELAY_ON, RELAY7_DELAY_OFF }); + #endif + #if RELAY8_PIN != GPIO_NONE + _relays.push_back((relay_t) { RELAY8_PIN, RELAY8_TYPE, RELAY8_RESET_PIN, RELAY8_DELAY_ON, RELAY8_DELAY_OFF }); + #endif + + #endif + + _relayBackwards(); + _relayConfigure(); + _relayBoot(); + _relayLoop(); + + espurnaRegisterLoop(_relayLoop); + + #if WEB_SUPPORT + relaySetupAPI(); + relaySetupWS(); + #endif + #if MQTT_SUPPORT + relaySetupMQTT(); + #endif + #if TERMINAL_SUPPORT + _relayInitCommands(); + #endif + + DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size()); + +} diff --git a/espurna/rf.ino b/espurna/rf.ino new file mode 100755 index 0000000..68756b6 --- /dev/null +++ b/espurna/rf.ino @@ -0,0 +1,192 @@ +/* + +RF MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if RF_SUPPORT + +#include + +RCSwitch * _rfModem; + +unsigned long _rf_learn_start = 0; +unsigned char _rf_learn_id = 0; +bool _rf_learn_status = true; +bool _rf_learn_active = false; + +// ----------------------------------------------------------------------------- +// RF +// ----------------------------------------------------------------------------- + +unsigned long _rfRetrieve(unsigned char id, bool status) { + String code = getSetting(status ? "rfbON" : "rfbOFF", id, "0"); + return strtoul(code.c_str(), 0, 16); +} + +void _rfStore(unsigned char id, bool status, unsigned long code) { + DEBUG_MSG_P(PSTR("[RF] Storing %d-%s => %X\n"), id, status ? "ON" : "OFF", code); + char buffer[20]; + snprintf_P(buffer, sizeof(buffer), PSTR("%X"), code); + setSetting(status ? "rfbON" : "rfbOFF", id, buffer); +} + +void _rfLearn(unsigned char id, bool status) { + _rf_learn_start = millis(); + _rf_learn_id = id; + _rf_learn_status = status; + _rf_learn_active = true; +} + +void _rfForget(unsigned char id, bool status) { + + delSetting(status ? "rfbON" : "rfbOFF", id); + + // Websocket update + #if WEB_SUPPORT + char wsb[100]; + snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0); + wsSend(wsb); + #endif + +} + + +bool _rfMatch(unsigned long code, unsigned char& relayID, unsigned char& value) { + + bool found = false; + DEBUG_MSG_P(PSTR("[RF] Trying to match code %X\n"), code); + + for (unsigned char i=0; i()); +} + +// ----------------------------------------------------------------------------- + +void rfLoop() { + + if (_rfModem->available()) { + + static unsigned long last = 0; + if (millis() - last > RF_DEBOUNCE) { + last = millis(); + + if (_rfModem->getReceivedValue() > 0) { + + unsigned long rf_code = _rfModem->getReceivedValue(); + + DEBUG_MSG_P(PSTR("[RF] Received code: %X\n"), rf_code); + + if (_rf_learn_active) { + + _rf_learn_active = false; + + _rfStore(_rf_learn_id, _rf_learn_status, rf_code); + + // Websocket update + #if WEB_SUPPORT + char wsb[100]; + snprintf_P( + wsb, sizeof(wsb), + PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%X\"}]}"), + _rf_learn_id, _rf_learn_status ? 1 : 0, rf_code); + wsSend(wsb); + #endif + + } else { + + unsigned char id; + unsigned char value; + if (_rfMatch(rf_code, id, value)) { + if (2 == value) { + relayToggle(id); + } else { + relayStatus(id, 1 == value); + } + } + + } + + } + + } + + _rfModem->resetAvailable(); + + } + + if (_rf_learn_active && (millis() - _rf_learn_start > RF_LEARN_TIMEOUT)) { + _rf_learn_active = false; + } + +} + +void rfSetup() { + + _rfModem = new RCSwitch(); + _rfModem->enableReceive(RF_PIN); + DEBUG_MSG_P(PSTR("[RF] RF receiver on GPIO %u\n"), RF_PIN); + + #if WEB_SUPPORT + wsOnSendRegister(_rfWebSocketOnSend); + wsOnActionRegister(_rfWebSocketOnAction); + #endif + + // Register loop + espurnaRegisterLoop(rfLoop); + +} + +#endif diff --git a/espurna/rfbridge.ino b/espurna/rfbridge.ino new file mode 100755 index 0000000..47436c0 --- /dev/null +++ b/espurna/rfbridge.ino @@ -0,0 +1,534 @@ +/* + +ITEAD RF BRIDGE MODULE + +Copyright (C) 2017-2018 by Xose Pérez + +*/ + +#ifdef ITEAD_SONOFF_RFBRIDGE + +#include +#include + +// ----------------------------------------------------------------------------- +// DEFINITIONS +// ----------------------------------------------------------------------------- + +#define RF_MESSAGE_SIZE 9 +#define RF_MAX_MESSAGE_SIZE (112+4) +#define RF_CODE_START 0xAA +#define RF_CODE_ACK 0xA0 +#define RF_CODE_LEARN 0xA1 +#define RF_CODE_LEARN_KO 0xA2 +#define RF_CODE_LEARN_OK 0xA3 +#define RF_CODE_RFIN 0xA4 +#define RF_CODE_RFOUT 0xA5 +#define RF_CODE_SNIFFING_ON 0xA6 +#define RF_CODE_SNIFFING_OFF 0xA7 +#define RF_CODE_RFOUT_NEW 0xA8 +#define RF_CODE_LEARN_NEW 0xA9 +#define RF_CODE_LEARN_KO_NEW 0xAA +#define RF_CODE_LEARN_OK_NEW 0xAB +#define RF_CODE_RFOUT_BUCKET 0xB0 +#define RF_CODE_STOP 0x55 + +// ----------------------------------------------------------------------------- +// GLOBALS TO THE MODULE +// ----------------------------------------------------------------------------- + +unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0}; +unsigned char _uartpos = 0; +unsigned char _learnId = 0; +bool _learnStatus = true; +bool _rfbin = false; + +typedef struct { + byte code[RF_MESSAGE_SIZE]; + byte times; +} rfb_message_t; +static std::queue _rfb_message_queue; +Ticker _rfb_ticker; +bool _rfb_ticker_active = false; + +// ----------------------------------------------------------------------------- +// PRIVATES +// ----------------------------------------------------------------------------- + +/* + From an hexa char array ("A220EE...") to a byte array (half the size) + */ +static int _rfbToArray(const char * in, byte * out, int length = RF_MESSAGE_SIZE * 2) { + int n = strlen(in); + if (n > RF_MAX_MESSAGE_SIZE*2 || (length > 0 && n != length)) return 0; + char tmp[3] = {0,0,0}; + n /= 2; + for (unsigned char p = 0; p()); +} + +void _rfbAck() { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n")); + Serial.println(); + Serial.write(RF_CODE_START); + Serial.write(RF_CODE_ACK); + Serial.write(RF_CODE_STOP); + Serial.flush(); + Serial.println(); +} + +void _rfbLearn() { + + DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n")); + Serial.println(); + Serial.write(RF_CODE_START); + Serial.write(RF_CODE_LEARN); + Serial.write(RF_CODE_STOP); + Serial.flush(); + Serial.println(); + + #if WEB_SUPPORT + char buffer[100]; + snprintf_P(buffer, sizeof(buffer), PSTR("{\"action\": \"rfbLearn\", \"data\":{\"id\": %d, \"status\": %d}}"), _learnId, _learnStatus ? 1 : 0); + wsSend(buffer); + #endif + +} + +void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) { + for (unsigned char j=0; j 1) { + message.times = message.times - 1; + _rfb_message_queue.push(message); + } + + // if there are still messages in the queue... + if (_rfb_message_queue.empty()) { + _rfb_ticker.detach(); + _rfb_ticker_active = false; + } + +} + +void _rfbSend(byte * code, unsigned char times) { + + char buffer[RF_MESSAGE_SIZE]; + _rfbToChar(code, buffer); + DEBUG_MSG_P(PSTR("[RFBRIDGE] Enqueuing MESSAGE '%s' %d time(s)\n"), buffer, times); + + rfb_message_t message; + memcpy(message.code, code, RF_MESSAGE_SIZE); + message.times = times; + _rfb_message_queue.push(message); + + // Enable the ticker if not running + if (!_rfb_ticker_active) { + _rfb_ticker_active = true; + _rfb_ticker.attach_ms(RF_SEND_DELAY, _rfbSend); + } + +} + +#if RF_RAW_SUPPORT + +void _rfbSendRawOnce(byte *code, unsigned char length) { + char buffer[length*2]; + _rfbToChar(code, buffer, length); + DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending RAW MESSAGE '%s'\n"), buffer); + _rfbSendRaw(code, length); +} + +#endif // RF_RAW_SUPPORT + +bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) { + + if (strlen(code) != 18) return false; + + bool found = false; + String compareto = String(&code[12]); + compareto.toUpperCase(); + DEBUG_MSG_P(PSTR("[RFBRIDGE] Trying to match code %s\n"), compareto.c_str()); + + for (unsigned char i=0; i= relayCount()) { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId); + return; + } + _learnStatus = (char)payload[0] != '0'; + _rfbLearn(); + return; + + } + + bool isRFOut = t.equals(MQTT_TOPIC_RFOUT); + #if RF_RAW_SUPPORT + bool isRFRaw = !isRFOut && t.equals(MQTT_TOPIC_RFRAW); + #else + bool isRFRaw = false; + #endif + + if (isRFOut || isRFRaw) { + + // The payload may be a code in HEX format ([0-9A-Z]{18}) or + // the code comma the number of times to transmit it. + char * tok = strtok((char *) payload, ","); + + // Check if a switch is linked to that message + unsigned char id; + unsigned char status = 0; + if (_rfbMatch(tok, id, status)) { + if (status == 2) { + relayToggle(id); + } else { + relayStatus(id, status == 1); + } + return; + } + + #if RF_RAW_SUPPORT + + byte message[RF_MAX_MESSAGE_SIZE]; + int len = _rfbToArray(tok, message, 0); + if ((len > 0) && (isRFRaw || len != RF_MESSAGE_SIZE)) { + _rfbSendRawOnce(message, len); + } else { + tok = strtok(NULL, ","); + byte times = (tok != NULL) ? atoi(tok) : 1; + _rfbSend(message, times); + } + + #else // RF_RAW_SUPPORT + + byte message[RF_MESSAGE_SIZE]; + if (_rfbToArray(tok, message)) { + tok = strtok(NULL, ","); + byte times = (tok != NULL) ? atoi(tok) : 1; + _rfbSend(message, times); + } + + #endif // RF_RAW_SUPPORT + + } + + } + +} +#endif + +// ----------------------------------------------------------------------------- +// PUBLIC +// ----------------------------------------------------------------------------- + +void rfbStore(unsigned char id, bool status, const char * code) { + DEBUG_MSG_P(PSTR("[RFBRIDGE] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code); + char key[8] = {0}; + snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id); + setSetting(key, code); +} + +String rfbRetrieve(unsigned char id, bool status) { + char key[8] = {0}; + snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id); + return getSetting(key); +} + +void rfbStatus(unsigned char id, bool status) { + + String value = rfbRetrieve(id, status); + if (value.length() > 0) { + + bool same = _rfbSameOnOff(id); + + #if RF_RAW_SUPPORT + + byte message[RF_MAX_MESSAGE_SIZE]; + int len = _rfbToArray(value.c_str(), message, 0); + + if (len == RF_MESSAGE_SIZE && // probably a standard msg + (message[0] != RF_CODE_START || // raw would start with 0xAA + message[1] != RF_CODE_RFOUT_BUCKET || // followed by 0xB0, + message[2] + 4 != len || // needs a valid length, + message[len-1] != RF_CODE_STOP)) { // and finish with 0x55 + + if (!_rfbin) { + unsigned char times = same ? 1 : RF_SEND_TIMES; + _rfbSend(message, times); + } + + } else { + _rfbSendRawOnce(message, len); // send a raw message + } + + #else // RF_RAW_SUPPORT + + if (!_rfbin) { + byte message[RF_MESSAGE_SIZE]; + _rfbToArray(value.c_str(), message); + unsigned char times = same ? 1 : RF_SEND_TIMES; + _rfbSend(message, times); + } + + #endif // RF_RAW_SUPPORT + + } + + _rfbin = false; + +} + +void rfbLearn(unsigned char id, bool status) { + _learnId = id; + _learnStatus = status; + _rfbLearn(); +} + +void rfbForget(unsigned char id, bool status) { + + char key[8] = {0}; + snprintf_P(key, sizeof(key), PSTR("rfb%s%d"), status ? "ON" : "OFF", id); + delSetting(key); + + // Websocket update + #if WEB_SUPPORT + char wsb[100]; + snprintf_P(wsb, sizeof(wsb), PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0); + wsSend(wsb); + #endif + +} + +// ----------------------------------------------------------------------------- +// SETUP & LOOP +// ----------------------------------------------------------------------------- + +void rfbSetup() { + + #if MQTT_SUPPORT + mqttRegister(_rfbMqttCallback); + #endif + + #if WEB_SUPPORT + wsOnSendRegister(_rfbWebSocketOnSend); + wsOnActionRegister(_rfbWebSocketOnAction); + #endif + + // Register oop + espurnaRegisterLoop(rfbLoop); + +} + +void rfbLoop() { + _rfbReceive(); +} + +#endif diff --git a/espurna/scheduler.ino b/espurna/scheduler.ino new file mode 100755 index 0000000..f090733 --- /dev/null +++ b/espurna/scheduler.ino @@ -0,0 +1,217 @@ +/* + +SCHEDULER MODULE + +Copyright (C) 2017 by faina09 +Adapted by Xose Pérez + +*/ + +#if SCHEDULER_SUPPORT + +#include + +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _schWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "sch", 3) == 0); +} + +void _schWebSocketOnSend(JsonObject &root){ + + if (relayCount() > 0) { + + root["schVisible"] = 1; + root["maxSchedules"] = SCHEDULER_MAX_SCHEDULES; + JsonArray &sch = root.createNestedArray("schedule"); + for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) { + if (!hasSetting("schSwitch", i)) break; + JsonObject &scheduler = sch.createNestedObject(); + scheduler["schEnabled"] = getSetting("schEnabled", i, 1).toInt() == 1; + scheduler["schSwitch"] = getSetting("schSwitch", i, 0).toInt(); + scheduler["schAction"] = getSetting("schAction", i, 0).toInt(); + scheduler["schType"] = getSetting("schType", i, 0).toInt(); + scheduler["schHour"] = getSetting("schHour", i, 0).toInt(); + scheduler["schMinute"] = getSetting("schMinute", i, 0).toInt(); + scheduler["schWDs"] = getSetting("schWDs", i, ""); + } + + } + +} + +#endif // WEB_SUPPORT + +// ----------------------------------------------------------------------------- + +void _schConfigure() { + + bool delete_flag = false; + + for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) { + + int sch_switch = getSetting("schSwitch", i, 0xFF).toInt(); + if (sch_switch == 0xFF) delete_flag = true; + + if (delete_flag) { + + delSetting("schEnabled", i); + delSetting("schSwitch", i); + delSetting("schAction", i); + delSetting("schHour", i); + delSetting("schMinute", i); + delSetting("schWDs", i); + delSetting("schType", i); + + } else { + + #if DEBUG_SUPPORT + + int sch_enabled = getSetting("schEnabled", i, 1).toInt() == 1; + int sch_action = getSetting("schAction", i, 0).toInt(); + int sch_hour = getSetting("schHour", i, 0).toInt(); + int sch_minute = getSetting("schMinute", i, 0).toInt(); + String sch_weekdays = getSetting("schWDs", i, ""); + unsigned char sch_type = getSetting("schType", i, SCHEDULER_TYPE_SWITCH).toInt(); + + DEBUG_MSG_P( + PSTR("[SCH] Schedule #%d: %s #%d to %d at %02d:%02d on %s%s\n"), + i, SCHEDULER_TYPE_SWITCH == sch_type ? "switch" : "channel", sch_switch, + sch_action, sch_hour, sch_minute, (char *) sch_weekdays.c_str(), + sch_enabled ? "" : " (disabled)" + ); + + #endif // DEBUG_SUPPORT + + } + + } + +} + +bool _schIsThisWeekday(String weekdays){ + + // Convert from Sunday to Monday as day 1 + int w = weekday(now()) - 1; + if (w == 0) w = 7; + + char pch; + char * p = (char *) weekdays.c_str(); + unsigned char position = 0; + while (pch = p[position++]) { + if ((pch - '0') == w) return true; + } + return false; + +} + +int _schMinutesLeft(unsigned char schedule_hour, unsigned char schedule_minute){ + time_t t = now(); + unsigned char now_hour = hour(t); + unsigned char now_minute = minute(t); + return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute; +} + +void _schCheck() { + + // Check schedules + for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) { + + int sch_switch = getSetting("schSwitch", i, 0xFF).toInt(); + if (sch_switch == 0xFF) break; + + // Skip disabled schedules + if (getSetting("schEnabled", i, 1).toInt() == 0) continue; + + String sch_weekdays = getSetting("schWDs", i, ""); + if (_schIsThisWeekday(sch_weekdays)) { + + int sch_hour = getSetting("schHour", i, 0).toInt(); + int sch_minute = getSetting("schMinute", i, 0).toInt(); + int minutes_to_trigger = _schMinutesLeft(sch_hour, sch_minute); + + if (minutes_to_trigger == 0) { + + unsigned char sch_type = getSetting("schType", i, SCHEDULER_TYPE_SWITCH).toInt(); + + if (SCHEDULER_TYPE_SWITCH == sch_type) { + int sch_action = getSetting("schAction", i, 0).toInt(); + DEBUG_MSG_P(PSTR("[SCH] Switching switch %d to %d\n"), sch_switch, sch_action); + if (sch_action == 2) { + relayToggle(sch_switch); + } else { + relayStatus(sch_switch, sch_action); + } + } + + #if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE + if (SCHEDULER_TYPE_DIM == sch_type) { + int sch_brightness = getSetting("schAction", i, -1).toInt(); + DEBUG_MSG_P(PSTR("[SCH] Set channel %d value to %d\n"), sch_switch, sch_brightness); + lightChannel(sch_switch, sch_brightness); + lightUpdate(true, true); + } + #endif + + DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), i); + + // Show minutes to trigger every 15 minutes + // or every minute if less than 15 minutes to scheduled time. + // This only works for schedules on this same day. + // For instance, if your scheduler is set for 00:01 you will only + // get one notification before the trigger (at 00:00) + } else if (minutes_to_trigger > 0) { + + #if DEBUG_SUPPORT + if ((minutes_to_trigger % 15 == 0) || (minutes_to_trigger < 15)) { + DEBUG_MSG_P( + PSTR("[SCH] %d minutes to trigger schedule #%d\n"), + minutes_to_trigger, i + ); + } + #endif + + } + + } + + } + +} + +void _schLoop() { + + // Check time has been sync'ed + if (!ntpSynced()) return; + + // Check schedules every minute at hh:mm:00 + static unsigned long last_minute = 60; + unsigned char current_minute = minute(); + if (current_minute != last_minute) { + last_minute = current_minute; + _schCheck(); + } + +} + +// ----------------------------------------------------------------------------- + +void schSetup() { + + _schConfigure(); + + // Update websocket clients + #if WEB_SUPPORT + wsOnSendRegister(_schWebSocketOnSend); + wsOnReceiveRegister(_schWebSocketOnReceive); + wsOnAfterParseRegister(_schConfigure); + #endif + + // Register loop + espurnaRegisterLoop(_schLoop); + +} + +#endif // SCHEDULER_SUPPORT diff --git a/espurna/sensor.ino b/espurna/sensor.ino new file mode 100755 index 0000000..487c947 --- /dev/null +++ b/espurna/sensor.ino @@ -0,0 +1,1065 @@ +/* + +SENSOR MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if SENSOR_SUPPORT + +#include +#include "filters/MaxFilter.h" +#include "filters/MedianFilter.h" +#include "filters/MovingAverageFilter.h" +#include "sensors/BaseSensor.h" + +typedef struct { + BaseSensor * sensor; // Sensor object + BaseFilter * filter; // Filter object + unsigned char local; // Local index in its provider + unsigned char type; // Type of measurement + unsigned char global; // Global index in its type + double current; // Current (last) value, unfiltered + double filtered; // Filtered (averaged) value + double reported; // Last reported value + double min_change; // Minimum value change to report +} sensor_magnitude_t; + +std::vector _sensors; +std::vector _magnitudes; +bool _sensors_ready = false; + +unsigned char _counts[MAGNITUDE_MAX]; +bool _sensor_realtime = API_REAL_TIME_VALUES; +unsigned long _sensor_read_interval = 1000 * SENSOR_READ_INTERVAL; +unsigned char _sensor_report_every = SENSOR_REPORT_EVERY; +unsigned char _sensor_power_units = SENSOR_POWER_UNITS; +unsigned char _sensor_energy_units = SENSOR_ENERGY_UNITS; +unsigned char _sensor_temperature_units = SENSOR_TEMPERATURE_UNITS; +double _sensor_temperature_correction = SENSOR_TEMPERATURE_CORRECTION; +double _sensor_humidity_correction = SENSOR_HUMIDITY_CORRECTION; + +// ----------------------------------------------------------------------------- +// Private +// ----------------------------------------------------------------------------- + +unsigned char _magnitudeDecimals(unsigned char type) { + + // Hardcoded decimals (these should be linked to the unit, instead of the magnitude) + + if (type == MAGNITUDE_ENERGY || + type == MAGNITUDE_ENERGY_DELTA) { + if (_sensor_energy_units == ENERGY_KWH) return 3; + } + if (type == MAGNITUDE_POWER_ACTIVE || + type == MAGNITUDE_POWER_APPARENT || + type == MAGNITUDE_POWER_REACTIVE) { + if (_sensor_power_units == POWER_KILOWATTS) return 3; + } + if (type < MAGNITUDE_MAX) return pgm_read_byte(magnitude_decimals + type); + return 0; + +} + +double _magnitudeProcess(unsigned char type, double value) { + + // Hardcoded conversions (these should be linked to the unit, instead of the magnitude) + + if (type == MAGNITUDE_TEMPERATURE) { + if (_sensor_temperature_units == TMP_FAHRENHEIT) value = value * 1.8 + 32; + value = value + _sensor_temperature_correction; + } + + if (type == MAGNITUDE_HUMIDITY) { + value = constrain(value + _sensor_humidity_correction, 0, 100); + } + + if (type == MAGNITUDE_ENERGY || + type == MAGNITUDE_ENERGY_DELTA) { + if (_sensor_energy_units == ENERGY_KWH) value = value / 3600000; + } + if (type == MAGNITUDE_POWER_ACTIVE || + type == MAGNITUDE_POWER_APPARENT || + type == MAGNITUDE_POWER_REACTIVE) { + if (_sensor_power_units == POWER_KILOWATTS) value = value / 1000; + } + + return roundTo(value, _magnitudeDecimals(type)); + +} + +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _sensorWebSocketOnReceive(const char * key, JsonVariant& value) { + if (strncmp(key, "pwr", 3) == 0) return true; + if (strncmp(key, "sns", 3) == 0) return true; + if (strncmp(key, "tmp", 3) == 0) return true; + if (strncmp(key, "hum", 3) == 0) return true; + if (strncmp(key, "energy", 6) == 0) return true; + return false; +} + +void _sensorWebSocketSendData(JsonObject& root) { + + char buffer[10]; + bool hasTemperature = false; + bool hasHumidity = false; + + JsonArray& list = root.createNestedArray("magnitudes"); + for (unsigned char i=0; i<_magnitudes.size(); i++) { + + sensor_magnitude_t magnitude = _magnitudes[i]; + unsigned char decimals = _magnitudeDecimals(magnitude.type); + dtostrf(magnitude.current, 1-sizeof(buffer), decimals, buffer); + + JsonObject& element = list.createNestedObject(); + element["index"] = int(magnitude.global); + element["type"] = int(magnitude.type); + element["value"] = String(buffer); + element["units"] = magnitudeUnits(magnitude.type); + element["description"] = magnitude.sensor->slot(magnitude.local); + element["error"] = magnitude.sensor->error(); + + if (magnitude.type == MAGNITUDE_TEMPERATURE) hasTemperature = true; + if (magnitude.type == MAGNITUDE_HUMIDITY) hasHumidity = true; + + } + + if (hasTemperature) root["temperatureVisible"] = 1; + if (hasHumidity) root["humidityVisible"] = 1; + +} + +void _sensorWebSocketStart(JsonObject& root) { + + for (unsigned char i=0; i<_sensors.size(); i++) { + + BaseSensor * sensor = _sensors[i]; + + #if EMON_ANALOG_SUPPORT + if (sensor->getID() == SENSOR_EMON_ANALOG_ID) { + root["emonVisible"] = 1; + root["pwrVisible"] = 1; + root["pwrVoltage"] = ((EmonAnalogSensor *) sensor)->getVoltage(); + } + #endif + + #if HLW8012_SUPPORT + if (sensor->getID() == SENSOR_HLW8012_ID) { + root["hlwVisible"] = 1; + root["pwrVisible"] = 1; + } + #endif + + #if CSE7766_SUPPORT + if (sensor->getID() == SENSOR_CSE7766_ID) { + root["cseVisible"] = 1; + root["pwrVisible"] = 1; + } + #endif + + #if V9261F_SUPPORT + if (sensor->getID() == SENSOR_V9261F_ID) { + root["pwrVisible"] = 1; + } + #endif + + #if ECH1560_SUPPORT + if (sensor->getID() == SENSOR_ECH1560_ID) { + root["pwrVisible"] = 1; + } + #endif + + #if PZEM004T_SUPPORT + if (sensor->getID() == SENSOR_PZEM004T_ID) { + root["pwrVisible"] = 1; + } + #endif + + } + + if (_magnitudes.size() > 0) { + root["sensorsVisible"] = 1; + //root["apiRealTime"] = _sensor_realtime; + root["pwrUnits"] = _sensor_power_units; + root["energyUnits"] = _sensor_energy_units; + root["tmpUnits"] = _sensor_temperature_units; + root["tmpCorrection"] = _sensor_temperature_correction; + root["humCorrection"] = _sensor_humidity_correction; + root["snsRead"] = _sensor_read_interval / 1000; + root["snsReport"] = _sensor_report_every; + } + + /* + // Sensors manifest + JsonArray& manifest = root.createNestedArray("manifest"); + #if BMX280_SUPPORT + BMX280Sensor::manifest(manifest); + #endif + + // Sensors configuration + JsonArray& sensors = root.createNestedArray("sensors"); + for (unsigned char i; i<_sensors.size(); i++) { + JsonObject& sensor = sensors.createNestedObject(); + sensor["index"] = i; + sensor["id"] = _sensors[i]->getID(); + _sensors[i]->getConfig(sensor); + } + */ + +} + +void _sensorAPISetup() { + + for (unsigned char magnitude_id=0; magnitude_id<_magnitudes.size(); magnitude_id++) { + + sensor_magnitude_t magnitude = _magnitudes[magnitude_id]; + + String topic = magnitudeTopic(magnitude.type); + if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) topic = topic + "/" + String(magnitude.global); + + apiRegister(topic.c_str(), [magnitude_id](char * buffer, size_t len) { + sensor_magnitude_t magnitude = _magnitudes[magnitude_id]; + unsigned char decimals = _magnitudeDecimals(magnitude.type); + double value = _sensor_realtime ? magnitude.current : magnitude.filtered; + dtostrf(value, 1-len, decimals, buffer); + }); + + } + +} +#endif + +#if TERMINAL_SUPPORT + +void _sensorInitCommands() { + settingsRegisterCommand(F("MAGNITUDES"), [](Embedis* e) { + for (unsigned char i=0; i<_magnitudes.size(); i++) { + sensor_magnitude_t magnitude = _magnitudes[i]; + DEBUG_MSG_P(PSTR("[SENSOR] * %2d: %s @ %s (%s/%d)\n"), + i, + magnitudeTopic(magnitude.type).c_str(), + magnitude.sensor->slot(magnitude.local).c_str(), + magnitudeTopic(magnitude.type).c_str(), + magnitude.global + ); + } + DEBUG_MSG_P(PSTR("+OK\n")); + }); +} + +#endif + +void _sensorTick() { + for (unsigned char i=0; i<_sensors.size(); i++) { + _sensors[i]->tick(); + } +} + +void _sensorPre() { + for (unsigned char i=0; i<_sensors.size(); i++) { + _sensors[i]->pre(); + if (!_sensors[i]->status()) { + DEBUG_MSG_P(PSTR("[SENSOR] Error reading data from %s (error: %d)\n"), + _sensors[i]->description().c_str(), + _sensors[i]->error() + ); + } + } +} + +void _sensorPost() { + for (unsigned char i=0; i<_sensors.size(); i++) { + _sensors[i]->post(); + } +} + +// ----------------------------------------------------------------------------- +// Sensor initialization +// ----------------------------------------------------------------------------- + +void _sensorLoad() { + + /* + + This is temporal, in the future sensors will be initialized based on + soft configuration (data stored in EEPROM config) so you will be able + to define and configure new sensors on the fly + + At the time being, only enabled sensors (those with *_SUPPORT to 1) are being + loaded and initialized here. If you want to add new sensors of the same type + just duplicate the block and change the arguments for the set* methods. + Check the DHT block below for an example + + */ + + #if ANALOG_SUPPORT + { + AnalogSensor * sensor = new AnalogSensor(); + _sensors.push_back(sensor); + } + #endif + + #if BH1750_SUPPORT + { + BH1750Sensor * sensor = new BH1750Sensor(); + sensor->setAddress(BH1750_ADDRESS); + sensor->setMode(BH1750_MODE); + _sensors.push_back(sensor); + } + #endif + + #if BMX280_SUPPORT + { + BMX280Sensor * sensor = new BMX280Sensor(); + sensor->setAddress(BMX280_ADDRESS); + _sensors.push_back(sensor); + } + #endif + + #if CSE7766_SUPPORT + { + CSE7766Sensor * sensor = new CSE7766Sensor(); + sensor->setRX(CSE7766_PIN); + _sensors.push_back(sensor); + } + #endif + + #if DALLAS_SUPPORT + { + DallasSensor * sensor = new DallasSensor(); + sensor->setGPIO(DALLAS_PIN); + _sensors.push_back(sensor); + } + #endif + + #if DHT_SUPPORT + { + DHTSensor * sensor = new DHTSensor(); + sensor->setGPIO(DHT_PIN); + sensor->setType(DHT_TYPE); + _sensors.push_back(sensor); + } + #endif + + /* + // Example on how to add a second DHT sensor + // DHT2_PIN and DHT2_TYPE should be defined in sensors.h file + #if DHT_SUPPORT + { + DHTSensor * sensor = new DHTSensor(); + sensor->setGPIO(DHT2_PIN); + sensor->setType(DHT2_TYPE); + _sensors.push_back(sensor); + } + #endif + */ + + #if DIGITAL_SUPPORT + { + DigitalSensor * sensor = new DigitalSensor(); + sensor->setGPIO(DIGITAL_PIN); + sensor->setMode(DIGITAL_PIN_MODE); + sensor->setDefault(DIGITAL_DEFAULT_STATE); + _sensors.push_back(sensor); + } + #endif + + #if ECH1560_SUPPORT + { + ECH1560Sensor * sensor = new ECH1560Sensor(); + sensor->setCLK(ECH1560_CLK_PIN); + sensor->setMISO(ECH1560_MISO_PIN); + sensor->setInverted(ECH1560_INVERTED); + _sensors.push_back(sensor); + } + #endif + + #if EMON_ADC121_SUPPORT + { + EmonADC121Sensor * sensor = new EmonADC121Sensor(); + sensor->setAddress(EMON_ADC121_I2C_ADDRESS); + sensor->setVoltage(EMON_MAINS_VOLTAGE); + sensor->setReference(EMON_REFERENCE_VOLTAGE); + sensor->setCurrentRatio(0, EMON_CURRENT_RATIO); + _sensors.push_back(sensor); + } + #endif + + #if EMON_ADS1X15_SUPPORT + { + EmonADS1X15Sensor * sensor = new EmonADS1X15Sensor(); + sensor->setAddress(EMON_ADS1X15_I2C_ADDRESS); + sensor->setType(EMON_ADS1X15_TYPE); + sensor->setMask(EMON_ADS1X15_MASK); + sensor->setGain(EMON_ADS1X15_GAIN); + sensor->setVoltage(EMON_MAINS_VOLTAGE); + sensor->setCurrentRatio(0, EMON_CURRENT_RATIO); + sensor->setCurrentRatio(1, EMON_CURRENT_RATIO); + sensor->setCurrentRatio(2, EMON_CURRENT_RATIO); + sensor->setCurrentRatio(3, EMON_CURRENT_RATIO); + _sensors.push_back(sensor); + } + #endif + + #if EMON_ANALOG_SUPPORT + { + EmonAnalogSensor * sensor = new EmonAnalogSensor(); + sensor->setVoltage(EMON_MAINS_VOLTAGE); + sensor->setReference(EMON_REFERENCE_VOLTAGE); + sensor->setCurrentRatio(0, EMON_CURRENT_RATIO); + _sensors.push_back(sensor); + } + #endif + + #if EVENTS_SUPPORT + { + EventSensor * sensor = new EventSensor(); + sensor->setGPIO(EVENTS_PIN); + sensor->setMode(EVENTS_PIN_MODE); + sensor->setDebounceTime(EVENTS_DEBOUNCE); + sensor->setInterruptMode(EVENTS_INTERRUPT_MODE); + _sensors.push_back(sensor); + } + #endif + + #if HLW8012_SUPPORT + { + HLW8012Sensor * sensor = new HLW8012Sensor(); + sensor->setSEL(HLW8012_SEL_PIN); + sensor->setCF(HLW8012_CF_PIN); + sensor->setCF1(HLW8012_CF1_PIN); + sensor->setSELCurrent(HLW8012_SEL_CURRENT); + _sensors.push_back(sensor); + } + #endif + + #if MHZ19_SUPPORT + { + MHZ19Sensor * sensor = new MHZ19Sensor(); + sensor->setRX(MHZ19_RX_PIN); + sensor->setTX(MHZ19_TX_PIN); + _sensors.push_back(sensor); + } + #endif + + #if PMSX003_SUPPORT + { + PMSX003Sensor * sensor = new PMSX003Sensor(); + sensor->setRX(PMS_RX_PIN); + sensor->setTX(PMS_TX_PIN); + _sensors.push_back(sensor); + } + #endif + + #if PZEM004T_SUPPORT + { + PZEM004TSensor * sensor = new PZEM004TSensor(); + #if PZEM004T_USE_SOFT + sensor->setRX(PZEM004T_RX_PIN); + sensor->setTX(PZEM004T_TX_PIN); + #else + sensor->setSerial(& PZEM004T_HW_PORT); + #endif + _sensors.push_back(sensor); + } + #endif + + #if SHT3X_I2C_SUPPORT + { + SHT3XI2CSensor * sensor = new SHT3XI2CSensor(); + sensor->setAddress(SHT3X_I2C_ADDRESS); + _sensors.push_back(sensor); + } + #endif + + #if SI7021_SUPPORT + { + SI7021Sensor * sensor = new SI7021Sensor(); + sensor->setAddress(SI7021_ADDRESS); + _sensors.push_back(sensor); + } + #endif + + #if V9261F_SUPPORT + { + V9261FSensor * sensor = new V9261FSensor(); + sensor->setRX(V9261F_PIN); + sensor->setInverted(V9261F_PIN_INVERSE); + _sensors.push_back(sensor); + } + #endif + + #if AM2320_SUPPORT + { + AM2320Sensor * sensor = new AM2320Sensor(); + sensor->setAddress(AM2320_ADDRESS); + _sensors.push_back(sensor); + } + #endif + + #if GUVAS12SD_SUPPORT + { + GUVAS12SDSensor * sensor = new GUVAS12SDSensor(); + sensor->setGPIO(GUVAS12SD_PIN); + _sensors.push_back(sensor); + } + #endif + +} + +void _sensorCallback(unsigned char i, unsigned char type, const char * payload) { + DEBUG_MSG_P(PSTR("[SENSOR] Sensor #%u callback, type %u, payload: '%s'\n"), i, type, payload); +} + +void _sensorInit() { + + _sensors_ready = true; + + for (unsigned char i=0; i<_sensors.size(); i++) { + + // Do not process an already initialized sensor + if (_sensors[i]->ready()) continue; + DEBUG_MSG_P(PSTR("[SENSOR] Initializing %s\n"), _sensors[i]->description().c_str()); + + // Force sensor to reload config + _sensors[i]->begin(); + if (!_sensors[i]->ready()) { + if (_sensors[i]->error() != 0) DEBUG_MSG_P(PSTR("[SENSOR] -> ERROR %d\n"), _sensors[i]->error()); + _sensors_ready = false; + continue; + } + + // Initialize magnitudes + for (unsigned char k=0; k<_sensors[i]->count(); k++) { + + unsigned char type = _sensors[i]->type(k); + + sensor_magnitude_t new_magnitude; + new_magnitude.sensor = _sensors[i]; + new_magnitude.local = k; + new_magnitude.type = type; + new_magnitude.global = _counts[type]; + new_magnitude.current = 0; + new_magnitude.filtered = 0; + new_magnitude.reported = 0; + new_magnitude.min_change = 0; + if (type == MAGNITUDE_DIGITAL) { + new_magnitude.filter = new MaxFilter(); + } else if (type == MAGNITUDE_EVENTS) { + new_magnitude.filter = new MovingAverageFilter(); + } else { + new_magnitude.filter = new MedianFilter(); + } + new_magnitude.filter->resize(_sensor_report_every); + _magnitudes.push_back(new_magnitude); + + DEBUG_MSG_P(PSTR("[SENSOR] -> %s:%d\n"), magnitudeTopic(type).c_str(), _counts[type]); + + _counts[type] = _counts[type] + 1; + + } + + // Hook callback + _sensors[i]->onEvent([i](unsigned char type, const char * payload) { + _sensorCallback(i, type, payload); + }); + + // Custom initializations + + #if EMON_ANALOG_SUPPORT + + if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) { + EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i]; + sensor->setCurrentRatio(0, getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat()); + sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt()); + } + + #endif // EMON_ANALOG_SUPPORT + + #if HLW8012_SUPPORT + + if (_sensors[i]->getID() == SENSOR_HLW8012_ID) { + + HLW8012Sensor * sensor = (HLW8012Sensor *) _sensors[i]; + + double value; + + value = getSetting("pwrRatioC", 0).toFloat(); + if (value > 0) sensor->setCurrentRatio(value); + + value = getSetting("pwrRatioV", 0).toFloat(); + if (value > 0) sensor->setVoltageRatio(value); + + value = getSetting("pwrRatioP", 0).toFloat(); + if (value > 0) sensor->setPowerRatio(value); + + } + + #endif // HLW8012_SUPPORT + + #if CSE7766_SUPPORT + + if (_sensors[i]->getID() == SENSOR_CSE7766_ID) { + + CSE7766Sensor * sensor = (CSE7766Sensor *) _sensors[i]; + + double value; + + value = getSetting("pwrRatioC", 0).toFloat(); + if (value > 0) sensor->setCurrentRatio(value); + + value = getSetting("pwrRatioV", 0).toFloat(); + if (value > 0) sensor->setVoltageRatio(value); + + value = getSetting("pwrRatioP", 0).toFloat(); + if (value > 0) sensor->setPowerRatio(value); + + } + + #endif // CSE7766_SUPPORT + + } + +} + +void _sensorConfigure() { + + // General sensor settings + _sensor_read_interval = 1000 * constrain(getSetting("snsRead", SENSOR_READ_INTERVAL).toInt(), SENSOR_READ_MIN_INTERVAL, SENSOR_READ_MAX_INTERVAL); + _sensor_report_every = constrain(getSetting("snsReport", SENSOR_REPORT_EVERY).toInt(), SENSOR_REPORT_MIN_EVERY, SENSOR_REPORT_MAX_EVERY); + _sensor_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1; + _sensor_power_units = getSetting("pwrUnits", SENSOR_POWER_UNITS).toInt(); + _sensor_energy_units = getSetting("energyUnits", SENSOR_ENERGY_UNITS).toInt(); + _sensor_temperature_units = getSetting("tmpUnits", SENSOR_TEMPERATURE_UNITS).toInt(); + _sensor_temperature_correction = getSetting("tmpCorrection", SENSOR_TEMPERATURE_CORRECTION).toFloat(); + _sensor_humidity_correction = getSetting("humCorrection", SENSOR_HUMIDITY_CORRECTION).toFloat(); + + // Specific sensor settings + for (unsigned char i=0; i<_sensors.size(); i++) { + + #if EMON_ANALOG_SUPPORT + + if (_sensors[i]->getID() == SENSOR_EMON_ANALOG_ID) { + + double value; + EmonAnalogSensor * sensor = (EmonAnalogSensor *) _sensors[i]; + + if (value = getSetting("pwrExpectedP", 0).toInt()) { + sensor->expectedPower(0, value); + setSetting("pwrRatioC", sensor->getCurrentRatio(0)); + } + + if (getSetting("pwrResetCalibration", 0).toInt() == 1) { + sensor->setCurrentRatio(0, EMON_CURRENT_RATIO); + delSetting("pwrRatioC"); + } + + if (getSetting("pwrResetE", 0).toInt() == 1) { + sensor->resetEnergy(); + } + + sensor->setVoltage(getSetting("pwrVoltage", EMON_MAINS_VOLTAGE).toInt()); + + } + + #endif // EMON_ANALOG_SUPPORT + + #if EMON_ADC121_SUPPORT + if (_sensors[i]->getID() == SENSOR_EMON_ADC121_ID) { + EmonADC121Sensor * sensor = (EmonADC121Sensor *) _sensors[i]; + if (getSetting("pwrResetE", 0).toInt() == 1) { + sensor->resetEnergy(); + } + } + #endif + + #if EMON_ADS1X15_SUPPORT + if (_sensors[i]->getID() == SENSOR_EMON_ADS1X15_ID) { + EmonADS1X15Sensor * sensor = (EmonADS1X15Sensor *) _sensors[i]; + if (getSetting("pwrResetE", 0).toInt() == 1) { + sensor->resetEnergy(); + } + } + #endif + + #if HLW8012_SUPPORT + + + if (_sensors[i]->getID() == SENSOR_HLW8012_ID) { + + double value; + HLW8012Sensor * sensor = (HLW8012Sensor *) _sensors[i]; + + if (value = getSetting("pwrExpectedC", 0).toFloat()) { + sensor->expectedCurrent(value); + setSetting("pwrRatioC", sensor->getCurrentRatio()); + } + + if (value = getSetting("pwrExpectedV", 0).toInt()) { + sensor->expectedVoltage(value); + setSetting("pwrRatioV", sensor->getVoltageRatio()); + } + + if (value = getSetting("pwrExpectedP", 0).toInt()) { + sensor->expectedPower(value); + setSetting("pwrRatioP", sensor->getPowerRatio()); + } + + if (getSetting("pwrResetE", 0).toInt() == 1) { + sensor->resetEnergy(); + } + + if (getSetting("pwrResetCalibration", 0).toInt() == 1) { + sensor->resetRatios(); + delSetting("pwrRatioC"); + delSetting("pwrRatioV"); + delSetting("pwrRatioP"); + } + + } + + #endif // HLW8012_SUPPORT + + #if CSE7766_SUPPORT + + if (_sensors[i]->getID() == SENSOR_CSE7766_ID) { + + double value; + CSE7766Sensor * sensor = (CSE7766Sensor *) _sensors[i]; + + if (value = getSetting("pwrExpectedC", 0).toFloat()) { + sensor->expectedCurrent(value); + setSetting("pwrRatioC", sensor->getCurrentRatio()); + } + + if (value = getSetting("pwrExpectedV", 0).toInt()) { + sensor->expectedVoltage(value); + setSetting("pwrRatioV", sensor->getVoltageRatio()); + } + + if (value = getSetting("pwrExpectedP", 0).toInt()) { + sensor->expectedPower(value); + setSetting("pwrRatioP", sensor->getPowerRatio()); + } + + if (getSetting("pwrResetE", 0).toInt() == 1) { + sensor->resetEnergy(); + } + + if (getSetting("pwrResetCalibration", 0).toInt() == 1) { + sensor->resetRatios(); + delSetting("pwrRatioC"); + delSetting("pwrRatioV"); + delSetting("pwrRatioP"); + } + + } + + #endif // CSE7766_SUPPORT + + } + + // Update filter sizes + for (unsigned char i=0; i<_magnitudes.size(); i++) { + _magnitudes[i].filter->resize(_sensor_report_every); + } + + // Save settings + delSetting("pwrExpectedP"); + delSetting("pwrExpectedC"); + delSetting("pwrExpectedV"); + delSetting("pwrResetCalibration"); + delSetting("pwrResetE"); + saveSettings(); + +} + +// ----------------------------------------------------------------------------- +// Public +// ----------------------------------------------------------------------------- + +unsigned char sensorCount() { + return _sensors.size(); +} + +unsigned char magnitudeCount() { + return _magnitudes.size(); +} + +String magnitudeName(unsigned char index) { + if (index < _magnitudes.size()) { + sensor_magnitude_t magnitude = _magnitudes[index]; + return magnitude.sensor->slot(magnitude.local); + } + return String(); +} + +unsigned char magnitudeType(unsigned char index) { + if (index < _magnitudes.size()) { + return int(_magnitudes[index].type); + } + return MAGNITUDE_NONE; +} + +unsigned char magnitudeIndex(unsigned char index) { + if (index < _magnitudes.size()) { + return int(_magnitudes[index].global); + } + return 0; +} + +String magnitudeTopic(unsigned char type) { + char buffer[16] = {0}; + if (type < MAGNITUDE_MAX) strncpy_P(buffer, magnitude_topics[type], sizeof(buffer)); + return String(buffer); +} + +String magnitudeTopicIndex(unsigned char index) { + char topic[32] = {0}; + if (index < _magnitudes.size()) { + sensor_magnitude_t magnitude = _magnitudes[index]; + if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) { + snprintf(topic, sizeof(topic), "%s/%u", magnitudeTopic(magnitude.type).c_str(), magnitude.global); + } else { + snprintf(topic, sizeof(topic), "%s", magnitudeTopic(magnitude.type).c_str()); + } + } + return String(topic); +} + + +String magnitudeUnits(unsigned char type) { + char buffer[8] = {0}; + if (type < MAGNITUDE_MAX) { + if ((type == MAGNITUDE_TEMPERATURE) && (_sensor_temperature_units == TMP_FAHRENHEIT)) { + strncpy_P(buffer, magnitude_fahrenheit, sizeof(buffer)); + } else if ( + (type == MAGNITUDE_ENERGY || type == MAGNITUDE_ENERGY_DELTA) && + (_sensor_energy_units == ENERGY_KWH)) { + strncpy_P(buffer, magnitude_kwh, sizeof(buffer)); + } else if ( + (type == MAGNITUDE_POWER_ACTIVE || type == MAGNITUDE_POWER_APPARENT || type == MAGNITUDE_POWER_REACTIVE) && + (_sensor_power_units == POWER_KILOWATTS)) { + strncpy_P(buffer, magnitude_kw, sizeof(buffer)); + } else { + strncpy_P(buffer, magnitude_units[type], sizeof(buffer)); + } + } + return String(buffer); +} + +// ----------------------------------------------------------------------------- + +void sensorSetup() { + + // Backwards compatibility + moveSetting("powerUnits", "pwrUnits"); + + // Load sensors + _sensorLoad(); + _sensorInit(); + + // Configure stored values + _sensorConfigure(); + + #if WEB_SUPPORT + + // Websockets + wsOnSendRegister(_sensorWebSocketStart); + wsOnReceiveRegister(_sensorWebSocketOnReceive); + wsOnSendRegister(_sensorWebSocketSendData); + wsOnAfterParseRegister(_sensorConfigure); + + // API + _sensorAPISetup(); + + #endif + + #if TERMINAL_SUPPORT + _sensorInitCommands(); + #endif + + // Register loop + espurnaRegisterLoop(sensorLoop); + +} + +void sensorLoop() { + + // Check if we still have uninitialized sensors + static unsigned long last_init = 0; + if (!_sensors_ready) { + if (millis() - last_init > SENSOR_INIT_INTERVAL) { + last_init = millis(); + _sensorInit(); + } + } + + if (_magnitudes.size() == 0) return; + + // Tick hook + _sensorTick(); + + // Check if we should read new data + static unsigned long last_update = 0; + static unsigned long report_count = 0; + if (millis() - last_update > _sensor_read_interval) { + + last_update = millis(); + report_count = (report_count + 1) % _sensor_report_every; + + double current; + double filtered; + char buffer[64]; + + // Pre-read hook + _sensorPre(); + + // Get the first relay state + #if SENSOR_POWER_CHECK_STATUS + bool relay_off = (relayCount() > 0) && (relayStatus(0) == 0); + #endif + + // Get readings + for (unsigned char i=0; i<_magnitudes.size(); i++) { + + sensor_magnitude_t magnitude = _magnitudes[i]; + + if (magnitude.sensor->status()) { + + current = magnitude.sensor->value(magnitude.local); + + // Completely remove spurious values if relay is OFF + #if SENSOR_POWER_CHECK_STATUS + if (relay_off) { + if (magnitude.type == MAGNITUDE_POWER_ACTIVE || + magnitude.type == MAGNITUDE_POWER_REACTIVE || + magnitude.type == MAGNITUDE_POWER_APPARENT || + magnitude.type == MAGNITUDE_CURRENT || + magnitude.type == MAGNITUDE_ENERGY_DELTA + ) { + current = 0; + } + } + #endif + + magnitude.filter->add(current); + + // Special case + if (magnitude.type == MAGNITUDE_EVENTS) { + current = magnitude.filter->result(); + } + + current = _magnitudeProcess(magnitude.type, current); + _magnitudes[i].current = current; + + unsigned char decimals = _magnitudeDecimals(magnitude.type); + + // Debug + #if SENSOR_DEBUG + { + dtostrf(current, 1-sizeof(buffer), decimals, buffer); + DEBUG_MSG_P(PSTR("[SENSOR] %s - %s: %s%s\n"), + magnitude.sensor->slot(magnitude.local).c_str(), + magnitudeTopic(magnitude.type).c_str(), + buffer, + magnitudeUnits(magnitude.type).c_str() + ); + } + #endif // SENSOR_DEBUG + + // Time to report (we do it every _sensor_report_every readings) + if (report_count == 0) { + + filtered = magnitude.filter->result(); + magnitude.filter->reset(); + filtered = _magnitudeProcess(magnitude.type, filtered); + _magnitudes[i].filtered = filtered; + + // Check if there is a minimum change threshold to report + if (fabs(filtered - magnitude.reported) >= magnitude.min_change) { + + _magnitudes[i].reported = filtered; + dtostrf(filtered, 1-sizeof(buffer), decimals, buffer); + + #if BROKER_SUPPORT + brokerPublish(magnitudeTopic(magnitude.type).c_str(), magnitude.local, buffer); + #endif + + #if MQTT_SUPPORT + + mqttSend(magnitudeTopicIndex(i).c_str(), buffer); + + #if SENSOR_PUBLISH_ADDRESSES + char topic[32]; + snprintf(topic, sizeof(topic), "%s/%s", SENSOR_ADDRESS_TOPIC, magnitudeTopic(magnitude.type).c_str()); + if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) { + mqttSend(topic, magnitude.global, magnitude.sensor->address(magnitude.local).c_str()); + } else { + mqttSend(topic, magnitude.sensor->address(magnitude.local).c_str()); + } + #endif // SENSOR_PUBLISH_ADDRESSES + + #endif // MQTT_SUPPORT + + #if INFLUXDB_SUPPORT + if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) { + idbSend(magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer); + } else { + idbSend(magnitudeTopic(magnitude.type).c_str(), buffer); + } + #endif // INFLUXDB_SUPPORT + + #if THINGSPEAK_SUPPORT + tspkEnqueueMeasurement(i, buffer); + #endif + + #if DOMOTICZ_SUPPORT + { + char key[15]; + snprintf_P(key, sizeof(key), PSTR("dczMagnitude%d"), i); + if (magnitude.type == MAGNITUDE_HUMIDITY) { + int status; + if (filtered > 70) { + status = HUMIDITY_WET; + } else if (filtered > 45) { + status = HUMIDITY_COMFORTABLE; + } else if (filtered > 30) { + status = HUMIDITY_NORMAL; + } else { + status = HUMIDITY_DRY; + } + char status_buf[5]; + itoa(status, status_buf, 10); + domoticzSend(key, buffer, status_buf); + } else { + domoticzSend(key, 0, buffer); + } + } + #endif // DOMOTICZ_SUPPORT + + } // if (fabs(filtered - magnitude.reported) >= magnitude.min_change) + } // if (report_count == 0) + } // if (magnitude.sensor->status()) + } // for (unsigned char i=0; i<_magnitudes.size(); i++) + + // Post-read hook + _sensorPost(); + + #if WEB_SUPPORT + wsSend(_sensorWebSocketSendData); + #endif + + #if THINGSPEAK_SUPPORT + if (report_count == 0) tspkFlush(); + #endif + + } + +} + +#endif // SENSOR_SUPPORT diff --git a/espurna/sensors/AM2320Sensor.h b/espurna/sensors/AM2320Sensor.h new file mode 100755 index 0000000..be97de1 --- /dev/null +++ b/espurna/sensors/AM2320Sensor.h @@ -0,0 +1,193 @@ +// ----------------------------------------------------------------------------- +// AM2320 Humidity & Temperature sensor over I2C +// Copyright (C) 2018 by Mustafa Tufan +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && AM2320_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "I2CSensor.h" + +// https://akizukidenshi.com/download/ds/aosong/AM2320.pdf +#define AM2320_I2C_READ_REGISTER_DATA 0x03 // Read one or more data registers +#define AM2320_I2C_WRITE_MULTIPLE_REGISTERS 0x10 // Multiple sets of binary data to write multiple registers +/* +Register | Address | Register | Address | Register | Address | Register | Address +-----------------+---------+--------------------+---------+-------------------------+---------+-----------+-------- +High humidity | 0x00 | Model High | 0x08 | Users register a high | 0x10 | Retention | 0x18 +Low humidity | 0x01 | Model Low | 0x09 | Users register a low | 0x11 | Retention | 0x19 +High temperature | 0x02 | The version number | 0x0A | Users register 2 high | 0x12 | Retention | 0x1A +Low temperature | 0x03 | Device ID(24-31)Bit| 0x0B | Users register 2 low | 0x13 | Retention | 0x1B +Retention | 0x04 | Device ID(24-31)Bit| 0x0C | Retention | 0x14 | Retention | 0x1C +Retention | 0x05 | Device ID(24-31)Bit| 0x0D | Retention | 0x15 | Retention | 0x1D +Retention | 0x06 | Device ID(24-31)Bit| 0x0E | Retention | 0x16 | Retention | 0x1E +Retention | 0x07 | Status Register | 0x0F | Retention | 0x17 | Retention | 0x1F +*/ + +class AM2320Sensor : public I2CSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + AM2320Sensor(): I2CSensor() { + _count = 2; + _sensor_id = SENSOR_AM2320_ID; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + if (!_dirty) return; + + // I2C auto-discover + unsigned char addresses[] = {0x23, 0x5C, 0xB8}; + _address = _begin_i2c(_address, sizeof(addresses), addresses); + if (_address == 0) return; + + _ready = true; + _dirty = false; + } + + // Descriptive name of the sensor + String description() { + char buffer[25]; + snprintf(buffer, sizeof(buffer), "AM2320 @ I2C (0x%02X)", _address); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_TEMPERATURE; + if (index == 1) return MAGNITUDE_HUMIDITY; + return MAGNITUDE_NONE; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + _error = SENSOR_ERROR_OK; + _read(); + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _temperature; + if (index == 1) return _humidity; + return 0; + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + +/* + // Get device model, version, device_id + + void _init() { + i2c_wakeup(); + delayMicroseconds(800); + + unsigned char _buffer[11]; + + // 0x08 = read address + // 7 = number of bytes to read + if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x08, 7) != I2C_TRANS_SUCCESS) { + _error = SENSOR_ERROR_TIMEOUT; + return false; + } + + uint16_t model = (_buffer[2] << 8) | _buffer[3]; + uint8_t version = _buffer[4]; + uint32_t device_id = _buffer[8] << 24 | _buffer[7] << 16 | _buffer[6] << 8 | _buffer[5]; + } +*/ + + void _read() { + i2c_wakeup(); + // waiting time of at least 800 μs, the maximum 3000 μs + delayMicroseconds(800); // just to be on safe side + + // 0x00 = read address + // 4 = number of bytes to read + if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x00, 4) != I2C_TRANS_SUCCESS) { + _error = SENSOR_ERROR_TIMEOUT; + return false; + } + + unsigned char _buffer[8]; + + // waiting time of at least 800 μs, the maximum 3000 μs + delayMicroseconds(800 + ((3000-800)/2) ); + i2c_read_buffer(_address, _buffer, 8); + + // Humidity : 01F4 = (1×256)+(F×16)+4 = 500 => humidity = 500÷10 = 50.0 % + // 0339 = (3×256)+(3×16)+9 = 825 => humidity = 825÷10 = 82.5 % + // Temperature: 00FA = (F×16)+A = 250 => temperature = 250÷10 = 25.0 C + // 0115 = (1×256)+(1×16)+5 = 277 => temperature = 277÷10 = 27.7 C + // Temperature resolution is 16Bit, temperature highest bit (Bit 15) is equal to 1 indicates a negative temperature + + // _buffer 0 = function code + // _buffer 1 = number of bytes + // _buffer 2-3 = high/low humidity + // _buffer 4-5 = high/low temperature + // _buffer 6-7 = CRC low/high + + unsigned int responseCRC = 0; + responseCRC = ((responseCRC | _buffer[7]) << 8 | _buffer[6]); + + if (responseCRC == _CRC16(_buffer)) { + int foo = (_buffer[2] << 8) | _buffer[3]; + _humidity = foo / 10.0; + + foo = ((_buffer[4] & 0x7F) << 8) | _buffer[5]; // clean bit 15 and merge + _temperature = foo / 10.0; + + if (_buffer[4] & 0x80) { // is bit 15 == 1 + _temperature = _temperature * -1; // negative temperature + } + + _error = SENSOR_ERROR_OK; + } else { + _error = SENSOR_ERROR_CRC; + return; + } + } + + unsigned int _CRC16(unsigned char buffer[]) { + unsigned int crc16 = 0xFFFF; + + for (unsigned int i = 0; i < 6; i++) { + crc16 ^= buffer[i]; + + for (unsigned int b = 8; b != 0; b--) { + if (crc16 & 0x01) { // is lsb set + crc16 >>= 1; + crc16 ^= 0xA001; + } else { + crc16 >>= 1; + } + } + } + + return crc16; + } + + double _temperature = 0; + double _humidity = 0; +}; + +#endif // SENSOR_SUPPORT && AM2320_SUPPORT diff --git a/espurna/sensors/AnalogSensor.h b/espurna/sensors/AnalogSensor.h new file mode 100755 index 0000000..920538c --- /dev/null +++ b/espurna/sensors/AnalogSensor.h @@ -0,0 +1,66 @@ +// ----------------------------------------------------------------------------- +// Analog Sensor (maps to an analogRead) +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && ANALOG_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +class AnalogSensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + AnalogSensor(): BaseSensor() { + _count = 1; + _sensor_id = SENSOR_ANALOG_ID; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + pinMode(0, INPUT); + _ready = true; + } + + // Descriptive name of the sensor + String description() { + return String("ANALOG @ TOUT"); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return String("0"); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_ANALOG; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return analogRead(0); + return 0; + } + + +}; + +#endif // SENSOR_SUPPORT && ANALOG_SUPPORT diff --git a/espurna/sensors/BH1750Sensor.h b/espurna/sensors/BH1750Sensor.h new file mode 100755 index 0000000..9cc2b87 --- /dev/null +++ b/espurna/sensors/BH1750Sensor.h @@ -0,0 +1,135 @@ +// ----------------------------------------------------------------------------- +// BH1750 Liminosity sensor over I2C +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && BH1750_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "I2CSensor.h" + +#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1lx resolution. Measurement time is approx 120ms. +#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 0x11 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms. +#define BH1750_CONTINUOUS_LOW_RES_MODE 0x13 // Start measurement at 4lx resolution. Measurement time is approx 16ms. +#define BH1750_ONE_TIME_HIGH_RES_MODE 0x20 // Start measurement at 1lx resolution. Measurement time is approx 120ms. + // Device is automatically set to Power Down after measurement. +#define BH1750_ONE_TIME_HIGH_RES_MODE_2 0x21 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms. + // Device is automatically set to Power Down after measurement. +#define BH1750_ONE_TIME_LOW_RES_MODE 0x23 // Start measurement at 1lx resolution. Measurement time is approx 120ms. + // Device is automatically set to Power Down after measurement. + +class BH1750Sensor : public I2CSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + BH1750Sensor(): I2CSensor() { + _sensor_id = SENSOR_BH1750_ID; + _count = 1; + } + + // --------------------------------------------------------------------- + + void setMode(unsigned char mode) { + if (_mode == mode) return; + _mode = mode; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getMode() { + return _mode; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + // I2C auto-discover + unsigned char addresses[] = {0x23, 0x5C}; + _address = _begin_i2c(_address, sizeof(addresses), addresses); + if (_address == 0) return; + + // Run configuration on next update + _run_configure = true; + _ready = true; + _dirty = false; + + } + + // Descriptive name of the sensor + String description() { + char buffer[25]; + snprintf(buffer, sizeof(buffer), "BH1750 @ I2C (0x%02X)", _address); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_LUX; + return MAGNITUDE_NONE; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + _error = SENSOR_ERROR_OK; + _lux = _read(); + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _lux; + return 0; + } + + protected: + + double _read() { + + // For one-shot modes reconfigure sensor & wait for conversion + if (_run_configure) { + + // Configure mode + i2c_write_uint8(_address, _mode); + + // According to datasheet + // conversion time is ~16ms for low resolution + // and ~120 for high resolution + // but more time is needed + unsigned long wait = (_mode & 0x02) ? 24 : 180; + unsigned long start = millis(); + while (millis() - start < wait) delay(1); + + // Keep on running configure each time if one-shot mode + _run_configure = _mode & 0x20; + + } + + double level = (double) i2c_read_uint16(_address); + if (level == 0xFFFF) { + _error = SENSOR_ERROR_CRC; + _run_configure = true; + return 0; + } + return level / 1.2; + + } + + unsigned char _mode; + bool _run_configure = false; + double _lux = 0; + +}; + +#endif // SENSOR_SUPPORT && BH1750_SUPPORT diff --git a/espurna/sensors/BMX280Sensor.h b/espurna/sensors/BMX280Sensor.h new file mode 100755 index 0000000..68914be --- /dev/null +++ b/espurna/sensors/BMX280Sensor.h @@ -0,0 +1,438 @@ +// ----------------------------------------------------------------------------- +// BME280/BMP280 Sensor over I2C +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && BMX280_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "I2CSensor.h" + +#define BMX280_CHIP_BMP280 0x58 +#define BMX280_CHIP_BME280 0x60 + +#define BMX280_REGISTER_DIG_T1 0x88 +#define BMX280_REGISTER_DIG_T2 0x8A +#define BMX280_REGISTER_DIG_T3 0x8C + +#define BMX280_REGISTER_DIG_P1 0x8E +#define BMX280_REGISTER_DIG_P2 0x90 +#define BMX280_REGISTER_DIG_P3 0x92 +#define BMX280_REGISTER_DIG_P4 0x94 +#define BMX280_REGISTER_DIG_P5 0x96 +#define BMX280_REGISTER_DIG_P6 0x98 +#define BMX280_REGISTER_DIG_P7 0x9A +#define BMX280_REGISTER_DIG_P8 0x9C +#define BMX280_REGISTER_DIG_P9 0x9E + +#define BMX280_REGISTER_DIG_H1 0xA1 +#define BMX280_REGISTER_DIG_H2 0xE1 +#define BMX280_REGISTER_DIG_H3 0xE3 +#define BMX280_REGISTER_DIG_H4 0xE4 +#define BMX280_REGISTER_DIG_H5 0xE5 +#define BMX280_REGISTER_DIG_H6 0xE7 + +#define BMX280_REGISTER_CHIPID 0xD0 +#define BMX280_REGISTER_VERSION 0xD1 +#define BMX280_REGISTER_SOFTRESET 0xE0 + +#define BMX280_REGISTER_CAL26 0xE1 + +#define BMX280_REGISTER_CONTROLHUMID 0xF2 +#define BMX280_REGISTER_CONTROL 0xF4 +#define BMX280_REGISTER_CONFIG 0xF5 +#define BMX280_REGISTER_PRESSUREDATA 0xF7 +#define BMX280_REGISTER_TEMPDATA 0xFA +#define BMX280_REGISTER_HUMIDDATA 0xFD + +class BMX280Sensor : public I2CSensor { + + public: + + static unsigned char addresses[2]; + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + BMX280Sensor(): I2CSensor() { + _sensor_id = SENSOR_BMX280_ID; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + if (!_dirty) return; + _init(); + _dirty = !_ready; + } + + // Descriptive name of the sensor + String description() { + char buffer[20]; + snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", _chip == BMX280_CHIP_BME280 ? "BME280" : "BMP280", _address); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + unsigned char i = 0; + #if BMX280_TEMPERATURE > 0 + if (index == i++) return MAGNITUDE_TEMPERATURE; + #endif + #if BMX280_PRESSURE > 0 + if (index == i++) return MAGNITUDE_PRESSURE; + #endif + #if BMX280_HUMIDITY > 0 + if (_chip == BMX280_CHIP_BME280) { + if (index == i) return MAGNITUDE_HUMIDITY; + } + #endif + return MAGNITUDE_NONE; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + virtual void pre() { + + if (_run_init) { + i2cClearBus(); + _init(); + } + + if (_chip == 0) { + _error = SENSOR_ERROR_UNKNOWN_ID; + return; + } + _error = SENSOR_ERROR_OK; + + #if BMX280_MODE == 1 + _forceRead(); + #endif + + _error = _read(); + + if (_error != SENSOR_ERROR_OK) { + _run_init = true; + } + + } + + // Current value for slot # index + double value(unsigned char index) { + unsigned char i = 0; + #if BMX280_TEMPERATURE > 0 + if (index == i++) return _temperature; + #endif + #if BMX280_PRESSURE > 0 + if (index == i++) return _pressure / 100; + #endif + #if BMX280_HUMIDITY > 0 + if (_chip == BMX280_CHIP_BME280) { + if (index == i) return _humidity; + } + #endif + return 0; + } + + // Load the configuration manifest + static void manifest(JsonArray& sensors) { + + char buffer[10]; + + JsonObject& sensor = sensors.createNestedObject(); + sensor["sensor_id"] = SENSOR_BMX280_ID; + JsonArray& fields = sensor.createNestedArray("fields"); + + { + JsonObject& field = fields.createNestedObject(); + field["tag"] = UI_TAG_SELECT; + field["name"] = "address"; + field["label"] = "Address"; + JsonArray& options = field.createNestedArray("options"); + { + JsonObject& option = options.createNestedObject(); + option["name"] = "auto"; + option["value"] = 0; + } + for (unsigned char i=0; i< sizeof(BMX280Sensor::addresses); i++) { + JsonObject& option = options.createNestedObject(); + snprintf(buffer, sizeof(buffer), "0x%02X", BMX280Sensor::addresses[i]); + option["name"] = String(buffer); + option["value"] = BMX280Sensor::addresses[i]; + } + } + + }; + + void getConfig(JsonObject& root) { + root["sensor_id"] = _sensor_id; + root["address"] = _address; + }; + + void setConfig(JsonObject& root) { + if (root.containsKey("address")) setAddress(root["address"]); + }; + + protected: + + void _init() { + + // Make sure sensor had enough time to turn on. BMX280 requires 2ms to start up + nice_delay(10); + + // No chip ID by default + _chip = 0; + + // I2C auto-discover + _address = _begin_i2c(_address, sizeof(BMX280Sensor::addresses), BMX280Sensor::addresses); + if (_address == 0) return; + + // Check sensor correctly initialized + _chip = i2c_read_uint8(_address, BMX280_REGISTER_CHIPID); + if ((_chip != BMX280_CHIP_BME280) && (_chip != BMX280_CHIP_BMP280)) { + + _chip = 0; + i2cReleaseLock(_address); + _previous_address = 0; + _error = SENSOR_ERROR_UNKNOWN_ID; + + // Setting _address to 0 forces auto-discover + // This might be necessary at this stage if there is a + // different sensor in the hardcoded address + _address = 0; + + return; + + } + + _count = 0; + #if BMX280_TEMPERATURE > 0 + ++_count; + #endif + #if BMX280_PRESSURE > 0 + ++_count; + #endif + #if BMX280_HUMIDITY > 0 + if (_chip == BMX280_CHIP_BME280) ++_count; + #endif + + _readCoefficients(); + + unsigned char data = 0; + i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data); + + data = (BMX280_STANDBY << 0x5) & 0xE0; + data |= (BMX280_FILTER << 0x02) & 0x1C; + i2c_write_uint8(_address, BMX280_REGISTER_CONFIG, data); + + data = (BMX280_HUMIDITY) & 0x07; + i2c_write_uint8(_address, BMX280_REGISTER_CONTROLHUMID, data); + + data = (BMX280_TEMPERATURE << 5) & 0xE0; + data |= (BMX280_PRESSURE << 2) & 0x1C; + data |= (BMX280_MODE) & 0x03; + i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, data); + + _measurement_delay = _measurementTime(); + _run_init = false; + _ready = true; + + } + + void _readCoefficients() { + _bmx280_calib.dig_T1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_T1); + _bmx280_calib.dig_T2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T2); + _bmx280_calib.dig_T3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_T3); + + _bmx280_calib.dig_P1 = i2c_read_uint16_le(_address, BMX280_REGISTER_DIG_P1); + _bmx280_calib.dig_P2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P2); + _bmx280_calib.dig_P3 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P3); + _bmx280_calib.dig_P4 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P4); + _bmx280_calib.dig_P5 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P5); + _bmx280_calib.dig_P6 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P6); + _bmx280_calib.dig_P7 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P7); + _bmx280_calib.dig_P8 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P8); + _bmx280_calib.dig_P9 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_P9); + + _bmx280_calib.dig_H1 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H1); + _bmx280_calib.dig_H2 = i2c_read_int16_le(_address, BMX280_REGISTER_DIG_H2); + _bmx280_calib.dig_H3 = i2c_read_uint8(_address, BMX280_REGISTER_DIG_H3); + _bmx280_calib.dig_H4 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H4+1) & 0xF); + _bmx280_calib.dig_H5 = (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5+1) << 4) | (i2c_read_uint8(_address, BMX280_REGISTER_DIG_H5) >> 4); + _bmx280_calib.dig_H6 = (int8_t) i2c_read_uint8(_address, BMX280_REGISTER_DIG_H6); + } + + unsigned long _measurementTime() { + + // Measurement Time (as per BMX280 datasheet section 9.1) + // T_max(ms) = 1.25 + // + (2.3 * T_oversampling) + // + (2.3 * P_oversampling + 0.575) + // + (2.4 * H_oversampling + 0.575) + // ~ 9.3ms for current settings + + double t = 1.25; + #if BMX280_TEMPERATURE > 0 + t += (2.3 * BMX280_TEMPERATURE); + #endif + #if BMX280_PRESSURE > 0 + t += (2.3 * BMX280_PRESSURE + 0.575); + #endif + #if BMX280_HUMIDITY > 0 + if (_chip == BMX280_CHIP_BME280) { + t += (2.4 * BMX280_HUMIDITY + 0.575); + } + #endif + + return round(t + 1); // round up + + } + + void _forceRead() { + + // We set the sensor in "forced mode" to force a reading. + // After the reading the sensor will go back to sleep mode. + uint8_t value = i2c_read_uint8(_address, BMX280_REGISTER_CONTROL); + value = (value & 0xFC) + 0x01; + i2c_write_uint8(_address, BMX280_REGISTER_CONTROL, value); + + nice_delay(_measurement_delay); + + } + + unsigned char _read() { + + #if BMX280_TEMPERATURE > 0 + int32_t adc_T = i2c_read_uint16(_address, BMX280_REGISTER_TEMPDATA); + if (0xFFFF == adc_T) return SENSOR_ERROR_I2C; + adc_T <<= 8; + adc_T |= i2c_read_uint8(_address, BMX280_REGISTER_TEMPDATA+2); + adc_T >>= 4; + + int32_t var1t = ((((adc_T>>3) - + ((int32_t)_bmx280_calib.dig_T1 <<1))) * + ((int32_t)_bmx280_calib.dig_T2)) >> 11; + + int32_t var2t = (((((adc_T>>4) - + ((int32_t)_bmx280_calib.dig_T1)) * + ((adc_T>>4) - ((int32_t)_bmx280_calib.dig_T1))) >> 12) * + ((int32_t)_bmx280_calib.dig_T3)) >> 14; + + int32_t t_fine = var1t + var2t; + + double T = (t_fine * 5 + 128) >> 8; + _temperature = T / 100; + #else + int32_t t_fine = 102374; // ~20ºC + #endif + + // ----------------------------------------------------------------- + + #if BMX280_PRESSURE > 0 + int64_t var1, var2, p; + + int32_t adc_P = i2c_read_uint16(_address, BMX280_REGISTER_PRESSUREDATA); + if (0xFFFF == adc_P) return SENSOR_ERROR_I2C; + adc_P <<= 8; + adc_P |= i2c_read_uint8(_address, BMX280_REGISTER_PRESSUREDATA+2); + adc_P >>= 4; + + var1 = ((int64_t)t_fine) - 128000; + var2 = var1 * var1 * (int64_t)_bmx280_calib.dig_P6; + var2 = var2 + ((var1*(int64_t)_bmx280_calib.dig_P5)<<17); + var2 = var2 + (((int64_t)_bmx280_calib.dig_P4)<<35); + var1 = ((var1 * var1 * (int64_t)_bmx280_calib.dig_P3)>>8) + + ((var1 * (int64_t)_bmx280_calib.dig_P2)<<12); + var1 = (((((int64_t)1)<<47)+var1))*((int64_t)_bmx280_calib.dig_P1)>>33; + if (var1 == 0) return SENSOR_ERROR_I2C; // avoid exception caused by division by zero + + p = 1048576 - adc_P; + p = (((p<<31) - var2)*3125) / var1; + var1 = (((int64_t)_bmx280_calib.dig_P9) * (p>>13) * (p>>13)) >> 25; + var2 = (((int64_t)_bmx280_calib.dig_P8) * p) >> 19; + + p = ((p + var1 + var2) >> 8) + (((int64_t)_bmx280_calib.dig_P7)<<4); + _pressure = (double) p / 256; + #endif + + // ----------------------------------------------------------------- + + #if BMX280_HUMIDITY > 0 + if (_chip == BMX280_CHIP_BME280) { + + int32_t adc_H = i2c_read_uint16(_address, BMX280_REGISTER_HUMIDDATA); + if (0xFFFF == adc_H) return SENSOR_ERROR_I2C; + + int32_t v_x1_u32r; + + v_x1_u32r = (t_fine - ((int32_t)76800)); + + v_x1_u32r = (((((adc_H << 14) - (((int32_t)_bmx280_calib.dig_H4) << 20) - + (((int32_t)_bmx280_calib.dig_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) * + (((((((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H6)) >> 10) * + (((v_x1_u32r * ((int32_t)_bmx280_calib.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + + ((int32_t)2097152)) * ((int32_t)_bmx280_calib.dig_H2) + 8192) >> 14)); + + v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * + ((int32_t)_bmx280_calib.dig_H1)) >> 4)); + + v_x1_u32r = (v_x1_u32r < 0) ? 0 : v_x1_u32r; + v_x1_u32r = (v_x1_u32r > 419430400) ? 419430400 : v_x1_u32r; + double h = (v_x1_u32r >> 12); + _humidity = h / 1024.0; + + } + #endif + + return SENSOR_ERROR_OK; + + } + + // --------------------------------------------------------------------- + + unsigned char _chip; + unsigned long _measurement_delay; + bool _run_init = false; + double _temperature = 0; + double _pressure = 0; + double _humidity = 0; + + typedef struct { + + uint16_t dig_T1; + int16_t dig_T2; + int16_t dig_T3; + + uint16_t dig_P1; + int16_t dig_P2; + int16_t dig_P3; + int16_t dig_P4; + int16_t dig_P5; + int16_t dig_P6; + int16_t dig_P7; + int16_t dig_P8; + int16_t dig_P9; + + uint8_t dig_H1; + int16_t dig_H2; + uint8_t dig_H3; + int16_t dig_H4; + int16_t dig_H5; + int8_t dig_H6; + + } bmx280_calib_t; + + bmx280_calib_t _bmx280_calib; + +}; + +// Static inizializations + +unsigned char BMX280Sensor::addresses[2] = {0x76, 0x77}; + +#endif // SENSOR_SUPPORT && BMX280_SUPPORT diff --git a/espurna/sensors/BaseSensor.h b/espurna/sensors/BaseSensor.h new file mode 100755 index 0000000..9f3854f --- /dev/null +++ b/espurna/sensors/BaseSensor.h @@ -0,0 +1,101 @@ +// ----------------------------------------------------------------------------- +// Abstract sensor class (other sensor classes extend this class) +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT + +#pragma once + +#include +#include + +#define SENSOR_ERROR_OK 0 // No error +#define SENSOR_ERROR_OUT_OF_RANGE 1 // Result out of sensor range +#define SENSOR_ERROR_WARM_UP 2 // Sensor is warming-up +#define SENSOR_ERROR_TIMEOUT 3 // Response from sensor timed out +#define SENSOR_ERROR_UNKNOWN_ID 4 // Sensor did not report a known ID +#define SENSOR_ERROR_CRC 5 // Sensor data corrupted +#define SENSOR_ERROR_I2C 6 // Wrong or locked I2C address +#define SENSOR_ERROR_GPIO_USED 7 // The GPIO is already in use +#define SENSOR_ERROR_CALIBRATION 8 // Calibration error or Not calibrated +#define SENSOR_ERROR_OTHER 99 // Any other error + +typedef std::function TSensorCallback; + +class BaseSensor { + + public: + + // Constructor + BaseSensor() {} + + // Destructor + ~BaseSensor() {} + + // Initialization method, must be idempotent + virtual void begin() {} + + // Loop-like method, call it in your main loop + virtual void tick() {} + + // Pre-read hook (usually to populate registers with up-to-date data) + virtual void pre() {} + + // Post-read hook (usually to reset things) + virtual void post() {} + + // Descriptive name of the sensor + virtual String description() {} + + // Address of the sensor (it could be the GPIO or I2C address) + virtual String address(unsigned char index) {} + + // Descriptive name of the slot # index + virtual String slot(unsigned char index) {}; + + // Type for slot # index + virtual unsigned char type(unsigned char index) {} + + // Current value for slot # index + virtual double value(unsigned char index) {} + + // Retrieve current instance configuration + virtual void getConfig(JsonObject& root) {}; + + // Save current instance configuration + virtual void setConfig(JsonObject& root) {}; + + // Load the configuration manifest + static void manifest(JsonArray& root) {}; + + // Sensor ID + unsigned char getID() { return _sensor_id; }; + + // Return status (true if no errors) + bool status() { return 0 == _error; } + + // Return ready status (true for ready) + bool ready() { return _ready; } + + // Return sensor last internal error + int error() { return _error; } + + // Number of available slots + unsigned char count() { return _count; } + + // Hook for event callback + void onEvent(TSensorCallback fn) { _callback = fn; }; + + protected: + + TSensorCallback _callback = NULL; + unsigned char _sensor_id = 0x00; + int _error = 0; + bool _dirty = true; + unsigned char _count = 0; + bool _ready = false; + +}; + +#endif diff --git a/espurna/sensors/CSE7766Sensor.h b/espurna/sensors/CSE7766Sensor.h new file mode 100755 index 0000000..9a01302 --- /dev/null +++ b/espurna/sensors/CSE7766Sensor.h @@ -0,0 +1,372 @@ +// ----------------------------------------------------------------------------- +// CSE7766 based power monitor +// Copyright (C) 2018 by Xose Pérez +// http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && CSE7766_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +#include + +class CSE7766Sensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + CSE7766Sensor(): BaseSensor(), _data() { + _count = 4; + _sensor_id = SENSOR_CSE7766_ID; + } + + ~CSE7766Sensor() { + if (_serial) delete _serial; + } + + // --------------------------------------------------------------------- + + void setRX(unsigned char pin_rx) { + if (_pin_rx == pin_rx) return; + _pin_rx = pin_rx; + _dirty = true; + } + + void setInverted(bool inverted) { + if (_inverted == inverted) return; + _inverted = inverted; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getRX() { + return _pin_rx; + } + + bool getInverted() { + return _inverted; + } + + // --------------------------------------------------------------------- + + void expectedCurrent(double expected) { + if ((expected > 0) && (_current > 0)) { + _ratioC = _ratioC * (expected / _current); + } + } + + void expectedVoltage(unsigned int expected) { + if ((expected > 0) && (_voltage > 0)) { + _ratioV = _ratioV * (expected / _voltage); + } + } + + void expectedPower(unsigned int expected) { + if ((expected > 0) && (_active > 0)) { + _ratioP = _ratioP * (expected / _active); + } + } + + void setCurrentRatio(double value) { + _ratioC = value; + }; + + void setVoltageRatio(double value) { + _ratioV = value; + }; + + void setPowerRatio(double value) { + _ratioP = value; + }; + + double getCurrentRatio() { + return _ratioC; + }; + + double getVoltageRatio() { + return _ratioV; + }; + + double getPowerRatio() { + return _ratioP; + }; + + void resetRatios() { + _ratioC = _ratioV = _ratioP = 1.0; + } + + void resetEnergy() { + _energy = 0; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + if (_serial) delete _serial; + + if (1 == _pin_rx) { + Serial.begin(CSE7766_BAUDRATE); + } else { + _serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32); + _serial->enableIntTx(false); + _serial->begin(CSE7766_BAUDRATE); + } + + _ready = true; + _dirty = false; + + } + + // Descriptive name of the sensor + String description() { + char buffer[28]; + if (1 == _pin_rx) { + snprintf(buffer, sizeof(buffer), "CSE7766 @ HwSerial"); + } else { + snprintf(buffer, sizeof(buffer), "CSE7766 @ SwSerial(%u,NULL)", _pin_rx); + } + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return String(_pin_rx); + } + + // Loop-like method, call it in your main loop + void tick() { + _read(); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_CURRENT; + if (index == 1) return MAGNITUDE_VOLTAGE; + if (index == 2) return MAGNITUDE_POWER_ACTIVE; + if (index == 3) return MAGNITUDE_ENERGY; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _current; + if (index == 1) return _voltage; + if (index == 2) return _active; + if (index == 3) return _energy; + return 0; + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + /** + * " + * Checksum is the sum of all data + * except for packet header and packet tail lowering by 8bit (...) + * " + * @return bool + */ + bool _checksum() { + unsigned char checksum = 0; + for (unsigned char i = 2; i < 23; i++) { + checksum += _data[i]; + } + return checksum == _data[23]; + } + + void _process() { + + // Sample data: + // 55 5A 02 E9 50 00 03 31 00 3E 9E 00 0D 30 4F 44 F8 00 12 65 F1 81 76 72 (w/ load) + // F2 5A 02 E9 50 00 03 2B 00 3E 9E 02 D7 7C 4F 44 F8 CF A5 5D E1 B3 2A B4 (w/o load) + + #if SENSOR_DEBUG + DEBUG_MSG("[SENSOR] CSE7766: _process: "); + for (byte i=0; i<24; i++) DEBUG_MSG("%02X ", _data[i]); + DEBUG_MSG("\n"); + #endif + + // Checksum + if (!_checksum()) { + _error = SENSOR_ERROR_CRC; + #if SENSOR_DEBUG + DEBUG_MSG("[SENSOR] CSE7766: Checksum error\n"); + #endif + return; + } + + // Calibration + if (0xAA == _data[0]) { + _error = SENSOR_ERROR_CALIBRATION; + #if SENSOR_DEBUG + DEBUG_MSG("[SENSOR] CSE7766: Chip not calibrated\n"); + #endif + return; + } + + if ((_data[0] & 0xFC) > 0xF0) { + _error = SENSOR_ERROR_OTHER; + #if SENSOR_DEBUG + if (0xF1 == _data[0] & 0xF1) DEBUG_MSG("[SENSOR] CSE7766: Abnormal coefficient storage area\n"); + if (0xF2 == _data[0] & 0xF2) DEBUG_MSG("[SENSOR] CSE7766: Power cycle exceeded range\n"); + if (0xF4 == _data[0] & 0xF4) DEBUG_MSG("[SENSOR] CSE7766: Current cycle exceeded range\n"); + if (0xF8 == _data[0] & 0xF8) DEBUG_MSG("[SENSOR] CSE7766: Voltage cycle exceeded range\n"); + #endif + return; + } + + // Calibration coefficients + unsigned long _coefV = (_data[2] << 16 | _data[3] << 8 | _data[4] ); // 190770 + unsigned long _coefC = (_data[8] << 16 | _data[9] << 8 | _data[10]); // 16030 + unsigned long _coefP = (_data[14] << 16 | _data[15] << 8 | _data[16]); // 5195000 + + // Adj: this looks like a sampling report + uint8_t adj = _data[20]; // F1 11110001 + + // Calculate voltage + _voltage = 0; + if ((adj & 0x40) == 0x40) { + unsigned long voltage_cycle = _data[5] << 16 | _data[6] << 8 | _data[7]; // 817 + _voltage = _ratioV * _coefV / voltage_cycle / CSE7766_V2R; // 190700 / 817 = 233.41 + } + + // Calculate power + _active = 0; + if ((adj & 0x10) == 0x10) { + if ((_data[0] & 0xF2) != 0xF2) { + unsigned long power_cycle = _data[17] << 16 | _data[18] << 8 | _data[19]; // 4709 + _active = _ratioP * _coefP / power_cycle / CSE7766_V1R / CSE7766_V2R; // 5195000 / 4709 = 1103.20 + } + } + + // Calculate current + _current = 0; + if ((adj & 0x20) == 0x20) { + if (_active > 0) { + unsigned long current_cycle = _data[11] << 16 | _data[12] << 8 | _data[13]; // 3376 + _current = _ratioC * _coefC / current_cycle / CSE7766_V1R; // 16030 / 3376 = 4.75 + } + } + + // Calculate energy + static unsigned long cf_pulses_last = 0; + unsigned long cf_pulses = _data[21] << 8 | _data[22]; + if (0 == cf_pulses_last) cf_pulses_last = cf_pulses; + _energy += (cf_pulses - cf_pulses_last) * (float) _coefP / 1000000.0; + cf_pulses_last = cf_pulses; + + } + + void _read() { + + _error = SENSOR_ERROR_OK; + + static unsigned char index = 0; + static unsigned long last = millis(); + + while (_serial_available()) { + + // A 24 bytes message takes ~55ms to go through at 4800 bps + // Reset counter if more than 1000ms have passed since last byte. + if (millis() - last > CSE7766_SYNC_INTERVAL) index = 0; + last = millis(); + + uint8_t byte = _serial_read(); + + // first byte must be 0x55 or 0xF? + if (0 == index) { + if ((0x55 != byte) && (byte < 0xF0)) { + continue; + } + + // second byte must be 0x5A + } else if (1 == index) { + if (0x5A != byte) { + index = 0; + continue; + } + } + + _data[index++] = byte; + if (index > 23) { + _serial_flush(); + break; + } + + } + + // Process packet + if (24 == index) { + _process(); + index = 0; + } + + } + + // --------------------------------------------------------------------- + + bool _serial_available() { + if (1 == _pin_rx) { + return Serial.available(); + } else { + return _serial->available(); + } + } + + void _serial_flush() { + if (1 == _pin_rx) { + return Serial.flush(); + } else { + return _serial->flush(); + } + } + + uint8_t _serial_read() { + if (1 == _pin_rx) { + return Serial.read(); + } else { + return _serial->read(); + } + } + + // --------------------------------------------------------------------- + + unsigned int _pin_rx = CSE7766_PIN; + bool _inverted = CSE7766_PIN_INVERSE; + SoftwareSerial * _serial = NULL; + + double _active = 0; + double _voltage = 0; + double _current = 0; + double _energy = 0; + + double _ratioV = 1.0; + double _ratioC = 1.0; + double _ratioP = 1.0; + + unsigned char _data[24]; + +}; + +#endif // SENSOR_SUPPORT && CSE7766_SUPPORT diff --git a/espurna/sensors/DHTSensor.h b/espurna/sensors/DHTSensor.h new file mode 100755 index 0000000..2651ead --- /dev/null +++ b/espurna/sensors/DHTSensor.h @@ -0,0 +1,245 @@ +// ----------------------------------------------------------------------------- +// DHTXX Sensor +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && DHT_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +#define DHT_MAX_DATA 5 +#define DHT_MAX_ERRORS 5 +#define DHT_MIN_INTERVAL 2000 + +#define DHT_CHIP_DHT11 11 +#define DHT_CHIP_DHT22 22 +#define DHT_CHIP_DHT21 21 +#define DHT_CHIP_AM2301 21 + +class DHTSensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + DHTSensor(): BaseSensor() { + _count = 2; + _sensor_id = SENSOR_DHTXX_ID; + } + + ~DHTSensor() { + if (_previous != GPIO_NONE) gpioReleaseLock(_previous); + } + + // --------------------------------------------------------------------- + + void setGPIO(unsigned char gpio) { + _gpio = gpio; + } + + void setType(unsigned char type) { + _type = type; + } + + // --------------------------------------------------------------------- + + unsigned char getGPIO() { + return _gpio; + } + + unsigned char getType() { + return _type; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + _count = 0; + + // Manage GPIO lock + if (_previous != GPIO_NONE) gpioReleaseLock(_previous); + _previous = GPIO_NONE; + if (!gpioGetLock(_gpio)) { + _error = SENSOR_ERROR_GPIO_USED; + return; + } + _previous = _gpio; + + _count = 2; + _ready = true; + + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + _error = SENSOR_ERROR_OK; + _read(); + } + + // Descriptive name of the sensor + String description() { + char buffer[20]; + snprintf(buffer, sizeof(buffer), "DHT%d @ GPIO%d", _type, _gpio); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return String(_gpio); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_TEMPERATURE; + if (index == 1) return MAGNITUDE_HUMIDITY; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _temperature; + if (index == 1) return _humidity; + return 0; + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + void _read() { + + if ((_last_ok > 0) && (millis() - _last_ok < DHT_MIN_INTERVAL)) { + _error = SENSOR_ERROR_OK; + return; + } + + unsigned long low = 0; + unsigned long high = 0; + + unsigned char dhtData[DHT_MAX_DATA] = {0}; + unsigned char byteInx = 0; + unsigned char bitInx = 7; + + // Send start signal to DHT sensor + if (++_errors > DHT_MAX_ERRORS) { + _errors = 0; + digitalWrite(_gpio, HIGH); + nice_delay(250); + } + pinMode(_gpio, OUTPUT); + noInterrupts(); + digitalWrite(_gpio, LOW); + if (_type == DHT_CHIP_DHT11) { + nice_delay(20); + } else { + delayMicroseconds(500); + } + digitalWrite(_gpio, HIGH); + delayMicroseconds(40); + pinMode(_gpio, INPUT_PULLUP); + delayMicroseconds(10); + + // No errors, read the 40 data bits + for( int k = 0; k < 41; k++ ) { + + // Starts new data transmission with >50us low signal + low = _signal(100, LOW); + if (low == 0) { + _error = SENSOR_ERROR_TIMEOUT; + return; + } + + // Check to see if after >70us rx data is a 0 or a 1 + high = _signal(100, HIGH); + if (high == 0) { + _error = SENSOR_ERROR_TIMEOUT; + return; + } + + // Skip the first bit + if (k == 0) continue; + + // add the current read to the output data + // since all dhtData array where set to 0 at the start, + // only look for "1" (>28us us) + if (high > low) dhtData[byteInx] |= (1 << bitInx); + + // index to next byte + if (bitInx == 0) { + bitInx = 7; + ++byteInx; + } else { + --bitInx; + } + + } + + interrupts(); + + // Verify checksum + if (dhtData[4] != ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) { + _error = SENSOR_ERROR_CRC; + return; + } + + // Get humidity from Data[0] and Data[1] + if (_type == DHT_CHIP_DHT11) { + _humidity = dhtData[0]; + } else { + _humidity = dhtData[0] * 256 + dhtData[1]; + _humidity /= 10; + } + + // Get temp from Data[2] and Data[3] + if (_type == DHT_CHIP_DHT11) { + _temperature = dhtData[2]; + } else { + _temperature = (dhtData[2] & 0x7F) * 256 + dhtData[3]; + _temperature /= 10; + if (dhtData[2] & 0x80) _temperature *= -1; + } + + _last_ok = millis(); + _errors = 0; + _error = SENSOR_ERROR_OK; + + } + + unsigned long _signal(int usTimeOut, bool state) { + unsigned long uSec = 1; + while (digitalRead(_gpio) == state) { + if (++uSec > usTimeOut) return 0; + delayMicroseconds(1); + } + return uSec; + } + + unsigned char _gpio = GPIO_NONE; + unsigned char _previous = GPIO_NONE; + unsigned char _type = DHT_CHIP_DHT22; + + unsigned long _last_ok = 0; + unsigned char _errors = 0; + + double _temperature = 0; + unsigned int _humidity = 0; + +}; + +#endif // SENSOR_SUPPORT && DHT_SUPPORT diff --git a/espurna/sensors/DallasSensor.h b/espurna/sensors/DallasSensor.h new file mode 100755 index 0000000..e5dbba1 --- /dev/null +++ b/espurna/sensors/DallasSensor.h @@ -0,0 +1,319 @@ +// ----------------------------------------------------------------------------- +// Dallas OneWire Sensor +// Uses OneWire library +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && DALLAS_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" +#include +#include + +#define DS_CHIP_DS18S20 0x10 +#define DS_CHIP_DS1822 0x22 +#define DS_CHIP_DS18B20 0x28 +#define DS_CHIP_DS1825 0x3B + +#define DS_DATA_SIZE 9 +#define DS_PARASITE 1 +#define DS_DISCONNECTED -127 + +#define DS_CMD_START_CONVERSION 0x44 +#define DS_CMD_READ_SCRATCHPAD 0xBE + +class DallasSensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + DallasSensor(): BaseSensor() { + _sensor_id = SENSOR_DALLAS_ID; + } + + ~DallasSensor() { + if (_wire) delete _wire; + if (_previous != GPIO_NONE) gpioReleaseLock(_previous); + } + + // --------------------------------------------------------------------- + + void setGPIO(unsigned char gpio) { + if (_gpio == gpio) return; + _gpio = gpio; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getGPIO() { + return _gpio; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + // Manage GPIO lock + if (_previous != GPIO_NONE) gpioReleaseLock(_previous); + _previous = GPIO_NONE; + if (!gpioGetLock(_gpio)) { + _error = SENSOR_ERROR_GPIO_USED; + return; + } + + // OneWire + if (_wire) delete _wire; + _wire = new OneWire(_gpio); + + // Search devices + loadDevices(); + + // If no devices found check again pulling up the line + if (_count == 0) { + pinMode(_gpio, INPUT_PULLUP); + loadDevices(); + } + + // Check connection + if (_count == 0) { + gpioReleaseLock(_gpio); + } else { + _previous = _gpio; + } + _ready = true; + _dirty = false; + + } + + // Loop-like method, call it in your main loop + void tick() { + + static unsigned long last = 0; + if (millis() - last < DALLAS_READ_INTERVAL) return; + last = millis(); + + // Every second we either start a conversion or read the scratchpad + static bool conversion = true; + if (conversion) { + + // Start conversion + _wire->reset(); + _wire->skip(); + _wire->write(DS_CMD_START_CONVERSION, DS_PARASITE); + + } else { + + // Read scratchpads + for (unsigned char index=0; index<_devices.size(); index++) { + + // Read scratchpad + if (_wire->reset() == 0) { + // Force a CRC check error + _devices[index].data[0] = _devices[index].data[0] + 1; + return; + } + + _wire->select(_devices[index].address); + _wire->write(DS_CMD_READ_SCRATCHPAD); + + uint8_t data[DS_DATA_SIZE]; + for (unsigned char i = 0; i < DS_DATA_SIZE; i++) { + data[i] = _wire->read(); + } + + #if false + Serial.printf("[DS18B20] Data = "); + for (unsigned char i = 0; i < DS_DATA_SIZE; i++) { + Serial.printf("%02X ", data[i]); + } + Serial.printf(" CRC = %02X\n", OneWire::crc8(data, DS_DATA_SIZE-1)); + #endif + + + if (_wire->reset() != 1) { + // Force a CRC check error + _devices[index].data[0] = _devices[index].data[0] + 1; + return; + } + + memcpy(_devices[index].data, data, DS_DATA_SIZE); + + } + + } + + conversion = !conversion; + + } + + + // Descriptive name of the sensor + String description() { + char buffer[20]; + snprintf(buffer, sizeof(buffer), "Dallas @ GPIO%d", _gpio); + return String(buffer); + } + + // Address of the device + String address(unsigned char index) { + char buffer[20] = {0}; + if (index < _count) { + uint8_t * address = _devices[index].address; + snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X%02X%02X", + address[0], address[1], address[2], address[3], + address[4], address[5], address[6], address[7] + ); + } + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + if (index < _count) { + char buffer[40]; + uint8_t * address = _devices[index].address; + snprintf(buffer, sizeof(buffer), "%s (%02X%02X%02X%02X%02X%02X%02X%02X) @ GPIO%d", + chipAsString(index).c_str(), + address[0], address[1], address[2], address[3], + address[4], address[5], address[6], address[7], + _gpio + ); + return String(buffer); + } + return String(); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index < _count) return MAGNITUDE_TEMPERATURE; + return MAGNITUDE_NONE; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + _error = SENSOR_ERROR_OK; + } + + // Current value for slot # index + double value(unsigned char index) { + + if (index >= _count) return 0; + + uint8_t * data = _devices[index].data; + + if (OneWire::crc8(data, DS_DATA_SIZE-1) != data[DS_DATA_SIZE-1]) { + _error = SENSOR_ERROR_CRC; + return 0; + } + + // Registers + // byte 0: temperature LSB + // byte 1: temperature MSB + // byte 2: high alarm temp + // byte 3: low alarm temp + // byte 4: DS18S20: store for crc + // DS18B20 & DS1822: configuration register + // byte 5: internal use & crc + // byte 6: DS18S20: COUNT_REMAIN + // DS18B20 & DS1822: store for crc + // byte 7: DS18S20: COUNT_PER_C + // DS18B20 & DS1822: store for crc + // byte 8: SCRATCHPAD_CRC + + int16_t raw = (data[1] << 8) | data[0]; + if (chip(index) == DS_CHIP_DS18S20) { + raw = raw << 3; // 9 bit resolution default + if (data[7] == 0x10) { + raw = (raw & 0xFFF0) + 12 - data[6]; // "count remain" gives full 12 bit resolution + } + } else { + byte cfg = (data[4] & 0x60); + if (cfg == 0x00) raw = raw & ~7; // 9 bit res, 93.75 ms + else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms + else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms + // 12 bit res, 750 ms + } + + double value = (float) raw / 16.0; + if (value == DS_DISCONNECTED) { + _error = SENSOR_ERROR_CRC; + return 0; + } + + return value; + + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + bool validateID(unsigned char id) { + return (id == DS_CHIP_DS18S20) || (id == DS_CHIP_DS18B20) || (id == DS_CHIP_DS1822) || (id == DS_CHIP_DS1825); + } + + unsigned char chip(unsigned char index) { + if (index < _count) return _devices[index].address[0]; + return 0; + } + + String chipAsString(unsigned char index) { + unsigned char chip_id = chip(index); + if (chip_id == DS_CHIP_DS18S20) return String("DS18S20"); + if (chip_id == DS_CHIP_DS18B20) return String("DS18B20"); + if (chip_id == DS_CHIP_DS1822) return String("DS1822"); + if (chip_id == DS_CHIP_DS1825) return String("DS1825"); + return String("Unknown"); + } + + void loadDevices() { + + uint8_t address[8]; + _wire->reset(); + _wire->reset_search(); + while (_wire->search(address)) { + + // Check CRC + if (_wire->crc8(address, 7) == address[7]) { + + // Check ID + if (validateID(address[0])) { + ds_device_t device; + memcpy(device.address, address, 8); + _devices.push_back(device); + } + + } + + } + _count = _devices.size(); + + } + + typedef struct { + uint8_t address[8]; + uint8_t data[DS_DATA_SIZE]; + } ds_device_t; + std::vector _devices; + + unsigned char _gpio = GPIO_NONE; + unsigned char _previous = GPIO_NONE; + OneWire * _wire = NULL; + +}; + +#endif // SENSOR_SUPPORT && DALLAS_SUPPORT diff --git a/espurna/sensors/DigitalSensor.h b/espurna/sensors/DigitalSensor.h new file mode 100755 index 0000000..fc1ca79 --- /dev/null +++ b/espurna/sensors/DigitalSensor.h @@ -0,0 +1,106 @@ +// ----------------------------------------------------------------------------- +// Digital Sensor (maps to a digitalRead) +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && DIGITAL_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +class DigitalSensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + DigitalSensor(): BaseSensor() { + _count = 1; + _sensor_id = SENSOR_DIGITAL_ID; + } + + // --------------------------------------------------------------------- + + void setGPIO(unsigned char gpio) { + _gpio = gpio; + } + + void setMode(unsigned char mode) { + _mode = mode; + } + + void setDefault(bool value) { + _default = value; + } + + // --------------------------------------------------------------------- + + unsigned char getGPIO() { + return _gpio; + } + + unsigned char getMode() { + return _mode; + } + + bool getDefault() { + return _default; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + pinMode(_gpio, _mode); + _ready = true; + } + + // Descriptive name of the sensor + String description() { + char buffer[20]; + snprintf(buffer, sizeof(buffer), "DIGITAL @ GPIO%d", _gpio); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return String(_gpio); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_DIGITAL; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return (digitalRead(_gpio) == _default) ? 0 : 1; + return 0; + } + + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + unsigned char _gpio; + unsigned char _mode; + bool _default = false; + +}; + +#endif // SENSOR_SUPPORT && DIGITAL_SUPPORT diff --git a/espurna/sensors/ECH1560Sensor.h b/espurna/sensors/ECH1560Sensor.h new file mode 100755 index 0000000..894ec72 --- /dev/null +++ b/espurna/sensors/ECH1560Sensor.h @@ -0,0 +1,349 @@ +// ----------------------------------------------------------------------------- +// ECH1560 based power monitor +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && ECH1560_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +class ECH1560Sensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + ECH1560Sensor(): BaseSensor(), _data() { + _count = 3; + _sensor_id = SENSOR_ECH1560_ID; + } + + ~ECH1560Sensor() { + _enableInterrupts(false); + } + + // --------------------------------------------------------------------- + + void setCLK(unsigned char clk) { + if (_clk == clk) return; + _clk = clk; + _dirty = true; + } + + void setMISO(unsigned char miso) { + if (_miso == miso) return; + _miso = miso; + _dirty = true; + } + + void setInverted(bool inverted) { + _inverted = inverted; + } + + // --------------------------------------------------------------------- + + unsigned char getCLK() { + return _clk; + } + + unsigned char getMISO() { + return _miso; + } + + bool getInverted() { + return _inverted; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + pinMode(_clk, INPUT); + pinMode(_miso, INPUT); + _enableInterrupts(true); + + _dirty = false; + _ready = true; + + } + + // Loop-like method, call it in your main loop + void tick() { + if (_dosync) _sync(); + } + + // Descriptive name of the sensor + String description() { + char buffer[35]; + snprintf(buffer, sizeof(buffer), "ECH1560 (CLK,SDO) @ GPIO(%u,%u)", _clk, _miso); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + char buffer[6]; + snprintf(buffer, sizeof(buffer), "%u:%u", _clk, _miso); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_CURRENT; + if (index == 1) return MAGNITUDE_VOLTAGE; + if (index == 2) return MAGNITUDE_POWER_APPARENT; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _current; + if (index == 1) return _voltage; + if (index == 2) return _apparent; + return 0; + } + + void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) { + + (void) gpio; + + // if we are trying to find the sync-time (CLK goes high for 1-2ms) + if (_dosync == false) { + + _clk_count = 0; + + // register how long the ClkHigh is high to evaluate if we are at the part where clk goes high for 1-2 ms + while (digitalRead(_clk) == HIGH) { + _clk_count += 1; + delayMicroseconds(30); //can only use delayMicroseconds in an interrupt. + } + + // if the Clk was high between 1 and 2 ms than, its a start of a SPI-transmission + if (_clk_count >= 33 && _clk_count <= 67) { + _dosync = true; + } + + // we are in sync and logging CLK-highs + } else { + + // increment an integer to keep track of how many bits we have read. + _bits_count += 1; + _nextbit = true; + + } + + } + + protected: + + // --------------------------------------------------------------------- + // Interrupt management + // --------------------------------------------------------------------- + + void _attach(ECH1560Sensor * instance, unsigned char gpio, unsigned char mode); + void _detach(unsigned char gpio); + + void _enableInterrupts(bool value) { + + static unsigned char _interrupt_clk = GPIO_NONE; + + if (value) { + if (_interrupt_clk != _clk) { + if (_interrupt_clk != GPIO_NONE) _detach(_interrupt_clk); + _attach(this, _clk, RISING); + _interrupt_clk = _clk; + } + } else if (_interrupt_clk != GPIO_NONE) { + _detach(_interrupt_clk); + _interrupt_clk = GPIO_NONE; + } + + } + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + void _sync() { + + unsigned int byte1 = 0; + unsigned int byte2 = 0; + unsigned int byte3 = 0; + + _bits_count = 0; + while (_bits_count < 40); // skip the uninteresting 5 first bytes + _bits_count = 0; + + while (_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in byte1 and byte2 + + if (_nextbit) { + + if (_bits_count < 9) { // first Byte/8 bits in byte1 + + byte1 = byte1 << 1; + if (digitalRead(_miso) == HIGH) byte1 |= 1; + _nextbit = false; + + } else if (_bits_count < 17) { // bit 9-16 is byte 7, store in byte2 + + byte2 = byte2 << 1; + if (digitalRead(_miso) == HIGH) byte2 |= 1; + _nextbit = false; + + } + + } + + } + + if (byte2 != 3) { // if bit byte2 is not 3, we have reached the important part, U is allready in byte1 and byte2 and next 8 Bytes will give us the Power. + + // voltage = 2 * (byte1 + byte2 / 255) + _voltage = 2.0 * ((float) byte1 + (float) byte2 / 255.0); + + // power: + _bits_count = 0; + while (_bits_count < 40); // skip the uninteresting 5 first bytes + _bits_count = 0; + + byte1 = 0; + byte2 = 0; + byte3 = 0; + + while (_bits_count < 24) { //store byte 6, 7 and 8 in byte1 and byte2 & byte3. + + if (_nextbit) { + + if (_bits_count < 9) { + + byte1 = byte1 << 1; + if (digitalRead(_miso) == HIGH) byte1 |= 1; + _nextbit = false; + + } else if (_bits_count < 17) { + + byte2 = byte2 << 1; + if (digitalRead(_miso) == HIGH) byte2 |= 1; + _nextbit = false; + + } else { + + byte3 = byte3 << 1; + if (digitalRead(_miso) == HIGH) byte3 |= 1; + _nextbit = false; + + } + } + } + + if (_inverted) { + byte1 = 255 - byte1; + byte2 = 255 - byte2; + byte3 = 255 - byte3; + } + + // power = (byte1*255+byte2+byte3/255)/2 + _apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2; + _current = _apparent / _voltage; + + _dosync = false; + + } + + // If byte2 is not 3 or something else than 0, something is wrong! + if (byte2 == 0) { + _dosync = false; + #if SENSOR_DEBUG + DEBUG_MSG_P(PSTR("Nothing connected, or out of sync!\n")); + #endif + } + } + + // --------------------------------------------------------------------- + + unsigned char _clk = 0; + unsigned char _miso = 0; + bool _inverted = false; + + volatile long _bits_count = 0; + volatile long _clk_count = 0; + volatile bool _dosync = false; + volatile bool _nextbit = true; + + double _apparent = 0; + double _voltage = 0; + double _current = 0; + + unsigned char _data[24]; + +}; + +// ----------------------------------------------------------------------------- +// Interrupt helpers +// ----------------------------------------------------------------------------- + +ECH1560Sensor * _ech1560_sensor_instance[10] = {NULL}; + +void ICACHE_RAM_ATTR _ech1560_sensor_isr(unsigned char gpio) { + unsigned char index = gpio > 5 ? gpio-6 : gpio; + if (_ech1560_sensor_instance[index]) { + _ech1560_sensor_instance[index]->handleInterrupt(gpio); + } +} + +void ICACHE_RAM_ATTR _ech1560_sensor_isr_0() { _ech1560_sensor_isr(0); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_1() { _ech1560_sensor_isr(1); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_2() { _ech1560_sensor_isr(2); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_3() { _ech1560_sensor_isr(3); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_4() { _ech1560_sensor_isr(4); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_5() { _ech1560_sensor_isr(5); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_12() { _ech1560_sensor_isr(12); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_13() { _ech1560_sensor_isr(13); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_14() { _ech1560_sensor_isr(14); } +void ICACHE_RAM_ATTR _ech1560_sensor_isr_15() { _ech1560_sensor_isr(15); } + +static void (*_ech1560_sensor_isr_list[10])() = { + _ech1560_sensor_isr_0, _ech1560_sensor_isr_1, _ech1560_sensor_isr_2, + _ech1560_sensor_isr_3, _ech1560_sensor_isr_4, _ech1560_sensor_isr_5, + _ech1560_sensor_isr_12, _ech1560_sensor_isr_13, _ech1560_sensor_isr_14, + _ech1560_sensor_isr_15 +}; + +void ECH1560Sensor::_attach(ECH1560Sensor * instance, unsigned char gpio, unsigned char mode) { + if (!gpioValid(gpio)) return; + _detach(gpio); + unsigned char index = gpio > 5 ? gpio-6 : gpio; + _ech1560_sensor_instance[index] = instance; + attachInterrupt(gpio, _ech1560_sensor_isr_list[index], mode); + #if SENSOR_DEBUG + DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str()); + #endif +} + +void ECH1560Sensor::_detach(unsigned char gpio) { + if (!gpioValid(gpio)) return; + unsigned char index = gpio > 5 ? gpio-6 : gpio; + if (_ech1560_sensor_instance[index]) { + detachInterrupt(gpio); + #if SENSOR_DEBUG + DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _ech1560_sensor_instance[index]->description().c_str()); + #endif + _ech1560_sensor_instance[index] = NULL; + } +} + +#endif // SENSOR_SUPPORT && ECH1560_SUPPORT diff --git a/espurna/sensors/EmonADC121Sensor.h b/espurna/sensors/EmonADC121Sensor.h new file mode 100755 index 0000000..293b5c9 --- /dev/null +++ b/espurna/sensors/EmonADC121Sensor.h @@ -0,0 +1,150 @@ +// ----------------------------------------------------------------------------- +// ADS121-based Energy Monitor Sensor over I2C +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && EMON_ADC121_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "EmonSensor.h" + +// ADC121 Registers +#define ADC121_REG_RESULT 0x00 +#define ADC121_REG_ALERT 0x01 +#define ADC121_REG_CONFIG 0x02 +#define ADC121_REG_LIMITL 0x03 +#define ADC121_REG_LIMITH 0x04 +#define ADC121_REG_HYST 0x05 +#define ADC121_REG_CONVL 0x06 +#define ADC121_REG_CONVH 0x07 + +#define ADC121_RESOLUTION 12 +#define ADC121_CHANNELS 1 + +class EmonADC121Sensor : public EmonSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + EmonADC121Sensor(): EmonSensor() { + _channels = ADC121_CHANNELS; + _sensor_id = SENSOR_EMON_ADC121_ID; + init(); + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + _dirty = false; + + // Discover + unsigned char addresses[] = {0x50, 0x51, 0x52, 0x54, 0x55, 0x56, 0x58, 0x59, 0x5A}; + _address = _begin_i2c(_address, sizeof(addresses), addresses); + if (_address == 0) return; + + // Init sensor + _init(); + + // Just one channel + _count = _magnitudes; + + // Bit depth + _resolution = ADC121_RESOLUTION; + + // Call the parent class method + EmonSensor::begin(); + + // warmup channel 0 (the only one) + read(0); + + } + + // Descriptive name of the sensor + String description() { + char buffer[30]; + snprintf(buffer, sizeof(buffer), "EMON @ ADC121 @ I2C (0x%02X)", _address); + return String(buffer); + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + + if (_address == 0) { + _error = SENSOR_ERROR_UNKNOWN_ID; + return; + } + + _current[0] = read(0); + + #if EMON_REPORT_ENERGY + static unsigned long last = 0; + if (last > 0) { + _energy[0] += (_current[0] * _voltage * (millis() - last) / 1000); + } + last = millis(); + #endif + + _error = SENSOR_ERROR_OK; + + } + + // Type for slot # index + unsigned char type(unsigned char index) { + unsigned char i=0; + #if EMON_REPORT_CURRENT + if (index == i++) return MAGNITUDE_CURRENT; + #endif + #if EMON_REPORT_POWER + if (index == i++) return MAGNITUDE_POWER_APPARENT; + #endif + #if EMON_REPORT_ENERGY + if (index == i) return MAGNITUDE_ENERGY; + #endif + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + unsigned char channel = index / _magnitudes; + unsigned char i=0; + #if EMON_REPORT_CURRENT + if (index == i++) return _current[channel]; + #endif + #if EMON_REPORT_POWER + if (index == i++) return _current[channel] * _voltage; + #endif + #if EMON_REPORT_ENERGY + if (index == i) return _energy[channel]; + #endif + return 0; + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + void _init() { + i2c_write_uint8(_address, ADC121_REG_CONFIG, 0); + } + + unsigned int readADC(unsigned char channel) { + (void) channel; + unsigned int value = i2c_read_uint16(_address, ADC121_REG_RESULT) & 0x0FFF; + return value; + } + +}; + +#endif // SENSOR_SUPPORT && EMON_ADC121_SUPPORT diff --git a/espurna/sensors/EmonADS1X15Sensor.h b/espurna/sensors/EmonADS1X15Sensor.h new file mode 100755 index 0000000..56bbb16 --- /dev/null +++ b/espurna/sensors/EmonADS1X15Sensor.h @@ -0,0 +1,347 @@ +// ----------------------------------------------------------------------------- +// ADS1X15-based Energy Monitor Sensor over I2C +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "EmonSensor.h" + +#define ADS1X15_CHANNELS (4) + +#define ADS1X15_CHIP_ADS1015 (0) +#define ADS1X15_CHIP_ADS1115 (1) + +#define ADS1X15_RESOLUTION (16) + +#define ADS1015_CONVERSIONDELAY (1) +#define ADS1115_CONVERSIONDELAY (8) + +#define ADS1015_BIT_SHIFT (4) +#define ADS1115_BIT_SHIFT (0) + +#define ADS1X15_REG_POINTER_MASK (0x03) +#define ADS1X15_REG_POINTER_CONVERT (0x00) +#define ADS1X15_REG_POINTER_CONFIG (0x01) +#define ADS1X15_REG_POINTER_LOWTHRESH (0x02) +#define ADS1X15_REG_POINTER_HITHRESH (0x03) + +#define ADS1X15_REG_CONFIG_OS_MASK (0x8000) +#define ADS1X15_REG_CONFIG_OS_SINGLE (0x8000) // Write: Set to start a single-conversion +#define ADS1X15_REG_CONFIG_OS_BUSY (0x0000) // Read: Bit = 0 when conversion is in progress +#define ADS1X15_REG_CONFIG_OS_NOTBUSY (0x8000) // Read: Bit = 1 when device is not performing a conversion + +#define ADS1X15_REG_CONFIG_MUX_MASK (0x7000) +#define ADS1X15_REG_CONFIG_MUX_DIFF_0_1 (0x0000) // Differential P = AIN0, N = AIN1 (default) +#define ADS1X15_REG_CONFIG_MUX_DIFF_0_3 (0x1000) // Differential P = AIN0, N = AIN3 +#define ADS1X15_REG_CONFIG_MUX_DIFF_1_3 (0x2000) // Differential P = AIN1, N = AIN3 +#define ADS1X15_REG_CONFIG_MUX_DIFF_2_3 (0x3000) // Differential P = AIN2, N = AIN3 +#define ADS1X15_REG_CONFIG_MUX_SINGLE_0 (0x4000) // Single-ended AIN0 +#define ADS1X15_REG_CONFIG_MUX_SINGLE_1 (0x5000) // Single-ended AIN1 +#define ADS1X15_REG_CONFIG_MUX_SINGLE_2 (0x6000) // Single-ended AIN2 +#define ADS1X15_REG_CONFIG_MUX_SINGLE_3 (0x7000) // Single-ended AIN3 + +#define ADS1X15_REG_CONFIG_PGA_MASK (0x0E00) +#define ADS1X15_REG_CONFIG_PGA_6_144V (0x0000) // +/-6.144V range = Gain 2/3 +#define ADS1X15_REG_CONFIG_PGA_4_096V (0x0200) // +/-4.096V range = Gain 1 +#define ADS1X15_REG_CONFIG_PGA_2_048V (0x0400) // +/-2.048V range = Gain 2 (default) +#define ADS1X15_REG_CONFIG_PGA_1_024V (0x0600) // +/-1.024V range = Gain 4 +#define ADS1X15_REG_CONFIG_PGA_0_512V (0x0800) // +/-0.512V range = Gain 8 +#define ADS1X15_REG_CONFIG_PGA_0_256V (0x0A00) // +/-0.256V range = Gain 16 + +#define ADS1X15_REG_CONFIG_MODE_MASK (0x0100) +#define ADS1X15_REG_CONFIG_MODE_CONTIN (0x0000) // Continuous conversion mode +#define ADS1X15_REG_CONFIG_MODE_SINGLE (0x0100) // Power-down single-shot mode (default) + +#define ADS1X15_REG_CONFIG_DR_MASK (0x00E0) +#define ADS1015_REG_CONFIG_DR_128SPS (0x0000) // 128 samples per second +#define ADS1015_REG_CONFIG_DR_250SPS (0x0020) // 250 samples per second +#define ADS1015_REG_CONFIG_DR_490SPS (0x0040) // 490 samples per second +#define ADS1015_REG_CONFIG_DR_920SPS (0x0060) // 920 samples per second +#define ADS1015_REG_CONFIG_DR_1600SPS (0x0080) // 1600 samples per second (default) +#define ADS1015_REG_CONFIG_DR_2400SPS (0x00A0) // 2400 samples per second +#define ADS1015_REG_CONFIG_DR_3300SPS (0x00C0) // 3300 samples per second +#define ADS1115_REG_CONFIG_DR_8SPS (0x0000) // 8 samples per second +#define ADS1115_REG_CONFIG_DR_16SPS (0x0020) // 16 samples per second +#define ADS1115_REG_CONFIG_DR_32SPS (0x0040) // 32 samples per second +#define ADS1115_REG_CONFIG_DR_64SPS (0x0060) // 64 samples per second +#define ADS1115_REG_CONFIG_DR_128SPS (0x0080) // 128 samples per second (default) +#define ADS1115_REG_CONFIG_DR_250SPS (0x00A0) // 250 samples per second +#define ADS1115_REG_CONFIG_DR_475SPS (0x00C0) // 475 samples per second +#define ADS1115_REG_CONFIG_DR_860SPS (0x00E0) // 860 samples per second + +#define ADS1X15_REG_CONFIG_CMODE_MASK (0x0010) +#define ADS1X15_REG_CONFIG_CMODE_TRAD (0x0000) // Traditional comparator with hysteresis (default) +#define ADS1X15_REG_CONFIG_CMODE_WINDOW (0x0010) // Window comparator + +#define ADS1X15_REG_CONFIG_CPOL_MASK (0x0008) +#define ADS1X15_REG_CONFIG_CPOL_ACTVLOW (0x0000) // ALERT/RDY pin is low when active (default) +#define ADS1X15_REG_CONFIG_CPOL_ACTVHI (0x0008) // ALERT/RDY pin is high when active + +#define ADS1X15_REG_CONFIG_CLAT_MASK (0x0004) // Determines if ALERT/RDY pin latches once asserted +#define ADS1X15_REG_CONFIG_CLAT_NONLAT (0x0000) // Non-latching comparator (default) +#define ADS1X15_REG_CONFIG_CLAT_LATCH (0x0004) // Latching comparator + +#define ADS1X15_REG_CONFIG_CQUE_MASK (0x0003) +#define ADS1X15_REG_CONFIG_CQUE_1CONV (0x0000) // Assert ALERT/RDY after one conversions +#define ADS1X15_REG_CONFIG_CQUE_2CONV (0x0001) // Assert ALERT/RDY after two conversions +#define ADS1X15_REG_CONFIG_CQUE_4CONV (0x0002) // Assert ALERT/RDY after four conversions +#define ADS1X15_REG_CONFIG_CQUE_NONE (0x0003) // Disable the comparator and put ALERT/RDY in high state (default) + +class EmonADS1X15Sensor : public EmonSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + EmonADS1X15Sensor(): EmonSensor() { + _channels = ADS1X15_CHANNELS; + _sensor_id = SENSOR_EMON_ADS1X15_ID; + init(); + } + + // --------------------------------------------------------------------- + + void setType(unsigned char type) { + if (_type == type) return; + _type = type; + _dirty = true; + } + + void setMask(unsigned char mask) { + if (_mask == mask) return; + _mask = mask; + _dirty = true; + } + + void setGain(unsigned int gain) { + if (_gain == gain) return; + _gain = gain; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getType() { + return _type; + } + + unsigned char getMask() { + return _mask; + } + + unsigned char getGain() { + return _gain; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + // Discover + unsigned char addresses[] = {0x48, 0x49, 0x4A, 0x4B}; + _address = _begin_i2c(_address, sizeof(addresses), addresses); + if (_address == 0) return; + + // Calculate ports + _ports = 0; + unsigned char mask = _mask; + while (mask) { + if (mask & 0x01) ++_ports; + mask = mask >> 1; + } + _count = _ports * _magnitudes; + + // Bit depth + _resolution = ADS1X15_RESOLUTION; + + // Reference based on gain + if (_gain == ADS1X15_REG_CONFIG_PGA_6_144V) _reference = 12.288; + if (_gain == ADS1X15_REG_CONFIG_PGA_4_096V) _reference = 8.192; + if (_gain == ADS1X15_REG_CONFIG_PGA_2_048V) _reference = 4.096; + if (_gain == ADS1X15_REG_CONFIG_PGA_1_024V) _reference = 2.048; + if (_gain == ADS1X15_REG_CONFIG_PGA_0_512V) _reference = 1.024; + if (_gain == ADS1X15_REG_CONFIG_PGA_0_256V) _reference = 0.512; + + // Call the parent class method + EmonSensor::begin(); + + // warmup all channels + warmup(); + + } + + // Descriptive name of the sensor + String description() { + char buffer[30]; + snprintf(buffer, sizeof(buffer), "EMON @ ADS1%d15 @ I2C (0x%02X)", _type == ADS1X15_CHIP_ADS1015 ? 0 : 1, _address); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + char buffer[35]; + unsigned char channel = getChannel(index % _ports); + snprintf(buffer, sizeof(buffer), "EMON @ ADS1%d15 (A%d) @ I2C (0x%02X)", _type == ADS1X15_CHIP_ADS1015 ? 0 : 1, channel, _address); + return String(buffer); + } + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + char buffer[10]; + unsigned char channel = getChannel(index % _ports); + snprintf(buffer, sizeof(buffer), "0x%02X:%u", _address, channel); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + unsigned char magnitude = index / _ports; + unsigned char i=0; + #if EMON_REPORT_CURRENT + if (magnitude == i++) return MAGNITUDE_CURRENT; + #endif + #if EMON_REPORT_POWER + if (magnitude == i++) return MAGNITUDE_POWER_APPARENT; + #endif + #if EMON_REPORT_ENERGY + if (magnitude == i) return MAGNITUDE_ENERGY; + #endif + return MAGNITUDE_NONE; + } + + void pre() { + static unsigned long last = 0; + for (unsigned char port=0; port<_ports; port++) { + unsigned char channel = getChannel(port); + _current[port] = getCurrent(channel); + #if EMON_REPORT_ENERGY + _energy[port] += (_current[port] * _voltage * (millis() - last) / 1000); + #endif + } + last = millis(); + _error = SENSOR_ERROR_OK; + } + + // Current value for slot # index + double value(unsigned char index) { + unsigned char port = index % _ports; + unsigned char magnitude = index / _ports; + unsigned char i=0; + #if EMON_REPORT_CURRENT + if (magnitude == i++) return _current[port]; + #endif + #if EMON_REPORT_POWER + if (magnitude == i++) return _current[port] * _voltage; + #endif + #if EMON_REPORT_ENERGY + if (magnitude == i) return _energy[port]; + #endif + return 0; + } + + protected: + + //---------------------------------------------------------------------- + // Protected + //---------------------------------------------------------------------- + + unsigned char getChannel(unsigned char port) { + unsigned char count = 0; + unsigned char bit = 1; + for (unsigned char channel=0; channel> 1; + getCurrent(channel); + } + } + + //---------------------------------------------------------------------- + // I2C + //---------------------------------------------------------------------- + + void setConfigRegistry(unsigned char channel, bool continuous, bool start) { + + // Start with default values + uint16_t config = 0; + config |= _gain; // Set PGA/voltage range (0x0200) + config |= ADS1X15_REG_CONFIG_DR_MASK; // Always at max speed (0x00E0) + //config |= ADS1X15_REG_CONFIG_CMODE_TRAD; // Traditional comparator (default val) (0x0000) + //config |= ADS1X15_REG_CONFIG_CPOL_ACTVLOW; // Alert/Rdy active low (default val) (0x0000) + //config |= ADS1X15_REG_CONFIG_CLAT_NONLAT; // Non-latching (default val) (0x0000) + config |= ADS1X15_REG_CONFIG_CQUE_NONE; // Disable the comparator (default val) (0x0003) + if (start) { + config |= ADS1X15_REG_CONFIG_OS_SINGLE; // Start a single-conversion (0x8000) + } + if (continuous) { + //config |= ADS1X15_REG_CONFIG_MODE_CONTIN; // Continuous mode (default) (0x0000) + } else { + config |= ADS1X15_REG_CONFIG_MODE_SINGLE; // Single-shot mode (0x0100) + } + config |= ((channel + 4) << 12); // Set single-ended input channel (0x4000 - 0x7000) + + #if SENSOR_DEBUG + //Serial.printf("[EMON] ADS1X115 Config Registry: %04X\n", config); + #endif + + // Write config register to the ADC + i2c_write_uint16(_address, ADS1X15_REG_POINTER_CONFIG, config); + + } + + double getCurrent(unsigned char channel) { + + // Force stop by setting single mode and back to continuous + static unsigned char previous = 9; + if (previous != channel) { + setConfigRegistry(channel, true, false); + setConfigRegistry(channel, false, false); + setConfigRegistry(channel, false, true); + nice_delay(10); + readADC(channel); + previous = channel; + } + setConfigRegistry(channel, true, true); + + return read(channel); + + } + + unsigned int readADC(unsigned char channel) { + (void) channel; + unsigned int value = i2c_read_uint16(_address, ADS1X15_REG_POINTER_CONVERT); + if (_type = ADS1X15_CHIP_ADS1015) value >>= ADS1015_BIT_SHIFT; + delayMicroseconds(500); + return value; + } + + unsigned char _type = ADS1X15_CHIP_ADS1115; + unsigned char _mask = 0x0F; + unsigned int _gain = ADS1X15_REG_CONFIG_PGA_4_096V; + unsigned char _ports; + + +}; + +#endif // SENSOR_SUPPORT && EMON_ADS1X15_SUPPORT diff --git a/espurna/sensors/EmonAnalogSensor.h b/espurna/sensors/EmonAnalogSensor.h new file mode 100755 index 0000000..bfc5a2f --- /dev/null +++ b/espurna/sensors/EmonAnalogSensor.h @@ -0,0 +1,124 @@ +// ----------------------------------------------------------------------------- +// Energy Monitor Sensor using builtin ADC +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && EMON_ANALOG_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "EmonSensor.h" + +#define EMON_ANALOG_RESOLUTION 10 +#define EMON_ANALOG_CHANNELS 1 + +class EmonAnalogSensor : public EmonSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + EmonAnalogSensor(): EmonSensor() { + _channels = EMON_ANALOG_CHANNELS; + _sensor_id = SENSOR_EMON_ANALOG_ID; + init(); + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + _dirty = false; + + // Number of slots + _count = _magnitudes; + + // Bit depth + _resolution = EMON_ANALOG_RESOLUTION; + + // Init analog PIN) + pinMode(0, INPUT); + + // Call the parent class method + EmonSensor::begin(); + + // warmup channel 0 (the only one) + read(0); + + } + + // Descriptive name of the sensor + String description() { + return String("EMON @ ANALOG @ GPIO0"); + } + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return String("0"); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + unsigned char i=0; + #if EMON_REPORT_CURRENT + if (index == i++) return MAGNITUDE_CURRENT; + #endif + #if EMON_REPORT_POWER + if (index == i++) return MAGNITUDE_POWER_APPARENT; + #endif + #if EMON_REPORT_ENERGY + if (index == i) return MAGNITUDE_ENERGY; + #endif + return MAGNITUDE_NONE; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + + _current[0] = read(0); + + #if EMON_REPORT_ENERGY + static unsigned long last = 0; + if (last > 0) { + _energy[0] += (_current[0] * _voltage * (millis() - last) / 1000); + } + last = millis(); + #endif + + _error = SENSOR_ERROR_OK; + + } + + // Current value for slot # index + double value(unsigned char index) { + unsigned char channel = index / _magnitudes; + unsigned char i=0; + #if EMON_REPORT_CURRENT + if (index == i++) return _current[channel]; + #endif + #if EMON_REPORT_POWER + if (index == i++) return _current[channel] * _voltage; + #endif + #if EMON_REPORT_ENERGY + if (index == i) return _energy[channel]; + #endif + return 0; + } + + protected: + + unsigned int readADC(unsigned char channel) { + (void) channel; + return analogRead(0); + } + +}; + +#endif // SENSOR_SUPPORT && EMON_ANALOG_SUPPORT diff --git a/espurna/sensors/EmonSensor.h b/espurna/sensors/EmonSensor.h new file mode 100755 index 0000000..4cd05eb --- /dev/null +++ b/espurna/sensors/EmonSensor.h @@ -0,0 +1,242 @@ +// ----------------------------------------------------------------------------- +// Abstract Energy Monitor Sensor (other EMON sensors extend this class) +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "I2CSensor.h" + +class EmonSensor : public I2CSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + EmonSensor(): I2CSensor() { + + // Calculate # of magnitudes + #if EMON_REPORT_CURRENT + ++_magnitudes; + #endif + #if EMON_REPORT_POWER + ++_magnitudes; + #endif + #if EMON_REPORT_ENERGY + ++_magnitudes; + #endif + + } + + void expectedPower(unsigned char channel, unsigned int expected) { + if (channel >= _channels) return; + unsigned int actual = _current[channel] * _voltage; + if (actual == 0) return; + if (expected == actual) return; + _current_ratio[channel] = _current_ratio[channel] * ((double) expected / (double) actual); + _dirty = true; + } + + void resetEnergy() { + for (unsigned char i=0; i<_channels; i++) { + _energy[i] = 0; + } + } + + // --------------------------------------------------------------------- + + void setVoltage(double voltage) { + if (_voltage == voltage) return; + _voltage = voltage; + _dirty = true; + } + + void setReference(double reference) { + if (_reference == reference) return; + _reference = reference; + _dirty = true; + } + + void setCurrentRatio(unsigned char channel, double current_ratio) { + if (channel >= _channels) return; + if (_current_ratio[channel] == current_ratio) return; + _current_ratio[channel] = current_ratio; + _dirty = true; + } + + // --------------------------------------------------------------------- + + double getVoltage() { + return _voltage; + } + + double getReference() { + return _reference; + } + + double getCurrentRatio(unsigned char channel) { + if (channel >= _channels) return 0; + return _current_ratio[channel]; + } + + unsigned char getChannels() { + return _channels; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + void begin() { + + // Resolution + _adc_counts = 1 << _resolution; + + // Calculations + for (unsigned char i=0; i<_channels; i++) { + _energy[i] = _current[i] = 0; + _pivot[i] = _adc_counts >> 1; + _current_factor[i] = _current_ratio[i] * _reference / _adc_counts; + _multiplier[i] = calculateMultiplier(_current_factor[i]); + } + + #if SENSOR_DEBUG + DEBUG_MSG("[EMON] Reference (mV): %d\n", int(1000 * _reference)); + DEBUG_MSG("[EMON] ADC counts: %d\n", _adc_counts); + for (unsigned char i=0; i<_channels; i++) { + DEBUG_MSG("[EMON] Channel #%d current ratio (mA/V): %d\n", i, int(1000 * _current_ratio[i])); + DEBUG_MSG("[EMON] Channel #%d current factor (mA/bit): %d\n", i, int(1000 * _current_factor[i])); + DEBUG_MSG("[EMON] Channel #%d Multiplier: %d\n", i, int(_multiplier[i])); + } + #endif + + _ready = true; + _dirty = false; + + + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + // Initializes internal variables + void init() { + _current_ratio = new double[_channels]; + _current_factor = new double[_channels]; + _multiplier = new uint16_t[_channels]; + _pivot = new double[_channels]; + _current = new double[_channels]; + #if EMON_REPORT_ENERGY + _energy = new uint32_t[_channels]; + #endif + } + + virtual unsigned int readADC(unsigned char channel) {} + + unsigned int calculateMultiplier(double current_factor) { + unsigned int s = 1; + unsigned int i = 1; + unsigned int m = s * i; + unsigned int multiplier; + while (m * current_factor < 1) { + multiplier = m; + i = (i == 1) ? 2 : (i == 2) ? 5 : 1; + if (i == 1) s *= 10; + m = s * i; + } + return multiplier; + } + + double read(unsigned char channel) { + + int max = 0; + int min = _adc_counts; + double sum = 0; + + unsigned long time_span = millis(); + for (unsigned long i=0; i<_samples; i++) { + + int sample; + double filtered; + + // Read analog value + sample = readADC(channel); + if (sample > max) max = sample; + if (sample < min) min = sample; + + // Digital low pass filter extracts the VDC offset + _pivot[channel] = (_pivot[channel] + (sample - _pivot[channel]) / EMON_FILTER_SPEED); + filtered = sample - _pivot[channel]; + + // Root-mean-square method + sum += (filtered * filtered); + + } + time_span = millis() - time_span; + + // Quick fix + if (_pivot[channel] < min || max < _pivot[channel]) { + _pivot[channel] = (max + min) / 2.0; + } + + // Calculate current + double rms = _samples > 0 ? sqrt(sum / _samples) : 0; + double current = _current_factor[channel] * rms; + current = (double) (int(current * _multiplier[channel]) - 1) / _multiplier[channel]; + if (current < 0) current = 0; + + #if SENSOR_DEBUG + DEBUG_MSG("[EMON] Channel: %d\n", channel); + DEBUG_MSG("[EMON] Total samples: %d\n", _samples); + DEBUG_MSG("[EMON] Total time (ms): %d\n", time_span); + DEBUG_MSG("[EMON] Sample frequency (Hz): %d\n", int(1000 * _samples / time_span)); + DEBUG_MSG("[EMON] Max value: %d\n", max); + DEBUG_MSG("[EMON] Min value: %d\n", min); + DEBUG_MSG("[EMON] Midpoint value: %d\n", int(_pivot[channel])); + DEBUG_MSG("[EMON] RMS value: %d\n", int(rms)); + DEBUG_MSG("[EMON] Current (mA): %d\n", int(current)); + #endif + + // Check timing + if ((time_span > EMON_MAX_TIME) + || ((time_span < EMON_MAX_TIME) && (_samples < EMON_MAX_SAMPLES))) { + _samples = (_samples * EMON_MAX_TIME) / time_span; + } + + return current; + + } + + unsigned char _channels = 0; // Number of ADC channels available + unsigned char _magnitudes = 0; // Number of magnitudes per channel + unsigned long _samples = EMON_MAX_SAMPLES; // Samples (dynamically modificable) + + unsigned char _resolution = 10; // ADC resolution in bits + unsigned long _adc_counts; // Max count + + double _voltage = EMON_MAINS_VOLTAGE; // Mains voltage + double _reference = EMON_REFERENCE_VOLTAGE; // ADC reference voltage (100%) + + double * _current_ratio; // Ratio ampers in main loop to voltage in secondary (per channel) + double * _current_factor; // Calculated, reads (RMS) to current (per channel) + uint16_t * _multiplier; // Calculated, error (per channel) + + double * _pivot; // Moving average mid point (per channel) + double * _current; // Last current reading (per channel) + #if EMON_REPORT_ENERGY + uint32_t * _energy; // Aggregated energy (per channel) + #endif + + + +}; + +#endif // SENSOR_SUPPORT diff --git a/espurna/sensors/EventSensor.h b/espurna/sensors/EventSensor.h new file mode 100755 index 0000000..d46fc06 --- /dev/null +++ b/espurna/sensors/EventSensor.h @@ -0,0 +1,211 @@ +// ----------------------------------------------------------------------------- +// Event Counter Sensor +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && EVENTS_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +class EventSensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + EventSensor(): BaseSensor() { + _count = 1; + _sensor_id = SENSOR_EVENTS_ID; + } + + ~EventSensor() { + _enableInterrupts(false); + } + + // --------------------------------------------------------------------- + + void setGPIO(unsigned char gpio) { + _gpio = gpio; + } + + void setMode(unsigned char mode) { + _mode = mode; + } + + void setInterruptMode(unsigned char mode) { + _interrupt_mode = mode; + } + + void setDebounceTime(unsigned long debounce) { + _debounce = debounce; + } + + // --------------------------------------------------------------------- + + unsigned char getGPIO() { + return _gpio; + } + + unsigned char getMode() { + return _mode; + } + + unsigned char getInterruptMode() { + return _interrupt_mode; + } + + unsigned long getDebounceTime() { + return _debounce; + } + + // --------------------------------------------------------------------- + // Sensors API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + // Defined outside the class body + void begin() { + pinMode(_gpio, _mode); + _enableInterrupts(true); + _ready = true; + } + + // Descriptive name of the sensor + String description() { + char buffer[20]; + snprintf(buffer, sizeof(buffer), "INTERRUPT @ GPIO%d", _gpio); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return String(_gpio); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_EVENTS; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) { + double value = _events; + _events = 0; + return value; + }; + return 0; + } + + // Handle interrupt calls + void handleInterrupt(unsigned char gpio) { + (void) gpio; + static unsigned long last = 0; + if (millis() - last > _debounce) { + _events = _events + 1; + last = millis(); + } + } + + protected: + + // --------------------------------------------------------------------- + // Interrupt management + // --------------------------------------------------------------------- + + void _attach(EventSensor * instance, unsigned char gpio, unsigned char mode); + void _detach(unsigned char gpio); + + void _enableInterrupts(bool value) { + + static unsigned char _interrupt_gpio = GPIO_NONE; + + if (value) { + if (_interrupt_gpio != GPIO_NONE) _detach(_interrupt_gpio); + _attach(this, _gpio, _interrupt_mode); + _interrupt_gpio = _gpio; + } else if (_interrupt_gpio != GPIO_NONE) { + _detach(_interrupt_gpio); + _interrupt_gpio = GPIO_NONE; + } + + } + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + volatile unsigned long _events = 0; + unsigned long _debounce = EVENTS_DEBOUNCE; + unsigned char _gpio; + unsigned char _mode; + unsigned char _interrupt_mode; + +}; + +// ----------------------------------------------------------------------------- +// Interrupt helpers +// ----------------------------------------------------------------------------- + +EventSensor * _event_sensor_instance[10] = {NULL}; + +void ICACHE_RAM_ATTR _event_sensor_isr(unsigned char gpio) { + unsigned char index = gpio > 5 ? gpio-6 : gpio; + if (_event_sensor_instance[index]) { + _event_sensor_instance[index]->handleInterrupt(gpio); + } +} + +void ICACHE_RAM_ATTR _event_sensor_isr_0() { _event_sensor_isr(0); } +void ICACHE_RAM_ATTR _event_sensor_isr_1() { _event_sensor_isr(1); } +void ICACHE_RAM_ATTR _event_sensor_isr_2() { _event_sensor_isr(2); } +void ICACHE_RAM_ATTR _event_sensor_isr_3() { _event_sensor_isr(3); } +void ICACHE_RAM_ATTR _event_sensor_isr_4() { _event_sensor_isr(4); } +void ICACHE_RAM_ATTR _event_sensor_isr_5() { _event_sensor_isr(5); } +void ICACHE_RAM_ATTR _event_sensor_isr_12() { _event_sensor_isr(12); } +void ICACHE_RAM_ATTR _event_sensor_isr_13() { _event_sensor_isr(13); } +void ICACHE_RAM_ATTR _event_sensor_isr_14() { _event_sensor_isr(14); } +void ICACHE_RAM_ATTR _event_sensor_isr_15() { _event_sensor_isr(15); } + +static void (*_event_sensor_isr_list[10])() = { + _event_sensor_isr_0, _event_sensor_isr_1, _event_sensor_isr_2, + _event_sensor_isr_3, _event_sensor_isr_4, _event_sensor_isr_5, + _event_sensor_isr_12, _event_sensor_isr_13, _event_sensor_isr_14, + _event_sensor_isr_15 +}; + +void EventSensor::_attach(EventSensor * instance, unsigned char gpio, unsigned char mode) { + if (!gpioValid(gpio)) return; + _detach(gpio); + unsigned char index = gpio > 5 ? gpio-6 : gpio; + _event_sensor_instance[index] = instance; + attachInterrupt(gpio, _event_sensor_isr_list[index], mode); + #if SENSOR_DEBUG + DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str()); + #endif +} + +void EventSensor::_detach(unsigned char gpio) { + if (!gpioValid(gpio)) return; + unsigned char index = gpio > 5 ? gpio-6 : gpio; + if (_event_sensor_instance[index]) { + detachInterrupt(gpio); + #if SENSOR_DEBUG + DEBUG_MSG_P(PSTR("[SENSOR] GPIO%d interrupt detached from %s\n"), gpio, _event_sensor_instance[index]->description().c_str()); + #endif + _event_sensor_instance[index] = NULL; + } +} + +#endif // SENSOR_SUPPORT && EVENTS_SUPPORT diff --git a/espurna/sensors/GUVAS12SDSensor.h b/espurna/sensors/GUVAS12SDSensor.h new file mode 100755 index 0000000..1589196 --- /dev/null +++ b/espurna/sensors/GUVAS12SDSensor.h @@ -0,0 +1,170 @@ +// ----------------------------------------------------------------------------- +// GUVA-S12SD UV Sensor +// Copyright (C) 2017-2018 by Xose Pérez +// by Mustafa Tufan +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && GUVAS12SD_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +// http://www.eoc-inc.com/genicom/GUVA-S12SD.pdf +// +// GUVA-S12D has a wide spectral range of 200nm-400nm +// The output voltage and the UV index is linear, illumination intensity = 307 * Vsig where: Vsig is the value of voltage measured from the SIG pin of the interface, unit V. +// illumination intensity unit: mW/m2 for the combination strength of UV light with wavelength range: 200nm-400nm +// UV Index = illumination intensity / 200 +// +// UV Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 10+ +// -----------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-------- +// mV | <50 | 227 | 318 | 408 | 503 | 606 | 696 | 795 | 881 | 976 | 1079 | 1170+ +// analog val | <10 | 46 | 65 | 83 | 103 | 124 | 142 | 162 | 180 | 200 | 221 | 240+ +// + +#define UV_SAMPLE_RATE 1 + +class GUVAS12SDSensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + GUVAS12SDSensor(): BaseSensor() { + _count = 1; + _sensor_id = SENSOR_GUVAS12SD_ID; + } + + ~GUVAS12SDSensor() { + if (_previous != GPIO_NONE) gpioReleaseLock(_previous); + } + + // --------------------------------------------------------------------- + + void setGPIO(unsigned char gpio) { + _gpio = gpio; + } + + // --------------------------------------------------------------------- + + unsigned char getGPIO() { + return _gpio; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + // Manage GPIO lock + if (_previous != GPIO_NONE) gpioReleaseLock(_previous); + _previous = GPIO_NONE; + if (!gpioGetLock(_gpio)) { + _error = SENSOR_ERROR_GPIO_USED; + return; + } + _previous = _gpio; + + _ready = true; + + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + _error = SENSOR_ERROR_OK; + _read(); + } + + // Descriptive name of the sensor + String description() { + char buffer[18]; + snprintf(buffer, sizeof(buffer), "GUVAS12SD @ GPIO%d", _gpio); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return String(_gpio); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_UV; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _uvindex; + return 0; + } + + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + void _read() { + int _average = 0; + + #if UV_SAMPLE_RATE == 1 + _average = analogRead(0); + #else + for (unsigned int i=0; i < UV_SAMPLE_RATE; i++) { + _average += analogRead(0); + nice_delay(2); + } + _average = (_average / UV_SAMPLE_RATE); + #endif + // _sensormV = _average / 1023*3.3; + + if (_average < 10) { + _uvindex = 0; + } else if (_average < 46) { + _uvindex = (_average - 10) / (46-10); + } else if (_average < 65) { + _uvindex = 1 + ((_average - 46) / (65-46)); + } else if (_average < 83) { + _uvindex = 2 + ((_average - 65) / (83-65)); + } else if (_average < 103) { + _uvindex = 3 + ((_average - 83) / (103- 83)); + } else if (_average < 124) { + _uvindex = 4 + ((_average - 103) / (124-103)); + } else if (_average < 142) { + _uvindex = 5 + ((_average - 124) / (142-124)); + } else if (_average < 162) { + _uvindex = 6 + ((_average - 142) / (162-142)); + } else if (_average < 180) { + _uvindex = 7 + ((_average - 162) / (180-162)); + } else if (_average < 200) { + _uvindex = 8 + ((_average - 180) / (200-180)); + } else if (_average < 221) { + _uvindex = 9 + ((_average - 200) / (221-200)); + } else { + _uvindex = 10; + } + + return _uvindex; + } + + unsigned char _gpio = GPIO_NONE; + unsigned char _previous = GPIO_NONE; + + double _uvindex = 0; + +}; + +#endif // SENSOR_SUPPORT && GUVAS12SD_SUPPORT diff --git a/espurna/sensors/HLW8012Sensor.h b/espurna/sensors/HLW8012Sensor.h new file mode 100755 index 0000000..be21118 --- /dev/null +++ b/espurna/sensors/HLW8012Sensor.h @@ -0,0 +1,328 @@ +// ----------------------------------------------------------------------------- +// Event Counter Sensor +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && HLW8012_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +#include +#include + +class HLW8012Sensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + HLW8012Sensor(): BaseSensor() { + _count = 7; + _sensor_id = SENSOR_HLW8012_ID; + _hlw8012 = new HLW8012(); + } + + ~HLW8012Sensor() { + _enableInterrupts(false); + delete _hlw8012; + } + + void expectedCurrent(double expected) { + _hlw8012->expectedCurrent(expected); + } + + void expectedVoltage(unsigned int expected) { + _hlw8012->expectedVoltage(expected); + } + + void expectedPower(unsigned int expected) { + _hlw8012->expectedActivePower(expected); + } + + void resetRatios() { + _hlw8012->resetMultipliers(); + } + + void resetEnergy() { + _hlw8012->resetEnergy(); + } + + // --------------------------------------------------------------------- + + void setSEL(unsigned char sel) { + if (_sel == sel) return; + _sel = sel; + _dirty = true; + } + + void setCF(unsigned char cf) { + if (_cf == cf) return; + _cf = cf; + _dirty = true; + } + + void setCF1(unsigned char cf1) { + if (_cf1 == cf1) return; + _cf1 = cf1; + _dirty = true; + } + + void setSELCurrent(bool value) { + _sel_current = value; + } + + void setCurrentRatio(double value) { + _hlw8012->setCurrentMultiplier(value); + }; + + void setVoltageRatio(double value) { + _hlw8012->setVoltageMultiplier(value); + }; + + void setPowerRatio(double value) { + _hlw8012->setPowerMultiplier(value); + }; + + // --------------------------------------------------------------------- + + unsigned char getSEL() { + return _sel; + } + + unsigned char getCF() { + return _cf; + } + + unsigned char getCF1() { + return _cf1; + } + + unsigned char getSELCurrent() { + return _sel_current; + } + + double getCurrentRatio() { + return _hlw8012->getCurrentMultiplier(); + }; + + double getVoltageRatio() { + return _hlw8012->getVoltageMultiplier(); + }; + + double getPowerRatio() { + return _hlw8012->getPowerMultiplier(); + }; + + // --------------------------------------------------------------------- + // Sensors API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + // Defined outside the class body + void begin() { + + // Initialize HLW8012 + // void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT); + // * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC + // * currentWhen is the value in sel_pin to select current sampling + // * set use_interrupts to true to use interrupts to monitor pulse widths + // * leave pulse_timeout to the default value, recommended when using interrupts + #if HLW8012_USE_INTERRUPTS + _hlw8012->begin(_cf, _cf1, _sel, _sel_current, true); + #else + _hlw8012->begin(_cf, _cf1, _sel, _sel_current, false, 1000000); + #endif + + // These values are used to calculate current, voltage and power factors as per datasheet formula + // These are the nominal values for the Sonoff POW resistors: + // * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line + // * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012 + // * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012 + _hlw8012->setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN); + + // Handle interrupts + #if HLW8012_USE_INTERRUPTS + _enableInterrupts(true); + #else + _onconnect_handler = WiFi.onStationModeGotIP([this](WiFiEventStationModeGotIP ipInfo) { + _enableInterrupts(true); + }); + _ondisconnect_handler = WiFi.onStationModeDisconnected([this](WiFiEventStationModeDisconnected ipInfo) { + _enableInterrupts(false); + }); + #endif + + _ready = true; + + } + + // Descriptive name of the sensor + String description() { + char buffer[25]; + snprintf(buffer, sizeof(buffer), "HLW8012 @ GPIO(%u,%u,%u)", _sel, _cf, _cf1); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + char buffer[10]; + snprintf(buffer, sizeof(buffer), "%u:%u:%u", _sel, _cf, _cf1); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_CURRENT; + if (index == 1) return MAGNITUDE_VOLTAGE; + if (index == 2) return MAGNITUDE_POWER_ACTIVE; + if (index == 3) return MAGNITUDE_POWER_REACTIVE; + if (index == 4) return MAGNITUDE_POWER_APPARENT; + if (index == 5) return MAGNITUDE_POWER_FACTOR; + if (index == 6) return MAGNITUDE_ENERGY; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _hlw8012->getCurrent(); + if (index == 1) return _hlw8012->getVoltage(); + if (index == 2) return _hlw8012->getActivePower(); + if (index == 3) return _hlw8012->getReactivePower(); + if (index == 4) return _hlw8012->getApparentPower(); + if (index == 5) return 100 * _hlw8012->getPowerFactor(); + if (index == 6) return _hlw8012->getEnergy(); + return 0; + } + + // Toggle between current and voltage monitoring + #if HLW8012_USE_INTERRUPTS == 0 + // Post-read hook (usually to reset things) + void post() { _hlw8012->toggleMode(); } + #endif // HLW8012_USE_INTERRUPTS == 0 + + // Handle interrupt calls + void ICACHE_RAM_ATTR handleInterrupt(unsigned char gpio) { + if (gpio == _cf) _hlw8012->cf_interrupt(); + if (gpio == _cf1) _hlw8012->cf1_interrupt(); + } + + protected: + + // --------------------------------------------------------------------- + // Interrupt management + // --------------------------------------------------------------------- + + void _attach(HLW8012Sensor * instance, unsigned char gpio, unsigned char mode); + void _detach(unsigned char gpio); + + void _enableInterrupts(bool value) { + + static unsigned char _interrupt_cf = GPIO_NONE; + static unsigned char _interrupt_cf1 = GPIO_NONE; + + if (value) { + + if (_interrupt_cf != _cf) { + if (_interrupt_cf != GPIO_NONE) _detach(_interrupt_cf); + _attach(this, _cf, CHANGE); + _interrupt_cf = _cf; + } + + if (_interrupt_cf1 != _cf1) { + if (_interrupt_cf1 != GPIO_NONE) _detach(_interrupt_cf1); + _attach(this, _cf1, CHANGE); + _interrupt_cf1 = _cf1; + } + + } else { + + _detach(_cf); + _detach(_cf1); + _interrupt_cf = GPIO_NONE; + _interrupt_cf1 = GPIO_NONE; + + } + + } + + // --------------------------------------------------------------------- + + unsigned char _sel = GPIO_NONE; + unsigned char _cf = GPIO_NONE; + unsigned char _cf1 = GPIO_NONE; + bool _sel_current = true; + + HLW8012 * _hlw8012 = NULL; + + #if HLW8012_USE_INTERRUPTS == 0 + WiFiEventHandler _onconnect_handler; + WiFiEventHandler _ondisconnect_handler; + #endif + +}; + +// ----------------------------------------------------------------------------- +// Interrupt helpers +// ----------------------------------------------------------------------------- + +HLW8012Sensor * _hlw8012_sensor_instance[10] = {NULL}; + +void ICACHE_RAM_ATTR _hlw8012_sensor_isr(unsigned char gpio) { + unsigned char index = gpio > 5 ? gpio-6 : gpio; + if (_hlw8012_sensor_instance[index]) { + _hlw8012_sensor_instance[index]->handleInterrupt(gpio); + } +} + +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_0() { _hlw8012_sensor_isr(0); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_1() { _hlw8012_sensor_isr(1); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_2() { _hlw8012_sensor_isr(2); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_3() { _hlw8012_sensor_isr(3); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_4() { _hlw8012_sensor_isr(4); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_5() { _hlw8012_sensor_isr(5); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_12() { _hlw8012_sensor_isr(12); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_13() { _hlw8012_sensor_isr(13); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_14() { _hlw8012_sensor_isr(14); } +void ICACHE_RAM_ATTR _hlw8012_sensor_isr_15() { _hlw8012_sensor_isr(15); } + +static void (*_hlw8012_sensor_isr_list[10])() = { + _hlw8012_sensor_isr_0, _hlw8012_sensor_isr_1, _hlw8012_sensor_isr_2, + _hlw8012_sensor_isr_3, _hlw8012_sensor_isr_4, _hlw8012_sensor_isr_5, + _hlw8012_sensor_isr_12, _hlw8012_sensor_isr_13, _hlw8012_sensor_isr_14, + _hlw8012_sensor_isr_15 +}; + +void HLW8012Sensor::_attach(HLW8012Sensor * instance, unsigned char gpio, unsigned char mode) { + if (!gpioValid(gpio)) return; + _detach(gpio); + unsigned char index = gpio > 5 ? gpio-6 : gpio; + _hlw8012_sensor_instance[index] = instance; + attachInterrupt(gpio, _hlw8012_sensor_isr_list[index], mode); + #if SENSOR_DEBUG + DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt attached to %s\n"), gpio, instance->description().c_str()); + #endif +} + +void HLW8012Sensor::_detach(unsigned char gpio) { + if (!gpioValid(gpio)) return; + unsigned char index = gpio > 5 ? gpio-6 : gpio; + if (_hlw8012_sensor_instance[index]) { + detachInterrupt(gpio); + #if SENSOR_DEBUG + DEBUG_MSG_P(PSTR("[SENSOR] GPIO%u interrupt detached from %s\n"), gpio, _hlw8012_sensor_instance[index]->description().c_str()); + #endif + _hlw8012_sensor_instance[index] = NULL; + } +} + +#endif // SENSOR_SUPPORT && HLW8012_SUPPORT diff --git a/espurna/sensors/I2CSensor.h b/espurna/sensors/I2CSensor.h new file mode 100755 index 0000000..3b1b8ae --- /dev/null +++ b/espurna/sensors/I2CSensor.h @@ -0,0 +1,95 @@ +// ----------------------------------------------------------------------------- +// Abstract I2C sensor class (other sensor classes extend this class) +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && ( I2C_SUPPORT || EMON_ANALOG_SUPPORT ) + +#if I2C_USE_BRZO + #define I2C_TRANS_SUCCESS 0 // All i2c commands were executed without errors + #define I2C_TRANS_ERROR_BUS_NOT_FREE 1 // Bus not free, i.e. either SDA or SCL is low + #define I2C_TRANS_ERROR_NACK_WRITE 2 // Not ACK ("NACK") by slave during write: + // Either the slave did not respond to the given slave address; or the slave did not ACK a byte transferred by the master. + #define I2C_TRANS_ERROR_NACK_READ 4 // Not ACK ("NACK") by slave during read, + // i.e. slave did not respond to the given slave address + #define I2C_TRANS_ERROR_CLOCK 8 // Clock Stretching by slave exceeded maximum clock stretching time. Most probably, there is a bus stall now! + #define I2C_TRANS_ERROR_READ_NULL 16 // Read was called with 0 bytes to be read by the master. Command not sent to the slave, since this could yield to a bus stall + #define I2C_TRANS_ERROR_TIMEOUT 32 // ACK Polling timeout exceeded +#else // Wire + #define I2C_TRANS_SUCCESS 0 // success + #define I2C_TRANS_ERROR_BUFFER_OVERLOW 1 // data too long to fit in transmit buffer + #define I2C_TRANS_ERROR_NACK_ADDRESS 2 // received NACK on transmit of address + #define I2C_TRANS_ERROR_NACK_DATA 3 // received NACK on transmit of data + #define I2C_TRANS_ERROR_OTHER 4 // other error +#endif + +#pragma once + +#include "BaseSensor.h" + +class I2CSensor : public BaseSensor { + + public: + + void setAddress(unsigned char address) { + if (_address == address) return; + _address = address; + _dirty = true; + } + + unsigned char getAddress() { + return _address; + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + char buffer[5]; + snprintf(buffer, sizeof(buffer), "0x%02X", _address); + return String(buffer); + } + + protected: + + // Specific for I2C sensors + unsigned char _begin_i2c(unsigned char address, size_t size, unsigned char * addresses) { + + // If we have already locked this address for this sensor quit + if ((address > 0) && (address == _previous_address)) { + return _previous_address; + } + + // Check if we should release a previously locked address + if ((_previous_address > 0) && (_previous_address != address)) { + i2cReleaseLock(_previous_address); + _previous_address = 0; + } + + // If requesting a specific address, try to ger a lock to it + if ((0 < address) && i2cGetLock(address)) { + _previous_address = address; + return _previous_address; + } + + // If everything else fails, perform an auto-discover + _previous_address = i2cFindAndLock(size, addresses); + + // Flag error + if (0 == _previous_address) { + _error = SENSOR_ERROR_I2C; + } + + return _previous_address; + + } + + unsigned char _previous_address = 0; + unsigned char _address = 0; + +}; + +#endif // SENSOR_SUPPORT && I2C_SUPPORT diff --git a/espurna/sensors/MHZ19Sensor.h b/espurna/sensors/MHZ19Sensor.h new file mode 100755 index 0000000..105adf2 --- /dev/null +++ b/espurna/sensors/MHZ19Sensor.h @@ -0,0 +1,221 @@ +// ----------------------------------------------------------------------------- +// MHZ19 CO2 sensor +// Based on: https://github.com/nara256/mhz19_uart +// http://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf +// Uses SoftwareSerial library +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && MHZ19_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" +#include + +#define MHZ19_REQUEST_LEN 8 +#define MHZ19_RESPONSE_LEN 9 +#define MHZ19_TIMEOUT 1000 +#define MHZ19_GETPPM 0x8600 +#define MHZ19_ZEROCALIB 0x8700 +#define MHZ19_SPANCALIB 0x8800 +#define MHZ19_AUTOCALIB_ON 0x79A0 +#define MHZ19_AUTOCALIB_OFF 0x7900 + +class MHZ19Sensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + MHZ19Sensor(): BaseSensor() { + _count = 1; + _sensor_id = SENSOR_MHZ19_ID; + } + + ~MHZ19Sensor() { + if (_serial) delete _serial; + } + + // --------------------------------------------------------------------- + + void setRX(unsigned char pin_rx) { + if (_pin_rx == pin_rx) return; + _pin_rx = pin_rx; + _dirty = true; + } + + void setTX(unsigned char pin_tx) { + if (_pin_tx == pin_tx) return; + _pin_tx = pin_tx; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getRX() { + return _pin_rx; + } + + unsigned char getTX() { + return _pin_tx; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + if (_serial) delete _serial; + + _serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32); + _serial->enableIntTx(false); + _serial->begin(9600); + calibrateAuto(false); + + _ready = true; + _dirty = false; + + } + + // Descriptive name of the sensor + String description() { + char buffer[28]; + snprintf(buffer, sizeof(buffer), "MHZ19 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + char buffer[6]; + snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_CO2; + return MAGNITUDE_NONE; + } + + void pre() { + _read(); + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _co2; + return 0; + } + + void calibrateAuto(boolean state){ + _write(state ? MHZ19_AUTOCALIB_ON : MHZ19_AUTOCALIB_OFF); + } + + void calibrateZero() { + _write(MHZ19_ZEROCALIB); + } + + void calibrateSpan(unsigned int ppm) { + if( ppm < 1000 ) return; + unsigned char buffer[MHZ19_REQUEST_LEN] = {0}; + buffer[0] = 0xFF; + buffer[1] = 0x01; + buffer[2] = MHZ19_SPANCALIB >> 8; + buffer[3] = ppm >> 8; + buffer[4] = ppm & 0xFF; + _write(buffer); + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + void _write(unsigned char * command) { + _serial->write(command, MHZ19_REQUEST_LEN); + _serial->write(_checksum(command)); + _serial->flush(); + } + + void _write(unsigned int command, unsigned char * response) { + + unsigned char buffer[MHZ19_REQUEST_LEN] = {0}; + buffer[0] = 0xFF; + buffer[1] = 0x01; + buffer[2] = command >> 8; + buffer[3] = command & 0xFF; + _write(buffer); + + if (response != NULL) { + unsigned long start = millis(); + while (_serial->available() == 0) { + if (millis() - start > MHZ19_TIMEOUT) { + _error = SENSOR_ERROR_TIMEOUT; + return; + } + yield(); + } + _serial->readBytes(response, MHZ19_RESPONSE_LEN); + } + + } + + void _write(unsigned int command) { + _write(command, NULL); + } + + void _read() { + + unsigned char buffer[MHZ19_RESPONSE_LEN] = {0}; + _write(MHZ19_GETPPM, buffer); + + // Check response + if ((buffer[0] == 0xFF) + && (buffer[1] == 0x86) + && (_checksum(buffer) == buffer[MHZ19_RESPONSE_LEN-1])) { + + unsigned int value = buffer[2] * 256 + buffer[3]; + if (0 <= value && value <= 5000) { + _co2 = value; + _error = SENSOR_ERROR_OK; + } else { + _error = SENSOR_ERROR_OUT_OF_RANGE; + } + + } else { + _error = SENSOR_ERROR_CRC; + } + + } + + uint8_t _checksum(uint8_t * command) { + uint8_t sum = 0x00; + for (unsigned char i = 1; i < MHZ19_REQUEST_LEN-1; i++) { + sum += command[i]; + } + sum = 0xFF - sum + 0x01; + return sum; + } + + double _co2 = 0; + unsigned int _pin_rx; + unsigned int _pin_tx; + SoftwareSerial * _serial = NULL; + +}; + +#endif // SENSOR_SUPPORT && MHZ19_SUPPORT diff --git a/espurna/sensors/PMSX003Sensor.h b/espurna/sensors/PMSX003Sensor.h new file mode 100755 index 0000000..95e5933 --- /dev/null +++ b/espurna/sensors/PMSX003Sensor.h @@ -0,0 +1,152 @@ +// ----------------------------------------------------------------------------- +// PMSX003 Dust Sensor +// Uses SoftwareSerial library +// Contribution by Òscar Rovira López +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && PMSX003_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +#include +#include + +class PMSX003Sensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + PMSX003Sensor(): BaseSensor() { + _count = 3; + _sensor_id = SENSOR_PMSX003_ID; + } + + ~PMSX003Sensor() { + if (_serial) delete _serial; + if (_pms) delete _pms; + } + + void setRX(unsigned char pin_rx) { + if (_pin_rx == pin_rx) return; + _pin_rx = pin_rx; + _dirty = true; + } + + void setTX(unsigned char pin_tx) { + if (_pin_tx == pin_tx) return; + _pin_tx = pin_tx; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getRX() { + return _pin_rx; + } + + unsigned char getTX() { + return _pin_tx; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + if (_serial) delete _serial; + if (_pms) delete _pms; + + _serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32); + _serial->enableIntTx(false); + _serial->begin(9600); + _pms = new PMS(* _serial); + _pms->passiveMode(); + + _startTime = millis(); + _ready = true; + _dirty = false; + + } + + // Descriptive name of the sensor + String description() { + char buffer[28]; + snprintf(buffer, sizeof(buffer), "PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + char buffer[36] = {0}; + if (index == 0) snprintf(buffer, sizeof(buffer), "PM1.0 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + if (index == 1) snprintf(buffer, sizeof(buffer), "PM2.5 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + if (index == 2) snprintf(buffer, sizeof(buffer), "PM10 @ PMSX003 @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + return String(buffer); + } + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + char buffer[6]; + snprintf(buffer, sizeof(buffer), "%u:%u", _pin_rx, _pin_tx); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_PM1dot0; + if (index == 1) return MAGNITUDE_PM2dot5; + if (index == 2) return MAGNITUDE_PM10; + return MAGNITUDE_NONE; + } + + void pre() { + + if (millis() - _startTime < 30000) { + _error = SENSOR_ERROR_WARM_UP; + return; + } + + _error = SENSOR_ERROR_OK; + + if(_pms->read(_data)) { + _pm1dot0 = _data.PM_AE_UG_1_0; + _pm2dot5 = _data.PM_AE_UG_2_5; + _pm10 = _data.PM_AE_UG_10_0; + } + + _pms->requestRead(); + + } + + // Current value for slot # index + double value(unsigned char index) { + if(index == 0) return _pm1dot0; + if(index == 1) return _pm2dot5; + if(index == 2) return _pm10; + return 0; + } + + protected: + unsigned int _pm1dot0; + unsigned int _pm2dot5; + unsigned int _pm10; + unsigned int _pin_rx; + unsigned int _pin_tx; + unsigned long _startTime; + SoftwareSerial * _serial = NULL; + PMS * _pms = NULL; + PMS::DATA _data; + +}; + +#endif // SENSOR_SUPPORT && PMSX003_SUPPORT diff --git a/espurna/sensors/PZEM004TSensor.h b/espurna/sensors/PZEM004TSensor.h new file mode 100755 index 0000000..2adc71e --- /dev/null +++ b/espurna/sensors/PZEM004TSensor.h @@ -0,0 +1,136 @@ +// ----------------------------------------------------------------------------- +// PZEM004T based power monitor +// Copyright (C) 2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && PZEM004T_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +#include + +class PZEM004TSensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + PZEM004TSensor(): BaseSensor(), _data() { + _count = 4; + _sensor_id = SENSOR_PZEM004T_ID; + } + + ~PZEM004TSensor() { + if (_pzem) delete _pzem; + } + + // --------------------------------------------------------------------- + + void setRX(unsigned char pin_rx) { + if (_pin_rx == pin_rx) return; + _pin_rx = pin_rx; + _dirty = true; + } + + void setTX(unsigned char pin_tx) { + if (_pin_tx == pin_tx) return; + _pin_tx = pin_tx; + _dirty = true; + } + + void setSerial(Stream & serial) { + _serial = serial; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getRX() { + return _pin_rx; + } + + unsigned char getTX() { + return _pin_tx; + } + + Stream & getSerial() { + return _serial; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + if (_pzem) delete _pzem; + if (_serial == NULL) { + _pzem = PZEM004T(_pin_rx, _pin_tx); + } else { + _pzem = PZEM004T(_serial); + } + _pzem->setAddress(_ip); + + _ready = true; + _dirty = false; + + } + + // Descriptive name of the sensor + String description() { + char buffer[28]; + snprintf(buffer, sizeof(buffer), "PZEM004T @ SwSerial(%u,%u)", _pin_rx, _pin_tx); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return _ip.toString(); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_CURRENT; + if (index == 1) return MAGNITUDE_VOLTAGE; + if (index == 2) return MAGNITUDE_POWER_ACTIVE; + if (index == 3) return MAGNITUDE_ENERGY; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _pzem->current(_ip); + if (index == 1) return _pzem->voltage(_ip); + if (index == 2) return _pzem->power(_ip); + if (index == 3) return _pzem->energy(_ip); + return 0; + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + unsigned int _pin_rx = PZEM004T_RX_PIN; + unsigned int _pin_tx = PZEM004T_TX_PIN; + Stream & _serial = NULL; + IPAddress _ip(192,168,1,1); + PZEM004T * _pzem = NULL; + +}; + +#endif // SENSOR_SUPPORT && PZEM004T_SUPPORT diff --git a/espurna/sensors/SHT3XI2CSensor.h b/espurna/sensors/SHT3XI2CSensor.h new file mode 100755 index 0000000..46ea2cc --- /dev/null +++ b/espurna/sensors/SHT3XI2CSensor.h @@ -0,0 +1,89 @@ +// ----------------------------------------------------------------------------- +// SHT3X Sensor over I2C (Wemos) +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && SHT3X_I2C_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "I2CSensor.h" + +class SHT3XI2CSensor : public I2CSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + SHT3XI2CSensor(): I2CSensor() { + _sensor_id = SENSOR_SHT3X_I2C_ID; + _count = 2; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + // I2C auto-discover + unsigned char addresses[] = {0x45}; + _address = _begin_i2c(_address, sizeof(addresses), addresses); + if (_address == 0) return; + + _ready = true; + _dirty = false; + + } + + // Descriptive name of the sensor + String description() { + char buffer[25]; + snprintf(buffer, sizeof(buffer), "SHT3X @ I2C (0x%02X)", _address); + return String(buffer); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_TEMPERATURE; + if (index == 1) return MAGNITUDE_HUMIDITY; + return MAGNITUDE_NONE; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + + _error = SENSOR_ERROR_OK; + + unsigned char buffer[6]; + i2c_write_uint8(_address, 0x2C, 0x06); + nice_delay(500); + i2c_read_buffer(_address, buffer, 6); + + // cTemp msb, cTemp lsb, cTemp crc, humidity msb, humidity lsb, humidity crc + _temperature = ((((buffer[0] * 256.0) + buffer[1]) * 175) / 65535.0) - 45; + _humidity = ((((buffer[3] * 256.0) + buffer[4]) * 100) / 65535.0); + + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _temperature; + if (index == 1) return _humidity; + return 0; + } + + protected: + + double _temperature = 0; + unsigned char _humidity = 0; + +}; + +#endif // SENSOR_SUPPORT && SHT3X_I2C_SUPPORT diff --git a/espurna/sensors/SI7021Sensor.h b/espurna/sensors/SI7021Sensor.h new file mode 100755 index 0000000..991f082 --- /dev/null +++ b/espurna/sensors/SI7021Sensor.h @@ -0,0 +1,168 @@ +// ----------------------------------------------------------------------------- +// SI7021 / HTU21D Sensor over I2C +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && SI7021_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "I2CSensor.h" + +#define SI7021_SCL_FREQUENCY 200 + +#define SI7021_CHIP_SI7021 0x15 +#define SI7021_CHIP_HTU21D 0x32 + +#define SI7021_CMD_TMP_HOLD 0xE3 +#define SI7021_CMD_HUM_HOLD 0xE5 +#define SI7021_CMD_TMP_NOHOLD 0xF3 +#define SI7021_CMD_HUM_NOHOLD 0xF5 + +PROGMEM const char si7021_chip_si7021_name[] = "SI7021"; +PROGMEM const char si7021_chip_htu21d_name[] = "HTU21D"; + +class SI7021Sensor : public I2CSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + SI7021Sensor(): I2CSensor() { + _sensor_id = SENSOR_SI7021_ID; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + if (!_dirty) return; + _init(); + _dirty = !_ready; + } + + // Descriptive name of the sensor + String description() { + char name[10]; + strncpy_P(name, + _chip == SI7021_CHIP_SI7021 ? + si7021_chip_si7021_name : + si7021_chip_htu21d_name, + sizeof(name) + ); + char buffer[25]; + snprintf(buffer, sizeof(buffer), "%s @ I2C (0x%02X)", name, _address); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_TEMPERATURE; + if (index == 1) return MAGNITUDE_HUMIDITY; + return MAGNITUDE_NONE; + } + + // Pre-read hook (usually to populate registers with up-to-date data) + void pre() { + + _error = SENSOR_ERROR_UNKNOWN_ID; + if (_chip == 0) return; + _error = SENSOR_ERROR_OK; + + double value; + + value = _read(SI7021_CMD_TMP_NOHOLD); + if (_error != SENSOR_ERROR_OK) return; + _temperature = (175.72 * value / 65536) - 46.85; + + value = _read(SI7021_CMD_HUM_NOHOLD); + if (_error != SENSOR_ERROR_OK) return; + value = (125.0 * value / 65536) - 6; + _humidity = constrain(value, 0, 100); + + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _temperature; + if (index == 1) return _humidity; + return 0; + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + void _init() { + + // I2C auto-discover + unsigned char addresses[] = {0x40}; + _address = _begin_i2c(_address, sizeof(addresses), addresses); + if (_address == 0) return; + + // Check device + i2c_write_uint8(_address, 0xFC, 0xC9); + _chip = i2c_read_uint8(_address); + + if ((_chip != SI7021_CHIP_SI7021) & (_chip != SI7021_CHIP_HTU21D)) { + + _count = 0; + i2cReleaseLock(_address); + _previous_address = 0; + _error = SENSOR_ERROR_UNKNOWN_ID; + + // Setting _address to 0 forces auto-discover + // This might be necessary at this stage if there is a + // different sensor in the hardcoded address + _address = 0; + + } else { + _count = 2; + } + + _ready = true; + + } + + unsigned int _read(uint8_t command) { + + // Request measurement + i2c_write_uint8(_address, command); + + // When not using clock stretching (*_NOHOLD commands) delay here + // is needed to wait for the measurement. + // According to datasheet the max. conversion time is ~22ms + unsigned long start = millis(); + nice_delay(50); + + // Clear the last to bits of LSB to 00. + // According to datasheet LSB of RH is always xxxxxx10 + unsigned int value = i2c_read_uint16(_address) & 0xFFFC; + + // We should be checking there are no pending bytes in the buffer + // and raise a CRC error if there are + _error = SENSOR_ERROR_OK; + + return value; + + } + + unsigned char _chip; + double _temperature = 0; + double _humidity = 0; + +}; + +#endif // SENSOR_SUPPORT && SI7021_SUPPORT diff --git a/espurna/sensors/V9261FSensor.h b/espurna/sensors/V9261FSensor.h new file mode 100755 index 0000000..c80ab44 --- /dev/null +++ b/espurna/sensors/V9261FSensor.h @@ -0,0 +1,259 @@ +// ----------------------------------------------------------------------------- +// V9261F based power monitor +// Copyright (C) 2017-2018 by Xose Pérez +// ----------------------------------------------------------------------------- + +#if SENSOR_SUPPORT && V9261F_SUPPORT + +#pragma once + +#include "Arduino.h" +#include "BaseSensor.h" + +#include + +class V9261FSensor : public BaseSensor { + + public: + + // --------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------- + + V9261FSensor(): BaseSensor(), _data() { + _count = 6; + _sensor_id = SENSOR_V9261F_ID; + } + + ~V9261FSensor() { + if (_serial) delete _serial; + } + + // --------------------------------------------------------------------- + + void setRX(unsigned char pin_rx) { + if (_pin_rx == pin_rx) return; + _pin_rx = pin_rx; + _dirty = true; + } + + void setInverted(bool inverted) { + if (_inverted == inverted) return; + _inverted = inverted; + _dirty = true; + } + + // --------------------------------------------------------------------- + + unsigned char getRX() { + return _pin_rx; + } + + bool getInverted() { + return _inverted; + } + + // --------------------------------------------------------------------- + // Sensor API + // --------------------------------------------------------------------- + + // Initialization method, must be idempotent + void begin() { + + if (!_dirty) return; + + if (_serial) delete _serial; + + _serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32); + _serial->enableIntTx(false); + _serial->begin(V9261F_BAUDRATE); + + _ready = true; + _dirty = false; + + } + + // Descriptive name of the sensor + String description() { + char buffer[28]; + snprintf(buffer, sizeof(buffer), "V9261F @ SwSerial(%u,NULL)", _pin_rx); + return String(buffer); + } + + // Descriptive name of the slot # index + String slot(unsigned char index) { + return description(); + }; + + // Address of the sensor (it could be the GPIO or I2C address) + String address(unsigned char index) { + return String(_pin_rx); + } + + // Loop-like method, call it in your main loop + void tick() { + _read(); + } + + // Type for slot # index + unsigned char type(unsigned char index) { + if (index == 0) return MAGNITUDE_CURRENT; + if (index == 1) return MAGNITUDE_VOLTAGE; + if (index == 2) return MAGNITUDE_POWER_ACTIVE; + if (index == 3) return MAGNITUDE_POWER_REACTIVE; + if (index == 4) return MAGNITUDE_POWER_APPARENT; + if (index == 5) return MAGNITUDE_POWER_FACTOR; + return MAGNITUDE_NONE; + } + + // Current value for slot # index + double value(unsigned char index) { + if (index == 0) return _current; + if (index == 1) return _voltage; + if (index == 2) return _active; + if (index == 3) return _reactive; + if (index == 4) return _apparent; + if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100; + return 0; + } + + protected: + + // --------------------------------------------------------------------- + // Protected + // --------------------------------------------------------------------- + + void _read() { + + static unsigned char state = 0; + static unsigned long last = 0; + static bool found = false; + static unsigned char index = 0; + + if (state == 0) { + + while (_serial->available()) { + _serial->flush(); + found = true; + last = millis(); + } + + if (found && (millis() - last > V9261F_SYNC_INTERVAL)) { + _serial->flush(); + index = 0; + state = 1; + } + + } else if (state == 1) { + + while (_serial->available()) { + _serial->read(); + if (index++ >= 7) { + _serial->flush(); + index = 0; + state = 2; + } + } + + } else if (state == 2) { + + while (_serial->available()) { + _data[index] = _serial->read(); + if (index++ >= 19) { + _serial->flush(); + last = millis(); + state = 3; + } + } + + } else if (state == 3) { + + if (_checksum()) { + + _active = (double) ( + (_data[3]) + + (_data[4] << 8) + + (_data[5] << 16) + + (_data[6] << 24) + ) / _ratioP; + + _reactive = (double) ( + (_data[7]) + + (_data[8] << 8) + + (_data[9] << 16) + + (_data[10] << 24) + ) / _ratioR; + + _voltage = (double) ( + (_data[11]) + + (_data[12] << 8) + + (_data[13] << 16) + + (_data[14] << 24) + ) / _ratioV; + + _current = (double) ( + (_data[15]) + + (_data[16] << 8) + + (_data[17] << 16) + + (_data[18] << 24) + ) / _ratioC; + + if (_active < 0) _active = 0; + if (_reactive < 0) _reactive = 0; + if (_voltage < 0) _voltage = 0; + if (_current < 0) _current = 0; + + _apparent = sqrt(_reactive * _reactive + _active * _active); + + } + + last = millis(); + index = 0; + state = 4; + + } else if (state == 4) { + + while (_serial->available()) { + _serial->flush(); + last = millis(); + } + + if (millis() - last > V9261F_SYNC_INTERVAL) { + state = 1; + } + + } + + } + + bool _checksum() { + unsigned char checksum = 0; + for (unsigned char i = 0; i < 19; i++) { + checksum = checksum + _data[i]; + } + checksum = ~checksum + 0x33; + return checksum == _data[19]; + } + + // --------------------------------------------------------------------- + + unsigned int _pin_rx = V9261F_PIN; + bool _inverted = V9261F_PIN_INVERSE; + SoftwareSerial * _serial = NULL; + + double _active = 0; + double _reactive = 0; + double _voltage = 0; + double _current = 0; + double _apparent = 0; + + double _ratioP = V9261F_POWER_FACTOR; + double _ratioC = V9261F_CURRENT_FACTOR; + double _ratioV = V9261F_VOLTAGE_FACTOR; + double _ratioR = V9261F_RPOWER_FACTOR; + + unsigned char _data[24]; + +}; + +#endif // SENSOR_SUPPORT && V9261F_SUPPORT diff --git a/espurna/settings.ino b/espurna/settings.ino new file mode 100755 index 0000000..cc07c7b --- /dev/null +++ b/espurna/settings.ino @@ -0,0 +1,479 @@ +/* + +SETTINGS MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#include +#include +#include "libs/EmbedisWrap.h" +#include + +#ifdef DEBUG_PORT + #define EMBEDIS_PORT DEBUG_PORT +#else + #define EMBEDIS_PORT Serial +#endif + +#if TELNET_SUPPORT + #include "libs/StreamInjector.h" + StreamInjector _serial = StreamInjector(EMBEDIS_PORT, TERMINAL_BUFFER_SIZE); + #undef EMBEDIS_PORT + #define EMBEDIS_PORT _serial +#endif + +EmbedisWrap embedis(EMBEDIS_PORT, TERMINAL_BUFFER_SIZE); + +#if TERMINAL_SUPPORT +#if SERIAL_RX_ENABLED + char _serial_rx_buffer[TERMINAL_BUFFER_SIZE]; + static unsigned char _serial_rx_pointer = 0; +#endif // SERIAL_RX_ENABLED +#endif // TERMINAL_SUPPORT + +bool _settings_save = false; + +// ----------------------------------------------------------------------------- +// Reverse engineering EEPROM storage format +// ----------------------------------------------------------------------------- + +unsigned long settingsSize() { + unsigned pos = SPI_FLASH_SEC_SIZE - 1; + while (size_t len = EEPROM.read(pos)) { + pos = pos - len - 2; + } + return SPI_FLASH_SEC_SIZE - pos; +} + +// ----------------------------------------------------------------------------- + +unsigned int _settingsKeyCount() { + unsigned count = 0; + unsigned pos = SPI_FLASH_SEC_SIZE - 1; + while (size_t len = EEPROM.read(pos)) { + pos = pos - len - 2; + len = EEPROM.read(pos); + pos = pos - len - 2; + count ++; + } + return count; +} + +String _settingsKeyName(unsigned int index) { + + String s; + + unsigned count = 0; + unsigned pos = SPI_FLASH_SEC_SIZE - 1; + while (size_t len = EEPROM.read(pos)) { + pos = pos - len - 2; + if (count == index) { + s.reserve(len); + for (unsigned char i = 0 ; i < len; i++) { + s += (char) EEPROM.read(pos + i + 1); + } + break; + } + count++; + len = EEPROM.read(pos); + pos = pos - len - 2; + } + + return s; + +} + +std::vector _settingsKeys() { + + // Get sorted list of keys + std::vector keys; + + //unsigned int size = settingsKeyCount(); + unsigned int size = _settingsKeyCount(); + for (unsigned int i=0; i 0) { + keys.insert(keys.begin() + j, key); + inserted = true; + break; + } + + } + + // If we could not insert it, just push it at the end + if (!inserted) keys.push_back(key); + + } + + return keys; +} + +// ----------------------------------------------------------------------------- +// Commands +// ----------------------------------------------------------------------------- + +void _settingsHelpCommand() { + + // Get sorted list of commands + std::vector commands; + unsigned char size = embedis.getCommandCount(); + for (unsigned int i=0; i 0) { + commands.insert(commands.begin() + j, command); + inserted = true; + break; + } + + } + + // If we could not insert it, just push it at the end + if (!inserted) commands.push_back(command); + + } + + // Output the list + DEBUG_MSG_P(PSTR("Available commands:\n")); + for (unsigned char i=0; i %s\n"), (commands[i]).c_str()); + } + +} + +void _settingsKeysCommand() { + + // Get sorted list of keys + std::vector keys = _settingsKeys(); + + // Write key-values + DEBUG_MSG_P(PSTR("Current settings:\n")); + for (unsigned int i=0; i %s => %s\n"), (keys[i]).c_str(), value.c_str()); + } + + unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize(); + DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size()); + DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE); + +} + +void _settingsFactoryResetCommand() { + for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) { + EEPROM.write(i, 0xFF); + } + EEPROM.commit(); +} + +void _settingsDumpCommand(bool ascii) { + for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) { + if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i); + byte c = EEPROM.read(i); + if (ascii && 32 <= c && c <= 126) { + DEBUG_MSG_P(PSTR(" %c "), c); + } else { + DEBUG_MSG_P(PSTR("%02X "), c); + } + } +} + +void _settingsInitCommands() { + + #if DEBUG_SUPPORT + settingsRegisterCommand(F("CRASH"), [](Embedis* e) { + debugDumpCrashInfo(); + debugClearCrashInfo(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + #endif + + settingsRegisterCommand(F("COMMANDS"), [](Embedis* e) { + _settingsHelpCommand(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) { + bool ascii = false; + if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1; + _settingsDumpCommand(ascii); + DEBUG_MSG_P(PSTR("\n+OK\n")); + }); + + settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) { + DEBUG_MSG_P(PSTR("+OK\n")); + resetReason(CUSTOM_RESET_TERMINAL); + ESP.eraseConfig(); + *((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494 + }); + + #if I2C_SUPPORT + + settingsRegisterCommand(F("I2C.SCAN"), [](Embedis* e) { + i2cScan(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("I2C.CLEAR"), [](Embedis* e) { + i2cClearBus(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + #endif + + settingsRegisterCommand(F("FACTORY.RESET"), [](Embedis* e) { + _settingsFactoryResetCommand(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("GPIO"), [](Embedis* e) { + if (e->argc < 2) { + DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n")); + return; + } + int pin = String(e->argv[1]).toInt(); + //if (!gpioValid(pin)) { + // DEBUG_MSG_P(PSTR("-ERROR: Invalid GPIO\n")); + // return; + //} + if (e->argc > 2) { + bool state = String(e->argv[2]).toInt() == 1; + digitalWrite(pin, state); + } + DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW"); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("HEAP"), [](Embedis* e) { + DEBUG_MSG_P(PSTR("Free HEAP: %d bytes\n"), getFreeHeap()); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("HELP"), [](Embedis* e) { + _settingsHelpCommand(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("INFO"), [](Embedis* e) { + info(); + wifiStatus(); + //StreamString s; + //WiFi.printDiag(s); + //DEBUG_MSG(s.c_str()); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("KEYS"), [](Embedis* e) { + _settingsKeysCommand(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("RESET"), [](Embedis* e) { + DEBUG_MSG_P(PSTR("+OK\n")); + deferredReset(100, CUSTOM_RESET_TERMINAL); + }); + + settingsRegisterCommand(F("RESET.SAFE"), [](Embedis* e) { + EEPROM.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX); + DEBUG_MSG_P(PSTR("+OK\n")); + deferredReset(100, CUSTOM_RESET_TERMINAL); + }); + + settingsRegisterCommand(F("UPTIME"), [](Embedis* e) { + DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime()); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + +} + +// ----------------------------------------------------------------------------- +// Key-value API +// ----------------------------------------------------------------------------- + +void moveSetting(const char * from, const char * to) { + String value = getSetting(from); + if (value.length() > 0) setSetting(to, value); + delSetting(from); +} + +template String getSetting(const String& key, T defaultValue) { + String value; + if (!Embedis::get(key, value)) value = String(defaultValue); + return value; +} + +template String getSetting(const String& key, unsigned int index, T defaultValue) { + return getSetting(key + String(index), defaultValue); +} + +String getSetting(const String& key) { + return getSetting(key, ""); +} + +template bool setSetting(const String& key, T value) { + return Embedis::set(key, String(value)); +} + +template bool setSetting(const String& key, unsigned int index, T value) { + return setSetting(key + String(index), value); +} + +bool delSetting(const String& key) { + return Embedis::del(key); +} + +bool delSetting(const String& key, unsigned int index) { + return delSetting(key + String(index)); +} + +bool hasSetting(const String& key) { + return getSetting(key).length() != 0; +} + +bool hasSetting(const String& key, unsigned int index) { + return getSetting(key, index, "").length() != 0; +} + +void saveSettings() { + #if not SETTINGS_AUTOSAVE + _settings_save = true; + #endif +} + +void resetSettings() { + _settingsFactoryResetCommand(); +} + +// ----------------------------------------------------------------------------- +// Settings +// ----------------------------------------------------------------------------- + +#if TELNET_SUPPORT + void settingsInject(void *data, size_t len) { + _serial.inject((char *) data, len); + } +#endif + +size_t settingsMaxSize() { + size_t size = EEPROM_SIZE; + if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE; + size = (size + 3) & (~3); + return size; +} + +bool settingsRestoreJson(JsonObject& data) { + + const char* app = data["app"]; + if (strcmp(app, APP_NAME) != 0) return false; + + for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) { + EEPROM.write(i, 0xFF); + } + + for (auto element : data) { + if (strcmp(element.key, "app") == 0) continue; + if (strcmp(element.key, "version") == 0) continue; + setSetting(element.key, element.value.as()); + } + + saveSettings(); + + DEBUG_MSG_P(PSTR("[SETTINGS] Settings restored successfully\n")); + return true; + +} + +bool settingsGetJson(JsonObject& root) { + + // Get sorted list of keys + std::vector keys = _settingsKeys(); + + // Add the key-values to the json object + for (unsigned int i=0; i char { return EEPROM.read(pos); }, + [](size_t pos, char value) { EEPROM.write(pos, value); }, + #if SETTINGS_AUTOSAVE + []() { _settings_save = true; } + #else + []() {} + #endif + ); + + _settingsInitCommands(); + + #if TERMINAL_SUPPORT + #if SERIAL_RX_ENABLED + SERIAL_RX_PORT.begin(SERIAL_RX_BAUDRATE); + #endif // SERIAL_RX_ENABLED + #endif // TERMINAL_SUPPORT + + // Register loop + espurnaRegisterLoop(settingsLoop); + +} + +void settingsLoop() { + + if (_settings_save) { + EEPROM.commit(); + _settings_save = false; + } + + #if TERMINAL_SUPPORT + + embedis.process(); + + #if SERIAL_RX_ENABLED + + while (SERIAL_RX_PORT.available() > 0) { + char rc = Serial.read(); + _serial_rx_buffer[_serial_rx_pointer++] = rc; + if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) { + settingsInject(_serial_rx_buffer, (size_t) _serial_rx_pointer); + _serial_rx_pointer = 0; + } + } + + #endif // SERIAL_RX_ENABLED + + #endif // TERMINAL_SUPPORT + +} diff --git a/espurna/ssdp.ino b/espurna/ssdp.ino new file mode 100755 index 0000000..6ecf0f5 --- /dev/null +++ b/espurna/ssdp.ino @@ -0,0 +1,83 @@ +/* + +SSDP MODULE + +Copyright (C) 2017-2018 by Xose Pérez +Uses SSDP library by PawelDino (https://github.com/PawelDino) +https://github.com/esp8266/Arduino/issues/2283#issuecomment-299635604 + +*/ + +#if SSDP_SUPPORT + +#include + +const char _ssdp_template[] PROGMEM = + "" + "" + "" + "1" + "0" + "" + "http://%s:%u/" + "" + "%s" + "%s" + "/" + "%u" + "%s" + "%s" + "%s" + "%s" + "%s" + "uuid:38323636-4558-4dda-9188-cda0e6%06x" + "" + "\r\n" + "\r\n"; + +void ssdpSetup() { + + webServer()->on("/description.xml", HTTP_GET, [](AsyncWebServerRequest *request) { + + DEBUG_MSG_P(PSTR("[SSDP] Schema request\n")); + + IPAddress ip = WiFi.localIP(); + uint32_t chipId = ESP.getChipId(); + + char response[strlen_P(_ssdp_template) + 100]; + snprintf_P(response, sizeof(response), _ssdp_template, + WiFi.localIP().toString().c_str(), // ip + webPort(), // port + SSDP_DEVICE_TYPE, // device type + getSetting("hostname").c_str(), // friendlyName + chipId, // serialNumber + APP_NAME, // modelName + APP_VERSION, // modelNumber + APP_WEBSITE, // modelURL + DEVICE_NAME, // manufacturer + "", // manufacturerURL + chipId // UUID + ); + + request->send(200, "text/xml", response); + + }); + + SSDP.setSchemaURL("description.xml"); + SSDP.setHTTPPort(webPort()); + SSDP.setDeviceType(SSDP_DEVICE_TYPE); //https://github.com/esp8266/Arduino/issues/2283 + SSDP.setName(getSetting("hostname")); + SSDP.setSerialNumber(String(ESP.getChipId())); + SSDP.setModelName(APP_NAME); + SSDP.setModelNumber(APP_VERSION); + SSDP.setModelURL(APP_WEBSITE); + SSDP.setManufacturer(DEVICE_NAME); + SSDP.setManufacturerURL(""); + SSDP.setURL("/"); + SSDP.begin(); + + DEBUG_MSG_P(PSTR("[SSDP] Started\n")); + +} + +#endif // SSDP_SUPPORT diff --git a/espurna/static/index.html.gz.h b/espurna/static/index.html.gz.h new file mode 100755 index 0000000..2359d91 --- /dev/null +++ b/espurna/static/index.html.gz.h @@ -0,0 +1,3262 @@ +#define index_html_gz_len 65168 +const uint8_t index_html_gz[] PROGMEM = { +0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xec,0xbd,0xf7,0x7e,0xdb,0x4a,0xb2,0x30,0xf8,0x2a, +0x30,0xcf,0x8c,0x2f,0x79,0xc5,0x9c,0x44,0x49,0x87,0xf6,0x65,0x52,0xb2,0x32,0x15,0x2c,0xfb,0xf3,0x9e, +0x1f,0x48,0x80,0x24,0x2c,0x10,0xa0,0x01,0x90,0x14,0x25,0x6b,0xdf,0x67,0x9f,0x60,0xff,0xdf,0x7d,0xb1, +0xad,0xea,0x00,0x34,0x12,0x83,0x2c,0xcf,0x99,0xfb,0xed,0x3d,0x9e,0xb1,0x09,0xa0,0x63,0x55,0x75,0xa5, +0xae,0xae,0xfe,0xf3,0x5d,0xfb,0xbc,0x75,0x7d,0x7f,0xd1,0x91,0x46,0xce,0x58,0xff,0xf0,0x27,0xfb,0x5b, +0x95,0x95,0x0f,0x7f,0x3a,0x9a,0xa3,0xab,0x1f,0x3a,0xdd,0x8b,0xa9,0x65,0xc8,0x52,0x3e,0x0b,0x7f,0xfe, +0xcc,0xd1,0x97,0x7f,0x8e,0x55,0x47,0x96,0xfa,0x23,0xd9,0xb2,0x55,0xa7,0x9e,0x98,0x3a,0x83,0x4c,0x2d, +0xf1,0xe1,0x4f,0x5d,0x33,0x1e,0x24,0x4b,0xd5,0xeb,0x09,0x7b,0x64,0x5a,0x4e,0x7f,0xea,0x48,0x5a,0xdf, +0x34,0x12,0xd2,0xc8,0x52,0x07,0xf5,0x84,0x22,0x3b,0xf2,0xae,0x36,0x96,0x87,0x6a,0x6e,0x62,0x0c,0xf7, +0x7a,0xb2,0xad,0x56,0xcb,0xe9,0x46,0xa3,0xd1,0x6c,0x34,0x3a,0x8d,0x0e,0xfc,0x8d,0xff,0x1e,0x35,0x9a, +0x26,0xfe,0xdc,0x1f,0xc2,0x5f,0x2d,0xfc,0xab,0x71,0x89,0x7f,0x1d,0x35,0xf8,0x77,0xfe,0x1f,0xbc,0x6e, +0x9e,0x9e,0xc0,0x8f,0xeb,0xd6,0xbc,0xb1,0xf1,0x7f,0xc7,0x17,0xb9,0x7c,0x63,0xb8,0x79,0xbd,0xa5,0xff, +0xb5,0xae,0x73,0x3b,0x87,0xb5,0x87,0x5c,0xee,0x72,0xf0,0x9a,0xf1,0xb4,0xc3,0x0d,0x56,0x73,0x4f,0xf7, +0xdf,0x73,0xb3,0xeb,0x1c,0x7e,0xef,0x05,0xe7,0x89,0xfd,0xed,0xdf,0x2f,0x1f,0xcf,0x71,0x07,0xc6,0xd3, +0xbd,0xc3,0xfa,0xc7,0xf4,0x6d,0x27,0x3f,0xa8,0x8e,0x0f,0x66,0x87,0xa5,0xdc,0xe2,0x73,0x6e,0xe7,0xd4, +0xa6,0x6f,0xcf,0x4e,0x72,0xfd,0xc7,0xd9,0x62,0x27,0x47,0xc6,0x3f,0x5e,0x36,0xd6,0x87,0x41,0xad,0x73, +0x38,0xfb,0x5c,0xdc,0xea,0x42,0xfd,0x8b,0xda,0xf7,0xda,0xe0,0xf3,0x59,0xf3,0x74,0xb6,0xb5,0xd5,0xc8, +0xe9,0x5b,0xb9,0xda,0x6d,0x35,0x67,0xe4,0x1a,0xf6,0xf5,0x56,0xa9,0x21,0x4f,0x73,0xb3,0xda,0x38,0x97, +0xbb,0xfe,0xb4,0x1e,0x14,0xda,0xc7,0xb9,0xd2,0xe9,0x59,0x29,0x37,0xcb,0x75,0xb6,0x66,0x95,0xdc,0xbc, +0xa9,0xe7,0x72,0xb5,0xc6,0x20,0x97,0xcb,0x1d,0x1c,0x0f,0xf2,0xb9,0xf9,0x7e,0x0f,0x9e,0x6f,0xf2,0x33, +0x83,0xc0,0xe3,0xee,0x32,0x7a,0x78,0x30,0xfe,0x23,0xfc,0x7e,0xd6,0x85,0xf9,0x1f,0x0c,0xa1,0xff,0xf3, +0x83,0xd6,0xce,0x53,0x2d,0x77,0xf9,0xf9,0x4b,0x6e,0xb6,0x73,0xa9,0x9e,0x19,0xb9,0xfe,0xd3,0xf7,0x83, +0x5c,0x61,0xd2,0xcc,0x43,0xfb,0x5f,0x66,0xb9,0x5c,0xfb,0xf8,0x7b,0x2d,0xf7,0xb8,0xdd,0xdb,0x99,0xd5, +0xa0,0xcf,0xeb,0xeb,0xe3,0x8b,0xed,0xbc,0x2c,0xb4,0x8f,0xed,0x5d,0x69,0xd0,0xde,0xa7,0x7b,0x68,0xff, +0xba,0x7c,0x3c,0xc8,0xe5,0x73,0x8f,0x6d,0x6d,0x6b,0x56,0xdb,0xb7,0x73,0xdb,0x00,0xb1,0xd9,0x20,0xb7, +0x3d,0xec,0x4d,0x2e,0xb6,0x1e,0x0f,0xab,0x83,0xdc,0xd9,0xfe,0xe1,0x18,0xc6,0x3f,0xc6,0xf1,0x7f,0xbe, +0xc8,0xe5,0x3a,0x3f,0x66,0x55,0x02,0xef,0x0b,0x84,0xef,0xf5,0x13,0xb6,0x77,0x13,0xc4,0x67,0xf3,0xf3, +0x97,0x9d,0x2f,0x97,0xfb,0xce,0x45,0x29,0xd7,0xa8,0xc2,0xa0,0x9e,0x16,0xe7,0xe5,0x59,0xed,0xec,0x33, +0xcc,0xb7,0xf7,0x64,0x1c,0xb1,0xf6,0x8e,0xbe,0xe7,0x4a,0xb9,0xce,0xf4,0xa4,0x5a,0x78,0x3c,0x34,0xb6, +0x8c,0xe1,0x24,0x97,0x3b,0x55,0xa2,0xe1,0xb9,0xa8,0xee,0x34,0xed,0x93,0xe9,0xec,0x73,0xae,0xa3,0x7e, +0xae,0xe6,0xe6,0xdb,0x43,0xc0,0xcf,0xe1,0xd3,0x00,0xda,0xdf,0xdf,0xce,0xe5,0xe6,0x3f,0xce,0xe0,0xb9, +0xd9,0x87,0xae,0x5a,0x66,0xbb,0x96,0x5b,0x7c,0xcf,0xd5,0x8a,0xa7,0x2b,0xb1,0xf4,0x90,0x3b,0xea,0xe2, +0xfc,0xed,0x45,0x0f,0xe7,0x73,0x55,0xb9,0xb0,0x72,0x4d,0xcb,0xd8,0xc9,0xcd,0xbb,0x1a,0xe0,0xfb,0x53, +0x69,0xf6,0x94,0x6b,0xee,0x58,0xf0,0x9c,0xeb,0x02,0x7c,0xbe,0x6f,0x5d,0x5c,0x6f,0x1f,0xe5,0x72,0xf9, +0x66,0x34,0xbe,0x5c,0xf8,0x9e,0x23,0xbd,0x5e,0x10,0xf8,0xe7,0xda,0x4a,0x6e,0xbb,0x7a,0x79,0x7d,0x96, +0x9b,0xd6,0xbe,0xd4,0x72,0xdf,0x73,0xc7,0xb3,0xdc,0x53,0xb9,0xd1,0x3e,0x81,0x5e,0x91,0x3e,0x46,0x04, +0xfe,0x0b,0x28,0x7f,0xb4,0x64,0xf1,0x63,0x7b,0x27,0x73,0x80,0xf7,0x55,0x85,0xbe,0x68,0x9f,0xe6,0xaa, +0x0f,0x47,0x85,0x8b,0xf9,0x16,0xf6,0x47,0xeb,0xdb,0xfe,0xf2,0x2d,0xa4,0x9f,0xeb,0x7b,0xfc,0x7e,0x18, +0xb5,0xde,0x04,0x0e,0xc4,0xff,0x7b,0x1a,0xd4,0xee,0xdc,0xef,0xe7,0x48,0x1f,0x97,0xa5,0xd8,0xf1,0x34, +0x57,0xc3,0x37,0xf0,0x1f,0xae,0xdf,0x30,0x6b,0xf8,0xc5,0xff,0x36,0xe5,0x7f,0x80,0xd7,0x46,0xe3,0xa2, +0x92,0x03,0x18,0xf6,0x15,0x80,0xcb,0x63,0xa7,0xdf,0x68,0x9c,0x36,0x2e,0xe0,0x79,0xd8,0x26,0x70,0xe2, +0xf3,0x6a,0x0f,0x1b,0xf0,0x3c,0x6f,0xd4,0xf0,0xd5,0x21,0x3c,0x37,0x9a,0xf0,0xbc,0x38,0x80,0xef,0x27, +0x93,0x6d,0x78,0xde,0xca,0xcd,0x59,0x7b,0x8d,0x7a,0x3d,0xc1,0xa4,0x89,0x21,0x8f,0xd5,0x7a,0x62,0xa6, +0xa9,0xf3,0x09,0xc8,0x8e,0x84,0x04,0x82,0xc3,0x51,0x0d,0x90,0x2e,0x73,0x4d,0x71,0x46,0x75,0x45,0x9d, +0x69,0x7d,0x35,0x43,0x1e,0xd2,0x9a,0xa1,0x39,0x9a,0xac,0x67,0xec,0xbe,0xac,0xab,0xf5,0x02,0x34,0x61, +0x3b,0x0b,0x90,0x4b,0xd9,0xc9,0xd4,0x52,0x33,0xbd,0xa9,0xe3,0x98,0xc6,0xee,0xc0,0xec,0x4f,0xed,0xb4, +0xbc,0x2b,0xf7,0x1d,0x6d,0xa6,0xc2,0x8f,0x91,0x39,0x53,0xad,0x67,0x73,0xea,0x80,0x9c,0x52,0x77,0xf3, +0x2f,0xb4,0xb4,0x23,0xf7,0x74,0x35,0x4d,0xfe,0x7e,0xee,0x99,0x96,0xa2,0x5a,0x99,0xbe,0xa9,0xeb,0xf2, +0xc4,0x56,0x77,0xf9,0x8f,0x3d,0xf6,0xc1,0x9e,0xc8,0x7d,0xcd,0x18,0x42,0x5d,0x14,0x94,0xcf,0x99,0xb1, +0x9d,0x71,0xd4,0x47,0x27,0x63,0x6b,0x4f,0x6a,0x46,0x56,0xbe,0x4f,0x6d,0x67,0xb7,0x90,0xcf,0xff,0x73, +0x2f,0x33,0x57,0x7b,0x0f,0x9a,0x13,0xf3,0x75,0x00,0x53,0xcb,0x0c,0xe4,0xb1,0xa6,0x2f,0x76,0x6d,0xd9, +0xb0,0x33,0xb6,0x6a,0x69,0x83,0x97,0x9e,0xa9,0x2c,0x9e,0xc7,0xb2,0x35,0xd4,0x0c,0xe8,0x41,0xb6,0x1c, +0xad,0x0f,0x23,0x93,0x6d,0x4d,0x51,0xd3,0x0a,0x80,0x48,0xd3,0xed,0xf4,0x40,0x1b,0xf6,0xe5,0x89,0xa3, +0x99,0x06,0xfe,0x84,0xf1,0xa7,0x07,0xa6,0xe9,0xa8,0x56,0x1a,0x65,0x36,0xfe,0x33,0xb4,0xcc,0xe9,0x24, +0x3d,0x96,0x35,0x23,0x3d,0x56,0x8d,0x69,0xda,0x90,0x67,0x69,0x5b,0xed,0x93,0x1a,0xf6,0x74,0x0c,0xcd, +0x2f,0x9e,0x15,0xcd,0x9e,0xe8,0xf2,0x62,0xb7,0xa7,0x9b,0xfd,0x87,0x17,0x79,0xaa,0x68,0x66,0xba,0x2f, +0x1b,0x33,0xd9,0x4e,0x4f,0x2c,0x73,0x68,0xa9,0xb6,0x9d,0x9e,0x41,0xaf,0xa6,0x5b,0x52,0x33,0x10,0x66, +0x19,0x52,0x61,0x0f,0xa0,0x08,0x43,0x03,0xf0,0xcb,0xba,0x36,0x34,0x76,0x51,0x8e,0xe3,0x57,0xda,0xd0, +0xae,0x61,0x3a,0xc9,0xaf,0x88,0x3c,0xcb,0xd4,0xed,0x6f,0x29,0xb7,0x09,0xc3,0x34,0xd4,0xbd,0x91,0xaa, +0x0d,0x47,0x0e,0xcc,0xee,0xeb,0x48,0x53,0x14,0xd5,0xf8,0x96,0x76,0xd4,0x31,0x7c,0x76,0x54,0x5f,0xb9, +0x17,0xf9,0xb9,0x27,0xf7,0x1f,0x70,0x2e,0x86,0x82,0xe8,0x30,0xad,0x5d,0xc7,0x02,0x48,0x4d,0x64,0x0b, +0x68,0xe2,0x45,0xee,0xf5,0xac,0xaf,0x44,0x15,0xf9,0xc6,0x71,0xd6,0x33,0x01,0xe9,0xe3,0xdd,0xc2,0xe4, +0x51,0x52,0xe0,0xa7,0xaa,0xbc,0xf4,0xd2,0xe6,0xc4,0xa1,0xe0,0xb0,0x61,0x30,0xc6,0xf0,0x99,0xc0,0x7d, +0x4e,0x87,0xb0,0x9d,0xcf,0xbf,0x28,0x03,0x83,0xbe,0x23,0xd4,0xb3,0xab,0x39,0x30,0xa1,0xfe,0xcb,0xa8, +0xc0,0x5e,0x02,0xda,0x76,0x8b,0xea,0x78,0x8f,0xa1,0x24,0x5b,0xdd,0x56,0xc7,0x52,0xfe,0x05,0x1e,0x1f, +0x84,0xe1,0xed,0xfe,0x31,0x18,0xe4,0xf7,0xe8,0x18,0xff,0xc8,0x43,0xab,0xf6,0x58,0xd6,0x75,0xa1,0x89, +0x5a,0xfe,0x9f,0x2f,0xf6,0xb4,0x07,0xd0,0x9f,0x08,0x6f,0xb7,0x2b,0xff,0xdc,0x23,0x30,0xe5,0x20,0xd9, +0x9b,0x98,0xb6,0x86,0x68,0xda,0x05,0x0d,0x4a,0x46,0x92,0x8d,0x05,0x34,0xb6,0xe4,0x98,0x93,0xdd,0x4c, +0xb6,0xa2,0x8e,0xb1,0xed,0x67,0x36,0xfb,0x4c,0xb6,0x88,0x6f,0xb4,0xf1,0x90,0x81,0x05,0x20,0x6d,0xcf, +0x86,0x04,0x27,0xbb,0x16,0x10,0x4a,0xea,0x19,0xd7,0xc0,0x40,0x37,0xe7,0xbb,0x14,0x01,0x2f,0x94,0x8a, +0x38,0xd9,0x15,0x60,0x86,0xe5,0xfc,0xe4,0xf1,0x65,0x64,0x41,0x0b,0x8f,0x38,0x54,0xa4,0x77,0xb6,0x16, +0x01,0xc8,0x8f,0x1e,0x0a,0x27,0x40,0x7c,0x48,0xdf,0x80,0x12,0xd9,0x6b,0x56,0x9e,0x3a,0xe6,0x4b,0xdf, +0x04,0x92,0x7d,0xe8,0x29,0x69,0x2c,0x63,0xcb,0x63,0x36,0x71,0x46,0xf2,0x63,0xd3,0x30,0x71,0x25,0xa9, +0x69,0xf7,0xd7,0x9e,0x07,0x18,0x18,0xc2,0x0b,0x5d,0xc1,0xb0,0xcc,0x27,0x53,0x47,0xc0,0xa2,0xaa,0x03, +0x21,0x7b,0x7d,0x52,0x98,0x6b,0xc6,0x08,0x56,0x8f,0x43,0x5a,0x70,0x1f,0xdc,0x55,0x24,0x72,0x04,0xda, +0xde,0xb3,0x08,0x75,0xc3,0xb4,0x00,0x5b,0xac,0x3f,0x6f,0x12,0x33,0xcd,0xd6,0x80,0x1f,0xf0,0x71,0xd0, +0x8e,0x9f,0xc9,0x62,0x26,0x64,0x38,0x80,0x6a,0x94,0x50,0x59,0x09,0x64,0x07,0x12,0x69,0xfe,0xab,0xb3, +0x98,0xa8,0x75,0xfa,0xfa,0x5b,0x5a,0x78,0x05,0xab,0x4a,0x75,0x7c,0x6f,0x00,0x71,0x63,0xcd,0xf9,0xf6, +0xcc,0x99,0x85,0x3c,0x99,0xa8,0x32,0x34,0xdf,0x57,0x77,0x69,0xfd,0xbd,0xfe,0xd4,0xb2,0x61,0x8a,0x13, +0x53,0x03,0xf8,0x5b,0xac,0xb3,0xaf,0xb0,0x52,0x90,0x5b,0x29,0xdf,0xc4,0x6e,0xdd,0x97,0xcf,0xac,0x92, +0xa2,0x0e,0xe4,0xa9,0xee,0xb0,0x4a,0xbb,0xbb,0x99,0xb1,0xf9,0x94,0x21,0x3c,0x31,0xa3,0x19,0x06,0xb0, +0x0a,0x52,0x2f,0xfc,0xde,0xa5,0x9c,0xbd,0x89,0xac,0x28,0x94,0xdb,0x09,0x83,0xee,0x8f,0xd4,0xfe,0x03, +0xd0,0x81,0x7f,0x6e,0x32,0xac,0xfc,0x6f,0x22,0xc5,0xb8,0xab,0xf2,0x31,0xba,0x19,0x63,0x3a,0xee,0xa9, +0xd6,0x37,0xe8,0x9e,0x4d,0x9e,0xf4,0x0d,0x0c,0x56,0x33,0x7c,0xd8,0x8a,0x29,0x0d,0x4c,0xdc,0x5f,0xfa, +0x99,0x21,0x94,0x90,0x9f,0x08,0x63,0x80,0x68,0x7f,0x14,0x09,0x63,0x44,0xe7,0x40,0x53,0x75,0x65,0x2f, +0x9a,0xd2,0xfd,0xb4,0x43,0x1f,0x10,0xf1,0x14,0xe0,0x94,0xcf,0x61,0x1f,0xdf,0x52,0xec,0x23,0xf2,0xdb, +0x68,0x18,0x44,0x8c,0xc8,0x9b,0x0b,0x7d,0x91,0xe9,0xe3,0xa0,0xf4,0x88,0xc9,0xc7,0x55,0x50,0xd4,0xbe, +0x69,0xc9,0xc8,0x31,0xa2,0x66,0x47,0xa8,0x93,0x4c,0x0f,0xc8,0x8e,0xe3,0x14,0xb9,0xa3,0x6d,0xea,0x9a, +0x22,0xd9,0x9a,0x0e,0xc4,0xee,0xae,0x13,0xa9,0x38,0xf1,0x10,0x95,0x2d,0x01,0x13,0x91,0xb2,0xd5,0x22, +0xf9,0x67,0x1b,0x39,0x8a,0xae,0x0e,0x55,0x43,0x49,0x3b,0xf0,0xbf,0xd1,0xb3,0x87,0x50,0xfa,0xda,0xe3, +0x35,0x59,0xca,0x54,0xd2,0x9c,0xbb,0xfb,0x98,0xfa,0x3b,0x6d,0x8c,0x92,0x5d,0x06,0xfe,0x4d,0xe1,0x85, +0x6c,0xca,0x27,0x8a,0xf6,0x04,0x24,0xc2,0xd0,0x1e,0xa9,0xb4,0x27,0x32,0x93,0x55,0x19,0x12,0x99,0x3b, +0xd0,0xd5,0xc7,0x0c,0x59,0xa8,0x96,0x39,0x97,0xe6,0x96,0x3c,0xd9,0x73,0x5f,0x93,0xd5,0x0d,0x2c,0xe5, +0x61,0xd7,0x06,0x2e,0xe1,0xb8,0x92,0x98,0xb0,0xd0,0x0c,0x43,0xef,0x2e,0x29,0xea,0x2f,0x10,0xd1,0x68, +0x6c,0x1d,0x3e,0x6a,0x7c,0xb5,0x17,0x51,0x51,0xe4,0x76,0xfb,0x96,0xaa,0x76,0x81,0x67,0xa4,0x1b,0x96, +0x36,0x36,0xd3,0x6d,0xcb,0x04,0xf0,0x93,0x17,0x87,0x2a,0xe0,0x00,0x19,0x3c,0x7e,0x92,0xf5,0xb4,0xa7, +0x0a,0xec,0xe9,0xaa,0xe3,0x08,0xfa,0x46,0x26,0x5b,0x02,0xb6,0xb8,0x47,0x18,0x10,0x08,0x40,0x00,0x36, +0xbe,0x05,0xc6,0xa8,0x8d,0x81,0x65,0xda,0x13,0x15,0xc4,0xdd,0x7f,0x8d,0x55,0x45,0x93,0x25,0x90,0x3d, +0x92,0x6c,0x28,0x52,0x12,0x21,0x32,0x02,0x70,0x92,0xf1,0x5b,0xb2,0xed,0x10,0x24,0xa4,0xd2,0x11,0x1f, +0xa8,0x86,0x94,0x7a,0x26,0x4a,0x90,0xc4,0x21,0xed,0xd7,0x12,0x5e,0xb2,0xe6,0x44,0xb5,0xe4,0x8c,0x69, +0xe8,0x0b,0x69,0x37,0x63,0x66,0x80,0xb5,0x53,0x0d,0x8b,0x97,0x9f,0x03,0x15,0x08,0x23,0x2e,0x97,0x80, +0x6e,0xe8,0xb7,0x29,0x2b,0x33,0xcd,0x14,0xbc,0x5f,0xfe,0xdf,0x45,0xe1,0xc1,0xf7,0xbb,0x2c,0x3c,0x94, +0x84,0xdf,0xe2,0xfb,0x8a,0xf0,0xbb,0x2a,0xfc,0xae,0x79,0xbf,0xf3,0xbe,0x96,0xfc,0x1d,0xfa,0x7b,0x29, +0xfa,0x9e,0x4a,0xbe,0xa7,0xb2,0xef,0xa9,0xe2,0x7b,0xaa,0xfa,0x9e,0xb6,0x7d,0x4f,0x35,0xdf,0xd3,0x8e, +0xf8,0x54,0xf4,0x3f,0x94,0x84,0xdf,0xde,0xb4,0x8a,0xbe,0xe1,0x17,0x7d,0x03,0x2e,0xfa,0x5b,0xf0,0x0d, +0xb8,0xe8,0x1b,0x70,0xc9,0xff,0x20,0xfe,0xae,0x08,0xbf,0x3d,0xa8,0xf9,0x6a,0x97,0x85,0x42,0x15,0x11, +0x7e,0x3e,0x30,0x54,0x7c,0xa5,0xaa,0xc2,0x6f,0xaf,0x59,0x1f,0xa4,0xb6,0xc5,0xa6,0xb6,0xfd,0x5f,0xbc, +0x2a,0x3e,0x00,0x22,0xfc,0xa2,0x35,0xd3,0xc0,0xb2,0xa1,0xd2,0x3d,0xb8,0x6c,0x08,0x77,0x09,0xa8,0x56, +0xa0,0x4d,0xed,0xf9,0xe8,0x97,0x55,0x7d,0x32,0x51,0xa7,0xe4,0xcc,0x47,0xfa,0xda,0xd7,0x65,0xdb,0xfe, +0xcf,0x3a,0x1d,0xc8,0xb7,0xe7,0x18,0x45,0x5e,0x24,0xdf,0x67,0xca,0xc3,0xca,0xd9,0x42,0xb5,0xba,0xfd, +0x4f,0xe1,0x93,0x30,0xed,0xa2,0x57,0xae,0x96,0x2d,0xc1,0x7f,0x62,0xb9,0x9a,0x0f,0x7f,0xac,0x58,0xa1, +0x98,0xad,0x88,0x85,0xaa,0x3e,0x9c,0xf1,0x42,0xd5,0x6c,0x35,0xd0,0x69,0x85,0x7d,0x2a,0xe6,0xbd,0xb7, +0x15,0xaf,0x46,0x31,0x9f,0xad,0x05,0xba,0x2f,0xfb,0xd0,0xc6,0xcb,0x09,0x9d,0x6f,0x0b,0xaf,0x77,0x42, +0xb3,0x2c,0xf9,0x50,0xc8,0xca,0x95,0x4a,0x81,0x59,0x8a,0x44,0xb7,0x23,0x94,0xdb,0x16,0x67,0x59,0x74, +0x47,0x5f,0x16,0x46,0xef,0x5f,0xd9,0x48,0x98,0xbc,0x50,0x21,0x38,0x7b,0x11,0x1d,0x95,0xd0,0x44,0x05, +0x86,0x20,0xe0,0xa3,0x22,0x76,0x25,0x20,0xa0,0x12,0x42,0x68,0x39,0x48,0xd5,0xbc,0x64,0x10,0xa5,0x25, +0x77,0x1e,0x55,0xb1,0xf1,0xc0,0x32,0xaa,0xf1,0x32,0x7e,0x4c,0x57,0x03,0xec,0x82,0x97,0x0a,0xa1,0x5a, +0x40,0xcb,0x76,0x08,0xab,0xb5,0x00,0x27,0xe0,0x05,0xc5,0x9e,0x04,0x3c,0x6c,0x07,0xf1,0x5a,0x76,0xa7, +0x50,0x13,0xa6,0x50,0x0c,0xa0,0xa2,0xca,0xcb,0x04,0xd1,0xed,0x67,0x60,0xdb,0xee,0x5c,0x6b,0x3e,0x7c, +0xfb,0xb9,0x74,0x51,0x40,0xca,0x4e,0x10,0xb5,0x45,0x01,0x33,0x3b,0x21,0xd4,0x46,0xca,0x1c,0x3f,0x6b, +0xac,0xb8,0x13,0x12,0x94,0x0d,0xa6,0x59,0x12,0x05,0x79,0x6a,0x23,0x5f,0x21,0x76,0x00,0xb5,0x64,0x51, +0x8a,0x86,0x5f,0x32,0x5d,0x82,0x7c,0x50,0x2c,0x79,0x18,0xf1,0x5a,0x2c,0xef,0xd7,0xef,0xf7,0x22,0x79, +0x1a,0xe1,0x5e,0x94,0x4b,0xf5,0x55,0x52,0x2c,0xd4,0x4c,0x80,0x99,0x8d,0x41,0xf9,0xd2,0xd5,0xbd,0xf9, +0x48,0x73,0x54,0xc2,0xd0,0x50,0x1b,0x24,0x7a,0x89,0x8f,0x9d,0xc5,0x19,0x06,0x51,0x06,0x80,0x58,0x23, +0x43,0xac,0xb1,0xe7,0x57,0xa8,0x28,0x2b,0x54,0x08,0x5f,0xf3,0xf1,0xda,0x04,0x43,0x4b,0xc8,0x41,0xf0, +0x87,0x5a,0xc5,0x3f,0xcc,0x57,0x23,0xfa,0x0b,0xb8,0xfb,0x06,0xcd,0x94,0xa9,0xbd,0x8b,0xba,0x2e,0xad, +0x62,0x0d,0x7b,0x72,0x32,0x9f,0xc6,0x3f,0xd9,0x5a,0xca,0xa7,0xb6,0x89,0x66,0x25,0x33,0x4c,0xd1,0x75, +0xe3,0xea,0xc8,0xa8,0x1b,0xbb,0x13,0xf6,0x54,0x71,0xaa,0x77,0xfb,0xe6,0x43,0xdc,0x4e,0xbe,0x29,0xee, +0x86,0x67,0xcd,0x9c,0x53,0xc2,0xa4,0xc8,0x46,0xc9,0x2e,0x12,0x82,0x6c,0x01,0x4c,0x60,0xe8,0x30,0x93, +0xa4,0x30,0xab,0xb4,0x38,0xfa,0x7c,0x25,0x05,0x86,0xfb,0x3f,0x7d,0xef,0x0a,0x29,0x98,0x92,0xa6,0x03, +0x96,0x76,0x65,0x7d,0x32,0x92,0x93,0x26,0x82,0xd3,0x59,0xd4,0x77,0xf2,0x29,0xff,0x10,0x99,0x8b,0xcc, +0x37,0x20,0xfa,0x8e,0xda,0x33,0x23,0x59,0x01,0xf5,0x36,0x2f,0xe1,0x1f,0xb4,0x21,0x7c,0xbd,0x40,0xcf, +0x9a,0x01,0x46,0x46,0x1a,0xbf,0x56,0x03,0x5f,0x8b,0xec,0xa3,0xbf,0x3f,0x6e,0xac,0xa6,0x23,0xdf,0xee, +0x46,0x0c,0xc7,0xfb,0x18,0x41,0x31,0xee,0xb7,0x30,0xa8,0x05,0xbb,0x38,0x04,0x5b,0xb2,0x76,0x5c,0x62, +0x17,0x26,0x2a,0xae,0x4d,0x30,0xf3,0x60,0x59,0x81,0x76,0xaf,0x2a,0xd1,0xc0,0x2c,0xe7,0x53,0x7b,0xec, +0xf7,0x6e,0xb6,0xbc,0xc7,0xd6,0x72,0x46,0x9d,0x01,0x8a,0xec,0x28,0x72,0x20,0xa6,0x91,0xdf,0xdd,0xe5, +0x2b,0x30,0x01,0x3b,0x41,0xb6,0x16,0xfe,0x29,0xd2,0xe5,0x0e,0x10,0x93,0x23,0xcb,0xca,0x91,0x85,0x23, +0xd6,0x48,0x3e,0xbf,0x5d,0x53,0xb7,0xb9,0xbb,0x6a,0x30,0x18,0x44,0x2c,0x6d,0xc9,0xbf,0xce,0x7c,0x6b, +0x27,0xef,0xae,0x25,0x62,0xa9,0x79,0x06,0xa5,0x1f,0xe9,0x31,0x9a,0x18,0x37,0x37,0x7f,0x41,0x25,0x5b, +0x35,0xde,0xdd,0x81,0x66,0xd9,0x4e,0xa6,0x3f,0xd2,0x74,0xc5,0xef,0x1b,0xcc,0xe8,0xea,0xc0,0x11,0x79, +0x00,0xfb,0x0a,0x1d,0x05,0x3f,0xad,0xec,0x04,0xd4,0xc0,0xe8,0x3e,0x08,0x58,0x22,0x3a,0xb1,0x98,0x93, +0x4f,0xe8,0x33,0x58,0x94,0x75,0xea,0xb9,0x18,0x98,0xc3,0x05,0x51,0xf5,0x2d,0xe4,0x7f,0xa0,0x1f,0x15, +0xd9,0x51,0x97,0x7d,0x03,0xee,0xab,0x66,0x40,0x8c,0xc8,0xfa,0xaa,0x52,0x71,0xdf,0xd5,0xb1,0xac,0xc5, +0x56,0x1e,0x03,0x73,0x1c,0xc5,0x7d,0x64,0x4e,0x9b,0x98,0xaf,0x13,0x50,0xa4,0x11,0xbd,0x71,0xdf,0x99, +0xd7,0x23,0xe6,0xab,0xa3,0xc6,0x8e,0x09,0x49,0x2b,0xf6,0xdb,0x92,0x89,0x4e,0xad,0xd8,0x26,0xe7,0xaa, +0xfa,0xe0,0xfb,0xc6,0x3c,0x91,0xc2,0x1b,0xd7,0x29,0x19,0xf2,0xb4,0xfc,0xd1,0xef,0xf7,0x03,0x02,0xa8, +0x4c,0xe8,0xc2,0x65,0x37,0x84,0x41,0x32,0xce,0x5a,0x82,0xff,0xff,0xa1,0x28,0x3e,0x9f,0x94,0xe0,0x4b, +0x8b,0xd4,0x10,0x7c,0x32,0x29,0x5b,0x05,0xa1,0x14,0xa9,0x10,0x84,0xe8,0x4b,0x74,0x61,0xbd,0xd9,0xb8, +0xd7,0x1b,0xe1,0x52,0x5a,0x77,0x1d,0x4c,0xd9,0x22,0x16,0xaf,0x44,0x14,0x17,0x87,0xee,0x93,0x08,0xd1, +0x2d,0x2e,0x2d,0x42,0xd6,0xd0,0xca,0x12,0xc2,0x4a,0x5a,0xab,0xec,0xf2,0x52,0x74,0x55,0x2d,0x2d,0x42, +0xd7,0xd6,0xd2,0x22,0xdc,0x2d,0xba,0xac,0x8c,0xbb,0xce,0x96,0x96,0xe2,0x3e,0xc6,0x65,0x65,0x70,0xcd, +0xad,0x28,0x00,0x2b,0x6f,0x79,0x89,0x95,0x80,0xc1,0x55,0xb8,0xb4,0x00,0x59,0x8b,0xe1,0x12,0x4c,0x1d, +0x0e,0xbd,0xe7,0xeb,0x92,0x7e,0x11,0x36,0xf8,0x50,0xfc,0x15,0x8a,0x3b,0x03,0x55,0xde,0x0b,0xee,0x08, +0x86,0x68,0x88,0xbb,0xbc,0x97,0x0e,0x0c,0x94,0x83,0x15,0x73,0xa3,0x5e,0x72,0x36,0x12,0xde,0x29,0x1b, +0x84,0x84,0x22,0x10,0x57,0x92,0x38,0x06,0xfa,0x93,0x77,0x9f,0x0e,0x7d,0x21,0x0d,0x06,0x5c,0xa8,0x7c, +0xb3,0xaa,0x42,0xf6,0xaa,0x96,0xad,0x1b,0x61,0x1f,0x61,0xd9,0xda,0x59,0x59,0x8c,0xac,0x9f,0xb5,0x4a, +0x09,0x6b,0x68,0xed,0xf2,0xab,0x4b,0xd2,0xb5,0xb4,0xb2,0x18,0x5d,0x4f,0x2b,0x8b,0xb1,0x35,0xb5,0xb2, +0x9c,0xbb,0xae,0x56,0x96,0x64,0x6b,0x6b,0x65,0x39,0x5c,0x5f,0x6b,0x14,0x82,0x35,0xb6,0xba,0xd4,0x5a, +0x80,0xc3,0xb5,0xb6,0xb2,0x10,0x59,0x6f,0xd1,0xa5,0xe8,0x9a,0x8b,0xfe,0xc6,0xd7,0x5d,0xb4,0xfe,0xcd, +0x0d,0x36,0x59,0x55,0x40,0xad,0x66,0x4f,0x7d,0x59,0x29,0x2a,0xa5,0x08,0xbd,0x3b,0xbc,0x2c,0xa1,0x61, +0x05,0xad,0xc8,0xa8,0xe1,0x44,0x7e,0x73,0x87,0xe3,0x7e,0x8d,0x1a,0x8e,0xea,0x6e,0xf4,0xf3,0x21,0x81, +0x08,0x64,0x3f,0xb7,0xb7,0xb7,0xc3,0xab,0x89,0x2c,0x65,0x10,0x75,0x33,0x90,0xb3,0x4a,0x1c,0x3b,0x8a, +0xfa,0xee,0x67,0x4b,0xbc,0x44,0x80,0x3d,0xa9,0x3b,0xa5,0x62,0xd1,0x05,0x4f,0x6f,0xa7,0x2c,0x97,0x6b, +0xeb,0xb1,0xa8,0xa8,0x2e,0xa3,0x59,0xd5,0x8a,0x92,0x22,0xcb,0x72,0x47,0xc9,0x58,0x97,0x7f,0x98,0x2f, +0xa1,0xd9,0x47,0x40,0x18,0xec,0x8d,0xbd,0x68,0x45,0x83,0xed,0xfc,0x14,0xe9,0x06,0x76,0x18,0xab,0xe3, +0xa9,0xee,0x68,0x13,0xdc,0xef,0x17,0x37,0xfa,0x84,0x72,0xba,0xdc,0x53,0xf5,0x67,0x1f,0xf7,0x93,0x50, +0x7f,0x10,0xcb,0x04,0xb7,0xc3,0xf2,0x9e,0x41,0xe2,0xdf,0xfb,0xca,0xb3,0x6d,0x2f,0xb1,0x7d,0x71,0xab, +0x4b,0x8c,0x36,0x60,0x93,0x50,0x2b,0xf8,0x87,0x23,0xab,0x54,0x2a,0xed,0x45,0xf1,0x65,0x5e,0x31,0x5b, +0x02,0x2d,0xcd,0xeb,0x13,0xbb,0xdc,0x0b,0xf9,0x9f,0xb0,0x5f,0xdc,0x70,0xea,0x3f,0xa8,0x4a,0xec,0xa6, +0x63,0xb8,0x50,0x8c,0xd9,0x10,0x55,0x28,0x68,0x3e,0xc4,0x95,0x89,0x36,0x23,0x96,0x95,0x5e,0x55,0x2e, +0x64,0x56,0x44,0x15,0x22,0x74,0xba,0xa2,0x4c,0xc8,0x04,0x89,0x2a,0x14,0x36,0x45,0xa2,0x4a,0x45,0x99, +0x24,0x51,0xe5,0xc2,0xa6,0x49,0x54,0xa9,0x80,0x89,0x12,0x5d,0xc4,0x6f,0xaa,0x44,0x96,0x59,0x03,0x98, +0x01,0xd3,0x25,0xaa,0x48,0xd0,0x84,0x71,0xcb,0x90,0x75,0x13,0xf5,0x21,0x64,0xe3,0xb8,0x5f,0x5c,0x5b, +0x27,0x5a,0xf3,0x28,0x06,0x55,0x0f,0x6a,0x85,0x40,0x45,0xfa,0x6a,0xa4,0xea,0x93,0x0c,0x35,0x0f,0xd2, +0x11,0x85,0x68,0xd0,0x47,0xc4,0x87,0xf0,0x80,0xf8,0x17,0x3e,0x20,0xf1,0xdb,0x58,0xb5,0x6d,0x79,0xa8, +0xb2,0x8e,0xd6,0x0a,0x64,0x0a,0x99,0x49,0xa1,0x0e,0x9e,0xc3,0x3e,0x8a,0xf8,0x79,0xb2,0x18,0x28,0xe6, +0xcf,0x0c,0xac,0x7f,0x3f,0x77,0x59,0x56,0x93,0x71,0xb6,0xc8,0x09,0xb8,0xdb,0xf7,0x05,0xc2,0xb4,0xf2, +0xa2,0xb3,0x98,0xb8,0x17,0xe2,0x5c,0xc3,0x8c,0xd5,0xac,0x31,0x08,0xdb,0x0d,0x0d,0x62,0x4c,0x15,0x3a, +0x2b,0xf8,0x59,0x23,0xfd,0x49,0xf0,0x96,0x21,0xec,0x5e,0x0d,0x8b,0x93,0x88,0x32,0x01,0xcf,0x52,0x51, +0xe4,0x88,0xcc,0xbb,0x1a,0xee,0x85,0x82,0xc4,0x65,0xe4,0x7e,0xa8,0x16,0xf2,0x51,0xfa,0x33,0xad,0x12, +0x24,0x2b,0xdf,0xc7,0x80,0xf5,0xee,0x39,0xbb,0x22,0x29,0x1c,0x61,0x90,0x29,0x08,0x01,0x13,0xd8,0x6f, +0x44,0xf8,0x16,0x09,0xd1,0x8a,0x54,0xe9,0x85,0x21,0x85,0x2d,0x86,0xa8,0x81,0x31,0xa3,0xe1,0x09,0x20, +0xa8,0xa8,0x8f,0xbb,0xa5,0x15,0x4d,0x7a,0x2e,0xb0,0xd5,0x0d,0x87,0xdd,0x65,0x9e,0xb5,0x2f,0xe1,0xff, +0x91,0xae,0x3c,0xc7,0x1d,0xcc,0x69,0xf5,0x94,0xbc,0x36,0x05,0x4f,0xd9,0x26,0x43,0x89,0x70,0xb0,0x09, +0x3e,0x88,0xcd,0x46,0xb3,0xc1,0x08,0x62,0x7b,0x45,0x94,0x33,0x78,0xf8,0x7b,0xcf,0x14,0xe3,0xbb,0x67, +0x0e,0x54,0xce,0x17,0x4b,0x21,0x93,0x4c,0x5c,0x14,0x85,0xf0,0xfe,0x53,0xa8,0x4c,0xd4,0x4e,0x5d,0xa8, +0x90,0xb8,0x29,0x18,0x5b,0xa8,0x90,0x29,0x86,0x36,0x38,0x23,0x0a,0x95,0xdc,0x0d,0xdb,0x25,0x85,0xc2, +0x9b,0xc2,0x42,0xa1,0x18,0x66,0x1f,0xe0,0xce,0x4c,0x6b,0xaa,0x56,0xab,0xd1,0xfe,0x23,0x6f,0x13,0x26, +0x5b,0x43,0xc5,0x8c,0xaf,0x3c,0xe2,0xb7,0xa5,0xca,0xd4,0x4a,0x5e,0xce,0xba,0x8c,0xea,0x2b,0xba,0x13, +0x1e,0x51,0x43,0xb6,0xab,0xec,0xbe,0xa5,0xaa,0x06,0x8d,0xac,0xf1,0x22,0x93,0x2a,0x9a,0x91,0x7a,0x16, +0xe6,0xcc,0xf6,0x1c,0x7c,0xd1,0x7d,0x1c,0xfd,0xdb,0x94,0x7b,0x2e,0x35,0xc9,0x97,0x1a,0xe2,0xff,0xe3, +0x02,0xfe,0x77,0x72,0x01,0xfb,0xa5,0x72,0x9c,0x8e,0xcf,0x23,0x47,0x04,0x76,0x14,0x46,0xb7,0xf0,0x35, +0x02,0xdf,0xa1,0xaf,0x22,0xc2,0x23,0x3f,0x86,0x31,0x1e,0x5b,0x2c,0xb6,0x80,0x0f,0xe7,0xa1,0xaf,0x3e, +0xa4,0x87,0xbe,0xfa,0xb1,0x1e,0xfa,0x1c,0x44,0x7b,0xa8,0x80,0x1f,0xef,0xa1,0xcf,0x02,0xe2,0x23,0xbe, +0x79,0x98,0x0f,0x7f,0x5c,0x36,0x61,0x01,0xf7,0xa1,0x6f,0x04,0xf9,0x01,0x5d,0x63,0x89,0x82,0xbb,0x4c, +0x7d,0x8b,0x35,0x06,0x05,0xcd,0x0d,0x99,0x5a,0x8c,0x35,0xb8,0xa6,0x8a,0xb6,0x29,0x1f,0x5e,0x43,0x71, +0x0e,0xf1,0x48,0x91,0x0f,0x53,0x4f,0x3d,0x58,0xcd,0x35,0x20,0x79,0xd6,0x39,0x46,0xc3,0x66,0x06,0xda, +0x23,0x28,0x7a,0x84,0x4b,0x0b,0x01,0xee,0xe4,0x2d,0x11,0x9f,0xf9,0xbd,0xa0,0x4a,0x43,0xaa,0x69,0x8e, +0x3a,0x4e,0x0b,0xcf,0xba,0x66,0x3b,0xcf,0x21,0x05,0xeb,0x25,0x58,0x02,0xff,0x62,0xb1,0xfc,0x64,0x0b, +0x37,0x64,0xdd,0xe7,0x83,0x9d,0x70,0x6f,0x02,0xd9,0xdb,0x5f,0x5e,0x1c,0x0f,0x58,0xc0,0x4b,0xff,0xb0, +0x8c,0x87,0x00,0x80,0xa2,0xc2,0x01,0x22,0x22,0x31,0x7c,0x0d,0x9b,0x96,0xf6,0x04,0x70,0x95,0xf5,0xe7, +0x88,0x90,0x8d,0x10,0x1d,0x04,0xea,0x48,0x41,0x20,0x44,0x09,0xd0,0xd5,0x55,0x23,0x66,0x17,0x5d,0x30, +0x88,0x9a,0xe8,0x52,0xb6,0x3a,0x91,0x01,0x02,0xa6,0xb5,0x81,0xe1,0xe5,0x8f,0x4e,0x71,0x3b,0x0b,0x76, +0x1e,0x88,0x4b,0x15,0xbe,0x12,0xc5,0xcd,0x0a,0xec,0xad,0xef,0x11,0xe2,0x8b,0xc1,0xaf,0x47,0x92,0x72, +0xcf,0x36,0xf5,0xa9,0xa3,0x2e,0xa3,0xca,0xe8,0xa9,0xba,0xbd,0x32,0x2a,0xc7,0x06,0xc8,0x7e,0x36,0xc5, +0x1c,0x8b,0x23,0x11,0xdb,0xa1,0xe1,0x0d,0x1f,0x22,0xda,0x10,0x01,0x4b,0x9c,0x9f,0x34,0x78,0x84,0xc6, +0x35,0x44,0x55,0x08,0x50,0x5f,0x68,0x3a,0xbe,0xe1,0xcb,0xb6,0x5b,0xef,0x43,0x80,0x8a,0x77,0xe5,0x81, +0xa3,0x5a,0xcf,0x3c,0xd4,0x39,0xf1,0xbf,0x8a,0x95,0x66,0x2d,0x21,0x2c,0x77,0x72,0xa8,0x25,0xa0,0x76, +0x79,0x36,0xec,0x32,0xc2,0xda,0xb0,0xd7,0x4e,0x42,0x6c,0x13,0xd4,0x2e,0x3c,0x82,0x85,0x67,0xb3,0xf8, +0xe1,0x8c,0xcc,0x23,0x3b,0xba,0xb2,0xe7,0xbe,0x59,0xec,0xd2,0x72,0xd1,0x35,0x63,0x57,0xc8,0x92,0xa5, +0xb1,0x59,0x43,0xab,0x97,0x5a,0xf4,0x94,0x30,0x86,0xcc,0x9d,0x84,0xc0,0xb7,0xdc,0x53,0x0e,0xee,0x37, +0x52,0x07,0x49,0xd6,0x31,0xa7,0xfd,0xd1,0x9e,0x00,0x0b,0x42,0x6a,0x02,0x24,0x18,0x6c,0x7c,0x96,0x74, +0x7e,0x6d,0x0e,0x14,0x39,0x4e,0xe1,0xe4,0x01,0x79,0xd7,0x93,0x2d,0xdf,0x12,0x5b,0x7f,0x8d,0x44,0xb2, +0x88,0x74,0x24,0xdf,0x08,0x3b,0x8f,0x05,0x37,0x71,0xc1,0x33,0xc3,0xa8,0xf3,0x74,0xf5,0x10,0xbc,0x96, +0x79,0x1b,0x44,0xec,0xba,0x26,0x3d,0x79,0x62,0xcc,0xd6,0x35,0xe9,0x5e,0x3b,0xa5,0xc0,0xb2,0xa4,0xcd, +0x0a,0x1e,0x6b,0x91,0xe5,0x72,0x83,0xa4,0x52,0xad,0x28,0xd5,0xf2,0x5e,0xe0,0x90,0xd1,0x74,0x32,0x51, +0xad,0xbe,0x6c,0x07,0x84,0x1d,0xc8,0x9d,0xd0,0xbe,0x84,0x9f,0x31,0x44,0x3a,0xdf,0xc5,0x92,0x81,0x18, +0xac,0xa5,0x32,0x2e,0xc6,0x2f,0xe3,0x6b,0xe7,0xd9,0x0d,0x82,0xaa,0x44,0x7d,0x96,0x82,0x6b,0x3f,0x14, +0xf0,0x16,0x3e,0xe6,0xb7,0x94,0x67,0x62,0x23,0xc1,0x81,0xfa,0x9c,0x29,0x2b,0xbb,0xc2,0x0d,0x1f,0x1f, +0xcb,0x60,0x61,0x53,0xc1,0xa1,0xa6,0xd7,0x28,0x43,0x8e,0x8c,0x61,0xc8,0x95,0x70,0x10,0x90,0x16,0x11, +0x4f,0x96,0xfa,0x76,0x3d,0x7a,0xf8,0x67,0x4f,0x1d,0x4f,0x9c,0x45,0xa6,0xaf,0xea,0xba,0xbd,0x6b,0x8f, +0xcc,0xb9,0x58,0x4b,0x62,0x47,0x3d,0x85,0x46,0xd9,0x31,0x37,0x72,0x52,0x51,0xaa,0x55,0xfe,0x99,0x2b, +0x48,0x72,0xf0,0xf0,0x88,0xeb,0x96,0x22,0x4b,0x3f,0x14,0x3f,0xea,0xeb,0xc1,0xe1,0xf8,0x67,0x8f,0x23, +0xee,0xf8,0xa0,0x62,0x33,0x34,0x5c,0xf6,0x95,0x52,0xb4,0x1b,0x10,0x28,0x08,0x8a,0xe0,0x09,0xbc,0xbd, +0xe0,0xa1,0xba,0x38,0x27,0x1f,0x1f,0x4f,0x84,0xf7,0x8a,0x8f,0x2d,0xca,0x59,0x45,0x22,0xb7,0xd8,0x70, +0xfc,0x2d,0x21,0x31,0x47,0x21,0x3d,0x8f,0x7f,0x84,0x03,0x9b,0x21,0xd5,0x3b,0x78,0xec,0x92,0x68,0xe9, +0x81,0x51,0xae,0x43,0xb8,0xa4,0x6c,0xc6,0x54,0x94,0x00,0x94,0x81,0xd5,0x5b,0xda,0x04,0xdd,0xca,0xd6, +0x2e,0xd8,0x51,0x74,0x3e,0xc9,0xa2,0x21,0x65,0xa4,0x42,0x2a,0xb2,0xed,0x3f,0x06,0x45,0xfc,0xe3,0x6b, +0x97,0xce,0x1f,0x5b,0x59,0xb2,0x2b,0x45,0xb1,0x16,0x53,0x0f,0xcf,0x1b,0x7f,0x80,0x31,0x78,0x5e,0xaf, +0x0f,0xc1,0xb6,0xa2,0x20,0x2b,0x32,0xc4,0xc0,0xc4,0xc4,0x2f,0xa3,0x55,0xa3,0x0a,0xd3,0x92,0xc7,0x78, +0xc3,0xcd,0x6d,0x32,0xd8,0x3f,0x73,0xf4,0x38,0x38,0x3b,0x15,0xce,0xfc,0x38,0x3e,0x17,0x0e,0x58,0x5f, +0xcc,0xb5,0x55,0x41,0x3a,0xe4,0x5e,0x1c,0x58,0xe0,0x63,0x21,0x48,0x1c,0x1f,0x42,0x8f,0x45,0xff,0x73, +0xf0,0xb1,0xec,0x7f,0x2e,0xf9,0x1f,0x03,0x5f,0x2b,0xfe,0xc7,0xaa,0xff,0xb1,0xe6,0x7b,0xcc,0x07,0xdb, +0x0e,0x8d,0x25,0xd4,0x7b,0x31,0xf8,0xa2,0x14,0x7c,0x51,0x0e,0xbe,0xa8,0x04,0x5f,0x54,0x83,0x2f,0xb6, +0x83,0x2f,0x6a,0xc1,0x17,0x3b,0x81,0x17,0xc1,0x61,0x14,0xfd,0x50,0x29,0xfa,0xc1,0x50,0x0c,0x4e,0xb4, +0x18,0x9c,0x57,0x31,0xd4,0x60,0x70,0x5e,0xc5,0xe0,0xbc,0x82,0x05,0x4a,0x99,0xc0,0x63,0xc5,0xff,0xe8, +0x03,0x7d,0xb0,0xb1,0xb2,0xbf,0x74,0x25,0x80,0x87,0x20,0x08,0x2b,0xc1,0xe2,0x55,0xff,0xa3,0xaf,0xaf, +0x20,0xb8,0xb7,0x03,0x8d,0x07,0xa1,0xbf,0xed,0xaf,0x1e,0xc4,0xc5,0xdf,0x75,0x56,0xca,0x5d,0x0d,0x31, +0xe7,0x9f,0x22,0x96,0xd2,0x92,0x53,0x50,0xe1,0xd5,0x10,0x7b,0x16,0x2a,0xbc,0x8e,0x96,0x9d,0x88,0xa2, +0x8b,0x30,0xe2,0x50,0x14,0x43,0x63,0xdc,0xb9,0xa8,0xf0,0x5a,0x8e,0x39,0x1d,0xc5,0x30,0x16,0x77,0x40, +0x2a,0xcc,0x22,0x96,0x1d,0x93,0x0a,0x53,0x66,0xec,0x61,0x29,0xba,0xac,0x22,0xce,0x4b,0x45,0x71,0x92, +0x65,0xa7,0xa6,0x38,0x5f,0x89,0x3b,0x38,0x15,0xc1,0x03,0x63,0x8e,0x4f,0x71,0xfe,0x13,0x77,0x82,0x2a, +0x8a,0x1d,0x2d,0x3b,0x47,0x45,0x97,0x6d,0xc4,0x51,0xaa,0x28,0x36,0x16,0x77,0xa0,0x2a,0x8a,0xc3,0x2d, +0x39,0x56,0xc5,0xf9,0x5f,0xdc,0xc9,0xaa,0x28,0x76,0x18,0x7d,0xbe,0x8a,0xf3,0xc9,0xb8,0x23,0x56,0x94, +0xcd,0x44,0x9c,0xb2,0x8a,0xe2,0x90,0x4b,0xce,0x5a,0x45,0xf1,0xcf,0xb8,0x13,0x57,0x51,0x42,0x65,0xe9, +0xb9,0x2b,0xce,0x7a,0xe3,0x8e,0x5e,0xad,0x10,0xa6,0x21,0x2e,0x1d,0x38,0x86,0xf5,0xb2,0x54,0x72,0x97, +0x6b,0xa2,0xdc,0x1e,0x2b,0x42,0xdb,0xf8,0x10,0x7a,0x2c,0xfa,0x9f,0x83,0x8f,0x65,0xff,0x73,0xc9,0xff, +0x18,0xf8,0x5a,0xf1,0x3f,0x56,0xfd,0x8f,0x35,0xdf,0x63,0x3e,0xd8,0x76,0x68,0x2c,0xa1,0xde,0x8b,0xc1, +0x17,0xa5,0xe0,0x8b,0x72,0xf0,0x45,0x25,0xf8,0xa2,0x1a,0x7c,0xb1,0x1d,0x7c,0x51,0x0b,0xbe,0xd8,0x09, +0xbc,0x08,0x0e,0xa3,0xe8,0x87,0x4a,0xd1,0x0f,0x86,0x62,0x70,0xa2,0xc5,0xe0,0xbc,0x8a,0xa1,0x06,0x83, +0xf3,0x2a,0x06,0xe7,0x15,0x2c,0x50,0xca,0x04,0x1e,0x2b,0xfe,0x47,0x1f,0xe8,0x83,0x8d,0x95,0xfd,0xa5, +0x2b,0x01,0x3c,0x04,0x41,0x58,0x09,0x16,0xaf,0xfa,0x1f,0x7d,0x7d,0x05,0xc1,0xbd,0x1d,0x68,0x3c,0x08, +0xfd,0x6d,0x7f,0xf5,0x20,0x2e,0xfe,0x3e,0xb9,0xcd,0x56,0x43,0x8c,0xdc,0x8e,0x58,0x4a,0x4b,0xe4,0x76, +0x78,0x35,0xc4,0xca,0xed,0xf0,0x3a,0x5a,0x26,0xb7,0xe9,0x22,0x8c,0x90,0xdb,0x0c,0x8d,0x71,0x72,0x3b, +0xbc,0x96,0x63,0xe4,0x36,0xc3,0x58,0x9c,0xdc,0x0e,0xb3,0x88,0x65,0x72,0x3b,0x4c,0x99,0xb1,0x72,0x9b, +0x2e,0xab,0x08,0xb9,0x1d,0xc5,0x49,0x96,0xc9,0x6d,0xce,0x57,0xe2,0xe4,0x76,0x04,0x0f,0x8c,0x91,0xdb, +0x9c,0xff,0xc4,0xc9,0xed,0x28,0x76,0xb4,0x4c,0x6e,0xd3,0x65,0x1b,0x21,0xb7,0xa3,0xd8,0x58,0x9c,0xdc, +0x8e,0xe2,0x70,0x4b,0xe4,0x36,0xe7,0x7f,0x71,0x72,0x3b,0x8a,0x1d,0x46,0xcb,0x6d,0xce,0x27,0xe3,0xe4, +0x36,0x65,0x33,0x11,0x72,0x3b,0x8a,0x43,0x2e,0x91,0xdb,0x51,0xfc,0x33,0x4e,0x6e,0x47,0x09,0x95,0xa5, +0x72,0x9b,0xb3,0xde,0x38,0xb9,0xbd,0x42,0x98,0x86,0xb8,0xf4,0x46,0x72,0xbb,0x5a,0x16,0xe5,0xb6,0x3e, +0x14,0xda,0xc6,0x87,0xd0,0x63,0xd1,0xff,0x1c,0x7c,0x2c,0xfb,0x9f,0x4b,0xfe,0xc7,0xc0,0xd7,0x8a,0xff, +0xb1,0xea,0x7f,0xac,0xf9,0x1e,0xf3,0xc1,0xb6,0x43,0x63,0x09,0xf5,0x5e,0x0c,0xbe,0x28,0x05,0x5f,0x94, +0x83,0x2f,0x2a,0xc1,0x17,0xd5,0xe0,0x8b,0xed,0xe0,0x8b,0x5a,0xf0,0xc5,0x4e,0xe0,0x45,0x70,0x18,0x45, +0x3f,0x54,0x8a,0x7e,0x30,0x14,0x83,0x13,0x2d,0x06,0xe7,0x55,0x0c,0x35,0x18,0x9c,0x57,0x31,0x38,0xaf, +0x60,0x81,0x52,0x26,0xf0,0x58,0xf1,0x3f,0xfa,0x40,0x1f,0x6c,0xac,0xec,0x2f,0x5d,0x09,0xe0,0x21,0x08, +0xc2,0x4a,0xb0,0x78,0xd5,0xff,0xe8,0xeb,0x2b,0x08,0xee,0xed,0x40,0xe3,0x41,0xe8,0x6f,0xfb,0xab,0x07, +0x71,0xf1,0xf7,0xc9,0x6d,0xb6,0x1a,0x62,0xe4,0x76,0xc4,0x52,0x5a,0x22,0xb7,0xc3,0xab,0x21,0x56,0x6e, +0x87,0xd7,0xd1,0x32,0xb9,0x4d,0x17,0x61,0x84,0xdc,0x66,0x68,0x8c,0x93,0xdb,0xe1,0xb5,0x1c,0x23,0xb7, +0x19,0xc6,0xe2,0xe4,0x76,0x98,0x45,0x2c,0x93,0xdb,0x61,0xca,0x8c,0x95,0xdb,0x74,0x59,0x45,0xc8,0xed, +0x28,0x4e,0xb2,0x4c,0x6e,0x73,0xbe,0x12,0x27,0xb7,0x23,0x78,0x60,0x8c,0xdc,0xe6,0xfc,0x27,0x4e,0x6e, +0x47,0xb1,0xa3,0x65,0x72,0x9b,0x2e,0xdb,0x08,0xb9,0x1d,0xc5,0xc6,0xe2,0xe4,0x76,0x14,0x87,0x5b,0x22, +0xb7,0x39,0xff,0x8b,0x93,0xdb,0x51,0xec,0x30,0x5a,0x6e,0x73,0x3e,0x19,0x27,0xb7,0x29,0x9b,0x89,0x90, +0xdb,0x51,0x1c,0x72,0x89,0xdc,0x8e,0xe2,0x9f,0x71,0x72,0x3b,0x4a,0xa8,0x2c,0x95,0xdb,0x9c,0xf5,0xc6, +0xc9,0xed,0x15,0xc2,0x34,0xc4,0xa5,0x37,0x92,0xdb,0xb5,0xbc,0x28,0xb7,0x1f,0x75,0xa1,0x6d,0x7c,0x08, +0x3d,0x16,0xfd,0xcf,0xc1,0xc7,0xb2,0xff,0xb9,0xe4,0x7f,0x0c,0x7c,0xad,0xf8,0x1f,0xab,0xfe,0xc7,0x9a, +0xef,0x31,0x1f,0x6c,0x3b,0x34,0x96,0x50,0xef,0xc5,0xe0,0x8b,0x52,0xf0,0x45,0x39,0xf8,0xa2,0x12,0x7c, +0x51,0x0d,0xbe,0xd8,0x0e,0xbe,0xa8,0x05,0x5f,0xec,0x04,0x5e,0x04,0x87,0x51,0xf4,0x43,0xa5,0xe8,0x07, +0x43,0x31,0x38,0xd1,0x62,0x70,0x5e,0xc5,0x50,0x83,0xc1,0x79,0x15,0x83,0xf3,0x0a,0x16,0x28,0x65,0x02, +0x8f,0x15,0xff,0xa3,0x0f,0xf4,0xc1,0xc6,0xca,0xfe,0xd2,0x95,0x00,0x1e,0x82,0x20,0xac,0x04,0x8b,0x57, +0xfd,0x8f,0xbe,0xbe,0x82,0xe0,0xde,0x0e,0x34,0x1e,0x84,0xfe,0xb6,0xbf,0x7a,0x10,0x17,0x7f,0x9f,0xdc, +0x66,0xab,0x21,0x46,0x6e,0x47,0x2c,0xa5,0x25,0x72,0x3b,0xbc,0x1a,0x62,0xe5,0x76,0x78,0x1d,0x2d,0x93, +0xdb,0x74,0x11,0x46,0xc8,0x6d,0x86,0xc6,0x38,0xb9,0x1d,0x5e,0xcb,0x31,0x72,0x9b,0x61,0x2c,0x4e,0x6e, +0x87,0x59,0xc4,0x32,0xb9,0x1d,0xa6,0xcc,0x58,0xb9,0x4d,0x97,0x55,0x84,0xdc,0x8e,0xe2,0x24,0xcb,0xe4, +0x36,0xe7,0x2b,0x71,0x72,0x3b,0x82,0x07,0xc6,0xc8,0x6d,0xce,0x7f,0xe2,0xe4,0x76,0x14,0x3b,0x5a,0x26, +0xb7,0xe9,0xb2,0x8d,0x90,0xdb,0x51,0x6c,0x2c,0x4e,0x6e,0x47,0x71,0xb8,0x25,0x72,0x9b,0xf3,0xbf,0x38, +0xb9,0x1d,0xc5,0x0e,0xa3,0xe5,0x36,0xe7,0x93,0x71,0x72,0x9b,0xb2,0x99,0x08,0xb9,0x1d,0xc5,0x21,0x97, +0xc8,0xed,0x28,0xfe,0x19,0x27,0xb7,0xa3,0x84,0xca,0x52,0xb9,0xcd,0x59,0x6f,0x9c,0xdc,0x5e,0x21,0x4c, +0x43,0x5c,0x3a,0x28,0xb7,0x03,0x1b,0xde,0x24,0x91,0x78,0x28,0x3e,0x48,0x1b,0x0f,0x81,0x63,0xd9,0x13, +0xd3,0xb0,0x31,0xb1,0xd3,0x92,0x5c,0xab,0x7f,0x00,0x13,0x34,0xa7,0x4e,0xfa,0x0f,0x92,0x41,0x3c,0xeb, +0xc5,0x01,0x91,0x54,0x61,0x24,0xd0,0x81,0x85,0x19,0xea,0xba,0x94,0x2d,0xda,0x92,0x2a,0xdb,0x2a,0xe6, +0xe4,0x25,0x59,0xd1,0x96,0x7e,0x37,0x97,0x7f,0xe6,0x69,0xd3,0x97,0x94,0x59,0xf2,0x8d,0x0f,0xdc,0x0d, +0x38,0x16,0xc3,0x15,0x23,0x12,0x6c,0xf3,0xf2,0x59,0x1a,0x5d,0x24,0x91,0xf9,0x3e,0xb3,0x80,0xd1,0x49, +0xdf,0x8d,0xbd,0x9d,0xf4,0x83,0x25,0x05,0x98,0xb8,0xc5,0x5f,0xb2,0x2c,0x90,0xd1,0x97,0x61,0xba,0x40, +0x12,0xb2,0xb8,0x61,0x66,0x24,0xcd,0x43,0x25,0x4f,0xe2,0xd7,0xdc,0x53,0x24,0xd8,0x97,0x1b,0x93,0x2a, +0x91,0x43,0xcb,0x34,0x9d,0xfb,0x92,0xe3,0xc6,0xaa,0x2a,0x9e,0x35,0x0e,0x85,0xb6,0x16,0x49,0x74,0x4d, +0x31,0x2e,0x02,0x88,0x36,0x2f,0xf9,0x52,0x9d,0x63,0x00,0x9c,0x98,0x21,0xbd,0x94,0xcf,0x7b,0x67,0x3a, +0x69,0x90,0x1d,0xaf,0x56,0x7c,0x16,0x62,0xf2,0xa2,0xeb,0x64,0x68,0x20,0xad,0x10,0x4a,0xcd,0x53,0x2b, +0xdb,0xd3,0x1e,0x09,0xcd,0x61,0x4d,0xd4,0x6a,0xb5,0xb8,0x6e,0x11,0x4e,0x12,0x00,0x04,0x8f,0xf5,0x51, +0xdc,0xac,0x8c,0x8c,0x14,0xf3,0xb2,0x17,0x76,0x0a,0xb5,0x42,0x6d,0x8f,0x07,0xec,0xef,0x31,0x32,0x60, +0xe3,0x23,0x4f,0x19,0x82,0x67,0x21,0x7c,0x92,0x2c,0x87,0xc8,0x40,0x75,0x8f,0x18,0xdc,0xe8,0xe0,0x02, +0x46,0x79,0x91,0x81,0x49,0xb2,0x77,0xa6,0x9c,0xcd,0x6b,0x67,0x67,0xc7,0x0b,0x75,0xaa,0xd2,0xf0,0xf8, +0x2a,0x4f,0x7f,0x43,0x2b,0xd1,0x75,0x49,0x16,0x59,0xf0,0x85,0x34,0xd5,0xc5,0x1c,0xf3,0x78,0x06,0xcf, +0xcd,0xb2,0x1c,0x2a,0x9b,0x75,0x83,0xa3,0x33,0x8a,0x86,0x89,0xfb,0x95,0xe8,0xf6,0xdc,0xdc,0x53,0x22, +0x25,0x01,0xf9,0x84,0x5b,0xd4,0x35,0x89,0x9d,0x39,0x0c,0xb7,0x44,0xbe,0x05,0x43,0xea,0x76,0x23,0xdb, +0x71,0x03,0x0a,0x43,0x1f,0x22,0x52,0x94,0x01,0xc2,0x06,0x35,0x45,0xa9,0x86,0x5b,0x71,0x83,0xee,0x78, +0xce,0x77,0x12,0xcb,0x18,0xd7,0x99,0x50,0x46,0xcc,0xde,0x57,0x10,0x22,0xc0,0x5f,0x84,0xf5,0x2b,0x0c, +0x40,0xcc,0x61,0xb6,0x9d,0x8a,0x3d,0x1e,0x46,0x8e,0x7b,0x8a,0xec,0xd3,0xcf,0x6e,0x60,0xed,0x61,0xe4, +0x1d,0x5d,0xf8,0x4b,0x48,0x09,0x0f,0xbc,0x7a,0x94,0x24,0x0c,0x89,0x47,0x31,0xc6,0x07,0x30,0xb2,0xf8, +0x42,0xb7,0x80,0x04,0x2a,0x66,0x6c,0x2c,0xb8,0x77,0x5c,0xc2,0x5f,0x3e,0x1d,0x78,0xa6,0xd1,0xd8,0xa1, +0xb7,0x3d,0x75,0x60,0x5a,0x6a,0x4c,0x32,0x07,0x06,0x05,0xe4,0x0f,0xbe,0x93,0x0a,0x9b,0xb4,0xec,0x86, +0x7f,0x4b,0x09,0x91,0x77,0x64,0xfc,0xf0,0xf3,0x22,0xda,0xa3,0xda,0x7e,0x16,0x2a,0x92,0x05,0xc6,0x2c, +0xd9,0xf0,0x76,0x31,0xeb,0x2d,0xcd,0x39,0xac,0x4f,0x46,0x08,0x67,0x90,0x59,0xc6,0x36,0x64,0xc6,0xcb, +0xa4,0x0a,0x15,0x0e,0x7e,0xb9,0x21,0x52,0x57,0xd4,0x51,0x04,0xe4,0xf6,0x3e,0xb2,0x58,0x4b,0xba,0x78, +0x53,0x72,0x05,0x07,0x9d,0x92,0xbf,0xf2,0x73,0x44,0x27,0x2e,0x05,0x04,0x15,0x85,0xac,0x76,0x31,0x82, +0x81,0xb5,0x30,0x7b,0x48,0x0b,0x03,0xed,0x34,0xcc,0xff,0xe9,0x93,0xc2,0x24,0x0a,0x99,0xfc,0xc2,0x5b, +0x3a,0x4a,0x0a,0x5d,0x1f,0xa9,0x60,0xea,0xd2,0x11,0x67,0xde,0x5e,0x64,0x76,0x85,0x30,0x6f,0xb2,0x58, +0x02,0x97,0x4e,0x44,0xd1,0x66,0xd4,0x48,0xe8,0x19,0xac,0x67,0x5f,0xda,0xc5,0x73,0x96,0x76,0x31,0x9f, +0xa2,0xe0,0x24,0x3d,0xf2,0xd0,0xe3,0xb8,0x03,0x1d,0x15,0x0c,0x2c,0x8c,0xec,0x81,0x9e,0xce,0x8a,0xc9, +0xc3,0x2a,0x2c,0x7c,0x96,0x15,0xd4,0x4d,0xd9,0x2e,0x9d,0xa9,0x53,0x95,0xe5,0x6d,0xf7,0xf2,0xb8,0x0b, +0x41,0xb8,0x02,0xbb,0xd8,0xe6,0x01,0xb2,0xde,0xd5,0x23,0x6e,0xbe,0x13,0xfc,0xe6,0x53,0x17,0xb6,0xa3, +0xc0,0xc5,0x28,0x8e,0x4d,0x25,0xf6,0xd4,0x4a,0xec,0xf1,0x21,0x1a,0x7a,0x1e,0x05,0x80,0xf4,0x32,0xb0, +0x64,0x1e,0xf0,0x82,0x89,0xa8,0xdc,0xb7,0x51,0x19,0x71,0x83,0x2f,0x7c,0x1d,0xb6,0x79,0x94,0x78,0x24, +0x32,0x2b,0x62,0x0e,0xcd,0xca,0x0b,0x08,0x31,0xb1,0x6e,0x93,0xc8,0xad,0x73,0x23,0x9c,0xc1,0x73,0x6a, +0xe9,0xc9,0xe8,0x5b,0xe5,0xb4,0xdb,0xe6,0xf9,0xd5,0x3c,0xff,0xe9,0x60,0x68,0xe2,0x95,0x44,0x67,0xdd, +0x9b,0x51,0xe7,0xc6,0xbb,0x4c,0xae,0xd7,0x6a,0xe0,0xad,0x69,0xcd,0x5e,0xbf,0x5b,0x26,0x77,0x17,0xed, +0xdc,0xe8,0x9d,0xcb,0xdb,0xab,0x72,0xe3,0xee,0xf8,0xa1,0xd1,0x38,0xda,0x7f,0x2a,0x4c,0x26,0x47,0x67, +0x37,0x27,0x8f,0x6d,0xf9,0x54,0xd3,0x2b,0x85,0xea,0x43,0xfb,0xe0,0xf4,0xb2,0x75,0x56,0x2e,0x77,0x94, +0xcb,0xcb,0xef,0xed,0x9b,0x83,0xfd,0xc9,0xf0,0xa2,0xab,0x1a,0x2d,0xad,0xb4,0x98,0xda,0x95,0xdb,0x62, +0xf5,0xa6,0x34,0xb3,0x3f,0xcd,0xda,0xce,0xac,0x71,0x3e,0x7b,0xca,0xdd,0x3c,0xf5,0x2e,0xae,0x7f,0x4c, +0xcf,0xe7,0xca,0xe5,0xe9,0xa7,0xed,0xed,0xdc,0x54,0x7f,0x2c,0x6e,0xef,0x34,0x6e,0x9a,0xad,0xb6,0x9e, +0x2b,0x0d,0x1b,0x63,0x73,0xfb,0xd3,0xa7,0xa7,0xeb,0xf3,0xfd,0x76,0xaf,0x79,0x67,0x1f,0xea,0x4f,0xd5, +0xb3,0x83,0xdb,0x6e,0xa9,0x73,0x67,0x1e,0x1c,0x7f,0xba,0xfe,0x51,0x2c,0xff,0xb8,0x1f,0x3d,0xec,0x9b, +0x8d,0xee,0xf8,0xa6,0x73,0x74,0xb4,0x68,0x7d,0xaa,0xb4,0xd5,0xc3,0x4e,0x67,0xbf,0x78,0x7e,0x7b,0x7f, +0x7b,0x3a,0x2d,0xef,0xdf,0x9c,0x76,0xb7,0x7b,0x8d,0xf9,0xe7,0x5a,0x4b,0xbe,0x1a,0x3e,0xea,0xc5,0xb3, +0xf3,0x1f,0x5f,0x16,0x3b,0x87,0x0f,0x05,0xf3,0xe0,0x20,0x7f,0x2c,0x37,0x5a,0xce,0x77,0xb5,0x3a,0x94, +0xa7,0x8d,0xdb,0xf9,0xe2,0xfb,0x69,0xab,0x3b,0xaf,0x7c,0xb9,0xfa,0x32,0xbe,0x69,0xb5,0x07,0x97,0x57, +0xca,0xe9,0xa7,0x42,0xee,0xd3,0x8f,0xee,0xe0,0x74,0x7f,0xd4,0x97,0xfb,0x07,0xe7,0xe6,0x97,0xb3,0xc6, +0xc1,0xd5,0xc5,0x53,0x6f,0xa8,0xe5,0xb6,0xda,0xf7,0x9f,0x2e,0x86,0xb7,0xb5,0x4e,0x4e,0x3f,0x3c,0xd8, +0x3f,0x3e,0xef,0x95,0xaa,0xb5,0x52,0x7b,0x5f,0xab,0x8d,0x8c,0xc3,0xc2,0x56,0xae,0xb0,0xb5,0x93,0x3b, +0x2c,0x0e,0xcf,0x72,0x17,0x4a,0xfe,0x2c,0xf7,0x63,0xeb,0xf3,0xf9,0x79,0xe3,0xee,0xd3,0xf4,0x72,0x3c, +0x1c,0x37,0x4e,0x1b,0x0f,0x95,0x9b,0x41,0xae,0xb8,0xf5,0x65,0xc7,0xce,0x93,0xbb,0xa0,0xba,0x37,0xb7, +0xe7,0x57,0x9f,0x2a,0xad,0xfb,0xa3,0xa3,0x7a,0x4a,0xd0,0xce,0xc0,0x12,0x99,0xa8,0x32,0x12,0x0a,0xfb, +0xb5,0x27,0x9e,0xc4,0x0b,0x2a,0xec,0x2e,0x9d,0x33,0xbe,0x07,0xcb,0x99,0x50,0xa8,0x48,0x25,0x27,0xf8, +0xe2,0xad,0x89,0x04,0xaf,0xfa,0x6a,0xdf,0x74,0xb6,0x75,0x03,0x5f,0x18,0x84,0x48,0x2e,0xef,0x0f,0x2e, +0x3f,0x37,0xe7,0xb7,0xfb,0xbd,0x9b,0xab,0x46,0x67,0x7e,0x5c,0xd9,0xa9,0x15,0x6f,0x8e,0x07,0x87,0x8d, +0x4a,0xe3,0xf2,0x7b,0x43,0xbb,0x6a,0xdc,0xc8,0xd7,0x66,0xc7,0x98,0x0f,0x9b,0x85,0xde,0x8f,0xed,0xed, +0xc7,0xad,0x41,0xe3,0xc2,0xda,0xde,0xee,0xd7,0xba,0x9d,0x5e,0x5e,0x9d,0xcd,0xbb,0x77,0x3b,0xb3,0xce, +0xd3,0xc1,0xcd,0x34,0x7f,0x51,0xa8,0xe8,0x77,0x27,0xdd,0xd2,0x7e,0xe5,0xb0,0x53,0xb5,0xf4,0xe9,0xf0, +0xec,0xa6,0xb2,0xff,0xd8,0xa8,0x76,0xe6,0x2d,0xe5,0xa9,0x21,0x2f,0xc6,0x93,0xf1,0xa3,0xb6,0x35,0x3a, +0xb5,0x4e,0xbf,0x77,0x1f,0x6e,0xf2,0xf3,0xe2,0x6d,0x79,0x67,0x78,0xfe,0x7d,0xe7,0x7c,0xe8,0xb4,0xa0, +0xc1,0xf6,0xe8,0x50,0x5e,0x7c,0x2a,0xf4,0xba,0xdd,0xfe,0x7c,0xd1,0x3b,0xdd,0x39,0xba,0x9f,0x1c,0x16, +0x9b,0xc7,0x3d,0x73,0x7c,0x7f,0x79,0xf6,0xe3,0x09,0x7e,0x2a,0xf6,0xb9,0x5c,0xbe,0xc8,0x5f,0x37,0xef, +0xad,0x9b,0xc1,0xb4,0xff,0xa5,0x75,0xbe,0xbd,0x33,0x98,0xdf,0xb5,0x64,0x67,0x90,0xcb,0x95,0x5b,0x5b, +0x8d,0x9d,0x4f,0xaa,0xe6,0xdc,0xe9,0xf6,0xb1,0x5a,0x26,0xf3,0x6e,0x1e,0x5f,0xdd,0x54,0x3a,0xd6,0xc3, +0xf1,0x70,0x38,0xac,0x47,0x22,0x8c,0xfe,0x93,0x79,0x14,0x52,0xc8,0x46,0x29,0xd6,0x5e,0xa2,0x04,0xbf, +0x21,0xe0,0xf2,0x30,0x7a,0x33,0x96,0x97,0x58,0xb8,0x18,0x48,0x1c,0x5c,0x4d,0xc5,0xa2,0x98,0x6a,0x3b, +0x3e,0x31,0xbc,0x84,0x22,0x06,0x83,0xdf,0x49,0x12,0xe3,0x07,0xc6,0x37,0x6e,0x3e,0x9f,0x8d,0x8a,0x87, +0xfb,0x97,0x8d,0x61,0xfb,0xa6,0x61,0xe4,0xd5,0xc2,0xec,0xe8,0xbc,0xf4,0x7d,0xa8,0x9e,0x9c,0xde,0x34, +0xd5,0xd3,0x71,0xa1,0xa4,0x94,0x1a,0xa7,0xca,0xe3,0x63,0xf3,0xfe,0x6c,0x5e,0x6a,0x0f,0x73,0x83,0x87, +0xc6,0xf1,0xf4,0x7b,0xab,0x9a,0x7b,0xa8,0x29,0x93,0xed,0x8b,0xd1,0xbc,0xd3,0x6a,0xce,0xee,0xca,0x67, +0x4f,0xa3,0xeb,0xc2,0xd6,0xd4,0x29,0xcc,0x0b,0xdd,0x4a,0xcd,0xfe,0x32,0x90,0xab,0x5a,0xcb,0xba,0xef, +0xf7,0x5b,0x07,0xb7,0xf3,0x7d,0x53,0x1f,0xf5,0xe7,0x5f,0x66,0x5f,0x2e,0x46,0x17,0x76,0xaf,0x55,0xfd, +0x3c,0x3f,0xe8,0x35,0xcf,0xf7,0xb5,0x56,0xf3,0xe4,0xa8,0xd5,0x99,0x94,0xaf,0x2b,0xb7,0x47,0xcd,0xb3, +0xfc,0xbc,0x66,0x1e,0xcd,0x77,0x8a,0xe7,0xda,0x70,0x51,0x1e,0xde,0x7c,0x9a,0x6b,0xc5,0xf1,0xf6,0xc3, +0xd9,0xa8,0xab,0x35,0x4b,0xca,0xb0,0xd3,0xc8,0x0d,0xee,0xe7,0xd3,0xfd,0x83,0xcf,0xb5,0xad,0xcb,0x9a, +0x52,0xdd,0x52,0x8d,0x81,0xa5,0x8e,0xf2,0xb9,0xee,0xfe,0xc9,0x60,0x4b,0x2e,0xb6,0x6f,0x0b,0xea,0x0d, +0xf0,0x82,0xc6,0xeb,0xc8,0xa1,0xd6,0xc3,0x3f,0x1c,0xf5,0x54,0x87,0x0a,0xd1,0x02,0x4f,0x86,0x1b,0xca, +0x56,0x13,0x45,0x14,0x23,0x50,0x3c,0x90,0x28,0xfe,0x99,0x46,0xd6,0xb0,0x8c,0x30,0x06,0x03,0x3f,0x65, +0xb8,0xbd,0xc7,0x08,0x94,0xb7,0xa6,0x0c,0xbf,0x44,0xd9,0xca,0x73,0x89,0x72,0x32,0xdf,0xc9,0x29,0xb9, +0x86,0x7d,0x69,0x0d,0xf7,0x35,0xb9,0x71,0x79,0xfd,0xa3,0xba,0x35,0xcb,0x7d,0x29,0xde,0xea,0xb9,0x79, +0xed,0xb4,0x7d,0x38,0x3f,0x79,0xdc,0x37,0x4f,0x1f,0x16,0x5f,0xce,0xb6,0x9c,0x5c,0xaf,0x98,0x9b,0xe6, +0x7e,0x7c,0xfa,0x6e,0x0e,0xf2,0xa3,0xa3,0xd6,0x51,0x6b,0x52,0xbb,0x6e,0xda,0x8d,0x86,0xdc,0x2c,0x5f, +0x9c,0x5f,0x4c,0xbf,0x2f,0x5a,0x8f,0xb5,0x71,0xf5,0xee,0xba,0x79,0x5b,0x6e,0x4d,0xae,0xbe,0xe7,0x7a, +0xc7,0xb3,0x6b,0xa5,0x65,0xed,0xf4,0xad,0xe3,0xc3,0xc2,0xdc,0xb8,0xeb,0x4e,0xb5,0xbe,0xa3,0x37,0x77, +0xb4,0xad,0xca,0x43,0xf7,0x6a,0x5e,0x6c,0x34,0xe7,0xed,0xce,0xc1,0xd6,0xf8,0xbb,0x39,0xeb,0x5f,0x28, +0xf3,0x5b,0xd3,0x92,0xc7,0x05,0x98,0x7e,0xa1,0xf1,0xe9,0xf2,0x6e,0x04,0x45,0xca,0xcd,0x8b,0x6a,0xa3, +0xff,0x70,0x38,0xe8,0xa8,0xdf,0xd5,0xa3,0x7b,0xa5,0xa3,0x1d,0x2a,0x8d,0x2f,0x93,0xa3,0xb9,0x62,0x1d, +0xdc,0xb5,0x7e,0xd4,0xce,0xf5,0xc1,0xac,0xfb,0xa3,0x3d,0xbc,0x38,0x3f,0x36,0x95,0x73,0xbd,0x5b,0x79, +0x38,0xe9,0x9f,0x9f,0xd6,0x4e,0x4e,0x3b,0xed,0xda,0xf6,0x8f,0xc6,0x59,0xfe,0xc6,0x29,0x6f,0x7f,0x69, +0xdc,0xcb,0xf3,0x4b,0x75,0x5e,0xd8,0x79,0xf8,0xbc,0x68,0xef,0x6f,0x7f,0x6f,0x74,0x87,0xa5,0x87,0x2f, +0x8b,0xd2,0xf4,0xb8,0x3b,0x34,0x47,0xb3,0xed,0xf9,0xd6,0xe5,0xce,0x49,0x6d,0x76,0x7d,0x51,0xfb,0x32, +0xbc,0x68,0x0f,0x2e,0xca,0xf7,0xa5,0x5e,0x61,0xab,0xdd,0x3c,0xbc,0x7f,0xba,0x38,0xb4,0xf2,0xd5,0x8b, +0xdc,0x63,0x79,0x31,0xd8,0xb2,0x0a,0x20,0x23,0xb5,0xfc,0xa5,0xfc,0xb4,0x6f,0x17,0x4f,0xda,0xa3,0xf3, +0xab,0xd6,0xac,0x39,0x3e,0x9e,0x3f,0x35,0x3a,0x8d,0xad,0xd2,0x69,0xe5,0xe1,0xee,0xe9,0xfc,0x61,0xdc, +0xff,0x05,0xc9,0xb2,0x4c,0xa4,0x88,0x24,0x72,0x28,0x1b,0x8a,0xae,0xfe,0x4e,0xfa,0x78,0xca,0x0b,0x1a, +0xc7,0xd1,0xf1,0xbe,0xf6,0xf8,0xe5,0xb8,0x51,0xd5,0xd5,0xa3,0x93,0x2f,0xa3,0xe6,0xe1,0xc9,0xcd,0xf0, +0xfe,0xf1,0xfb,0x9d,0x3e,0x91,0x2f,0x27,0x9f,0xba,0x0f,0xc3,0xb9,0x7d,0xd9,0xa9,0x9e,0x18,0x93,0xaa, +0xfa,0x70,0xd2,0x9a,0x9f,0x1e,0xb5,0x2d,0xfb,0x5a,0xb3,0x0f,0x8e,0xe4,0x56,0xf9,0xf4,0xf1,0xd3,0x4e, +0x61,0xa1,0x0c,0x9d,0xe3,0xa2,0x66,0x74,0xf6,0xef,0xfb,0xe5,0x92,0x5d,0xf8,0xfe,0xd4,0x3f,0xd4,0xb7, +0x07,0x8b,0x7e,0x5b,0x3d,0x19,0xcd,0x0e,0xbe,0x97,0xb7,0x2e,0xaa,0x87,0xd6,0x8f,0xf3,0x89,0xf2,0xe3, +0xea,0xa6,0xa9,0xb7,0xad,0xc1,0x13,0xe8,0x76,0xc3,0xe3,0xe3,0x23,0x67,0x7e,0xd4,0x6c,0xf7,0x8f,0xd4, +0xce,0xf0,0xac,0x51,0x52,0x4f,0xbe,0x9f,0xdf,0x3d,0x74,0x8b,0x9d,0xf3,0xfb,0xcb,0xad,0xc5,0x7d,0x43, +0xd1,0xdb,0x3b,0xd6,0xd1,0x7e,0xf5,0x4c,0x7e,0x78,0x3a,0x2c,0xe4,0xb7,0x3b,0xba,0xf1,0xd0,0x2c,0x98, +0x47,0x9f,0xbf,0x18,0xed,0xda,0x77,0x55,0x35,0x4e,0x26,0x67,0x0d,0x6b,0x2a,0x3b,0x87,0xf6,0xfd,0xd9, +0xf9,0x62,0x51,0xd9,0x6a,0x1a,0x97,0x57,0xe5,0xdc,0x6c,0x96,0x3b,0xaa,0x6d,0x3b,0x57,0xa5,0x82,0x5d, +0xac,0x7c,0xe9,0xb6,0x46,0x66,0x6b,0xe7,0x93,0x5d,0x51,0xb6,0xb6,0xfb,0xd3,0x8a,0x5d,0xea,0x3d,0x9d, +0xd4,0xb6,0x06,0xa7,0xbd,0x8e,0xf3,0xfd,0x44,0x5f,0xd4,0x0e,0x1f,0x95,0x79,0xe7,0xe6,0x74,0xde,0x6c, +0x74,0xb4,0xed,0x93,0xde,0x4e,0xb3,0x5f,0x3d,0x2a,0x12,0x24,0x77,0xf4,0xfd,0xeb,0x87,0x2e,0x28,0x17, +0xad,0xd6,0x72,0x1c,0x2f,0xd5,0xe0,0x7d,0xca,0x76,0x84,0x11,0x57,0x5e,0xa6,0x53,0xb3,0x33,0x45,0x61, +0xe2,0x68,0x11,0x5f,0xda,0xef,0x14,0x2e,0x23,0xae,0x94,0xde,0x14,0x2f,0xe7,0x72,0xe9,0xe0,0xb4,0xb1, +0x68,0x6d,0x5d,0x3d,0xcd,0x2e,0xf2,0xdd,0xd9,0xa1,0x5c,0x9c,0x9d,0xe6,0x1a,0x37,0x95,0xf3,0xd1,0xec, +0xda,0xfe,0x32,0xec,0x9c,0x1f,0x54,0x4f,0x26,0xa5,0xc3,0xda,0xd3,0x62,0x71,0xdc,0x98,0x55,0xb7,0x4b, +0x05,0xe3,0xe4,0xf3,0xdd,0xd5,0xfc,0xea,0xf8,0xc7,0x8f,0x33,0xdd,0x6a,0x14,0x5b,0xd3,0xe6,0x8f,0xc6, +0xfc,0xf6,0xf3,0xdd,0xe9,0x59,0x79,0x78,0x74,0x7c,0xda,0x6f,0x5c,0x1a,0xfa,0x83,0xae,0xef,0x3b,0xe3, +0xcb,0xdc,0x6d,0xaf,0x71,0x65,0x5b,0xa6,0xdd,0xad,0xa9,0xc7,0xd6,0xc5,0xf5,0xb8,0xd8,0xe8,0xdf,0xc8, +0x95,0x83,0x4b,0xfd,0xac,0xa9,0x74,0x1e,0xad,0x41,0xab,0xfb,0x90,0x3f,0x6e,0x7f,0x3f,0x1f,0x7d,0x69, +0x1d,0xef,0x74,0x1b,0x95,0xc7,0xed,0x47,0xf3,0xe0,0xbb,0xb9,0x30,0x4b,0x07,0x53,0xa7,0x54,0x18,0x28, +0x3b,0xd5,0xd2,0x53,0x6e,0xd8,0x39,0xfe,0xd1,0xfd,0xd2,0x3f,0xd0,0xfb,0xed,0xe9,0x7c,0x43,0x01,0xb2, +0xce,0x2a,0xa5,0xce,0x85,0x10,0x26,0xae,0xb0,0xe6,0xbf,0x66,0xad,0xde,0x5d,0x9c,0xde,0x55,0x3b,0xa7, +0xcd,0xab,0xce,0x63,0xf1,0xe2,0xd3,0xed,0x81,0xde,0x28,0x8e,0x1c,0x7b,0xbe,0xd0,0x17,0x76,0xe5,0xe9, +0x01,0x94,0xb7,0xc9,0x55,0x6d,0x78,0xfb,0xe9,0xd3,0x78,0x32,0xd4,0x8f,0x1d,0xfd,0x7a,0x74,0x52,0x99, +0x2f,0x3a,0xbd,0x27,0xe3,0x93,0x76,0x7a,0xa0,0x9e,0x3c,0x5d,0x5c,0xcc,0x4f,0x9f,0x54,0xf8,0xb3,0xff, +0xd4,0xda,0xbe,0x77,0xe4,0x8a,0x65,0xfc,0xb8,0x9f,0xc8,0xa5,0xd6,0xb5,0x95,0x7f,0x1c,0xb4,0xee,0x4a, +0x3d,0x73,0x9a,0xdb,0xbe,0x9a,0xb7,0x2f,0x9e,0x4e,0xb5,0xf2,0x60,0x27,0x77,0xa5,0x5c,0x15,0x1f,0x8e, +0xcb,0xf7,0x97,0x8d,0x76,0xfe,0xf1,0x6a,0xfc,0x3d,0x97,0x07,0x8e,0xbe,0xb8,0x55,0xbb,0x97,0xf7,0xfb, +0x7a,0xfe,0x62,0x7e,0xb0,0xdf,0x3e,0xb8,0x34,0xf7,0xe7,0x5b,0xda,0x9d,0x32,0x3f,0x5b,0xdc,0x96,0x9b, +0xf9,0x87,0x7e,0xcb,0xd9,0x1e,0xed,0xb7,0x47,0x77,0xf7,0x37,0xea,0x53,0xe9,0xfc,0xfc,0xa1,0x7c,0x3e, +0xaf,0x5c,0x17,0xae,0x40,0x8d,0x38,0x90,0xfb,0xe3,0xc5,0x8f,0xc6,0xad,0x0e,0xec,0xbd,0x7d,0xdc,0x3a, +0x07,0xee,0x7f,0x55,0xb9,0x1c,0xcc,0x76,0x72,0xf2,0x17,0x58,0x78,0x27,0x5f,0x9c,0xc5,0x56,0x65,0x74, +0xd0,0x2e,0xf7,0x26,0x8b,0xa7,0x13,0xfb,0xc7,0xce,0xac,0xdb,0x32,0x74,0xf5,0xcb,0xf6,0xd3,0xf9,0x51, +0xb9,0xb4,0x75,0xf9,0x63,0xe7,0x62,0x7a,0xf5,0x79,0xeb,0xf2,0x64,0xd2,0xac,0x7d,0xbe,0xb8,0xba,0xbc, +0xbf,0x6f,0xfe,0x30,0x47,0x9b,0xad,0xd5,0xe5,0x98,0xe6,0xca,0x81,0xc7,0x97,0x83,0x4e,0x8b,0xef,0x97, +0x77,0xad,0x8b,0xcc,0xfc,0x4e,0x53,0x86,0xaa,0xe3,0xf3,0x8a,0x79,0x19,0x5c,0x77,0xa9,0x77,0x13,0xfd, +0x9c,0x7f,0xc8,0xb2,0xbc,0x3c,0x91,0x39,0x96,0x2a,0xb0,0x6c,0x67,0xa2,0x0e,0x5a,0x49,0xb9,0x63,0xad, +0xe5,0x63,0xf3,0xbd,0x05,0x68,0xb4,0x48,0xbc,0xfb,0x82,0x8b,0xb8,0xf0,0xe2,0x1f,0x31,0x7b,0x22,0xac, +0x29,0x90,0xf1,0x95,0x8e,0x34,0x90,0x60,0x9d,0x8f,0x8e,0xcc,0x05,0x1d,0xee,0x11,0xde,0x13,0xde,0xc1, +0x48,0x45,0xb3,0x1d,0xcd,0x73,0xff,0x84,0x2b,0xc2,0x9d,0xb4,0xfe,0x2f,0x3b,0x38,0x5a,0x01,0x5b,0x6e, +0xe3,0xec,0xb2,0x99,0x65,0x88,0x14,0xbe,0x11,0x4f,0x47,0x9f,0xfa,0x0f,0xd6,0x42,0x01,0x8e,0x88,0x31, +0xed,0x3e,0xf0,0x1d,0x98,0xb1,0x66,0xed,0x0d,0x74,0x13,0x3a,0x20,0x87,0x70,0x7d,0x80,0x67,0x1e,0xa5, +0x0c,0x01,0x03,0x6e,0x0a,0x90,0x5f,0x34,0x07,0x5f,0x38,0xed,0x9e,0xb7,0x41,0x44,0xbb,0x94,0xb2,0x85, +0x8a,0xcd,0x39,0x48,0x8d,0xe4,0x08,0x14,0xe1,0xc5,0xbd,0xab,0x3e,0x4c,0x54,0xab,0x55,0x7f,0xa9,0x73, +0x28,0x03,0x12,0xe5,0x97,0x80,0x4b,0x8f,0x1b,0x87,0x2b,0x47,0x98,0xbe,0xab,0x3c,0x5a,0xfe,0x74,0xb1, +0xe2,0x40,0x5b,0x04,0xa8,0xcf,0x3e,0x1c,0xa0,0x1e,0x2d,0x24,0xdf,0x15,0xfb,0x8e,0x58,0x06,0xa4,0x34, +0x8e,0x34,0x84,0x1f,0x36,0xd0,0x1a,0x97,0xa3,0x15,0xcf,0xa7,0x9e,0xa9,0xc6,0x0a,0x51,0x2c,0xc5,0xb6, +0x51,0x3d,0xd8,0xdb,0x30,0x2e,0x3c,0xfc,0x6b,0xe1,0x9d,0x95,0x56,0x9a,0x4f,0xe1,0xc2,0x52,0xf1,0x0a, +0xe8,0xe7,0xe5,0x94,0xc0,0x6f,0x1f,0x89,0xce,0xbb,0xc8,0x63,0x11,0xe2,0x3a,0xf3,0x65,0x04,0xf4,0xf7, +0x1b,0xf5,0x89,0x12,0x89,0x90,0x1a,0xd0,0x37,0x86,0xbc,0xbf,0x8f,0x60,0x83,0x4d,0xf3,0x31,0x8a,0x64, +0xca,0xee,0xcd,0x20,0x6b,0x32,0xaa,0xd0,0x4d,0x10,0x11,0x34,0x23,0xac,0x92,0x58,0xaa,0x59,0xbe,0x38, +0x04,0x62,0xa2,0xd3,0x79,0x0e,0x92,0x40,0x0c,0x44,0xc9,0x12,0x92,0xfc,0x55,0x63,0x17,0x94,0xdd,0x5f, +0x9f,0x44,0x63,0x38,0xf5,0x4a,0x12,0xad,0x7a,0xaa,0x1e,0x07,0x4b,0xc5,0xe5,0x1b,0xe4,0x57,0x29,0x12, +0x4a,0xae,0x69,0x8a,0xd0,0x72,0x17,0x56,0x03,0xbd,0x95,0xdd,0x58,0x04,0x0b,0x2b,0xfc,0x15,0x3a,0x47, +0xb3,0x41,0x15,0x8f,0x4b,0x72,0x7f,0xba,0x7e,0x71,0x97,0xd7,0x88,0xea,0xa4,0x37,0x4f,0x6f,0x3b,0xb7, +0x8d,0xcb,0x4e,0x33,0x37,0x28,0xed,0xa8,0xf2,0xf1,0xcd,0x14,0xdf,0xb7,0x3e,0x37,0x8f,0xee,0x3e,0x63, +0x59,0xfb,0x1a,0xfe,0x3a,0xe9,0xcc,0x3b,0x8d,0xf1,0x64,0x7e,0x4f,0xea,0x94,0xae,0x8e,0xaf,0x6f,0x6e, +0x0e,0x4b,0x97,0xf7,0x57,0xcd,0xf6,0xf0,0xd3,0x8e,0xf2,0x49,0x39,0x25,0x7d,0xe8,0xf9,0xab,0xdb,0x51, +0xfe,0xb2,0xb8,0xe3,0xf4,0xee,0x6e,0xa7,0x4a,0x03,0x54,0xfe,0xf1,0xed,0x48,0x39,0xb8,0x7d,0x38,0x3a, +0x54,0x26,0xca,0xc1,0x70,0x78,0x95,0xd7,0xcf,0x6e,0xf6,0xd5,0x66,0x7b,0x44,0xec,0xab,0xab,0xee,0xcd, +0x55,0xf3,0xb6,0xf1,0xfd,0xf3,0x7d,0xee,0xfb,0xc5,0xfc,0xa8,0x71,0x7b,0x7f,0x35,0xd8,0x69,0xcf,0x9b, +0x5b,0xb3,0x79,0xee,0xb1,0x3a,0x3b,0xbd,0x2e,0x44,0x29,0x78,0x12,0x95,0x1a,0x12,0xdf,0x67,0xa6,0xb0, +0x32,0x19,0xef,0x0c,0xee,0xc4,0x06,0x36,0xc7,0x7c,0xb0,0xf7,0x6f,0xb2,0xb2,0x86,0xc6,0x66,0x4f,0xd3, +0xd5,0xa0,0x02,0x10,0xba,0xa0,0x88,0xf5,0xb2,0x74,0xe1,0x90,0xfb,0xb7,0xc9,0x30,0xbc,0x7b,0x69,0xc9, +0x9e,0x3c,0x5f,0x58,0x95,0xd0,0x36,0x8d,0x9b,0xda,0x49,0xa8,0x21,0x2c,0x9c,0x80,0x9a,0x12,0xbb,0x3d, +0xb9,0xe4,0x46,0xb1,0x80,0x07,0x5f,0xb8,0xef,0x68,0xb8,0x24,0xfd,0x1c,0xcf,0x4d,0x1e,0xdc,0x63,0x08, +0x44,0xae,0xb9,0x2b,0xa1,0x40,0xb7,0xd5,0xa3,0xf3,0xeb,0x7e,0xf0,0x27,0x52,0x2f,0x47,0x26,0x52,0xa7, +0xfb,0x32,0xcb,0x93,0xef,0xc6,0x5f,0x5a,0x20,0x86,0xa6,0x44,0x45,0x9c,0xba,0xfb,0x5b,0xa5,0x22,0xd9, +0xb2,0xa3,0x3b,0x75,0x1f,0x42,0xf7,0xb2,0xfb,0xf6,0x4d,0x48,0x1c,0xcc,0xa8,0xf8,0xec,0xbf,0x48,0x3c, +0x3b,0x91,0x0d,0x21,0xfb,0x1e,0xdd,0x96,0xa0,0x9a,0x57,0x20,0x0b,0x14,0x8f,0xdd,0x10,0x6e,0x0e,0xc7, +0x6c,0xa1,0x82,0xdf,0x8a,0x4e,0x70,0x04,0x06,0x66,0x28,0xfa,0x81,0xdf,0xf3,0xee,0xb1,0xe0,0xbc,0xbb, +0xf9,0x45,0xab,0x48,0x81,0xeb,0xca,0x5f,0xb2,0x63,0x53,0x99,0xea,0x6a,0x3a,0xeb,0xa8,0xe3,0x09,0x6e, +0xad,0xb1,0x2b,0x9f,0x0d,0x79,0xac,0xd6,0xa7,0x13,0xbc,0xf0,0x4d,0xfd,0x96,0xa6,0x98,0x65,0x65,0xfd, +0xd3,0x60,0x49,0xf6,0xc3,0x48,0x10,0x69,0x92,0xe6,0x5e,0xa6,0xab,0x31,0x44,0x41,0xe8,0x15,0xf3,0x7d, +0xe2,0x79,0xf3,0x09,0xe4,0x40,0xa1,0x81,0x6e,0x03,0x9d,0xfe,0x01,0x78,0x52,0xa0,0x8a,0x26,0xeb,0xb6, +0x88,0x8d,0xbc,0xa7,0xc5,0x6c,0x57,0x26,0x4e,0x50,0x3b,0x60,0xc1,0x16,0x50,0xcc,0xef,0x0a,0xcc,0x54, +0x7c,0x12,0x9c,0xad,0xb2,0xd0,0x85,0x87,0x7e,0x25,0xa2,0x08,0x2b,0x93,0x8c,0xdd,0x76,0x00,0x6c,0x31, +0x51,0x0d,0x68,0x09,0x08,0x1d,0x15,0x05,0xf5,0x9d,0xbc,0x28,0x11,0xcc,0x90,0x16,0x24,0x68,0xeb,0xd9, +0x87,0x46,0xfe,0x81,0x78,0x13,0xfd,0x08,0x0e,0xac,0xb1,0x97,0x2c,0x61,0x5a,0xcf,0x41,0x4f,0x26,0x5f, +0xb5,0xa4,0x09,0x40,0xb1,0x35,0xd6,0x0c,0x99,0xa7,0x59,0x1f,0xba,0x09,0x87,0xdd,0x2f,0x91,0x37,0xb5, +0x79,0x49,0x6a,0x06,0x79,0xdf,0x2e,0x64,0xcb,0x9c,0x5a,0x1a,0x70,0xd9,0x33,0x75,0x9e,0x1e,0x9b,0x86, +0x49,0xf8,0x45,0x80,0x12,0x43,0x8b,0x83,0xf6,0x1d,0x79,0xa9,0x5b,0x79,0x12,0x70,0xe7,0x47,0xf2,0x0d, +0x1f,0x8d,0x71,0xae,0x55,0x0b,0xb9,0xf2,0xb9,0x88,0xf6,0x5f,0x08,0x07,0xf4,0x2e,0xbb,0x97,0xb5,0xbb, +0x09,0x2b,0x11,0x2d,0x24,0x24,0x2a,0x2a,0x46,0x49,0xac,0xc0,0x73,0x29,0xf3,0xf8,0xc0,0x09,0x40,0x98, +0x5f,0xfe,0xd7,0x1b,0x66,0xfa,0xba,0x2a,0x83,0x64,0xe6,0x6f,0x54,0x3d,0x63,0xa8,0xce,0xdc,0xb4,0x1e, +0x7c,0xef,0xec,0xfe,0x48,0xa5,0x6b,0x8e,0x5f,0xc5,0x27,0xe3,0x7b,0xf7,0xd1,0x52,0x7b,0xa6,0xe9,0x08, +0x8f,0xc0,0x13,0x0c,0x92,0x72,0x9e,0xbf,0x19,0xf4,0x90,0xb7,0x81,0xd0,0x71,0x5f,0x81,0x81,0xe6,0x00, +0x1c,0x6c,0x40,0x4d,0xdf,0x31,0xf1,0xd6,0x3e,0xf6,0x81,0x2d,0x61,0x9f,0x79,0xda,0xcf,0xe7,0x89,0x30, +0xe3,0xb7,0x2e,0x2a,0x4a,0x68,0x9c,0xf2,0x44,0x7b,0x50,0xbd,0x56,0x60,0x72,0xfd,0xb1,0x22,0x0e,0x18, +0x2a,0x89,0x8f,0x30,0xc4,0x81,0x36,0xf4,0x8d,0x10,0x61,0x61,0x84,0x07,0x88,0x03,0x99,0x4e,0xc2,0xef, +0x2d,0xd5,0x86,0x81,0xab,0xc2,0xc0,0x31,0x39,0x6c,0xe0,0x31,0xc3,0x93,0xb6,0x06,0xe7,0x97,0xe9,0x59, +0xe6,0xdc,0x56,0x03,0xb1,0x29,0xfd,0xe0,0x34,0x75,0x72,0xd7,0x5d,0x08,0x01,0xf8,0xc9,0x9e,0x6b,0x4e, +0x7f,0xe4,0x7e,0x8b,0x68,0x69,0x2f,0xc0,0xbe,0x69,0xdd,0x31,0x0c,0x3a,0x04,0x3e,0xf2,0x32,0xd4,0x0d, +0x42,0xc5,0xc6,0xfb,0xeb,0xf9,0x8b,0xb9,0x36,0xd0,0xa0,0x98,0x6c,0xf8,0x7a,0x1b,0x0c,0x6a,0xbe,0x71, +0x6f,0x82,0x0a,0x06,0x8e,0x38,0xf0,0x88,0x6c,0x10,0x83,0x11,0x28,0x5f,0x66,0x8a,0x72,0x50,0xc6,0x10, +0x9e,0xc1,0xbe,0x79,0x8b,0x7a,0x9b,0x28,0xfa,0xd1,0x2b,0x93,0xc7,0x7c,0xf8,0x98,0xeb,0x36,0x72,0x38, +0x30,0xa3,0x88,0xd2,0xb1,0xca,0x39,0x36,0xd4,0x06,0x5c,0x51,0x05,0x45,0xf0,0xe0,0xbc,0xad,0x8f,0xf6, +0x1b,0x8d,0x9b,0xc6,0x71,0xa7,0xdd,0x38,0x7d,0x3a,0x7d,0x3a,0x39,0x7f,0xb2,0x4b,0x3b,0xb9,0x41,0x0e, +0xfe,0x5b,0x1c,0xe6,0x5a,0xf9,0xca,0xfe,0xed,0xfe,0x59,0xfb,0xf2,0xb6,0xb9,0x7f,0xaa,0x95,0xe7,0x0d, +0x50,0x42,0xd1,0x61,0xb9,0x75,0x79,0xb9,0xdf,0x1a,0x36,0xda,0x8d,0x16,0xd1,0x0e,0x1b,0xa4,0x0d,0xd0, +0x59,0x2f,0x8e,0x17,0x17,0x3f,0xfa,0xd3,0xb3,0x23,0x67,0xd1,0xba,0x39,0x36,0x2f,0x9b,0x66,0xbe,0x71, +0x76,0xf4,0x38,0xf9,0x7c,0xde,0x77,0xee,0x0f,0xe5,0xcb,0xc9,0xfd,0x83,0x76,0x38,0x38,0x2d,0xf6,0x6f, +0xac,0xd6,0xd9,0x75,0xde,0xf8,0x61,0x95,0xa7,0x53,0xc5,0x7e,0xca,0x1d,0xb5,0x2a,0x86,0x9c,0x2b,0x9e, +0x8e,0xca,0x87,0xd3,0xad,0xc3,0xab,0xea,0x7d,0x53,0x9e,0xe8,0x57,0x6d,0x50,0x97,0xc5,0xbe,0xa0,0xf3, +0x23,0xf8,0xd1,0x38,0xc0,0xbe,0xf6,0x27,0x73,0xa5,0x7f,0x3f,0x9d,0xb4,0x6a,0xcd,0x79,0xd7,0x1c,0x5e, +0x95,0xab,0x8f,0x77,0x5f,0x0e,0xf5,0xbc,0x5e,0xfb,0x72,0x7f,0x09,0x45,0x8f,0x06,0x0f,0x9d,0xe6,0x25, +0x28,0xe1,0xf3,0x86,0xdd,0x82,0xf2,0x8d,0x86,0xd9,0x68,0xa1,0x92,0x3c,0xea,0xf7,0x4f,0x3f,0xe9,0xc5, +0xe9,0xe1,0xe3,0x41,0xab,0x35,0x3b,0xdf,0x52,0xaf,0xcf,0xc6,0x9a,0x3d,0xea,0xb7,0x5a,0xf7,0xdf,0xef, +0x3a,0x5f,0xf6,0x87,0xbe,0x3e,0x4f,0x51,0x67,0x6f,0x0e,0x1b,0xa7,0xa4,0xcf,0xc7,0xf9,0x5c,0x95,0xd5, +0xde,0x48,0x2f,0x7f,0x2a,0xdf,0x76,0xaa,0x56,0xb5,0xd0,0xd6,0xce,0x95,0x4a,0x77,0xa0,0x9d,0x55,0x6e, +0xb1,0xc4,0x61,0xa5,0xd9,0xb8,0xf9,0xd4,0x80,0xe2,0x27,0x8d,0x21,0x76,0xfa,0x09,0xfb,0x85,0x71,0xdf, +0x1b,0xed,0x5a,0x43,0xfd,0xf4,0xa3,0x7f,0x78,0x34,0x9f,0x8f,0x0e,0x0c,0xa7,0x73,0x77,0xf2,0x70,0x5e, +0x6a,0xf5,0x9b,0xe5,0x9e,0x76,0xd6,0x39,0xda,0xf7,0xd7,0xbd,0xc4,0x79,0x42,0x9f,0xa8,0xef,0x1f,0xdd, +0x19,0xed,0xee,0xa4,0x71,0x28,0x97,0x0f,0x0e,0x87,0xe6,0xa8,0x75,0xd8,0x3b,0x50,0x7a,0xda,0xc4,0x68, +0x2a,0xdd,0xc3,0xc9,0x28,0x00,0x23,0xe8,0x72,0x88,0xf3,0xfc,0x44,0xc6,0x5b,0x99,0xe7,0x7f,0x7c,0x96, +0xcb,0x83,0xfd,0xea,0xa7,0x1b,0xf3,0xf6,0x72,0xbb,0x72,0x23,0x37,0xb6,0x9b,0x76,0x69,0xa1,0x9e,0xdd, +0x37,0x1e,0xee,0xfc,0x7d,0x1e,0xa1,0x8d,0x72,0x80,0x00,0x83,0x3e,0x3f,0x1b,0xad,0x9b,0xa2,0x76,0x3a, +0x90,0xaf,0x86,0xa3,0x1f,0xd7,0xe3,0xd3,0x49,0x61,0x6c,0x36,0xcc,0xc3,0xc5,0xe0,0xe8,0xfe,0x08,0x2a, +0x36,0x1a,0xe7,0x73,0xf4,0xf4,0xfa,0x02,0x9f,0xb8,0x03,0x5e,0xf0,0xe5,0xd4,0xe8,0xcd,0x02,0x92,0xa0, +0xb1,0x78,0x71,0x9e,0x52,0x96,0xe4,0x1b,0x15,0xa8,0xbf,0xea,0x69,0xca,0x94,0xda,0x77,0xdc,0xb2,0xa8, +0x0a,0x0c,0x80,0x71,0x93,0x4c,0xa2,0x6e,0xe8,0xa5,0x5f,0x1a,0x8a,0x9e,0xbb,0x50,0x35,0xd0,0xcd,0xbc, +0x8c,0xb0,0x91,0xf7,0xf9,0xfe,0x41,0xd4,0x48,0xe4,0x23,0xc1,0xab,0x27,0xa8,0x32,0x5d,0x14,0x24,0x22, +0x9d,0x85,0x57,0xc1,0xa7,0x2b,0x53,0x13,0xa7,0xe2,0x2f,0x41,0x35,0xee,0xc0,0x06,0x79,0xb8,0x89,0xb0, +0x88,0xfc,0x83,0x73,0x99,0x89,0x65,0x0e,0x81,0x8f,0xdb,0xcf,0x2b,0xa0,0xee,0xf2,0x18,0x51,0x47,0xfc, +0x03,0xe4,0xb6,0x81,0x7c,0x02,0x8c,0x5b,0x68,0x91,0xfe,0x0a,0xa8,0x7d,0x8c,0xcf,0xda,0xdc,0x4a,0x48, +0xff,0xc1,0x99,0xac,0xfb,0x6a,0x79,0xac,0xf2,0x12,0x15,0x82,0xea,0xca,0x62,0x1f,0xc8,0xc5,0x03,0x03, +0x18,0xc9,0x2d,0x2a,0xe9,0xfe,0x40,0xb6,0x7d,0xa5,0xda,0x53,0xdd,0xf1,0xcf,0x36,0x38,0x43,0x3f,0xca, +0x41,0x9b,0xd5,0xcd,0x21,0x0f,0x7a,0x47,0x15,0x32,0x62,0x48,0x9e,0x09,0x97,0x43,0x9b,0xe3,0xc3,0x9f, +0x24,0xe9,0xdc,0x9f,0x40,0x29,0x92,0xa6,0xd4,0x13,0x5c,0x06,0x26,0xa4,0xbe,0x0e,0x3f,0xeb,0x09,0xa6, +0x22,0x27,0x68,0x11,0xf6,0x92,0x19,0x12,0xf0,0x92,0x9a,0x41,0x50,0x11,0x7f,0x5c,0x04,0x2b,0xbb,0x96, +0x52,0x42,0xc2,0xc8,0x3d,0xd3,0xa8,0x27,0x72,0x09,0x69,0xac,0x3a,0x23,0x13,0xfb,0x32,0x6d,0xc7,0xdf, +0x2e,0x21,0x07,0x89,0x58,0x2b,0xfe,0x0f,0xd4,0x3c,0x82,0x77,0xa3,0xc2,0x87,0x6e,0xa7,0x75,0x73,0x75, +0x74,0x7d,0x0f,0x13,0x28,0xc0,0x8b,0xe2,0x87,0x26,0x89,0xb1,0x94,0xa6,0x36,0x80,0x42,0x72,0x46,0x9a, +0x2d,0x29,0xea,0x4c,0xeb,0xab,0xd2,0xc2,0x9c,0x4a,0x23,0x79,0xa6,0x4a,0x8e,0x29,0xf5,0x47,0xb2,0x31, +0x24,0x19,0x13,0xe1,0xeb,0x40,0x06,0xd0,0x4a,0x7c,0xae,0x12,0x54,0x27,0x1f,0x30,0xba,0x4b,0xfa,0x0f, +0x59,0x01,0x75,0xf5,0x3f,0xb2,0xd2,0x35,0xb6,0xe4,0x96,0x99,0x6b,0x3a,0x8c,0x8c,0x94,0xf1,0x2a,0x80, +0x19,0x6c,0x99,0xc6,0xf0,0x43,0xe3,0x42,0x42,0x20,0x49,0x23,0xd3,0xb1,0x27,0xa6,0x83,0x20,0x26,0xef, +0xd3,0xbe,0x52,0x00,0x4a,0x89,0x6c,0xdd,0x81,0x1e,0xa5,0xba,0x65,0xa4,0xe4,0x1c,0xac,0x25,0x3a,0x58, +0xd0,0x97,0x25,0xc3,0x9c,0xa7,0x88,0x91,0x28,0x56,0x45,0xef,0x42,0x06,0x5e,0x64,0x64,0xcd,0x92,0xa8, +0xba,0x62,0xbb,0x2d,0x64,0x01,0x12,0x45,0xc0,0x27,0xc0,0x2b,0x00,0xcd,0x21,0xe2,0x8d,0xaf,0x66,0xff, +0x37,0x42,0xcf,0xf0,0x95,0x2c,0x4c,0xdf,0xdb,0x69,0xa6,0x20,0x89,0xc7,0x2b,0xcb,0x89,0x0f,0x0d,0x04, +0x89,0x0b,0x8b,0x3f,0x73,0xa4,0xd2,0x87,0x3f,0xc9,0x9a,0x95,0x88,0x35,0x97,0x20,0x50,0x43,0x02,0x48, +0x2c,0x69,0xac,0x04,0x8d,0x49,0xc4,0x5c,0x16,0x08,0xcd,0x91,0x7b,0xc4,0x0b,0x52,0x4f,0x14,0x12,0x44, +0x51,0xee,0x9b,0x60,0x2d,0xaa,0x0e,0x14,0x1a,0x80,0x09,0x16,0x20,0x3d,0xd6,0x58,0x3e,0x38,0xc4,0x88, +0xe9,0x47,0xf6,0x2f,0xa1,0xa5,0x9a,0xf8,0x70,0x0d,0xc0,0x25,0x43,0xd6,0x00,0x88,0x98,0xce,0xd6,0xc3, +0x34,0x60,0x9d,0x20,0x19,0x88,0x46,0xee,0xf7,0x81,0xe3,0x50,0x9a,0xf2,0x61,0x4f,0x4a,0x8a,0xb4,0x92, +0x4a,0xa3,0xee,0x2e,0xc1,0x60,0x4d,0x42,0x6a,0x54,0xa5,0xc6,0x9f,0x94,0xdc,0x08,0x31,0x02,0x96,0x0d, +0x68,0x40,0xe2,0xb4,0x82,0x24,0x64,0x4a,0x03,0x18,0xeb,0x48,0x92,0x25,0x43,0x9d,0x03,0xdb,0xb5,0xc6, +0x73,0x24,0x01,0x1f,0xb6,0x93,0xe7,0xd7,0x8d,0x54,0xf6,0xcf,0x9e,0xf5,0xe1,0xc8,0x91,0xc6,0x53,0xdb, +0xa1,0x24,0x2d,0x3b,0x12,0x28,0xc1,0xf0,0xc4,0x49,0x64,0x80,0x51,0xb5,0x40,0xe5,0x16,0xac,0x34,0xd5, +0xb2,0x05,0xfa,0xa2,0x77,0x15,0xd8,0x84,0xa8,0xa8,0x76,0x45,0x7f,0xcb,0xc6,0x42,0x32,0x07,0x38,0x46, +0x1b,0xed,0x40,0xb5,0x0f,0x16,0xaf,0xd0,0xc2,0xae,0xf4,0x57,0x3a,0xbb,0xb7,0xfb,0x7f,0xbe,0xfb,0xf8, +0x5f,0x7f,0xfc,0xe3,0x9f,0xff,0xc7,0x7b,0x79,0x3c,0xd9,0xfb,0xcf,0xf7,0xba,0xb3,0xf7,0x7e,0xe8,0xec, +0xfd,0xaf,0x9f,0xc9,0xd4,0xf3,0xcb,0xd7,0x6f,0x94,0x54,0x43,0x83,0x01,0x66,0x25,0xe1,0xbd,0x86,0x24, +0xad,0xaf,0x37,0x14,0x2c,0x2b,0x16,0x71,0x33,0xff,0x7a,0x45,0x00,0x2c,0x62,0x09,0x3a,0x76,0x91,0xd4, +0x09,0x9a,0xa3,0x91,0xbd,0x36,0x3d,0x5f,0x91,0xbd,0x9d,0xdf,0x4f,0xd0,0xc5,0x38,0x82,0x8e,0x23,0xd6, +0x00,0x59,0x4b,0x28,0x25,0xdc,0xe2,0x54,0xed,0xf6,0xd5,0x60,0xaf,0xa2,0xed,0x18,0x3e,0x32,0xfa,0x35, +0xf1,0xe1,0x86,0x7c,0xfe,0x33,0x47,0x9f,0xa1,0x55,0x8f,0x33,0x88,0x50,0xcd,0x21,0xc3,0xfe,0x10,0x02, +0x34,0x32,0x79,0x1a,0x85,0x1d,0x21,0x1b,0x64,0x69,0x64,0xa9,0x83,0x7a,0x82,0xa8,0x1d,0x09,0x52,0x16, +0x7f,0x9d,0x68,0xc6,0x83,0x5b,0xda,0x0d,0xf8,0x86,0xf2,0xa8,0xfe,0x43,0xe3,0xec,0x1f,0xd9,0xeb,0x81, +0xd4,0x0f,0x43,0x86,0xbd,0xc6,0xf2,0xa1,0xf7,0xdc,0xe9,0x98,0x60,0x88,0x1b,0x81,0x58,0xc1,0x5f,0x89, +0x0f,0x87,0xe7,0xdd,0xeb,0xb3,0xc6,0x69,0x87,0x75,0x24,0x2d,0xaf,0x2f,0x11,0x7d,0x8c,0xb7,0xe2,0x68, +0x8e,0x0e,0x4d,0x74,0xba,0x17,0x53,0xcb,0x90,0xa5,0x7c,0x16,0xfe,0xf0,0x01,0x4f,0xf5,0x70,0x23,0x98, +0x2c,0x1d,0xc9,0x4f,0x0b,0x7f,0xc2,0x43,0x2b,0x22,0x8c,0x12,0x51,0xb5,0x11,0x50,0x68,0xab,0x30,0x21, +0x88,0x17,0xd1,0x39,0x53,0x3b,0xf1,0xa1,0x7b,0xdd,0xb8,0xbe,0xe9,0x12,0x18,0xe5,0x74,0x2d,0xbe,0x03, +0x29,0x74,0x3e,0x66,0xe3,0x2e,0x87,0xaa,0xa1,0x5a,0xb2,0x9e,0xf8,0x70,0xd0,0x39,0xeb,0x5c,0x35,0x4e, +0xd6,0xe9,0x94,0x38,0xed,0xd8,0x3f,0x19,0xa5,0xff,0xb4,0x71,0xa7,0x8a,0x39,0x36,0x1d,0x0d,0x2b,0xb6, +0xcf,0x4f,0xcf,0xaf,0x8f,0x5a,0x5f,0x36,0xee,0x76,0x24,0x6f,0xdc,0x2b,0x56,0x39,0x6c,0x74,0xd7,0x82, +0xab,0xaf,0x2f,0x4d,0xe9,0x6d,0xdc,0x19,0xa9,0x73,0x74,0xb6,0x7f,0x72,0xf3,0xb9,0xdd,0xdc,0xb8,0x47, +0xa2,0xef,0x6f,0xdc,0x27,0xab,0x75,0x72,0x74,0x70,0x78,0xbd,0xf9,0x2c,0xc7,0x3f,0x1c,0x67,0xe3,0x2e, +0x69,0xa5,0xd3,0xcb,0xeb,0xeb,0x8d,0xfb,0x33,0x9c,0xc9,0xc6,0xdd,0x91,0x3a,0x67,0xd7,0x17,0x1b,0x77, +0x06,0xd6,0xc6,0xc6,0x9d,0x91,0x3a,0x57,0xfb,0x1b,0xf7,0x05,0x16,0xc4,0xe6,0x2b,0x9f,0x59,0x1d,0xb0, +0xf6,0x5b,0x87,0x9d,0xf6,0xcd,0x49,0x67,0xf3,0x6e,0x55,0xc3,0x36,0x2d,0x7b,0xf3,0xae,0x79,0xbd,0x6e, +0xe7,0xac,0x7b,0x7e,0xb5,0x39,0xe1,0xe0,0xfe,0xf4,0x62,0x73,0xe8,0xd2,0x5a,0xdd,0xbb,0xa3,0x6b,0x98, +0xf1,0xe6,0xbd,0x82,0xd2,0xfd,0xb0,0x71,0xa7,0xa0,0xd4,0x19,0x43,0xd0,0x7a,0x64,0xa8,0x7a,0x7d,0x78, +0x74,0x76,0xd0,0xbd,0xe8,0x34,0x3e,0xad,0xee,0x7b,0xe3,0x8e,0xd0,0x11,0x97,0xf8,0x70,0x77,0xb4,0x7f, +0xf4,0x2f,0xe1,0xe2,0x44,0x6f,0x01,0x95,0xbd,0x7d,0x7a,0x74,0xb6,0x39,0x07,0xef,0x0d,0x37,0xe7,0xe0, +0x58,0xa7,0xdd,0x69,0xde,0x1c,0x78,0xdd,0xe5,0xa6,0xba,0x4f,0x88,0x8b,0x7e,0xee,0xc4,0xda,0x9a,0x0c, +0x90,0x04,0xa8,0xba,0xae,0xc2,0x22,0xad,0xae,0xe7,0xfa,0xb3,0x51,0xc7,0x63,0x3f,0x37,0xab,0x8f,0xee, +0x71,0xac,0x8c,0xff,0x0a,0xaa,0x52,0x50,0x5f,0xa3,0x5e,0x95,0xc4,0x87,0xf7,0x7d,0x73,0xb2,0xd8,0x93, +0x8a,0x79,0xcc,0x1c,0x90,0x2f,0xd4,0x50,0x4f,0xff,0x6c,0x82,0x3e,0x7d,0xf1,0xff,0xfe,0x5f,0x96,0xfa, +0x84,0x8f,0x2e,0x38,0x47,0x8e,0x33,0xd9,0xcd,0xe5,0x1c,0x80,0xa0,0x6a,0x8d,0x65,0x23,0xdb,0x97,0x1d, +0xd4,0x16,0xd1,0xd9,0x5e,0x4f,0xfc,0xd5,0xd3,0x65,0xd4,0x8d,0xa2,0x4a,0x11,0xc0,0x06,0x9b,0xb2,0xa1, +0xd4,0x50,0x73,0x46,0xd3,0x5e,0x16,0x14,0xcc,0xdc,0x23,0xf4,0x0a,0xda,0xb4,0xfa,0x94,0x53,0xed,0x09, +0xea,0x2c,0xe1,0xa6,0xb9,0x32,0xf3,0x5f,0xd2,0x81,0xe6,0x1c,0x4e,0x7b,0xbc,0xd9,0x83,0x8b,0x93,0x59, +0x49,0xd2,0xc1,0x4a,0x31,0x40,0x15,0xc7,0x7e,0x7c,0xfa,0x60,0x70,0xee,0x9e,0xf5,0x1f,0x63,0xba,0x33, +0x4f,0x82,0xa8,0xc6,0xc4,0x1a,0xf3,0x4c,0xbd,0x61,0xa6,0x7c,0x6b,0x6a,0xe1,0xe5,0x02,0x12,0x75,0xf9, +0x4f,0xa9,0x7f,0x6a,0x85,0x79,0x8b,0x1e,0x88,0xa0,0xbf,0x41,0x72,0x7f,0xf1,0xeb,0xc4,0x42,0x86,0x30, +0x8e,0x91,0x30,0x1e,0x3b,0x11,0x54,0x73,0x89,0xf0,0x8c,0x78,0x3d,0x92,0x81,0xa0,0xf4,0xf0,0x87,0xb1, +0x3c,0x34,0x34,0x67,0xaa,0xa8,0xf6,0x12,0xd5,0xde,0x6f,0x84,0x14,0x25,0xb2,0xfd,0x16,0x69,0xdb,0xc2, +0x57,0x90,0xa3,0xb2,0x31,0xc5,0xfd,0x16,0x78,0x65,0xc5,0xb7,0x89,0xd9,0x1c,0x02,0x7a,0x31,0x71,0xbf, +0x71,0x2d,0x76,0x2c,0xb4,0x92,0xf0,0x14,0xee,0xb8,0xe6,0xb0,0xe3,0x36,0x31,0x57,0x5f,0xdf,0x25,0x35, +0x77,0xd7,0xec,0xac,0x35,0xd2,0x26,0xd2,0x51,0xfb,0xf5,0xbd,0xf5,0xa1,0x01,0x4d,0x59,0xb3,0xb7,0x3b, +0xe0,0xc4,0xd2,0x69,0xa3,0xf5,0x2b,0xf0,0xec,0xaf,0xd9,0x57,0xb7,0xfd,0x49,0x02,0x33,0xde,0x26,0xf4, +0xfb,0xda,0xee,0x6c,0xe5,0x61,0x5d,0x40,0xa2,0x07,0xec,0x97,0xfb,0xeb,0x33,0x73,0x73,0x8d,0x0e,0xf7, +0xb9,0xb3,0x02,0x6b,0xbe,0xbe,0x47,0x79,0x32,0xf9,0x8b,0x1a,0x6b,0x9b,0xf5,0xfa,0xcb,0x53,0xc5,0x8e, +0x59,0x23,0x9b,0xf6,0x8d,0x41,0x51,0xbf,0xde,0x39,0x6f,0x65,0xd3,0xde,0x7b,0x53,0x4d,0x57,0x24,0x6a, +0xc9,0xff,0x4a,0xff,0xa4,0x9d,0x4d,0x3b,0xc7,0xcd,0x86,0x5f,0x20,0xe7,0x07,0xd5,0xe9,0x8f,0xfe,0xc2, +0x46,0x12,0x12,0x7a,0x7f,0xeb,0x09,0xa9,0xb7,0x70,0x28,0xdb,0x5c,0x67,0x18,0x96,0x4a,0x02,0x16,0x7e, +0x85,0x3d,0x0d,0xa0,0x8d,0x35,0x46,0xb0,0x16,0x17,0xc7,0xbe,0x56,0xf1,0xf1,0x33,0xea,0xf1,0x7f,0xfd, +0x80,0xd9,0x96,0xc1,0x9a,0x20,0x6a,0x76,0xbb,0xbf,0xc2,0x4e,0x7b,0xb6,0xbd,0x36,0x37,0x6d,0x51,0x71, +0xf8,0x2b,0xbc,0x9b,0x34,0xb0,0x66,0x77,0x57,0x30,0xb3,0xd7,0xf7,0x65,0xc1,0xc4,0xd6,0xec,0xe8,0xe8, +0xe2,0x57,0x85,0x9f,0x36,0xd9,0x84,0xa0,0x41,0x1d,0x9a,0xbc,0xbe,0x47,0xac,0xfd,0xaa,0xc5,0x74,0x62, +0xca,0x8a,0x04,0xea,0xb5,0x05,0x6a,0xd4,0xeb,0xbb,0xc7,0x9d,0x34,0xd6,0x88,0xd7,0x2f,0xf9,0xfb,0x9f, +0x6b,0x8d,0xe2,0xb6,0xf5,0x0b,0xd2,0x78,0xd6,0xef,0x07,0x3a,0x1d,0xdf,0xae,0xd5,0x2b,0x3a,0x29,0xa4, +0x2e,0x51,0x4d,0x7f,0x41,0x17,0xf8,0xe1,0x38,0x5d,0xa6,0xde,0x9e,0x9d,0x5f,0x4b,0x8d,0xdb,0xc6,0xd1, +0x49,0xa3,0x79,0xd2,0x59,0x6b,0x08,0x67,0xd7,0x17,0xbf,0x3c,0x02,0xc3,0x99,0xbc,0x7e,0x00,0x5c,0xd5, +0xc6,0x1b,0x83,0x7f,0x61,0x08,0xe6,0x7c,0x4d,0x7a,0xbb,0x99,0xfc,0x5a,0x4f,0x53,0x52,0x7f,0x5d,0xe2, +0xc6,0xcd,0x88,0xe9,0xe4,0x17,0xa5,0xe4,0xd0,0x4c,0x7c,0xf8,0xe8,0xa3,0x2f,0xc9,0x46,0xab,0x52,0xb1, +0x25,0xf8,0x16,0x25,0x30,0x44,0x97,0x7a,0xd8,0x8d,0xee,0xdb,0x2c,0x45,0xd3,0x76,0x93,0x8d,0x52,0x49, +0x35,0xfa,0xd4,0x9f,0x3f,0x9e,0xea,0x8e,0x36,0x91,0x2d,0x87,0x74,0x91,0x41,0x5b,0x3c,0xc2,0x16,0x13, +0xad,0x30,0xd7,0xb3,0x1b,0x67,0x86,0xb9,0x1e,0x5f,0x66,0x87,0x1d,0xd0,0x0a,0x7e,0x3b,0x4c,0x9a,0xc9, +0xfa,0x14,0xf7,0x1d,0x7f,0xf3,0x6e,0xe3,0x21,0x73,0xe0,0x47,0x6e,0xcb,0xb8,0xde,0xfd,0x15,0xad,0xb0, +0xbd,0x0f,0x0c,0x28,0xf0,0x60,0xca,0xcc,0x7b,0xdf,0x96,0xe3,0xea,0x6d,0x98,0xe2,0xfa,0x1b,0x36,0xaf, +0xd8,0x87,0xd4,0x6c,0x32,0x33,0xba,0xc1,0xac,0x91,0xc0,0xd3,0xc1,0xc2,0xb7,0x8d,0xad,0x19,0xb8,0x39, +0x6c,0x49,0x4c,0x13,0x90,0x92,0xcc,0x45,0x80,0x7b,0x71,0x1c,0x1c,0xb8,0x27,0x97,0x25,0xf7,0xa1,0xa7, +0xb2,0xd2,0x3e,0xd9,0x9e,0x86,0x06,0x58,0x00,0x1c,0xd9,0x89,0x94,0x1f,0x54,0x49,0x1d,0x0c,0x70,0x63, +0x12,0xb7,0x9a,0xed,0x91,0x39,0x05,0x3d,0x12,0x23,0xe3,0x80,0x92,0xc8,0x46,0x25,0x3a,0xaa,0x84,0x2d, +0xce,0xde,0x02,0x66,0xa0,0xf5,0x1f,0xe8,0xae,0xba,0x2a,0x25,0x5c,0x8f,0x4a,0x82,0xb9,0x4b,0xde,0x68, +0x37,0xae,0x6d,0x4e,0xc9,0x45,0x90,0xd8,0x19,0xcc,0x19,0x4c,0xf2,0x48,0xcc,0xf7,0x1c,0xa3,0x4d,0x1c, +0x85,0xeb,0x61,0x9e,0xee,0x1c,0x86,0x71,0x3f,0xd6,0xe0,0x21,0x9f,0x00,0xbd,0x4d,0x9d,0x00,0xfe,0xf3, +0xf0,0x73,0x2c,0x3f,0x92,0x5f,0x79,0x91,0x32,0xaa,0x7f,0x33,0x65,0x90,0xb9,0x22,0xee,0xc7,0x40,0x18, +0x1a,0xe7,0x3b,0x80,0x49,0x45,0x75,0x10,0x89,0xb2,0xa4,0x88,0x70,0x4b,0x0e,0x2c,0x13,0xe3,0x9a,0xa1, +0x00,0xce,0x64,0x6c,0xd3,0x3d,0x63,0xdc,0xe7,0x26,0x1b,0xb1,0x94,0x20,0x28,0x50,0x08,0x3a,0x07,0xc0, +0x22,0xd9,0x4f,0xbe,0x43,0x8d,0x14,0x48,0xb3,0x27,0x92,0xdd,0x6f,0xd7,0x5b,0x06,0xed,0x93,0xe8,0x54, +0x52,0x78,0x24,0x93,0x23,0x35,0x9a,0xe3,0x86,0x44,0x40,0xd1,0xa1,0x1a,0x1c,0x50,0x56,0x3a,0x32,0xfa, +0x16,0x66,0x2d,0xf4,0xf5,0xac,0x0d,0xdc,0x40,0x87,0x91,0x3c,0x23,0xa4,0x65,0xd1,0x5a,0x38,0x33,0x71, +0x42,0xd8,0x17,0x23,0x33,0xa9,0xab,0x3a,0xb4,0x15,0xc2,0x81,0xb0,0x28,0x99,0x28,0x8b,0xb6,0x0f,0xf4, +0x7b,0x0f,0xed,0xcf,0x4d,0xe3,0x3f,0x1c,0x1c,0x9b,0xcc,0x9a,0xb6,0x55,0x47,0x9c,0xaa,0xb0,0x0f,0x8f, +0x2e,0x12,0x59,0xd7,0x17,0x64,0x82,0x64,0x9d,0x45,0x81,0x43,0x1b,0x93,0x20,0x7e,0x47,0x85,0x82,0x41, +0xd0,0x10,0x40,0x93,0x5e,0xb1,0x06,0x0f,0x38,0xe1,0xbb,0xd8,0x94,0xee,0x84,0xce,0x85,0xed,0x70,0x4c, +0xeb,0x45,0xc5,0x14,0x42,0x62,0x10,0xb1,0x6c,0xc1,0x52,0xd3,0x17,0x2b,0xd6,0x59,0xc0,0x87,0xab,0x13, +0x67,0xd7,0x5a,0x2b,0xef,0xa4,0xd3,0x26,0x40,0x70,0xd7,0x1b,0x0d,0xb8,0xe7,0xba,0x9d,0xaa,0x9c,0xc2, +0xc7,0xfc,0xea,0x05,0xe7,0x2e,0x9a,0x6d,0xe8,0xd9,0x9c,0x78,0xc2,0x82,0x30,0xd8,0x3b,0x6d,0x5f,0x93, +0x6c,0xa6,0xeb,0xd0,0xaf,0xc1,0x52,0x35,0x74,0xb9,0x22,0xbd,0x2f,0x2f,0x96,0x67,0xaa,0x1b,0xe0,0x0c, +0xa4,0x8d,0x12,0x57,0xac,0x8c,0x16,0x2b,0x20,0x0d,0x45,0x48,0x74,0x89,0x1d,0xde,0x1f,0x89,0x6a,0x90, +0x70,0x84,0x71,0x45,0x2b,0x6e,0x63,0x6b,0x14,0x06,0xa6,0xd1,0xd0,0xe7,0xf2,0xc2,0x96,0xce,0xcf,0xe2, +0xca,0x6c,0x7b,0x65,0xf6,0xf7,0xbd,0x42,0x39,0x0a,0xfb,0x7f,0x03,0x71,0xc4,0xc9,0x4f,0x51,0x07,0x9a, +0xa1,0xda,0x74,0x25,0xaa,0xb8,0x5c,0x71,0x75,0xd0,0x88,0x11,0x09,0x3d,0xfa,0x12,0xd2,0x0f,0xfc,0x43, +0x0a,0x98,0xc0,0x17,0xc8,0x4a,0xb8,0x63,0x81,0x2e,0x09,0x01,0xef,0x09,0x8f,0x5f,0x90,0xac,0x76,0xb2, +0x23,0x15,0x0e,0x9f,0x68,0x4c,0x8c,0x63,0x2d,0x18,0xb1,0x33,0xf1,0x02,0xac,0x63,0x20,0xd9,0x53,0x12, +0x7b,0x33,0x98,0xe2,0xd2,0x64,0x1f,0x54,0xc5,0x6b,0xc6,0xd2,0xd4,0x01,0x2e,0x5a,0xd2,0x9c,0x0a,0x16, +0xc8,0x42,0xaa,0xb8,0xda,0x19,0x70,0x19,0x18,0x40,0xf7,0xba,0xe1,0x86,0xda,0xd0,0x12,0xf4,0x3b,0xfb, +0xcc,0x18,0x80,0x7f,0xcc,0x22,0x15,0x26,0x68,0x6d,0x9c,0x1d,0x4e,0x94,0xb3,0xbb,0xf3,0x33,0x32,0x6e, +0x6c,0x91,0x84,0xd0,0x58,0x94,0x51,0x23,0xca,0xd3,0x24,0xc2,0x05,0xd0,0x2a,0x99,0x50,0xcb,0x9a,0x03, +0xdb,0x66,0x71,0x64,0xf0,0xbf,0xa1,0x6e,0xf6,0x40,0xa5,0xa2,0x6d,0x4b,0x86,0x09,0x72,0x5e,0xeb,0x13, +0xc5,0xca,0x3f,0x04,0x91,0xc2,0xd9,0x10,0x16,0x9c,0xb9,0x44,0x70,0x34,0x1c,0x1a,0x3b,0xca,0xa1,0x1a, +0x24,0x8e,0x41,0x06,0x5a,0xb5,0x6d,0x79,0x48,0x8a,0x25,0x50,0x4b,0xc0,0xa8,0xe7,0xbf,0x1c,0x73,0xa2, +0xf5,0x51,0x4f,0xc8,0xc1,0xc2,0xce,0xe5,0x81,0xde,0x40,0x1a,0xce,0x35,0x07,0xc3,0x8f,0x26,0xf2,0x02, +0x2d,0x39,0x44,0x6e,0x3e,0x2d,0x15,0x10,0x62,0x45,0x29,0x89,0x6a,0x83,0x39,0x1c,0x42,0x7f,0x9a,0x93, +0xf2,0x0f,0x92,0x2d,0x89,0xe5,0x20,0x92,0x64,0xc2,0x41,0xd1,0xc7,0x4e,0x38,0x3e,0x00,0xc6,0x03,0xc7, +0x58,0x95,0x0d,0x12,0x23,0x85,0x5a,0x0b,0x8e,0x9e,0xc4,0xc8,0x03,0xb9,0x01,0x6d,0x18,0xa8,0x86,0x47, +0x21,0xc6,0x5b,0x81,0xac,0x67,0x4e,0x0f,0x03,0x53,0x07,0x31,0x47,0x06,0x22,0x90,0x1d,0xe1,0xe9,0xe4, +0x3b,0x3c,0x2f,0xa0,0x86,0xed,0xe8,0x18,0xf3,0x34,0xa0,0xc3,0xf3,0x0f,0x8d,0x22,0x8f,0x95,0x71,0x67, +0xe0,0xc6,0x48,0x8d,0x29,0x8a,0x23,0xc1,0x10,0x31,0x2e,0xba,0x6c,0xcc,0x09,0x39,0xf8,0xa3,0xf2,0x55, +0x13,0x9e,0x46,0xfa,0x75,0x53,0x30,0x56,0xcf,0x00,0xa8,0x70,0xc9,0x14,0x12,0x2e,0x9f,0x4a,0x90,0x5a, +0x09,0x8f,0x27,0xd1,0x29,0xd0,0x56,0x81,0x29,0x0d,0x32,0xea,0xe3,0x44,0x07,0x7a,0xc4,0xb3,0x1f,0x9b, +0x89,0x22,0x59,0x57,0x1f,0xe5,0xb5,0x83,0x0c,0xb1,0x30,0xd1,0x3f,0x87,0x7c,0xdf,0x87,0x49,0xa5,0x15, +0x6e,0x38,0xc2,0xe7,0xa8,0xa6,0x48,0x55,0xbf,0x3e,0xa6,0xcd,0xe8,0x99,0x8f,0xae,0x21,0x87,0x2d,0x77, +0x0c,0x72,0x32,0xcf,0xa7,0xfc,0x97,0x12,0xf1,0xe6,0x5b,0xcc,0x34,0x83,0xa6,0x15,0xdf,0xf1,0x8e,0x31, +0xac,0xbc,0x3d,0x6d,0x66,0x59,0x75,0x09,0x99,0x4b,0x39,0xc6,0x36,0x36,0xd9,0xe8,0x72,0xc7,0x46,0x4f, +0xcc,0xb9,0x7b,0xab,0xfe,0xd0,0x0d,0x34,0x0e,0xd9,0xa0,0x98,0x15,0x07,0x70,0x24,0x15,0x56,0xe3,0x4b, +0xac,0xbc,0x1e,0xd2,0xd8,0x74,0xec,0x85,0xd1,0x5f,0xa2,0x49,0x90,0x26,0xbb,0x50,0x66,0x75,0x2c,0x9d, +0x8b,0x9c,0x52,0x48,0x95,0x00,0xe9,0x7f,0x66,0x92,0xae,0x46,0xa0,0x3f,0x81,0xee,0x47,0x61,0x16,0x2d, +0x65,0x41,0xef,0xf8,0xa2,0x5a,0x26,0x72,0x32,0x8c,0x23,0xf4,0xb8,0x0b,0xc9,0x20,0x1a,0x57,0x0b,0x64, +0xeb,0x39,0x94,0xc6,0x05,0xf1,0x1d,0x43,0x2d,0xbd,0xaa,0x2b,0x2a,0x96,0x90,0x7e,0x75,0x61,0x70,0xa2, +0x76,0xb2,0xb6,0x68,0x7f,0x8d,0x95,0x80,0x32,0x5a,0x1a,0x31,0xc6,0xa1,0x68,0x60,0xe1,0x11,0xb7,0x8e, +0x3b,0x61,0x66,0xe8,0x01,0x57,0x76,0x07,0xf7,0xa4,0x2a,0xe1,0x75,0xec,0xee,0x87,0xd2,0x20,0xf4,0xc4, +0x2f,0xac,0x09,0x1e,0xb2,0x14,0xb3,0x26,0x78,0x50,0x12,0x5b,0x11,0x27,0xc8,0xec,0xed,0xd7,0xad,0x84, +0x57,0xdb,0x9c,0x37,0x60,0x97,0x90,0x61,0xbe,0x19,0x93,0x99,0xda,0x6a,0x8b,0xcc,0x5b,0xb0,0x38,0x51, +0xa6,0x8a,0x34,0x5d,0x5b,0x5b,0x75,0xfb,0x9d,0x4a,0xde,0x8d,0x4d,0x25,0xf6,0x40,0xb3,0x6c,0xd4,0x21, +0xd0,0x09,0xcd,0xf7,0xb6,0x25,0xd9,0x96,0xae,0x0e,0x9a,0xee,0x33,0x93,0xd7,0x44,0x00,0x91,0xf8,0x66, +0xd5,0xa0,0x0a,0xc8,0x88,0xc1,0x4f,0x02,0xa5,0xe2,0x81,0xa4,0xb1,0xa5,0x3e,0x04,0xb5,0x27,0xdd,0x1c, +0x65,0x41,0x82,0x41,0x05,0xd3,0x00,0x39,0x44,0x5c,0x16,0xda,0x40,0xb4,0xbe,0x46,0xb2,0xed,0x85,0x0c, +0x97,0x80,0x68,0xc7,0x63,0xd2,0xa8,0xdb,0x29,0x0a,0xa8,0x2b,0x02,0x3d,0x52,0x6d,0xc2,0x74,0x19,0xea, +0xd0,0x73,0xfb,0x71,0x5d,0x15,0x6f,0xe4,0x87,0x40,0xb8,0xe0,0xdc,0xe9,0x8c,0xde,0x92,0x30,0xa0,0xd5, +0x65,0x64,0x51,0x28,0xfc,0xdb,0xd0,0x05,0xc1,0xbd,0x0f,0xaf,0x03,0x86,0x72,0x45,0x4a,0x4e,0x74,0xd4, +0x47,0x88,0x8f,0x14,0xec,0x02,0x3b,0x95,0xf6,0xd4,0x5c,0x0c,0x99,0x97,0x0e,0xbb,0xb7,0x52,0x72,0x34, +0x55,0x33,0xc0,0x9a,0xd9,0x42,0xce,0x10,0xfe,0x98,0x92,0xc8,0x99,0x93,0xb7,0x43,0x13,0xc9,0x21,0xc0, +0xe9,0xe5,0x2d,0x31,0x75,0x87,0x0d,0x2f,0xc3,0xd5,0xce,0xbf,0x0d,0xaa,0x06,0xa6,0x05,0xba,0x7b,0x70, +0xf1,0xe0,0x02,0xa6,0xd0,0x21,0xca,0x1f,0x5d,0xe3,0x25,0xe6,0x95,0x80,0x85,0x63,0xa3,0xaf,0x11,0xb1, +0x4c,0x10,0x43,0xb5,0xd9,0xb5,0xd7,0x6a,0x39,0x66,0xad,0x12,0xdd,0xca,0x3d,0x7d,0xc3,0x1c,0x27,0x78, +0x6f,0x10,0x92,0x85,0x4e,0x8f,0x50,0x80,0xf2,0xcb,0x76,0x0c,0x25,0x90,0xf3,0xf4,0xe8,0x27,0xe7,0x1a, +0x4c,0xd5,0xc5,0x75,0xfe,0x37,0xae,0xfd,0xa1,0x0c,0x73,0x03,0xea,0xb7,0x2c,0xb5,0xff,0xa6,0xfa,0x27, +0x00,0xe1,0x00,0xdb,0xf6,0xad,0xf9,0xfc,0xbf,0x0d,0x21,0x05,0xe7,0x4d,0x1c,0x51,0x3e,0x21,0x10,0x4d, +0x25,0x38,0x2f,0xca,0x2c,0x28,0xaf,0x80,0x65,0xd3,0x33,0x67,0xc4,0xf6,0x21,0xa2,0x02,0x2d,0x8d,0x37, +0x43,0x4e,0xab,0xdb,0xe5,0x4c,0xe4,0x0d,0x05,0x76,0xb7,0xfb,0x6f,0xca,0x87,0xdd,0xe9,0x22,0xed,0x5b, +0x2a,0xa6,0x62,0xa1,0xa0,0x26,0x2e,0x60,0xe2,0x22,0x40,0x25,0xf5,0xaa,0xd3,0xbd,0x96,0x1a,0x17,0x47, +0x6c,0xd5,0x78,0xc7,0xd4,0x68,0x15,0xbc,0x4e,0xc2,0x96,0x12,0x7f,0xec,0xef,0xe7,0x89,0x77,0x1b,0x90, +0x86,0x4e,0x0a,0x8f,0x6b,0x27,0x8a,0x95,0x0a,0xa6,0x2f,0x48,0xbc,0x0d,0xa2,0x88,0x02,0x24,0x79,0xf9, +0x97,0xec,0xb7,0x44,0xd6,0xb5,0xd7,0xac,0x0f,0x69,0xeb,0xa3,0xe2,0x77,0x22,0xed,0xc8,0x93,0x93,0x54, +0x7e,0xd2,0x03,0x87,0xb6,0x8b,0x11,0x7b,0x6c,0x22,0xdc,0x23,0x34,0xef,0x57,0x81,0xda,0x83,0x06,0xdb, +0xc1,0xdd,0x18,0xd0,0x81,0x52,0xc1,0x7d,0x13,0xe6,0x03,0x46,0x61,0x7f,0xad,0x11,0x4f,0x0f,0x6e,0x9c, +0x14,0xf8,0x76,0x49,0x25,0xb0,0x5d,0x22,0xd8,0xd2,0x7f,0xab,0x03,0x13,0x86,0xea,0xdf,0x34,0x21,0x0b, +0xc6,0x23,0x49,0x89,0xec,0x93,0xa0,0x5d,0x47,0xd1,0x84,0x3e,0x76,0x83,0x2c,0x88,0xd5,0x88,0x59,0x07, +0xb6,0x14,0x0d,0x64,0x81,0x62,0x2e,0x82,0x89,0x8b,0x98,0x35,0xe7,0x91,0xf0,0x6f,0x7a,0x61,0x90,0xc1, +0x01,0xb6,0xc3,0x6c,0x8b,0x30,0xd2,0x3c,0x0c,0x94,0x45,0xbd,0xc5,0x8d,0x83,0xfe,0x6d,0xa0,0x46,0x7b, +0x9e,0x01,0xb1,0xa7,0x3a,0x73,0xcc,0x6d,0xe4,0x99,0x9f,0x84,0x6e,0x6c,0x3f,0x48,0x5f,0x61,0x49,0xb2, +0x60,0xf6,0x38,0x4b,0x92,0x04,0xb9,0x1f,0x75,0xaf,0xaf,0x1a,0xd7,0x47,0xe8,0x70,0x67,0x16,0x25,0x0d, +0x61,0x15,0xcf,0x77,0x22,0xe2,0x91,0x5f,0x02,0x45,0x4c,0x2d,0xcd,0x59,0x70,0x1f,0xf7,0xef,0xdf,0xcc, +0xee,0xba,0x1d,0xc5,0x2f,0xd0,0x4c,0x29,0x84,0x8a,0x95,0x91,0xea,0x81,0x9c,0x27,0xbc,0x01,0x72,0x05, +0x5b,0xe2,0x43,0x93,0xbc,0x5c,0x12,0xbf,0xfe,0x26,0x5d,0xb3,0xb4,0x2a,0x81,0xbe,0xaf,0xe8,0xdb,0xdf, +0xdd,0x39,0x4b,0x46,0x23,0xb9,0x4b,0xe1,0xc3,0x3e,0x7b,0x03,0x03,0x50,0x43,0xb1,0xfb,0xbf,0xc8,0x6e, +0x7f,0xeb,0x09,0xe8,0x25,0xe1,0x09,0xff,0x73,0x24,0xfa,0x7f,0x8e,0x44,0xff,0x6f,0x70,0x24,0x7a,0x09, +0x85,0x6f,0x78,0x46,0x7a,0xfd,0x28,0xa2,0xeb,0xeb,0x0b,0x09,0x95,0xe0,0xc8,0xa9,0x00,0xe9,0x5e,0xc0, +0xb7,0xb7,0x8a,0x22,0x2a,0xfd,0x3b,0x84,0x11,0xb1,0x3d,0x27,0x62,0x2b,0xf0,0x0c,0x15,0xfe,0x25,0x8a, +0xc4,0x06,0xd6,0x02,0x58,0x07,0x3f,0xa6,0xc0,0xa5,0x6d,0xb2,0xd3,0xea,0x09,0x6d,0x07,0x54,0x56,0xa9, +0x96,0x97,0x92,0x60,0x8f,0x1b,0x8a,0x0c,0x0b,0xdf,0x05,0x62,0xca,0xdb,0x83,0xe4,0x01,0x0e,0xb2,0x42, +0xb6,0x63,0x71,0x73,0x48,0xeb,0x6b,0x0e,0x0d,0x8c,0x20,0x01,0x14,0xbc,0xf9,0x5d,0x89,0x45,0x2a,0x8d, +0x17,0xda,0x64,0x77,0xbc,0xc0,0x86,0x72,0x6f,0x43,0xae,0x74,0x37,0x47,0xba,0xeb,0x4a,0x8d,0xa9,0x33, +0x7a,0x33,0x33,0x63,0x6e,0x63,0x73,0x89,0x37,0x12,0x1a,0x6c,0x90,0x04,0x88,0x00,0xf6,0xb7,0xdb,0xcf, +0x9a,0x68,0x7c,0x37,0xeb,0x8d,0x46,0x7a,0xa5,0x02,0x9f,0x43,0x3b,0xe2,0xad,0xc7,0x89,0x0d,0x5f,0xb3, +0xb8,0xcd,0xbf,0xdd,0x38,0x68,0x2e,0x78,0xee,0x97,0xb4,0x64,0x9b,0x63,0x0c,0x62,0xe0,0x07,0xb7,0xc8, +0x66,0x67,0x4f,0x45,0x1f,0xd6,0x04,0x6c,0x67,0xcb,0x44,0x29,0x87,0xe6,0x33,0xac,0x18,0x7a,0x9d,0x17, +0x13,0x7f,0x33,0x53,0x03,0x35,0x12,0x5a,0xd7,0xcc,0x29,0x8b,0x47,0xa2,0xcb,0x88,0xac,0x0f,0xb6,0xa5, +0x8d,0xe1,0x50,0x16,0xcc,0x3c,0x43,0x40,0x4a,0x0b,0x81,0xd0,0x31,0x1d,0x5f,0xe3,0x29,0xee,0xfe,0xc2, +0x25,0xe9,0x4c,0x2d,0x03,0xb7,0x75,0xc5,0x00,0xa0,0x37,0x62,0xed,0x9c,0xfe,0xa4,0x4f,0x6a,0x74,0x64, +0x1d,0xe0,0x09,0x3e,0x05,0x79,0x21,0xc2,0xcd,0x8f,0x19,0x1f,0x37,0xf4,0x19,0x1e,0xd1,0xea,0x5d,0x79, +0x73,0xf5,0x8e,0x26,0xa4,0x0b,0x68,0x94,0xb0,0x26,0xcd,0xd5,0xea,0xe4,0x5b,0xf2,0x50,0x1c,0x43,0x88, +0xe1,0xa1,0x38,0xa3,0x01,0x13,0x34,0x9a,0x84,0xc0,0x95,0xf1,0x3a,0xae,0xd9,0x00,0x98,0xd3,0x92,0xaa, +0xa1,0x3d,0xc9,0x09,0x01,0x38,0xf1,0xdc,0x42,0x87,0x2c,0x27,0x16,0xdc,0x28,0x44,0x74,0xf4,0x65,0x5d, +0xb7,0xa9,0xfe,0xc2,0xb2,0xca,0xfb,0x72,0xfe,0x50,0x50,0x78,0x6a,0xc0,0x04,0xb4,0x90,0xb1,0x8a,0x11, +0x63,0x64,0x0c,0x58,0x94,0x86,0xc3,0x11,0xaf,0x5b,0x56,0xba,0x36,0xa5,0x07,0xc3,0x9c,0x83,0x56,0x05, +0x72,0x1c,0xda,0xa7,0x34,0xcd,0x9d,0x12,0x0a,0xd0,0x2e,0xe9,0x51,0x8c,0x4d,0xcb,0x41,0x1f,0xf6,0xba, +0x9a,0x44,0xf0,0xd0,0xb5,0xaa,0x1b,0xaa,0xb3,0x21,0x2b,0xbc,0xee,0x9c,0x9c,0x75,0xae,0xdf,0x8c,0xc1, +0xd0,0x31,0x74,0xaf,0x1b,0xff,0x16,0xec,0xe5,0x1a,0x97,0xf0,0xf9,0x19,0x89,0x0f,0xf4,0xe2,0x74,0xe8, +0x18,0x5d,0xd9,0xe8,0xaa,0xbe,0x1a,0x3a,0xd1,0xdd,0xb8,0x26,0xfe,0x79,0x84,0x6c,0x09,0xec,0x7d,0x07, +0xfd,0x11,0x18,0xbf,0x49,0x00,0x46,0x5d,0xaa,0x24,0x3e,0x83,0xe3,0x53,0x0c,0x60,0xda,0x04,0x6d,0x86, +0x39,0x98,0xda,0xf6,0xda,0x66,0x0f,0xac,0xbc,0x31,0x98,0xcf,0x7d,0xa0,0xf3,0xb1,0xe9,0xa8,0x3c,0xf7, +0x94,0x94,0x3c,0x33,0xf7,0x6f,0xba,0xdd,0xd4,0x9b,0xa1,0x92,0x8e,0x6b,0x4d,0xb1,0xf6,0x4b,0x73,0xa2, +0x23,0x07,0x1e,0x6b,0xcd,0x84,0x5d,0x3e,0x91,0x1f,0xd2,0xf6,0xba,0xa4,0xc0,0x9a,0xaa,0x6e,0x88,0x27, +0x56,0x7e,0x93,0x8d,0xc6,0xc3,0xc5,0x41,0xfb,0xc2,0x6c,0x78,0x3c,0xb0,0xc8,0x37,0x29,0xa2,0xfd,0xc9, +0xb1,0xa8,0xb3,0xf1,0xd0,0x11,0x3b,0x40,0xde,0xd3,0x9c,0xde,0xb4,0xff,0xa0,0x3a,0x59,0xd3,0x1a,0x0a, +0x67,0xc8,0x29,0x08,0x52,0x6f,0xe5,0xc0,0xa7,0x39,0xfc,0x02,0xc0,0x0e,0x8a,0x8b,0x62,0x50,0xdc,0xb0, +0x13,0x7b,0xb0,0x52,0x68,0xc4,0xbf,0x08,0x6a,0x10,0xaf,0x0a,0xee,0x46,0xa0,0x3a,0x4e,0x7f,0xad,0x25, +0x84,0x6a,0xeb,0x08,0x21,0x7f,0x62,0xd3,0xa0,0x6b,0x85,0xbc,0x5c,0xc7,0xbb,0xf1,0xfa,0x9e,0x03,0x5d, +0xba,0xe0,0xfb,0xfd,0x22,0xd0,0xb7,0xef,0x47,0x8f,0xc6,0x04,0xcf,0x4d,0xf2,0x13,0x38,0xf4,0xb4,0x19, +0x68,0x44,0xb2,0xa6,0x13,0x66,0x87,0x54,0x07,0x96,0x39,0x27,0x34,0xae,0x15,0x59,0x12,0x49,0xbf,0x8a, +0x3c,0x4c,0xc7,0x84,0x04,0x16,0xb5,0x33,0x88,0xa2,0x83,0xfe,0x59,0xb2,0x11,0xa8,0x98,0x34,0x72,0x71, +0xa3,0x0c,0x07,0xb9,0xb9,0xf6,0xa0,0xe5,0xae,0xe7,0x66,0xd7,0x51,0x27,0x34,0xbb,0x95,0x1d,0xce,0x7a, +0xc0,0x85,0x9d,0x33,0x37,0x33,0x18,0xf4,0xef,0x9e,0x0b,0x62,0xef,0x31,0x0b,0x42,0xf6,0xed,0x41,0x0a, +0x25,0x79,0xca,0x4a,0xe2,0xd0,0x0c,0xe6,0xb1,0xc4,0x96,0xf8,0x6f,0xde,0xa8,0xc8,0x84,0x58,0x79,0x4e, +0xf5,0xb8,0x0a,0x7c,0x0c,0xa6,0xfa,0x2b,0x11,0x3a,0x34,0x27,0x49,0x9c,0x5b,0x95,0xe6,0x2a,0x61,0xce, +0x54,0x0c,0x6c,0xef,0xe3,0x09,0x29,0x16,0x9f,0x83,0x8c,0x04,0xc5,0x55,0x45,0x30,0x1c,0x49,0xbc,0x22, +0xcf,0x6d,0x89,0x21,0x23,0xfe,0x33,0x05,0x8e,0xb5,0x10,0x9d,0x40,0x20,0xba,0xe8,0xb5,0x1c,0xc0,0xbb, +0x6c,0x6d,0x68,0x90,0xa0,0x58,0x68,0x67,0xe8,0x8c,0xd6,0x4e,0x65,0xc8,0x22,0xda,0x56,0x47,0xb8,0xad, +0xed,0xa7,0xc5,0x39,0xf2,0x29,0xbc,0x9d,0x3d,0x09,0x70,0xc6,0x96,0x83,0x47,0x8e,0xfe,0x7e,0x65,0x85, +0xe7,0x02,0xa1,0xa1,0xa5,0x38,0x79,0x5c,0xbe,0x78,0x24,0x9d,0xd8,0xd5,0x88,0x50,0x3c,0x4a,0x4c,0xdd, +0x62,0x01,0x04,0xc2,0x4f,0x37,0x91,0x29,0x0d,0x12,0xc7,0xf8,0x32,0x0c,0x5e,0x15,0x51,0xcb,0xd7,0x5d, +0x00,0xc5,0x42,0x72,0x4c,0x50,0xa3,0x0d,0xdc,0x93,0xa4,0x07,0xda,0x74,0xd4,0x9e,0x25,0x7b,0x84,0x5a, +0xab,0x1b,0xa9,0x80,0x63,0xc8,0x4a,0x24,0xde,0x96,0xdf,0xad,0x90,0x96,0x7c,0x63,0x0f,0x8f,0x0d,0x2b, +0xbb,0xe3,0xe3,0x11,0x06,0xd8,0x18,0x1d,0x1a,0x3c,0x2e,0x88,0x6a,0x8c,0x29,0xdb,0xf8,0xc8,0xb3,0x12, +0xbb,0xbd,0x99,0xf2,0x25,0x16,0xdc,0x27,0x9c,0x56,0xe1,0xb3,0x61,0xfd,0xf0,0xe3,0x19,0x12,0x26,0x1d, +0x85,0x5a,0xcc,0x2d,0x4a,0xee,0xe6,0x13,0xfc,0x8c,0x00,0x52,0xaa,0xcc,0xa5,0x98,0x73,0x53,0x76,0x1b, +0xa2,0xf7,0x5e,0x4b,0xf4,0xbc,0x76,0x40,0x03,0x5f,0x1f,0xdd,0x2b,0x25,0x8a,0x9b,0x06,0x3c,0x98,0x0d, +0x90,0x12,0xbc,0x39,0xf7,0xa4,0xca,0xca,0x5e,0x29,0x9d,0xb0,0x74,0xdb,0x6b,0xb8,0x1e,0x42,0xe9,0xf6, +0x90,0x18,0xf9,0x75,0x00,0x94,0x1d,0x79,0x99,0x6e,0xdd,0x24,0x01,0xde,0x9b,0xc0,0x59,0x4f,0xb6,0xc6, +0xcf,0xbc,0x45,0x2a,0x2c,0x7a,0x6c,0x8c,0x23,0x3d,0x08,0x1d,0xdf,0xc4,0x97,0x19,0x9f,0x5e,0xb2,0x7a, +0xf4,0xf8,0x2b,0x9c,0x8a,0x56,0xa6,0x4c,0x5c,0xc6,0x6a,0xbd,0x74,0x5e,0xb1,0x31,0xc2,0x6e,0x9a,0x2f, +0xc6,0x72,0x89,0x21,0xe1,0x06,0x70,0x82,0x45,0xc1,0x4f,0x15,0x60,0x1c,0xbf,0x42,0xbd,0x04,0x60,0x3c, +0x08,0x07,0x78,0x37,0x4a,0x01,0x4b,0xe1,0xce,0x52,0x1b,0xff,0x0a,0xac,0x02,0x69,0xed,0xa3,0x73,0x81, +0x21,0x1c,0x59,0x08,0x2d,0x2f,0x18,0xce,0xc8,0xb4,0x49,0xaf,0xfe,0x3c,0xfb,0xd1,0xd9,0xf2,0xb0,0x53, +0x1e,0x10,0x15,0xea,0xf5,0x35,0x58,0x64,0x19,0xf1,0x62,0x30,0x48,0x93,0xde,0xf1,0x1c,0x46,0xae,0xa0, +0x04,0xea,0xe7,0xab,0x9d,0x6c,0x08,0x83,0x3e,0xf9,0x20,0x78,0xee,0x43,0x07,0x3b,0x11,0xcd,0x51,0x47, +0x3d,0x84,0xdc,0xc5,0x1c,0x92,0x34,0x60,0x6a,0xa6,0xc9,0xd8,0x09,0x3d,0x31,0x42,0xcf,0x7c,0xfc,0xfe, +0x64,0xc0,0xcc,0x94,0xa7,0x73,0x7e,0x23,0x39,0x89,0xe0,0x8d,0x08,0xd0,0x2f,0xe5,0xd7,0xe0,0x32,0x6b, +0x0d,0x9a,0x80,0xa8,0xc9,0xe0,0xbf,0xcc,0x0a,0x09,0x6e,0x00,0x08,0x67,0xfd,0x99,0x3d,0x18,0x6d,0xf0, +0x15,0x0b,0x09,0x69,0xa2,0xcb,0x7d,0x75,0x64,0xea,0x40,0x17,0xf5,0xc4,0xd1,0x05,0xb2,0x7c,0xc1,0x4c, +0x23,0xa8,0xa6,0x24,0xf0,0xeb,0xbb,0x1b,0x64,0x3e,0x17,0xe1,0xdd,0x8d,0xb5,0x67,0x43,0x77,0x3f,0x62, +0xe6,0x02,0xb6,0x17,0x0f,0xa3,0xaf,0xd5,0xe2,0x03,0x3b,0x36,0x1b,0xed,0x8d,0xfd,0x7a,0xd8,0x63,0xdd, +0xd8,0xd1,0x96,0x02,0x90,0x3f,0x51,0xd1,0x61,0x47,0xb4,0x7f,0x94,0xe0,0x86,0x49,0x12,0x7b,0xff,0xa6, +0x6d,0x26,0x8a,0x88,0xe8,0x5d,0xb3,0xf5,0x91,0x21,0xfb,0x33,0xf6,0x46,0xe6,0x12,0x2e,0xaf,0x9c,0xe6, +0x84,0x6c,0xcd,0xfd,0xbe,0x69,0xb6,0x74,0x0d,0x05,0x0e,0x2a,0x2c,0xaf,0x9b,0x27,0x6d,0xe0,0xa8,0x1d, +0x8b,0xca,0x75,0xbc,0x26,0xbf,0x39,0x8a,0x0b,0x6f,0x5e,0x90,0xd4,0xf1,0xc4,0x59,0xf0,0x20,0x79,0xba, +0x03,0x4d,0x98,0x32,0x4d,0xa4,0xe0,0xa8,0xe8,0x4b,0xe5,0xc0,0xf0,0x4b,0x65,0xa0,0x34,0xdc,0x33,0x66, +0xc7,0x9d,0x99,0x77,0x06,0xb3,0x94,0xbd,0x91,0x2b,0x85,0x20,0xe2,0xd2,0xec,0x06,0x0f,0xda,0xac,0x8d, +0x03,0xa8,0xeb,0x03,0x39,0xda,0x91,0xa1,0x73,0x36,0xf9,0x5d,0xa9,0xe1,0x90,0x93,0x64,0x30,0xaf,0x7e, +0xec,0xa9,0x17,0x30,0x65,0x0a,0xa4,0x24,0xdd,0xea,0x5e,0x56,0x14,0xb0,0x56,0xdc,0x95,0x3a,0x8f,0x72, +0x9f,0x9e,0x5f,0x13,0x4b,0xba,0xe7,0x63,0xde,0x02,0x36,0x57,0x2a,0x7a,0xd2,0xdf,0x54,0x32,0xd1,0x26, +0x7d,0x50,0xdb,0x7e,0x53,0xc1,0xf4,0x49,0x55,0x27,0x52,0x43,0x27,0xe7,0x8b,0x36,0x59,0x59,0x51,0x11, +0x7b,0x38,0x60,0x6c,0x2f,0x18,0xb0,0x57,0xaa,0xfa,0x03,0xf6,0x8a,0xf1,0x87,0x51,0x42,0xc7,0xc2,0xa0, +0x49,0xdb,0xd6,0x37,0x09,0xd7,0x25,0x81,0x56,0xae,0x83,0x1b,0xe9,0x20,0xd9,0xed,0x9e,0xbc,0x9d,0xbb, +0x98,0x09,0x05,0x68,0xd3,0x37,0xa7,0x9d,0x55,0x68,0xf9,0xb5,0x99,0x41,0x77,0xd2,0x3e,0xd8,0x3e,0xaa, +0x35,0xb1,0x88,0xa1,0xb7,0x26,0xb2,0x4a,0xfe,0x25,0xb8,0x7f,0xe1,0x67,0x80,0x80,0x1f,0x9d,0xd8,0xc6, +0xf5,0x44,0x65,0x27,0xa4,0xff,0xfc,0xde,0x9d,0xae,0x81,0x37,0x1d,0x37,0x68,0x00,0xa7,0xd9,0x57,0x2d, +0x76,0x40,0xd9,0x3d,0xbc,0x4a,0x9d,0xcb,0x6e,0xb6,0x03,0xb4,0x05,0x71,0x7f,0x4b,0x73,0xd8,0xd5,0x1b, +0x21,0xdf,0xdd,0x7c,0x3e,0xcf,0x0e,0xad,0x3e,0x71,0xde,0x09,0xfd,0xd8,0xd9,0x91,0x33,0x8e,0x4e,0x7c, +0xba,0xaa,0x16,0xcf,0x58,0x6a,0x5a,0xac,0x4f,0x73,0xa2,0x1a,0x80,0x40,0x1a,0xd5,0x29,0x83,0x49,0x6f, +0x4c,0x1f,0x25,0xa0,0x13,0xcc,0x5e,0x02,0x20,0xc6,0xcb,0x51,0x48,0x42,0xd3,0x89,0xa5,0x7e,0xf8,0x87, +0x5b,0xda,0xfe,0x8b,0x71,0xef,0x0c,0x77,0x1b,0xf0,0x5c,0x2a,0x18,0xc8,0xb3,0x8b,0x0f,0x18,0xab,0x80, +0x0f,0xe4,0x8b,0x94,0x53,0xd4,0x59,0xce,0x98,0x82,0x04,0x28,0x92,0x13,0xd4,0xee,0xe3,0x4f,0xb7,0xcd, +0xc7,0x4a,0x7e,0x47,0xca,0x88,0xe0,0xcc,0x18,0x26,0xd8,0xfe,0x52,0x06,0xd4,0x7c,0x52,0xc1,0x76,0x14, +0x64,0x4c,0x38,0x94,0x37,0xe4,0x1d,0x57,0x98,0x5d,0xe2,0x1a,0xcf,0x76,0xbf,0x96,0x1c,0x49,0xe5,0x38, +0x91,0x5c,0x5a,0x27,0x43,0xce,0xaf,0x91,0xa0,0x45,0xf2,0x63,0xe0,0x20,0xbc,0x14,0x18,0xd4,0x7d,0x48, +0x5d,0x89,0xcf,0x3c,0xc9,0xcd,0x0b,0xb1,0x90,0x9e,0xc7,0x72,0xff,0x45,0x54,0x85,0x6c,0x31,0xea,0x1d, +0x5f,0x2b,0x04,0xfb,0x82,0x2f,0x9b,0xd5,0x27,0xd5,0x4f,0x1b,0x2d,0xae,0x95,0x13,0x52,0xce,0xb8,0x36, +0x1a,0x62,0x1a,0xc7,0x42,0x30,0x4c,0xac,0xd7,0xdc,0x1f,0x39,0x12,0x4a,0xc8,0x0d,0xb6,0x2e,0x1e,0xbb, +0x95,0xa5,0x3c,0xd1,0xec,0xa5,0x02,0x06,0xd7,0x7b,0x67,0xe7,0x89,0xe7,0x09,0xa7,0x44,0x66,0x82,0x27, +0xf4,0xa9,0xad,0xa6,0xa1,0x60,0x24,0xe7,0x50,0x07,0x03,0x9a,0x9d,0x04,0x57,0x0e,0x89,0x8c,0xb3,0x69, +0x7b,0x45,0x49,0x3c,0x69,0x6f,0xbb,0x96,0x3d,0x39,0xd6,0x9f,0x05,0x89,0x46,0x66,0x25,0xfd,0xe1,0x6d, +0xfc,0xb2,0xa6,0x41,0xf9,0x48,0x92,0xfc,0x3d,0x24,0x7d,0x08,0x49,0x00,0x93,0x22,0x6e,0x77,0x37,0x23, +0x03,0xf1,0xe3,0x93,0x53,0x1b,0xc2,0x51,0x56,0x21,0x77,0x4b,0x9e,0xc0,0xc0,0xe7,0xa9,0x89,0x34,0xa8, +0xe3,0xa0,0x34,0xec,0x05,0x21,0xe4,0x08,0x67,0x03,0x85,0x2b,0x79,0x08,0x54,0xd2,0xd4,0x08,0xc2,0xf9, +0xb3,0x3d,0x72,0x0a,0x01,0x43,0x4a,0xfc,0x71,0x75,0x75,0x70,0xd0,0x6c,0x32,0xc3,0x03,0xc1,0x95,0xb8, +0xba,0xba,0x4a,0x1f,0x1c,0x1c,0xa4,0x9b,0xf8,0x3a,0x99,0xcf,0x14,0x2b,0x15,0x49,0x95,0xfb,0x23,0x1a, +0xfa,0x27,0xe6,0xe5,0x7a,0xcd,0xd0,0x47,0xf6,0x6c,0xf5,0xd0,0x47,0x30,0x14,0xe8,0x19,0x64,0x67,0x2a, +0x2d,0x79,0xa7,0xda,0xf0,0x5d,0x21,0x9f,0xa7,0x91,0x7e,0x74,0xc0,0xfc,0x0d,0xdd,0xe4,0x4f,0x43,0x2b, +0x78,0xa8,0xc6,0x56,0x71,0xd7,0xde,0x51,0x95,0x37,0x19,0xb2,0x77,0xf4,0x2e,0x7a,0xe4,0xe4,0x3b,0x7e, +0x66,0xd0,0x7a,0x1b,0x40,0x31,0x3f,0x4a,0x78,0x2d,0x38,0x42,0x0c,0x02,0xd9,0x71,0xe4,0xce,0x50,0xef, +0x00,0x04,0xfa,0x5f,0xf8,0x58,0xa2,0xe8,0x98,0x97,0x89,0x20,0x64,0x02,0x5b,0xba,0xd5,0x50,0xe6,0x27, +0x92,0xee,0x5a,0x2c,0xb0,0xfc,0x6d,0x66,0x36,0xd6,0x2c,0x55,0x89,0x9e,0x15,0xde,0x58,0x8b,0x9a,0x3e, +0x55,0x62,0x70,0x3a,0xe4,0x50,0x81,0xe5,0xc7,0x64,0x4c,0xbb,0x3c,0x89,0x0d,0x6f,0x34,0xb8,0x1f,0xc2, +0xce,0xf3,0x20,0x07,0xf1,0x33,0x0d,0x1a,0x57,0x32,0x50,0xe7,0xa8,0xbb,0x4d,0xc9,0x1e,0xda,0xcd,0xc4, +0x64,0x0e,0x1e,0x45,0xb3,0x45,0xe7,0xb3,0x7b,0xd2,0x0e,0xcf,0x96,0xd0,0x00,0x83,0x3c,0x63,0x68,0xe7, +0x64,0x69,0xb1,0xb0,0x23,0xf7,0x24,0x50,0x52,0x51,0x27,0x2c,0x43,0x08,0xb3,0x55,0x48,0x0a,0xd8,0x14, +0xba,0xb7,0x77,0x85,0x98,0x59,0x6a,0xef,0x44,0x47,0xa8,0xba,0x69,0x77,0x5d,0x1f,0xbe,0xeb,0xce,0x76, +0x93,0xb6,0x85,0x3e,0x61,0x42,0xcd,0xd0,0x4b,0x92,0x86,0xd9,0x7d,0x2b,0xee,0x11,0x00,0x69,0x24,0x79, +0x29,0x9a,0xf4,0x93,0x3d,0xa4,0xbc,0xda,0x53,0x96,0x4c,0xd0,0x75,0xb4,0x03,0x76,0x58,0xf2,0x17,0xa1, +0xd4,0xc0,0x4b,0xaf,0x19,0x35,0x99,0x09,0xc9,0x8b,0x65,0x4f,0x31,0xc5,0xd2,0x5b,0x87,0xd3,0xa2,0x0e, +0x7c,0xdc,0x3d,0x3f,0xe3,0xd2,0x61,0x4d,0xc5,0xb7,0xb4,0xae,0xe2,0x7b,0x6c,0x9b,0x3e,0x83,0xa4,0xb4, +0xcc,0x2a,0x0e,0xab,0xd6,0x9b,0x09,0x6b,0x0c,0x58,0x62,0xee,0x45,0x60,0x2d,0xea,0x63,0x5f,0x9d,0xf8, +0xb2,0x69,0x51,0x7a,0x4f,0xb9,0xe4,0xa8,0x19,0x7d,0x7d,0xaa,0xd0,0x20,0x15,0xd9,0x07,0x06,0x10,0x7b, +0x00,0x64,0x6f,0xfd,0x23,0x16,0xa1,0xf6,0x78,0x42,0x10,0xe3,0x93,0xd3,0x36,0x4a,0xc0,0xa9,0xc1,0x76, +0x6c,0xa2,0x17,0x1a,0xa6,0x3e,0xf4,0x70,0x4b,0xd6,0x10,0x59,0x02,0xa7,0x7c,0xb0,0x7c,0x44,0x3f,0xa6, +0xea,0x94,0x45,0xf3,0x91,0x76,0x69,0xe6,0xad,0x02,0xe6,0x49,0xc3,0x50,0x40,0x61,0x2b,0xd3,0x9d,0x67, +0x9f,0xa7,0x4d,0x18,0xab,0xa0,0xa7,0xe2,0x5c,0xc4,0xfd,0x1e,0x36,0x1f,0xd2,0x5b,0x77,0xda,0xb3,0xfb, +0x96,0x46,0x8c,0x59,0x9b,0xe7,0x6a,0x61,0x1d,0x2b,0x28,0x76,0x51,0x17,0xa0,0xd5,0xc8,0x10,0x7f,0xfd, +0x58,0x0c,0xbd,0x2f,0x25,0xc6,0x19,0x4d,0xee,0x44,0x09,0xf9,0xa2,0x89,0xe0,0xc5,0xa4,0xa3,0x49,0xb6, +0x7f,0x22,0x91,0xa3,0x52,0x17,0x96,0x89,0xde,0x22,0x3d,0xc5,0xb4,0x7b,0x16,0x12,0x8f,0x89,0x04,0x03, +0xc9,0x1f,0x61,0x12,0x0f,0x68,0xa6,0x8a,0x51,0x4c,0x24,0xbc,0x91,0xf2,0x68,0x6a,0x20,0x90,0xf4,0x4a, +0xc8,0xac,0x49,0x29,0x92,0xd9,0x87,0xe4,0x37,0xfa,0xdd,0x3e,0x69,0x76,0xe2,0x87,0xe7,0x34,0xbd,0xd6, +0x42,0xd9,0x23,0xd7,0xd2,0x86,0x31,0x9b,0xe9,0xea,0x28,0x93,0x5f,0x64,0x0d,0x24,0xf3,0x6b,0x54,0xf0, +0xd1,0x7a,0x43,0x74,0x26,0xcb,0x1d,0xd1,0xe5,0xf8,0xfd,0xe5,0xf5,0x0f,0x17,0x22,0x5e,0xbf,0x00,0xe9, +0xbe,0xca,0xcb,0x04,0x43,0x3c,0x1f,0x0c,0x48,0x7a,0x29,0x61,0x58,0x84,0x41,0xbd,0x91,0xa3,0x87,0x6d, +0x41,0xb4,0xbb,0x6f,0xb7,0x03,0x01,0x83,0x86,0xe6,0xde,0xca,0xab,0x03,0x4d,0x81,0xba,0x33,0x14,0x0f, +0x91,0x6f,0x08,0x41,0x5a,0x3b,0xca,0x37,0xd7,0x99,0x5a,0x60,0x73,0x2e,0x71,0xc9,0xdd,0x74,0x1b,0xb1, +0xbe,0xb5,0x57,0x30,0x1b,0xef,0x86,0xae,0x38,0x8e,0xe3,0xdd,0xdc,0x15,0x62,0x3b,0x54,0xa1,0x76,0x9d, +0x40,0x3c,0xcc,0xb1,0xcd,0xda,0x74,0x7d,0x0a,0x7f,0x57,0xd8,0x07,0xa7,0x24,0x36,0x9e,0x37,0x23,0x27, +0xa5,0xff,0xf4,0x5b,0xf7,0xb3,0x5c,0x00,0x1e,0x9d,0xbd,0xde,0xf8,0x87,0x41,0x92,0xba,0x47,0xc6,0x52, +0xeb,0xff,0xad,0x86,0x7a,0x7e,0x73,0xfd,0xeb,0x63,0x3d,0x9f,0x06,0xb6,0xad,0xc2,0x47,0x5e,0xa3,0xd5, +0x22,0x46,0x2a,0x5d,0x7a,0xc5,0x15,0x4b,0xc1,0x86,0x57,0xa3,0xe0,0x51,0x37,0x7b,0x39,0x29,0x45,0x51, +0x03,0x3b,0x04,0xab,0xe2,0x16,0xc0,0xe7,0x50,0x4a,0x52,0x31,0xbd,0x9f,0x4d,0x2d,0x1a,0x07,0xe3,0xb2, +0x71,0x63,0x06,0xf8,0xaa,0xe1,0x44,0x27,0x60,0x82,0x59,0x5e,0x45,0xdf,0x49,0x03,0x5f,0x4e,0xc3,0xd7, +0xcc,0xbc,0x62,0x39,0x8f,0xe4,0xf8,0x85,0x7c,0x78,0x7e,0xda,0x91,0x1a,0xa0,0x73,0x77,0xaf,0x1b,0x67, +0xde,0x8e,0x36,0xee,0xa6,0x8b,0x09,0x89,0xf9,0x2a,0x3e,0xc4,0x60,0xe5,0x86,0x6d,0x6b,0x78,0x5e,0xc8, +0xd9,0x74,0x11,0xb7,0xd1,0x9e,0xa1,0x52,0xf0,0x57,0x57,0xb1,0xd0,0xd4,0xdb,0x2c,0xdf,0x51,0x64,0xba, +0xb8,0x65,0x9a,0xf3,0xbf,0x6e,0x8f,0xca,0x0f,0x75,0xb2,0xdd,0x97,0x51,0x18,0x00,0xd0,0x78,0x24,0x46, +0x6b,0x56,0x62,0x9c,0x8d,0xa8,0xbc,0xc2,0x89,0x2d,0x41,0x6d,0x77,0xd1,0xd8,0x20,0x71,0x98,0xa6,0xae, +0xb2,0x48,0x2b,0xea,0xfb,0x90,0x89,0xe1,0x8b,0x6a,0x1c,0x9a,0xdc,0x24,0x30,0x61,0x4c,0x7e,0xf1,0x53, +0x2e,0x9c,0xd6,0xbd,0x5c,0x15,0xa0,0x49,0x07,0x46,0x87,0xae,0x9e,0x89,0x6c,0xd9,0x42,0xc6,0xa8,0x37, +0xb2,0xb0,0x2e,0x2c,0x75,0xa0,0x3d,0xbe,0x66,0x27,0x72,0x24,0xd3,0xba,0x4b,0x63,0xb7,0x7d,0x4c,0xa3, +0xe5,0xcf,0x4d,0xf6,0xcb,0xf4,0x1a,0x6c,0x2f,0xfe,0x64,0x78,0x39,0x64,0x1c,0xae,0x8c,0xf8,0x1a,0xc9, +0x19,0xaa,0xb9,0xbb,0x47,0xa3,0x33,0x25,0x60,0x54,0x23,0x31,0xd4,0xeb,0x37,0x06,0x10,0x03,0xaa,0xdd, +0x08,0x3e,0x76,0x56,0x5b,0x4c,0x38,0x8e,0xf7,0xa5,0xc5,0x30,0x10,0x29,0xe1,0xb3,0x38,0xb2,0x0b,0x79, +0x0c,0x9c,0x0b,0x03,0x5f,0x89,0x4f,0xd3,0x3b,0x9b,0x8b,0x86,0x06,0x65,0xac,0x34,0xea,0x30,0x49,0xfd, +0x9a,0x69,0x4a,0xa9,0x69,0x89,0x5e,0x63,0x98,0x02,0x63,0x13,0xb5,0xf6,0x85,0xa4,0x3e,0x42,0x07,0x60, +0xe8,0x29,0x26,0xf2,0x65,0x49,0x99,0xe2,0x89,0x45,0xdc,0xd8,0xd0,0xb0,0xb0,0x36,0x9e,0x90,0x14,0xb3, +0x93,0x05,0xd7,0x56,0xf0,0x76,0x33,0x3b,0xd0,0x17,0xeb,0x8a,0x24,0xf5,0xc4,0xa4,0xb8,0xbc,0xed,0x89, +0xa5,0xda,0x91,0xec,0xfc,0x15,0xc1,0x70,0xfc,0x02,0x78,0x8f,0x50,0xbd,0x5c,0x7c,0xbe,0xbb,0x06,0x36, +0xe7,0xfa,0xe2,0x6d,0x87,0x71,0xdc,0x5f,0xbc,0x05,0x91,0x67,0xab,0x54,0x69,0x48,0x92,0xc5,0x60,0x6a, +0x93,0x9b,0xff,0x10,0x7f,0xd7,0x6e,0x83,0x7f,0xbb,0xf2,0xe6,0x0d,0xe5,0xed,0x0e,0x16,0xd9,0x93,0x87, +0xdf,0xaa,0xbf,0x79,0x63,0x8e,0x39,0x99,0xb7,0x96,0x52,0x84,0xc3,0x24,0x27,0xf7,0x56,0x6a,0x6f,0xbf, +0x47,0xfb,0xe9,0x18,0x3c,0x85,0x3c,0xc1,0xb2,0x9b,0x5e,0x9e,0xed,0x6c,0xa0,0xb3,0x9e,0x13,0x4c,0x1a, +0xf4,0xa3,0x5f,0x54,0x8e,0x70,0xb6,0x31,0xda,0x11,0x7e,0x7a,0x1b,0xf5,0x88,0x5e,0xf0,0x1b,0xb3,0x42, +0xbc,0x4b,0x7c,0xd7,0x35,0x74,0x8e,0x8c,0x81,0x3e,0x7d,0x6c,0x37,0xb9,0xa1,0x23,0xd1,0xa8,0x1d,0x92, +0x48,0x1f,0xa3,0x2b,0x28,0xdc,0x58,0xb8,0x89,0x27,0x52,0xdd,0x6a,0x5e,0x93,0xff,0xb2,0xc0,0x3e,0xde, +0xf7,0x9b,0x2d,0x26,0x00,0x69,0xc4,0x5a,0x2a,0xbf,0xd9,0x5a,0xc2,0xfb,0x42,0x5e,0xb3,0x7a,0x60,0x5c, +0x87,0xe4,0x66,0x95,0xdf,0xe7,0x48,0xd9,0x28,0x42,0xcf,0x3f,0xb4,0x65,0x01,0x7a,0xe5,0x78,0xfd,0x72, +0x7d,0x9d,0x19,0xd6,0x65,0x8f,0xe4,0x99,0x78,0xd5,0xf0,0x78,0xf5,0xd8,0x21,0xbe,0x41,0xd8,0x20,0x46, +0xfd,0x45,0x5c,0x04,0xb3,0xee,0x10,0x79,0xf5,0xd8,0x21,0x96,0x7f,0x53,0xb0,0xdc,0xc6,0xe1,0x80,0x01, +0xcc,0xcb,0xab,0xa3,0x01,0xcb,0x95,0xe5,0x63,0x7f,0x8d,0x9f,0xa7,0x37,0x8c,0xe7,0x7c,0xe4,0x7e,0x5f, +0xe9,0xe4,0xfc,0xc0,0x53,0x0d,0x40,0xa1,0x44,0x8b,0xb0,0x37,0x1d,0x7a,0x4e,0x73,0xc6,0xcb,0xbd,0xab, +0x2e,0x5e,0xc9,0xb2,0x82,0x51,0xdd,0x63,0x65,0xa9,0x1c,0xba,0x23,0xe7,0xab,0x65,0xba,0xe9,0x6a,0x50, +0x3f,0x3f,0xbd,0x45,0x84,0xc8,0x21,0x60,0xac,0xea,0xa3,0xda,0x9f,0x12,0x8d,0x8f,0x6f,0x7e,0x89,0x11, +0x06,0xe6,0xd4,0x99,0x4c,0xbd,0x4d,0x71,0x50,0x55,0xe7,0x06,0x3f,0x39,0x42,0x67,0x88,0xb4,0x83,0x7a, +0xad,0xcc,0x8e,0x8b,0x44,0x1c,0xd8,0x02,0xf8,0xe1,0x38,0x03,0x43,0x5c,0x72,0x46,0xb4,0x18,0x3d,0xa7, +0xd7,0x1c,0x9b,0xa7,0x7d,0x07,0x8e,0x0d,0xa2,0xf2,0xb6,0x7e,0x1a,0x24,0x1c,0x1d,0x99,0xe0,0x72,0x1d, +0x75,0xae,0xf6,0x74,0xd3,0xd5,0x50,0xf9,0xd3,0xdc,0x92,0x27,0xf5,0x84,0x39,0x18,0xc4,0xb9,0xcc,0x79, +0xeb,0x6f,0x39,0xe3,0x4c,0x5f,0x57,0x65,0x2b,0x30,0xe9,0x16,0xbe,0x8b,0x9e,0xf5,0x6b,0x0e,0x6f,0xb8, +0x17,0xa9,0xc7,0xac,0x0b,0x7a,0x51,0xba,0xd4,0x3a,0x3f,0xdb,0x3f,0x3a,0xb8,0x09,0xe4,0x21,0x13,0x4f, +0x02,0x28,0x78,0xf8,0x5e,0xeb,0x91,0xe0,0x51,0x71,0x0b,0x85,0xf5,0xf0,0xf7,0x29,0xd1,0x57,0x30,0x1b, +0x9a,0x22,0x66,0x26,0xeb,0xaf,0xf2,0x56,0xdb,0x86,0x8d,0x8d,0x84,0x7c,0xd5,0xd5,0xc4,0x87,0x2a,0xdf, +0x95,0x8d,0x75,0x57,0x83,0x16,0x50,0xc8,0xaf,0x2c,0x05,0x16,0x79,0xa1,0xb2,0xaa,0x14,0x6a,0xe7,0xa5, +0x95,0x6d,0x55,0xb1,0x47,0xb6,0xbf,0x1e,0xdf,0x12,0x14,0xaa,0xf0,0x4d,0xf8,0x7f,0xb3,0x3b,0x5a,0xba, +0x14,0x35,0xc8,0x9b,0x38,0xda,0xdc,0x04,0x7c,0xb8,0xe4,0xd0,0xb0,0x20,0x6c,0xcd,0x56,0x5d,0x8e,0xe6, +0xa6,0x30,0x21,0xdc,0x91,0xde,0xd7,0xa8,0xb8,0xf1,0x7e,0x34,0x44,0x80,0x9f,0xdb,0x24,0x09,0x52,0x48, +0x41,0x74,0xa9,0x8e,0xc7,0x98,0xc8,0x97,0x47,0xb9,0x68,0xb6,0xe4,0xa2,0xf4,0xed,0x12,0x4f,0x61,0x30, +0x04,0x09,0x7d,0x78,0x85,0xca,0x29,0x90,0xe0,0x24,0x2a,0xa1,0x53,0x30,0x48,0x96,0x06,0xc5,0xba,0xd7, +0x7f,0xf1,0xcb,0xbf,0xfe,0x3d,0x92,0x88,0x0a,0xa8,0xf5,0x02,0xc8,0x39,0x4e,0x69,0xfa,0x19,0x82,0x3a, +0x86,0x1d,0x9a,0xdd,0x6a,0x19,0x12,0x02,0x72,0x74,0x32,0xb7,0xd6,0xd7,0x5c,0x31,0x38,0x62,0x0a,0x16, +0x95,0x1d,0x64,0x0a,0x14,0xe4,0xd0,0xd6,0x0d,0x7e,0x0d,0x1c,0x4a,0x5e,0x89,0xb6,0xd0,0x7e,0xd6,0x9d, +0xec,0x38,0xb6,0x94,0xbc,0x4b,0x2d,0xd9,0xd2,0xfa,0xa4,0x81,0xd0,0xa5,0xe5,0x1e,0xc4,0x82,0xab,0x36, +0x14,0x03,0xf3,0x1f,0xe9,0x73,0x57,0xa5,0xb0,0xdd,0xb7,0xea,0x98,0x6c,0xb4,0xad,0x69,0x23,0xa9,0xd6, +0x70,0xb1,0x0c,0x2e,0x2a,0x29,0xf1,0x36,0xa0,0x39,0x36,0xf1,0x88,0x9e,0x94,0x3c,0x5e,0x0b,0x36,0xff, +0xcf,0xff,0x3d,0x42,0xc1,0x02,0x10,0x1a,0xbd,0x1e,0x44,0x42,0xe0,0xd3,0xda,0xbe,0x0c,0x21,0x56,0x6a, +0x09,0x60,0x9c,0xf1,0xe4,0x6d,0xa0,0xd2,0x52,0x75,0x5b,0x9b,0x02,0x58,0xde,0x2b,0xea,0x70,0xaf,0xb5, +0x0c,0x36,0xfb,0xf2,0xc8,0x52,0x8d,0x91,0x0a,0x6a,0x1f,0x2d,0xbd,0xff,0xb7,0x41,0x26,0x22,0xaf,0xb8, +0xc8,0xc1,0x00,0x3a,0x2d,0xb7,0xc4,0x5b,0x5c,0x71,0x98,0x21,0x57,0x1b,0x52,0x36,0x97,0xcf,0x16,0xbc, +0x5b,0x0e,0x7d,0xf0,0xaf,0xfd,0xcd,0x07,0x6e,0xa2,0x21,0xe4,0x49,0x1b,0x59,0x51,0x54,0x85,0xc7,0x97, +0x8c,0x55,0xd9,0x9e,0x5a,0xae,0x30,0x9a,0x8f,0xb4,0xfe,0x08,0xa6,0xb5,0xa0,0x31,0x47,0x72,0xbf,0x3f, +0x25,0xea,0x95,0x42,0xaf,0x08,0x1c,0xa3,0xc3,0x98,0x66,0x09,0x65,0x39,0x09,0x68,0x35,0xdc,0x92,0xe8, +0xe1,0x09,0xf1,0xa1,0x8c,0x37,0xba,0x6c,0x96,0x3e,0x67,0x34,0x1d,0x6b,0x8a,0xe6,0xac,0x7d,0x3f,0xce, +0x21,0x2b,0xbf,0x0a,0xfd,0xd0,0xee,0xff,0x1f,0xd1,0x1f,0x01,0x9e,0xbf,0x0f,0xf7,0xcb,0xae,0x55,0x5a, +0x26,0x39,0x98,0x4c,0x80,0xdf,0x9a,0x63,0x2e,0xdb,0xce,0x0c,0xd0,0xd2,0x26,0x72,0xe7,0xd6,0xd4,0x1d, +0x72,0x51,0xf7,0xe6,0x1e,0x05,0x10,0xd4,0xac,0x76,0x9c,0x21,0x5a,0xf9,0x3d,0x31,0xfe,0xa7,0xb2,0x66, +0xd8,0xd2,0x8c,0xf6,0xed,0x1e,0x76,0xb6,0x17,0x40,0x94,0x63,0x12,0xb1,0x79,0xbb,0x2a,0xbf,0xd0,0x52, +0xf1,0xbd,0xb6,0xc8,0x7e,0x9c,0xd0,0x1c,0x5b,0x2c,0x3a,0x6c,0x13,0x18,0x4a,0x00,0x3c,0x5e,0x5f,0x80, +0x27,0x7f,0xd5,0x8a,0x85,0x68,0x31,0x70,0x58,0xf3,0xf7,0x1c,0xe4,0x39,0x32,0xa4,0x06,0xf2,0x4f,0xd4, +0x11,0x1a,0x29,0x37,0x0d,0x21,0xee,0xbf,0xf1,0x3d,0x5c,0xac,0x86,0xb7,0x98,0x6a,0x36,0x12,0x3c,0x49, +0xde,0x20,0xe9,0xda,0x03,0x7a,0x4b,0x7a,0x53,0xbd,0x97,0xf6,0x07,0x12,0x13,0x6b,0x00,0xfd,0xf2,0xae, +0x45,0x41,0xa2,0x34,0xe7,0x26,0xee,0x73,0xcd,0x84,0x94,0x87,0x69,0x49,0xcb,0xaa,0x59,0x89,0x86,0xd0, +0xe6,0x38,0x92,0x03,0xc7,0x1b,0xf0,0x7e,0x08,0xd9,0x3d,0xcd,0x00,0x13,0xc2,0x38,0x4f,0xcb,0x9c,0x1a, +0x0a,0x39,0x8d,0xc0,0x36,0xd8,0x68,0x1b,0x73,0x0d,0x67,0xc1,0x52,0xe6,0x79,0x77,0xbd,0xfe,0x4b,0x09, +0xe4,0x15,0x8b,0x6c,0x05,0x81,0xdc,0xc6,0x12,0x48,0xf0,0xd0,0xf2,0x6f,0x23,0x10,0x9c,0x14,0x90,0x07, +0xac,0x35,0x89,0x6e,0xd7,0x90,0x55,0x48,0x4c,0x0c,0x93,0xf8,0x75,0xa4,0x46,0xcb,0x5d,0xa4,0x6e,0xe8, +0x26,0x68,0x92,0x36,0x19,0x1b,0x9e,0xc4,0x00,0x46,0xaa,0xe9,0xc0,0xa5,0xd3,0x12,0x39,0x18,0xa1,0xd2, +0x84,0x2d,0x34,0x17,0xe1,0x9b,0x62,0xec,0x55,0x1a,0x39,0xc7,0x1e,0xb1,0x58,0xde,0x10,0x77,0x17,0xb1, +0xb8,0x0b,0x9e,0xc4,0xfe,0x6d,0xb8,0x73,0x4d,0xa3,0xac,0xd4,0xf2,0x7b,0x8f,0xa8,0xdb,0x48,0x12,0x0e, +0x04,0x44,0x2f,0xf5,0xa4,0xb0,0xd6,0xe9,0x71,0x15,0x95,0x10,0xc1,0x48,0x65,0x7b,0x56,0x78,0xde,0x88, +0x13,0x02,0x5d,0x89,0x0c,0xc7,0xb2,0x80,0xe5,0x7f,0x35,0x4e,0x49,0x06,0x77,0xd7,0x61,0xf6,0x96,0x57, +0xf1,0x00,0x7e,0x49,0xe3,0x2d,0xaf,0x6d,0x1f,0x6a,0x2b,0xff,0x16,0x7e,0x80,0x53,0x73,0xc6,0x12,0x11, +0xb1,0x13,0x5b,0x8e,0xc9,0xd3,0xc0,0x4c,0x48,0x3a,0x89,0x44,0x57,0x9e,0xa1,0x38,0xc7,0xbb,0x61,0x66, +0xaa,0x45,0xc2,0x71,0x78,0x42,0x7c,0x01,0x6c,0x3c,0xe5,0xe8,0xdf,0x82,0x3e,0x6a,0x08,0xbf,0x39,0xe6, +0x3a,0x3e,0x7c,0x55,0xff,0xbb,0xe1,0xcb,0x83,0x0c,0x9e,0x30,0x30,0xf8,0xb9,0x9d,0x5f,0xf4,0x53,0x5b, +0x83,0x25,0xbb,0xd6,0x57,0x8d,0xf6,0xd1,0xb9,0xb4,0x7f,0xd5,0xb9,0xbc,0xe9,0x9c,0xb5,0xee,0xbd,0x1d, +0x1c,0xd3,0xc0,0xab,0x70,0xcb,0xa5,0x92,0x74,0xb5,0x2f,0x35,0x2d,0x4d,0x19,0xf2,0x9b,0x6c,0xe1,0xf9, +0x04,0x2f,0x56,0xf6,0x07,0x2a,0xe1,0xc1,0x27,0x72,0xa7,0xbc,0x46,0xef,0xea,0xc2,0xfb,0x7d,0x71,0x13, +0x08,0x75,0x0e,0x9a,0x82,0x4b,0xd8,0x0c,0x87,0x26,0xfa,0xe4,0x1a,0x59,0xf7,0x54,0x31,0xed,0xee,0x6a, +0x9f,0xf5,0x84,0xfd,0x12,0x77,0x24,0xaa,0x0a,0xf4,0xfc,0x58,0x4f,0xb6,0xb5,0x3e,0xd6,0x04,0x5b,0x40, +0xd5,0xf8,0x61,0x63,0xd2,0xa7,0x89,0x39,0x06,0x2c,0x83,0xdd,0x0b,0x80,0x2d,0xb3,0x1d,0x1e,0x7e,0x30, +0xe4,0xa4,0xd3,0xb8,0x3a,0x13,0x4e,0x04,0x45,0x75,0xc9,0xd4,0x1c,0x75,0x92,0xc2,0x11,0x19,0x0c,0x39, +0x32,0xbf,0xce,0x9d,0xed,0x0c,0xd1,0x7c,0x99,0x69,0x96,0x47,0x8c,0x75,0xc6,0xc2,0x97,0x70,0x6b,0x08, +0x4f,0x3c,0x24,0x49,0x5a,0x34,0x32,0x4d,0xb1,0x6d,0x76,0x13,0x3d,0xe9,0xc2,0x3d,0x88,0xc9,0x3c,0xfd, +0xc2,0x37,0x7a,0xab,0x2f,0x0d,0x19,0x50,0xf0,0x8b,0x4a,0x2e,0x84,0xe6,0x97,0x9c,0x69,0x0e,0x39,0xb6, +0x89,0x6f,0x7a,0x2a,0x19,0xa6,0x09,0x4a,0x9e,0xbe,0xa0,0x20,0x70,0xb2,0x34,0x5f,0x00,0xbd,0x37,0xdb, +0x85,0xd0,0xa9,0xa9,0x68,0x83,0x05,0x72,0xef,0xbe,0xa5,0x62,0x2b,0x7c,0xe8,0xb6,0x77,0xc5,0x3d,0x1b, +0xb4,0x11,0x00,0x5d,0xb7,0x71,0xdb,0x11,0x8f,0xd1,0x48,0xf4,0x5a,0x0f,0x7a,0x63,0xb0,0xb8,0x5b,0x06, +0x56,0xd8,0x18,0x2f,0x01,0x76,0x33,0x3b,0x62,0xb0,0x94,0x65,0xea,0x24,0xc1,0x30,0x2d,0x01,0xa2,0xc3, +0xf6,0x32,0xae,0x91,0xe9,0x79,0x47,0x61,0xbd,0x54,0x56,0x69,0x86,0x4e,0x17,0x06,0xee,0x91,0x20,0x28, +0xc3,0xd0,0x41,0x36,0x4d,0x30,0x2c,0x6b,0x22,0xdb,0x14,0x2a,0xcc,0x16,0xc4,0x54,0x58,0x94,0x55,0x00, +0x7f,0x48,0x8b,0x33,0xc2,0x99,0x70,0x34,0xea,0x32,0xbb,0xc0,0x55,0x9c,0x90,0x70,0xed,0x1d,0xfe,0xbf, +0xad,0xe2,0xe6,0x29,0x09,0x2d,0xf3,0x48,0x8a,0x1e,0xaf,0xf3,0x8e,0x1c,0xed,0x9f,0x5f,0x1d,0x60,0xd2, +0x62,0x0e,0x20,0x3a,0xbc,0xac,0xb4,0xe4,0xdc,0x21,0xac,0x47,0x4b,0x9e,0x27,0x3e,0xf0,0x7e,0xfc,0xa7, +0x7e,0xf1,0x5e,0x09,0x40,0x55,0xa1,0x50,0xcd,0xe0,0xdd,0x12,0xb6,0x44,0x0e,0x45,0x5d,0x35,0xee,0x28, +0xbe,0xb2,0xd2,0x95,0xcc,0x51,0x87,0xe9,0xa5,0x35,0xdc,0x40,0xc2,0xd3,0xf5,0x81,0x13,0xf3,0xf1,0x99, +0x32,0xad,0xd1,0x63,0xee,0x6a,0x3f,0x43,0x29,0x33,0xd3,0xd9,0x3f,0xad,0x35,0x9b,0x60,0xe3,0xd1,0x8e, +0xf1,0x98,0x35,0x4f,0x6a,0x82,0xcb,0x8e,0xaf,0x4f,0x56,0x8c,0xa5,0xc3,0x64,0xf1,0x66,0x9b,0x65,0x15, +0x83,0x69,0x9f,0x99,0x6b,0x04,0xe0,0xe4,0xa0,0xcf,0x71,0x98,0xbb,0x09,0x4d,0xa0,0x5f,0x48,0x97,0xf1, +0x2a,0x46,0xd6,0xa7,0xc3,0x5f,0x78,0xb1,0x4c,0x94,0xa6,0xfe,0xf8,0x93,0x0d,0x95,0xfd,0xb3,0x69,0x24, +0x53,0x90,0xf3,0x53,0x71,0xd5,0xe5,0x04,0x1b,0xb8,0xbf,0x69,0x75,0x98,0x69,0x29,0x36,0xb7,0x43,0xa1, +0xc6,0x65,0x19,0x4c,0x12,0xd1,0x9b,0x20,0xc1,0x51,0x19,0x9c,0x76,0x81,0xfd,0xa6,0x47,0xe9,0x68,0x92, +0xc8,0xa8,0x9d,0x52,0xff,0x8d,0x39,0x42,0x4e,0xd9,0xf8,0x0c,0x6a,0x6c,0x6b,0x94,0x56,0xf4,0x6f,0xa2, +0xc2,0x38,0x32,0x64,0x1d,0x26,0x38,0xfb,0xdc,0xf4,0xce,0x9e,0xb7,0x18,0x01,0xee,0xd1,0x27,0x18,0x13, +0xfa,0x3b,0xfa,0x07,0x62,0x1c,0x62,0x26,0x73,0xbe,0xce,0xd7,0xdd,0x31,0xdf,0x9c,0x9a,0xf6,0xf7,0xff, +0x26,0x72,0x8a,0xb6,0x50,0xfe,0x87,0x9c,0xfe,0x2e,0x72,0xf2,0xf3,0x3c,0x96,0xff,0x6f,0x19,0xcf,0x7b, +0xf5,0xf6,0x2a,0x3f,0xed,0x49,0xf3,0x8c,0xc6,0xea,0xe2,0x95,0x4c,0x55,0xa8,0x59,0xc4,0x88,0x71,0xff, +0x06,0xab,0xad,0x29,0x89,0x55,0xe7,0x6e,0x22,0x80,0xe4,0x26,0x92,0x13,0xb5,0xf6,0x7c,0xc0,0x7e,0x16, +0xc7,0x98,0xe0,0x22,0x4f,0xd9,0x28,0x36,0x0b,0x51,0x59,0xf5,0xcd,0x9d,0x6c,0xe9,0x6e,0x98,0x5e,0x12, +0x94,0x1b,0x95,0x67,0x00,0x75,0xa3,0xe7,0x13,0x1f,0xb2,0xd9,0x6c,0x10,0x8d,0x6b,0xc0,0x5e,0xc2,0xd6, +0x5e,0x17,0x0c,0x46,0xab,0x72,0xc3,0x47,0xc6,0xac,0x6e,0xf1,0x37,0x2a,0xb9,0xe0,0x8f,0x86,0x74,0x34, +0x14,0xd7,0x1f,0x7f,0xd7,0x21,0x49,0xe4,0xf1,0x10,0xff,0x2b,0x27,0xa0,0x4d,0x56,0x52,0x8e,0x3b,0x74, +0x91,0xbf,0x55,0xd6,0x9a,0xc9,0x4a,0x6b,0x8e,0xcd,0x63,0x23,0x93,0x8e,0xd5,0xa1,0xc1,0xba,0x34,0x3a, +0x17,0xd5,0xa4,0xf6,0x59,0x17,0xf7,0x0e,0x4c,0x47,0x63,0x5e,0x89,0x0d,0x69,0xe1,0x00,0x16,0xf3,0x5c, +0x5e,0xfc,0x0a,0x30,0x87,0xf3,0xff,0xae,0xc0,0xc4,0xa3,0x72,0x73,0xf1,0x78,0x91,0xed,0x51,0xd6,0x86, +0x70,0xe4,0x0c,0xe3,0x54,0xb6,0x1f,0x5e,0x0d,0xc9,0x31,0x54,0x5e,0x17,0x96,0xc5,0x4a,0x25,0xcb,0xff, +0x9f,0xff,0x77,0x03,0xec,0x8d,0x4d,0xad,0x3b,0xdf,0x20,0x09,0xbd,0xe6,0x40,0x4a,0x79,0x39,0xcd,0x37, +0x04,0x32,0x12,0xfb,0x2f,0x10,0xaa,0x62,0xd8,0xeb,0x42,0xb7,0x96,0x25,0x7f,0xfe,0xdd,0xe0,0xca,0x73, +0xc1,0xb4,0xcd,0x31,0xde,0xcd,0x73,0x46,0xf2,0x91,0xd3,0x0b,0x36,0x8e,0x2e,0xc8,0x9d,0xe4,0xb6,0xba, +0x9c,0xa2,0x37,0x1c,0xdd,0xea,0xa0,0x47,0xcc,0xfd,0xc0,0x68,0x9f,0xc2,0xda,0x9f,0xd3,0x9b,0x99,0xb3, +0xe1,0xb4,0xd5,0x11,0x4a,0x07,0x4f,0x8b,0xfc,0x5b,0xb4,0x0e,0x72,0x8c,0x90,0x64,0x87,0xd0,0x96,0xdf, +0x7c,0xea,0x8f,0xfc,0xac,0x24,0xa2,0xe9,0x0c,0xf5,0x11,0x37,0x3d,0xf8,0xe8,0xd0,0x9c,0x5a,0xc1,0x3d, +0x70,0xb2,0xe7,0x9d,0x0f,0x86,0x75,0x61,0x6a,0x58,0x2f,0x6e,0x24,0x66,0x00,0x04,0xe3,0x7d,0xe2,0x16, +0x4f,0x7c,0x78,0x6f,0xf4,0xec,0xc9,0xde,0x68,0xa9,0xee,0xff,0xda,0x71,0x9f,0x92,0x70,0xc2,0xf5,0x46, +0x8e,0x79,0x00,0x5f,0x33,0xf2,0xf1,0x8a,0x91,0xfb,0xe9,0xcf,0x8b,0xd1,0x5f,0xef,0x36,0x20,0x43,0x91, +0xe6,0xaa,0xfa,0xa0,0x80,0x08,0xc3,0x24,0xf9,0x64,0x0f,0x6f,0x09,0x7a,0x8b,0x99,0xca,0x7a,0x60,0xf2, +0xd4,0x69,0xaa,0x7e,0x52,0xcd,0xd1,0x05,0xdc,0x5d,0xdb,0x8e,0xb7,0x82,0x82,0x7c,0x82,0x07,0xff,0xa4, +0x8b,0xe9,0x52,0xba,0x9c,0xae,0xa4,0xab,0xe9,0xed,0x25,0xeb,0xbf,0x14,0x18,0x63,0x31,0x0a,0xaa,0x05, +0xc2,0x4d,0x4f,0x4d,0x03,0x66,0x9e,0x96,0x8a,0xe4,0xe9,0x7a,0xaa,0xda,0xf0,0x48,0x74,0xc3,0xe0,0xc2, +0x6a,0x10,0x56,0xd7,0xd6,0x66,0xe1,0x70,0xc4,0x8d,0x00,0xce,0x8e,0xcd,0xbc,0x59,0xf2,0x20,0x18,0x5a, +0xf0,0x86,0xa5,0xf5,0x9d,0xe1,0x6b,0x71,0x27,0x37,0xdb,0x7d,0x34,0x67,0x8a,0xc8,0xc5,0x1e,0xc5,0x9a, +0x88,0xb9,0x4c,0x81,0xb8,0xd4,0x11,0xb4,0x16,0xd1,0xf6,0x37,0xda,0x40,0x42,0x1a,0x5d,0x19,0xfc,0x1c, +0x24,0x50,0xda,0x47,0x54,0xac,0x1a,0xbd,0x13,0x0c,0xed,0xfe,0xd8,0x18,0x35,0x76,0x6d,0xd8,0x92,0xc4, +0xb9,0xd7,0x24,0x35,0x60,0x6c,0xe0,0xda,0x1a,0xb1,0xda,0x15,0x58,0xaf,0x34,0x1f,0xbf,0x37,0x68,0xea, +0x94,0x48,0x08,0xcd,0x89,0xa4,0x43,0xaf,0xa7,0x10,0x8a,0x5f,0xc3,0xeb,0x84,0x30,0xec,0x00,0xce,0xc8, +0xd1,0xdb,0x37,0x42,0x59,0xd3,0xcd,0x73,0xb7,0x11,0xda,0x56,0x73,0x60,0x86,0xa7,0xf5,0x64,0x47,0xa5, +0xe2,0x63,0xc1,0x1b,0xc1,0x9a,0x40,0xe3,0x8d,0x60,0x5d,0x0c,0xc1,0x9a,0x60,0xf2,0xb7,0xc8,0x6d,0x9f, +0x4f,0x95,0x17,0xd6,0x08,0xb7,0xe0,0xee,0xd5,0x5f,0xdd,0xcd,0x63,0xb5,0xc8,0x1c,0xba,0xf4,0x1a,0x83, +0x98,0x16,0x88,0x0b,0x4b,0x04,0x7e,0xa4,0xbf,0x18,0xdb,0xa1,0xdb,0x54,0x9b,0xfa,0x8c,0x23,0xe6,0x27, +0x25,0x0f,0x2e,0x8e,0xce,0x7d,0x5f,0x87,0x13,0xcd,0x74,0xbf,0xa7,0xde,0xca,0xbf,0xdc,0xc4,0x9c,0xa7, +0x78,0x7b,0x5f,0xc0,0x21,0xb8,0x92,0xbe,0x84,0xc0,0x2e,0x32,0x75,0x6c,0x28,0x8a,0xf1,0x34,0xe8,0x75, +0x81,0xcb,0x59,0x0f,0x2f,0xb4,0x8c,0xf9,0x74,0x49,0x42,0x36,0x3c,0x6a,0x3f,0x20,0xb7,0xba,0xc7,0x9c, +0x98,0xe0,0x5c,0x2a,0x54,0x6e,0x8d,0x9c,0x4f,0xeb,0x83,0xed,0x62,0xaa,0xd3,0x2d,0xe9,0x5f,0x86,0x1b, +0x69,0x29,0x0a,0x70,0x6d,0xd3,0xf8,0x0f,0x07,0x2a,0xea,0xf6,0xb2,0x1c,0x4b,0x67,0xa6,0x35,0x26,0x46, +0xd7,0x12,0x00,0x17,0xc5,0x62,0x67,0xbf,0x15,0x22,0x44,0xd5,0x4e,0xda,0xa9,0x00,0x54,0xd6,0x5f,0xa5, +0x02,0x64,0xc8,0x75,0xbd,0x21,0xbd,0x65,0x29,0xcf,0xf4,0x02,0x4c,0x49,0x96,0xf1,0x15,0xae,0xf3,0x88, +0x1c,0xdc,0x1b,0x4d,0x99,0x64,0xc3,0x1c,0x5a,0xe6,0x74,0xb2,0xd1,0x74,0x4b,0xa1,0xe9,0x62,0xcf,0x07, +0xd8,0x4e,0xd4,0x74,0x45,0x9d,0x92,0x33,0xa2,0x08,0x1b,0xf6,0x5f,0x36,0x57,0xc9,0x5e,0x18,0xfd,0x5f, +0xa0,0x7a,0x77,0xb2,0x47,0xc6,0x2c,0x8a,0xee,0xbb,0xe4,0xa0,0x6d,0x2c,0xc1,0x43,0x2d,0xd5,0xb2,0x37, +0x58,0xd7,0xbf,0x3a,0xf3,0xf3,0x50,0xda,0xd3,0x5f,0x9c,0xfc,0xb9,0x81,0x69,0x82,0xe2,0x97,0x3c,0xbd, +0x65,0x67,0x95,0xa2,0x26,0x64,0x5f,0x5e,0xbe,0xf4,0x43,0xa5,0xe3,0x39,0x80,0x5f,0xa2,0xf1,0x1c,0x50, +0xbf,0x51,0xc0,0xbf,0x42,0x8c,0xaf,0x50,0x86,0x25,0x3e,0xea,0x23,0x45,0xcc,0x81,0xe6,0xbd,0x8a,0x66, +0x1f,0x91,0xab,0x6c,0x99,0xb8,0x17,0xd3,0x60,0xfd,0x16,0x00,0xb9,0xad,0xff,0x06,0x18,0x31,0xa3,0xd2, +0x03,0x8f,0xdb,0xd9,0xab,0xe0,0xb3,0x6a,0x60,0x01,0x53,0x76,0x09,0x54,0xdd,0xcc,0x1a,0xff,0xad,0x68, +0x4e,0xcc,0x82,0x72,0x45,0x8d,0x9b,0x68,0x28,0x12,0xc9,0x54,0x7b,0x05,0xb5,0xf9,0xd2,0x8a,0xfc,0x37, +0x27,0x37,0xdf,0x5c,0x7e,0x09,0x52,0x6f,0x47,0x77,0x24,0x1f,0xd8,0xd5,0x41,0xf3,0xb7,0x80,0xb6,0x85, +0x8d,0x6f,0x96,0x1f,0x8c,0xec,0x99,0xcf,0xfb,0x93,0x0c,0x10,0x93,0x39,0x75,0xea,0x89,0x9e,0x6e,0xf6, +0x1f,0x38,0x04,0x69,0x32,0xf1,0xdf,0x92,0x13,0x36,0xc2,0xe2,0x15,0xad,0x27,0x0b,0x25,0x53,0x00,0x4b, +0xc4,0x3e,0x65,0xcd,0x82,0xbd,0x89,0x79,0x92,0x03,0xb3,0x41,0x08,0x7b,0x29,0xe3,0x13,0x1f,0xfc,0x01, +0x4b,0xac,0x8e,0x57,0x20,0xec,0xf6,0xf1,0xe5,0x9c,0x0a,0x23,0xee,0xb0,0x7b,0xfb,0xbf,0x0b,0xe2,0x02, +0xb3,0xa3,0x59,0xe9,0x7f,0xcf,0xe4,0x58,0xc6,0xfb,0x3f,0x7e,0x05,0xd1,0x6c,0x80,0x41,0x94,0xb1,0x95, +0xba,0xb3,0x13,0x83,0xeb,0x0d,0x10,0x3c,0xfe,0xad,0x5c,0xef,0x37,0x32,0x3b,0xd1,0x4d,0xcc,0xf7,0xf9, +0x5c,0xa6,0x47,0xe1,0x53,0xac,0x54,0x37,0x5a,0xc5,0x9b,0xf3,0x37,0x6d,0x60,0xa1,0xd5,0x4c,0x14,0x16, +0x73,0x6e,0x60,0x24,0x3e,0x2d,0x49,0x3f,0xf0,0x39,0xd1,0x6b,0xa1,0xd9,0x57,0xf1,0x9e,0x67,0x28,0xd9, +0x33,0x95,0x05,0xe8,0xb8,0x24,0xaf,0xf8,0x87,0x77,0x83,0xa9,0x41,0x2c,0x8f,0xa4,0x9a,0x76,0x52,0xcf, +0x09,0xdc,0x78,0xb2,0x1d,0x4b,0x03,0xfb,0x63,0x2f,0x61,0xf6,0xbe,0xa3,0x21,0x52,0xaf,0x63,0x03,0xe6, +0x80,0x29,0xdc,0xef,0xdf,0xc7,0x7c,0xc8,0xaa,0x8f,0x78,0x6e,0xdc,0xfe,0xe8,0x7f,0xac,0xab,0x59,0xc5, +0xec,0x4f,0xc7,0x30,0xa9,0x8f,0x0e,0x74,0xf3,0x2e,0x9f,0xda,0xf5,0x7a,0x4d,0x3d,0x6b,0x83,0xe4,0x3b, +0xaf,0x48,0xca,0x19,0x59,0xe6,0x9c,0x84,0x98,0x76,0x2c,0xcb,0xb4,0x92,0x89,0xef,0x97,0x53,0x4c,0x3f, +0xc9,0x82,0x37,0x30,0xa8,0x76,0x0e,0x12,0x04,0xca,0x90,0xc0,0x4e,0x59,0xe2,0x35,0x13,0xa9,0x3d,0x4b, +0x75,0x88,0x6e,0x0c,0xcd,0xbe,0xec,0x92,0xbf,0x93,0x09,0x4c,0xf4,0x4e,0x2e,0x12,0x4e,0xbc,0xe3,0xc3, +0xa5,0xf5,0x3f,0xd2,0x7f,0x76,0x31,0xba,0x3a,0xbd,0x04,0x0c,0xfc,0x93,0xc4,0x3e,0x3a,0x75,0xe7,0xe7, +0x4f,0x43,0xdd,0x9b,0xc9,0x96,0x64,0xd4,0x9d,0x2c,0x0d,0x8a,0xed,0xe8,0x2a,0x0e,0x22,0x99,0xa0,0x80, +0x85,0xc1,0x18,0x59,0xa4,0x95,0x3a,0xd4,0xc9,0x62,0xcc,0x74,0x56,0x9e,0xe0,0x25,0x09,0xad,0x91,0xa6, +0x2b,0x49,0x23,0x95,0x9d,0xc8,0x78,0x76,0x09,0x23,0x12,0xb3,0x18,0x16,0x3c,0x53,0xf9,0x97,0x17,0xb7, +0x43,0x0b,0xc1,0x83,0xdd,0x38,0xf5,0x77,0xef,0x10,0xee,0x74,0x6b,0x22,0xa1,0x19,0x12,0x3c,0xa9,0x59, +0xfa,0x98,0x36,0xea,0x43,0x35,0x8b,0x53,0x83,0xe2,0x0c,0x04,0x09,0xde,0x06,0x4c,0xba,0x6e,0xbc,0x7f, +0xff,0x0e,0x4a,0x68,0xf6,0x1d,0x99,0x30,0x94,0x7a,0xff,0x3e,0x99,0x90,0x2d,0x0b,0xf4,0x9a,0x3a,0x7c, +0xfe,0xf9,0x33,0x0f,0xff,0xc0,0xa4,0xb8,0xd0,0x76,0xf1,0xea,0xbc,0x7f,0xef,0x7c,0xc8,0xc3,0x5f,0x40, +0xa7,0xd8,0xa9,0x30,0x36,0x8d,0x02,0x83,0x41,0x5c,0xcd,0x1a,0x30,0x11,0xdc,0xc7,0xc4,0x71,0xf1,0xdf, +0x59,0xc7,0x3c,0xc1,0x83,0x1f,0x2d,0xd9,0x56,0x93,0x29,0xec,0xc3,0xff,0xc6,0x6b,0xcd,0xc4,0xd6,0xd2, +0x86,0xdb,0x1e,0x19,0xee,0x3e,0xc7,0x89,0x93,0xfa,0x08,0x2f,0x86,0x96,0x3a,0x81,0x62,0x02,0xa6,0x2c, +0x5e,0xfe,0xdd,0x3b,0x40,0x83,0xac,0xeb,0xf8,0x2e,0xad,0xa6,0x70,0xce,0x2f,0xa9,0x5d,0x87,0x0c,0x04, +0x3d,0x9e,0x91,0xd5,0xbd,0xc1,0xe3,0xc8,0x58,0x9d,0x04,0x62,0xdd,0x18,0x7a,0xb4,0xe2,0x2c,0xaf,0x6b, +0xab,0xb4,0x63,0x07,0xba,0xfd,0x90,0x29,0xb0,0x56,0x1e,0x60,0xea,0xaa,0xed,0xb0,0x81,0xd3,0x14,0x19, +0x58,0x04,0x66,0xb8,0x9b,0x74,0xea,0xbe,0x77,0xa9,0xf4,0xa6,0x1d,0xbc,0x7f,0x5f,0x80,0x11,0xab,0xee, +0xec,0x5e,0x52,0x02,0x24,0x65,0x8a,0x97,0x01,0xac,0x9c,0xbd,0xa4,0x5a,0x57,0xbf,0x3a,0xdf,0x00,0xdd, +0x58,0xcf,0xab,0xb0,0xe7,0xae,0x14,0xd5,0xab,0x68,0x7b,0xc4,0xf6,0xfc,0xb2,0xe7,0xe1,0x01,0x53,0xed, +0x25,0xd5,0xec,0x58,0x06,0x4d,0x3b,0xf9,0x43,0x4d,0xfd,0xfc,0xf9,0xf5,0x9b,0x88,0x04,0x40,0x9a,0xf3, +0xd5,0xf8,0x56,0x7f,0x97,0x7f,0x49,0xa5,0x1d,0xaf,0xbd,0xa9,0x08,0x61,0xef,0xb5,0x8e,0xaf,0xe9,0x1a, +0x17,0xde,0xf6,0x29,0xfe,0x11,0xa3,0x38,0x04,0x6d,0xcf,0xb1,0x16,0xcf,0x40,0x4b,0x7e,0x3a,0xd0,0x60, +0x0e,0x13,0xcb,0x1c,0x6b,0xb6,0x9a,0xfa,0xa8,0x31,0x8c,0xa7,0xb2,0x78,0x13,0x02,0x80,0x3a,0x3b,0x90, +0x35,0x1d,0xd6,0xcf,0x6e,0x64,0x3d,0x0c,0xe5,0xf6,0x2a,0x11,0x62,0x03,0x02,0x91,0xf1,0x86,0x8e,0xe4, +0xcc,0xd4,0x14,0x29,0x9f,0xfe,0xaa,0x7e,0xcb,0x82,0x3c,0xeb,0xab,0x49,0x0b,0x20,0xda,0x27,0x13,0x86, +0xc1,0x1a,0xa1,0x52,0xa9,0x17,0x6f,0xe4,0x83,0x24,0x94,0xe0,0x4b,0xb8,0x33,0x83,0x25,0x7d,0x82,0x97, +0x7f,0x1b,0x80,0xdc,0x44,0xfb,0xfc,0xb4,0x45,0x73,0x7e,0x9e,0x20,0x27,0x56,0x12,0xe9,0x41,0x2a,0x1d, +0x53,0x16,0x79,0x35,0xf9,0x3e,0xc4,0x02,0xb2,0xb2,0x10,0x97,0xc7,0x24,0x89,0x30,0xd3,0x6c,0x64,0xa9, +0x32,0xac,0x61,0xa4,0x20,0xf6,0x73,0x6b,0x92,0x9d,0x6a,0xca,0xd6,0x96,0x57,0x58,0xf1,0x00,0x9f,0x70, +0xac,0xa9,0x8a,0x6b,0x5c,0x85,0xc5,0x4d,0x03,0x1b,0x90,0x10,0x70,0xf9,0xe3,0x1d,0x63,0xe4,0xcb,0x47, +0xfc,0xb5,0x8b,0x6b,0x60,0x4b,0xdd,0x4a,0x24,0x3e,0x6e,0xa9,0xbb,0xa7,0x8c,0x7c,0x01,0xca,0x78,0xc5, +0x46,0x96,0xa4,0xd9,0x85,0xa7,0x5d,0x91,0x01,0x8c,0xf8,0x92,0x45,0x84,0x59,0x7b,0xc0,0xc5,0x29,0x80, +0xea,0x11,0x14,0x9a,0x82,0xaf,0x16,0x48,0x2c,0xd4,0xa5,0x12,0x5b,0x4e,0x96,0xdd,0xa6,0x95,0x3c,0x52, +0xd3,0x89,0xcc,0x3f,0xde,0x27,0x52,0x7e,0xbe,0x00,0x1c,0x0d,0x96,0x84,0xea,0x34,0x1c,0x58,0x8c,0xbd, +0xa9,0x83,0x08,0x49,0xf3,0x95,0xe9,0x32,0x27,0x24,0x3c,0xa0,0x12,0xa3,0x4e,0x98,0x26,0x45,0x97,0x96, +0x7a,0x7e,0xb9,0x52,0xb3,0xb6,0xea,0xb0,0xd1,0xbd,0xa8,0xe8,0xc4,0x34,0xea,0x74,0x6c,0x9c,0xac,0x0d, +0x6f,0x1a,0xc3,0x00,0xe5,0xa5,0xcd,0x7a,0x21,0x2d,0xd7,0x8b,0xf9,0xb4,0x5d,0xb7,0x3e,0xba,0x74,0xee, +0x92,0xb2,0x95,0xed,0x4f,0x2d,0xc0,0xcd,0x6e,0xf8,0x13,0x20,0xa5,0x6f,0xdb,0xa4,0xbd,0x44,0x22,0xf5, +0x92,0x9e,0xd6,0x6d,0x98,0x8b,0x8e,0xe0,0x30,0xbe,0x96,0xbe,0xfd,0xfc,0x99,0xa4,0x25,0xce,0x08,0x97, +0x85,0x85,0xf9,0x31,0x91,0xd8,0x4d,0x4c,0x1e,0x13,0xa9,0x74,0xbf,0x1e,0xfc,0x06,0x08,0x83,0x2f,0x80, +0x2d,0xfd,0xfd,0xfb,0xad,0x29,0x2c,0xe1,0x7f,0x20,0xca,0xd5,0x7e,0xd2,0xeb,0x25,0x95,0x42,0xa8,0xf7, +0xdf,0xbf,0xef,0x43,0xeb,0x58,0x32,0xf5,0xac,0xd7,0xf5,0x9f,0x3f,0xf1,0x11,0x40,0x68,0x90,0x95,0xda, +0xaf,0x6f,0x4d,0x7f,0xfe,0x2c,0xec,0x29,0xa6,0x64,0xd6,0x4d,0x68,0x35,0x5b,0x49,0xa4,0xfb,0xb9,0xba, +0x89,0x84,0x46,0x72,0x2b,0x93,0x01,0xf7,0xb7,0xf4,0xd4,0xde,0x1c,0x24,0x90,0x9a,0x34,0xa1,0xa9,0xa4, +0x89,0x63,0xcf,0x4d,0x19,0xeb,0x30,0xdf,0xbf,0xcf,0x64,0xe4,0xd4,0x0b,0x07,0x1f,0x10,0x10,0xb4,0xdb, +0xff,0xf9,0x13,0xdb,0xce,0xa7,0xb5,0xba,0xf1,0xb5,0xf0,0xed,0x63,0x7f,0x2b,0x89,0xff,0x6e,0x15,0x52, +0xff,0x69,0x7c,0x2d,0x7e,0xdb,0xdd,0xc2,0xbf,0xd3,0x16,0x14,0xb6,0xb2,0x98,0xbd,0xa3,0xae,0xa7,0xad, +0x2c,0xb9,0x8b,0xa9,0xde,0x87,0x5f,0x20,0x0c,0xeb,0x5a,0x2a,0x95,0xd6,0x3c,0x64,0xcc,0x5c,0x1e,0x44, +0x48,0x00,0x14,0x1c,0xd5,0x6a,0x33,0xf1,0x9e,0xb6,0xea,0x9e,0x64,0x81,0x2e,0x3f,0xab,0x5f,0xad,0x6f, +0x1c,0xa1,0xda,0x47,0x0d,0x19,0xab,0x91,0x45,0xe5,0xc6,0x2f,0x69,0x03,0xf2,0xd9,0xc2,0x1e,0xeb,0x0c, +0x88,0x80,0x28,0x45,0xb3,0x27,0x68,0xe0,0x03,0xef,0x8a,0x13,0xc9,0x0e,0x10,0x9f,0x01,0x3c,0x06,0x97, +0x8b,0x06,0xb3,0xd1,0xb8,0x11,0x90,0x4a,0x93,0x41,0xd4,0xb5,0xb4,0x26,0x2c,0x8c,0x85,0xc7,0x81,0x89, +0x8a,0x00,0x22,0x49,0xab,0x03,0x26,0xcc,0x7a,0x1e,0x08,0x8b,0x4b,0xed,0x3d,0xf3,0x4f,0x79,0xcf,0xdc, +0xda,0x4a,0xc1,0xb4,0xbe,0x9a,0xdf,0x08,0x64,0x16,0xa8,0x5c,0x25,0x8d,0x3a,0xfb,0x9d,0x65,0x63,0x4b, +0x3b,0x1f,0x93,0xee,0x00,0x10,0xf6,0x1a,0x54,0xa8,0x5f,0x90,0x25,0x92,0xb4,0x84,0x29,0x80,0x5a,0x02, +0x6b,0x39,0x8d,0x9f,0x81,0xd8,0x02,0xad,0xd4,0x81,0x26,0x61,0x22,0xd8,0x46,0xe0,0xcb,0xfb,0xf7,0x7f, +0xe1,0x0a,0xe3,0x0d,0xcf,0x10,0x46,0x20,0x09,0x49,0x8f,0xef,0x84,0x1e,0xe9,0x9b,0xf4,0x05,0x5d,0x5f, +0x42,0xc7,0xb0,0xce,0x80,0x16,0x71,0xc2,0x30,0x47,0x77,0x62,0x38,0x96,0x77,0x75,0xac,0x09,0x0d,0xe0, +0x1c,0x03,0xe3,0xc1,0x2f,0x51,0x82,0x68,0x4c,0xe1,0x47,0x60,0xe7,0x2e,0xd8,0x7a,0x94,0x1e,0x47,0x20, +0xc0,0x10,0x6b,0x37,0x17,0xd7,0xf2,0x10,0x69,0xe3,0x63,0xf4,0xeb,0x24,0x2a,0x38,0xff,0x99,0x80,0x89, +0x45,0xb6,0xf4,0x03,0x95,0x4c,0x9a,0x25,0xc9,0xb4,0x1a,0xba,0xfe,0x31,0xfc,0xca,0x6d,0x01,0x90,0xe9, +0xf2,0x3a,0x78,0x07,0x5a,0x12,0xd5,0x86,0x50,0xd8,0x93,0x3b,0x7d,0x92,0x20,0x24,0x50,0xc0,0x08,0x7c, +0xe6,0x31,0x40,0x15,0x40,0x0c,0x96,0x47,0x0c,0xc6,0x9f,0xd6,0x9e,0x01,0x30,0x63,0xb0,0x55,0x41,0xa0, +0xa6,0x13,0x43,0xdd,0xec,0xc9,0x7a,0x67,0x26,0xeb,0x89,0xf4,0x3b,0xe8,0x87,0x61,0xdc,0x09,0x7e,0x14, +0xc5,0x7f,0x8f,0xb3,0x33,0x20,0x49,0xb7,0x33,0x33,0x2d,0xa7,0xed,0xf4,0x34,0xad,0xa7,0xfb,0xe9,0x81, +0xab,0xb1,0xf2,0x85,0xb5,0x6f,0xc9,0x43,0xb2,0x34,0x52,0xe9,0x09,0xd2,0xa9,0x02,0x43,0x1b,0x79,0x43, +0x53,0xfe,0x84,0xff,0xc3,0xd0,0x80,0xd5,0x98,0x40,0xaa,0x0a,0x10,0x32,0x55,0x18,0x4d,0x7c,0xe5,0x99, +0x02,0xae,0x12,0x6a,0xa6,0x52,0x2e,0x1c,0x26,0x69,0xd3,0x53,0xc2,0x00,0xdf,0xbb,0x66,0x6a,0x8f,0x70, +0x64,0xa8,0x7a,0xc9,0xa4,0x0c,0x94,0x27,0x23,0x95,0xeb,0xf2,0xcf,0x9f,0x03,0xdf,0xe2,0x0d,0x29,0xd7, +0x60,0x03,0x21,0x15,0xdb,0xf5,0xe4,0x2d,0x63,0x87,0x26,0x2a,0x24,0x89,0x04,0x10,0xf6,0xb7,0x14,0x30, +0x9f,0x80,0x20,0x99,0xd6,0xef,0xd5,0xaf,0x36,0xac,0x86,0x7b,0x35,0xfb,0x17,0x4b,0x40,0x96,0x96,0xb3, +0x1a,0xd8,0xb8,0xd6,0xe1,0xf5,0xe9,0x49,0x7d,0x8a,0x0c,0x0b,0x86,0x3b,0x72,0xc6,0x3a,0xc9,0xdd,0x4e, +0xd4,0x32,0x33,0xb5,0x35,0x45,0xde,0xd5,0x87,0xef,0xf9,0x6f,0x7b,0xfd,0x4c,0x66,0x2f,0x05,0xc3,0xcb, +0x82,0xe9,0xe6,0x90,0x91,0xed,0x09,0x33,0x94,0xb3,0x7d,0x7c,0x45,0x8e,0x27,0xa5,0x60,0x85,0x0f,0x40, +0xb9,0xb3,0x58,0x39,0xf8,0x88,0x46,0x00,0xd3,0x02,0x60,0x09,0x52,0x79,0x34,0xc9,0x4e,0xa6,0xf6,0xc8, +0x9d,0xde,0x35,0x14,0xc1,0xea,0x08,0x0a,0xb2,0x8c,0x06,0x81,0x5a,0x88,0x93,0x3d,0xb3,0x3e,0xf9,0x0a, +0x88,0xf8,0xb6,0x47,0xa4,0x29,0xd5,0x71,0x8c,0x06,0x6a,0xf1,0x49,0x13,0xc4,0x17,0x28,0x87,0x29,0xe0, +0x4e,0x1a,0x6d,0x5a,0x80,0xb3,0x4e,0xb8,0x1d,0xb4,0x85,0x49,0x33,0x92,0x66,0x80,0xab,0x9a,0x38,0xe4, +0x71,0xd2,0x0f,0x77,0x13,0xc5,0x2d,0xb3,0x63,0xd2,0x20,0x80,0x1e,0x93,0x32,0x08,0xe5,0x14,0x0e,0xad, +0x4f,0x46,0x22,0x7f,0xed,0x93,0x91,0x1c,0x70,0x1c,0x12,0xd4,0xc3,0xf2,0x48,0x00,0x1b,0x31,0xdc,0x21, +0xb0,0xd5,0x3b,0xf0,0xe8,0x73,0xee,0x8a,0x4b,0xd0,0x17,0xdd,0xb7,0xd7,0xde,0xdb,0x82,0xf7,0xb6,0x95, +0xa4,0x12,0x9e,0xf3,0x00,0x35,0x8b,0x9b,0x64,0x33,0x4e,0x0d,0x9e,0x86,0x26,0xa8,0x63,0x1d,0x8f,0xfe, +0x61,0x6a,0x84,0x89,0x00,0xed,0xef,0xf9,0x28,0x95,0x6b,0xf6,0x60,0xe7,0x05,0xb5,0x7d,0xe4,0x75,0x56, +0xdd,0x02,0x46,0x9a,0xe6,0x3a,0x03,0xc5,0x88,0x4d,0x8e,0x2b,0xa6,0xb0,0x79,0x9b,0x34,0xef,0x00,0x59, +0xa5,0x4d,0x81,0x7f,0x41,0x17,0xc8,0xf1,0x80,0xbb,0x02,0x04,0xc8,0x0f,0xed,0x23,0x88,0x0a,0x28,0x5b, +0x77,0x9b,0xda,0x65,0x1f,0x50,0x01,0x0b,0xa9,0x33,0x58,0xda,0x82,0xd2,0xbc,0x2c,0x7b,0x14,0x46,0x82, +0x92,0xab,0x5e,0x7f,0x07,0x78,0xae,0x5f,0xbb,0xe8,0x7d,0xa7,0xa5,0xf8,0x18,0xf8,0x60,0x50,0xf9,0x02, +0x91,0x0d,0x2b,0x0a,0x24,0x53,0x3d,0xc2,0xa8,0x00,0xba,0x4d,0x65,0xcd,0xc1,0x00,0x5e,0x01,0x85,0x52, +0xf5,0x96,0x98,0xc1,0xb2,0x35,0x24,0x64,0x61,0x83,0xfa,0xa2,0x65,0x87,0xa0,0x59,0x02,0xd5,0xe3,0x3f, +0x20,0x4f,0xe8,0x0f,0x24,0xa6,0x21,0xd1,0x38,0x53,0xa8,0xcb,0x12,0xeb,0x40,0x50,0x85,0x50,0x31,0x45, +0xc5,0x36,0x2b,0x2b,0x0a,0x6d,0xd2,0x01,0x4c,0x58,0xa8,0x89,0x09,0x5c,0xea,0xc1,0x67,0x3c,0x22,0xf3, +0x4c,0x38,0x18,0x3c,0x86,0xd4,0xa3,0x25,0x0b,0xa8,0x72,0x08,0xc6,0x9b,0x03,0x8a,0xba,0xb0,0xa4,0x40, +0xa9,0x4d,0x20,0xab,0x4d,0x26,0x3e,0x38,0x28,0xee,0x13,0x60,0x19,0xc1,0x2a,0xfd,0xf9,0x53,0xdd,0x15, +0xa4,0x48,0x57,0x34,0x3f,0x08,0x75,0xd6,0x93,0x54,0x22,0x05,0x75,0xcb,0x04,0x7e,0x04,0xae,0xb2,0x95, +0xc8,0x25,0xb6,0x68,0xd1,0xb4,0xd0,0xd0,0x99,0x67,0x17,0x69,0x0e,0xe5,0x39,0xb4,0x90,0xe7,0x6e,0xf8, +0xc8,0x3a,0x70,0x80,0x99,0xec,0x72,0xdd,0x21,0xd4,0x81,0xd8,0x68,0x5b,0x90,0x71,0x94,0x56,0x39,0x8b, +0x46,0x52,0x2d,0xd4,0xc5,0xf9,0x13,0x0f,0x09,0xf0,0xfe,0x91,0x6c,0x63,0x6a,0x64,0x6a,0xbe,0x9b,0x28, +0xff,0xe5,0x7e,0x5f,0x45,0x55,0x10,0x97,0x30,0x93,0x1c,0x64,0x41,0xeb,0x75,0x93,0x62,0xc1,0x06,0xd6, +0xaa,0xb0,0x83,0xa8,0x50,0xdf,0x50,0x74,0x15,0xf0,0x4d,0x3f,0xa1,0x95,0x87,0x94,0xad,0x21,0x65,0xeb, +0x64,0x6d,0x53,0xa1,0xa4,0x7f,0xd5,0xbe,0x05,0xe5,0x92,0x1f,0xad,0x30,0x5e,0x2c,0x04,0x72,0x28,0x85, +0xda,0xb6,0x6f,0x60,0x76,0xfd,0x4a,0x1c,0xd8,0x94,0x9a,0x2a,0xc0,0xc7,0x94,0xe4,0xf3,0x4b,0x1a,0x18, +0xe4,0x15,0x1f,0xe8,0x34,0x25,0xda,0x51,0xdf,0x05,0x88,0xb0,0xc9,0x87,0x5d,0x08,0x7b,0x09,0xe2,0xde, +0x62,0x7a,0xd0,0x0d,0x37,0x53,0x28,0x3a,0x3e,0x02,0x3b,0xc5,0x58,0x20,0x55,0x01,0x0c,0xb3,0x5f,0xbb, +0xac,0x02,0x55,0x63,0x12,0x3c,0x57,0x2c,0x79,0x06,0x92,0x76,0xb2,0x4c,0x26,0xdc,0x92,0xcd,0x55,0xd5, +0xf7,0x28,0xd0,0x6b,0x83,0x70,0x15,0x22,0x53,0x9d,0xba,0xa9,0xb2,0x15,0x03,0xb2,0xd2,0x49,0xed,0x85, +0x05,0x6c,0x1e,0xe4,0xa8,0xeb,0x9c,0x51,0xea,0x93,0x4c,0x01,0xe4,0xa9,0x03,0x24,0x9a,0x1e,0xd6,0xfd, +0x86,0xe9,0x88,0x68,0xf0,0xc3,0x9f,0x3f,0x27,0x1f,0x0a,0x30,0xbc,0x20,0x4b,0x18,0xbd,0x7f,0xff,0x4e, +0x61,0x73,0x69,0xe9,0xa0,0x7c,0xbd,0x7f,0x6f,0x39,0x74,0xce,0xa3,0x94,0xbb,0xea,0x03,0x6b,0x90,0x59, +0x64,0x20,0xab,0xb3,0xea,0x0f,0x78,0xda,0x1b,0x02,0x56,0xb0,0xf7,0xfa,0x88,0x79,0x12,0x70,0x55,0x1a, +0x20,0x98,0x51,0xe6,0x25,0x41,0x73,0x4b,0x37,0x40,0x80,0xd0,0xe9,0xbd,0x90,0x01,0x4d,0x08,0x81,0xf5, +0xd0,0xe7,0x00,0xf5,0x02,0x02,0xe3,0x5d,0x21,0x0d,0xea,0x37,0x92,0x9c,0x29,0xae,0x4a,0xc2,0x73,0x04, +0x49,0xc8,0xe6,0x4f,0x5a,0x02,0xe1,0x01,0xa2,0x5d,0x63,0x72,0xde,0x46,0x20,0x8c,0xe5,0x49,0x72,0x0c, +0xdd,0x7a,0x62,0xa6,0x8b,0xc4,0xc2,0xab,0xed,0x0d,0xfe,0x9c,0xec,0x0d,0x80,0xf0,0x80,0x94,0xd3,0x03, +0xc0,0x96,0x02,0x0d,0x51,0x41,0x86,0x70,0x48,0xea,0xe9,0x77,0x79,0x74,0x24,0xa6,0xa7,0x44,0x0e,0x52, +0x51,0x6c,0xa7,0xc7,0xf0,0xc1,0x6d,0x11,0x26,0x66,0x31,0x73,0xff,0xeb,0xe0,0x1b,0xe0,0x66,0x40,0x26, +0x37,0x65,0x62,0xcc,0xfe,0xca,0x3b,0xcb,0x14,0x82,0x73,0x64,0x03,0xb4,0xd3,0x67,0x29,0xc4,0x27,0x8c, +0x66,0xca,0x46,0x63,0x63,0x53,0x5c,0xea,0xe9,0xa2,0xd4,0x7b,0xe7,0x2d,0x48,0xdd,0xaf,0x9e,0x91,0x21, +0xba,0x02,0xb8,0x9f,0xd6,0x71,0x9d,0xe8,0x59,0xdb,0xea,0xa3,0xc2,0xf8,0x97,0x0a,0x85,0x6e,0x2c,0x9d, +0x94,0xe2,0x0f,0xf4,0x33,0xc8,0x0b,0xec,0xc3,0x53,0x05,0x5c,0xc3,0xd9,0x24,0x26,0x66,0xba,0x9f,0x72, +0x6d,0x32,0x81,0xb9,0xfc,0xe0,0x96,0x39,0xd7,0xff,0xd0,0x00,0x71,0xfc,0x9e,0x28,0xb0,0xe5,0xd1,0x20, +0xd9,0xa3,0x2c,0x11,0x64,0x1e,0xd5,0xc6,0x89,0xde,0x0e,0xe6,0xe2,0x3b,0x34,0x12,0x38,0x03,0xfa,0xf9, +0x93,0xc0,0x5d,0x95,0x0d,0xb2,0xcc,0xc7,0xc4,0x86,0xb2,0x04,0x6b,0x09,0x8d,0x16,0xff,0x24,0xad,0xa0, +0xed,0x96,0x42,0x25,0x62,0x8c,0x46,0x83,0x8b,0x1e,0x5f,0x13,0x3e,0x83,0x0b,0xda,0x8f,0xb0,0x0b,0x4e, +0x7c,0x0e,0x07,0xce,0x33,0xeb,0xcc,0x8a,0xf5,0x2c,0x05,0x18,0xff,0x14,0x7d,0x17,0x69,0x83,0x88,0x40, +0x03,0xb9,0xfd,0x05,0xc9,0x73,0xe0,0x2c,0xc8,0xc2,0x06,0x83,0x0e,0x04,0x3c,0xd8,0xd8,0x00,0x43,0x98, +0xa8,0x4c,0xe7,0xc7,0x87,0x1e,0x34,0x3b,0xd1,0xe3,0x05,0xcd,0x88,0xd6,0x32,0x8c,0x1d,0xd7,0xe5,0x44, +0x7b,0x54,0xf5,0x53,0x10,0x99,0x9a,0x71,0x85,0x5b,0x5d,0x49,0x98,0xa4,0xcd,0xd6,0xa7,0x0c,0xbf,0x65, +0x87,0x7b,0x01,0x89,0x56,0x61,0x67,0xe7,0x9a,0x02,0x1c,0x41,0x83,0x5f,0x63,0xcd,0xb8,0x23,0x0f,0x26, +0x3e,0xc8,0x8f,0xf4,0xc1,0x7b,0x2f,0xbc,0xe5,0xf5,0xea,0x72,0x1a,0x27,0x33,0x67,0x25,0xe9,0x3b,0x4b, +0xac,0xa3,0xa5,0x85,0x5a,0xa0,0x4a,0x32,0x23,0x05,0xa7,0xf8,0x51,0xde,0x4a,0x24,0x76,0x65,0x0f,0x98, +0x87,0xa2,0x04,0x06,0xa9,0xed,0x44,0xf8,0x33,0x80,0xe1,0x7e,0x24,0x4d,0x30,0x11,0x42,0x3c,0x50,0x58, +0x34,0xc9,0x7f,0xd5,0x9d,0x54,0x8c,0xf6,0x20,0x70,0xf6,0x7d,0xe6,0xe4,0x27,0x89,0xda,0x46,0x8e,0xa7, +0xad,0x70,0xf2,0x74,0xea,0x84,0xcd,0x38,0xe6,0x0d,0xa8,0xa0,0x8c,0xd3,0x83,0x20,0xa6,0xae,0xb8,0x02, +0x7a,0x83,0x14,0xc7,0x15,0x48,0xa8,0x8a,0x63,0x63,0xf0,0x0e,0xe4,0xcf,0x16,0xe0,0x27,0xd0,0xac,0xd7, +0xf1,0xb9,0x27,0xb9,0xa9,0xbd,0x8f,0x54,0x60,0x83,0x69,0xe6,0x0a,0x6e,0x14,0x01,0x81,0x6f,0x75,0x1c, +0x2e,0xe8,0x12,0x3e,0x5f,0xe6,0x85,0x8f,0xf4,0xea,0xdc,0x15,0xe3,0xb8,0x64,0x6a,0x7d,0x3c,0x95,0x9d, +0x11,0x42,0x3f,0x09,0x22,0x14,0x4c,0x87,0x4c,0x12,0xdd,0xea,0xa0,0x54,0x24,0x2d,0xe2,0xfa,0x21,0x6e, +0x9e,0x5d,0xa1,0xc9,0x2b,0xd1,0x50,0x63,0x32,0x04,0x39,0x0d,0x31,0xa2,0x0d,0x60,0xa6,0x49,0xeb,0x63, +0xa2,0x67,0x5a,0xb8,0x81,0xb3,0x9b,0x60,0x57,0x07,0x81,0xde,0x53,0xde,0x4d,0x10,0xd4,0xa3,0x0c,0x74, +0x3e,0x16,0x76,0xd1,0xde,0x2e,0xc3,0xba,0xad,0x17,0x53,0x89,0x31,0xa1,0x45,0xee,0x25,0x90,0xb7,0xea, +0xae,0xaf,0xc8,0xd8,0x6a,0x12,0x2f,0x03,0x70,0x4d,0x74,0xb9,0x58,0x1f,0x93,0x6e,0x93,0xbc,0x74,0xc6, +0x2b,0x9d,0x98,0x80,0x98,0x47,0x49,0xe4,0xaf,0xc5,0xdb,0x7f,0x17,0x51,0x83,0x8d,0x94,0x56,0xd8,0x4a, +0x10,0x1a,0x4c,0xb0,0x8a,0xa0,0xca,0x8a,0x63,0x89,0x6c,0xdd,0x7b,0xfb,0x2e,0x3c,0xfa,0xe5,0xad,0x73, +0x1c,0x08,0xd4,0x7d,0x1a,0x60,0x15,0x75,0xca,0x10,0xcc,0x3a,0xe5,0x21,0x44,0x7a,0xb1,0x46,0x33,0x18, +0x3a,0x4c,0xad,0x57,0xb7,0xb3,0xc7,0xae,0xf6,0x84,0x43,0x41,0x59,0xa7,0xb9,0xed,0xf3,0xa5,0x6d,0xa6, +0x3e,0x9a,0xbb,0xb0,0x9e,0x65,0x18,0x25,0x70,0x01,0xb7,0xf8,0x95,0xaa,0x6b,0xa8,0xc4,0x26,0x81,0x7c, +0x4c,0xe2,0xfe,0x24,0x0c,0x03,0x1d,0xf0,0xe9,0x04,0x9e,0x31,0x4b,0x30,0xb5,0x1c,0xcd,0x68,0xcc,0x68, +0x8f,0xf7,0x98,0x6e,0x39,0x61,0xd2,0x77,0x5c,0xd2,0xff,0x86,0x63,0x26,0xde,0xd7,0x7d,0xdd,0x94,0x1d, +0x62,0xec,0xe6,0xd3,0xe6,0x16,0x23,0x1e,0x64,0x4c,0x51,0x44,0x42,0x88,0x6a,0x8b,0x90,0x9c,0x07,0x93, +0x23,0x91,0xe0,0x5c,0x93,0x6a,0x2e,0x1d,0xa1,0x4b,0xdd,0x31,0x51,0x92,0x81,0x3d,0xa9,0x39,0x42,0x31, +0xaf,0xf2,0x1d,0x70,0x85,0x99,0x83,0x7c,0x1e,0xd4,0x3d,0x12,0x53,0x4e,0x0c,0x11,0xdc,0xec,0xc1,0x7d, +0x39,0x00,0x4b,0xc3,0xd0,0xc6,0xe4,0xe8,0xea,0xbe,0x45,0x7d,0x30,0x91,0xef,0x93,0x77,0x20,0x78,0x50, +0xff,0xc3,0x50,0x50,0x13,0xb0,0x72,0x87,0xb2,0x76,0xf0,0x98,0xe5,0xf9,0xc6,0x53,0xec,0xd9,0xd1,0xfa, +0x0f,0x49,0xd1,0xb1,0xf1,0x0f,0x81,0x2f,0x89,0x0d,0x88,0xb6,0x87,0xc3,0x0c,0xa5,0x17,0x68,0x85,0xac, +0x6b,0xc3,0x9c,0x8b,0x6e,0xf4,0xa6,0x4f,0xeb,0xae,0xa3,0xe3,0xf2,0x79,0xa4,0x22,0xeb,0x06,0xcb,0x81, +0x2c,0x3d,0x87,0xad,0x29,0x0b,0xd6,0x94,0x05,0x6b,0x2a,0xe3,0xa4,0x8c,0x7a,0x13,0x9d,0x7c,0x69,0xed, +0x2b,0xa7,0xff,0x2d,0xe3,0x1b,0x88,0x4b,0x8f,0x8c,0xe1,0xd1,0x15,0x3f,0x08,0x23,0x2d,0x6b,0x4e,0xe4, +0xbe,0xe6,0x2c,0xea,0x1a,0x63,0xd3,0xaa,0xcf,0xcb,0xf9,0x57,0x94,0x7c,0x4e,0xde,0x64,0x49,0x5a,0x44, +0xd5,0xb2,0x89,0xf7,0xf7,0xeb,0xb7,0x14,0xca,0x24,0xb0,0x71,0xc5,0x2f,0x89,0xff,0x4c,0x10,0x9a,0x40, +0x4f,0xa2,0x16,0xf4,0x24,0x12,0x27,0x3b,0xca,0x71,0xaa,0xf5,0x18,0x69,0x14,0x5e,0x9c,0x2f,0x5a,0x5e, +0xff,0x4f,0x91,0x82,0xd4,0x55,0x5f,0x19,0x7f,0xd1,0x08,0x7f,0x4c,0x50,0xf8,0x90,0x27,0x50,0x6b,0x09, +0x9b,0x57,0xc0,0x74,0x20,0x1e,0x22,0x42,0xdf,0xa0,0xd4,0x7a,0x8e,0x7e,0xe2,0x4a,0x84,0xd9,0xce,0xb8, +0x7b,0x12,0xd6,0xd3,0xe0,0x11,0x53,0x1b,0x91,0x0d,0x51,0x72,0x59,0x36,0x17,0xa7,0x7f,0x91,0xa7,0x43, +0xd3,0x7c,0xb0,0x69,0x31,0x20,0x5c,0x6a,0x1f,0xcb,0xd9,0xa9,0x41,0xef,0xd5,0x46,0x46,0xe0,0x3e,0xd4, +0xd1,0x27,0x0f,0xe6,0x0b,0x1e,0x9a,0x46,0xd5,0x93,0xd8,0x32,0xee,0x43,0x5d,0xa0,0x04,0xaf,0xce,0xcf, +0x9f,0x36,0x50,0x00,0x5a,0xb9,0xfc,0xcd,0xd6,0x56,0x7a,0x92,0x95,0x49,0x78,0xba,0x48,0x3c,0x51,0xef, +0xbc,0x4a,0x99,0x0c,0xd2,0x25,0xf9,0xcd,0xc7,0xca,0xa0,0xff,0xf3,0xa7,0x38,0x08,0xec,0xeb,0x85,0x39, +0x71,0x2c,0xea,0x32,0x00,0xac,0x80,0xf6,0x85,0x14,0xb4,0x60,0x0c,0x04,0x15,0x61,0x78,0xcb,0xa5,0x2a, +0x7e,0xa2,0xae,0x79,0x87,0xc4,0xb9,0x13,0x37,0x33,0xb1,0xf4,0x93,0xc3,0x8f,0x78,0x82,0x43,0x85,0xb5, +0x4d,0x41,0x48,0xea,0xd1,0xdf,0xc0,0x26,0xb5,0x9f,0x3f,0xdf,0xcd,0x7e,0xfe,0x74,0x5d,0x91,0x33,0x68, +0x29,0x85,0x4c,0x40,0x33,0xa6,0xea,0xde,0x10,0x77,0xe4,0x14,0x74,0x4f,0xcf,0xde,0xbf,0xc7,0x4f,0x44, +0xcd,0xe1,0x3a,0x8c,0x95,0x42,0x87,0xc5,0xb4,0x4e,0x37,0x86,0x3b,0x38,0x81,0x73,0xe2,0x1f,0x41,0x6f, +0xf7,0x14,0x1a,0x0e,0xbd,0x57,0x50,0x7d,0x0f,0x6e,0xec,0x20,0x47,0xc8,0xe2,0xf5,0x90,0x03,0xdd,0x9c, +0xd7,0xbf,0x8e,0xdc,0xdf,0x69,0xef,0xe7,0x67,0xe1,0xf7,0x3d,0xe8,0xe1,0x64,0x3c,0xae,0x6f,0x9b,0x62, +0x5c,0x27,0xea,0xbd,0x47,0x33,0xae,0x4b,0x1b,0xb7,0x46,0x3c,0xd6,0xec,0x39,0xeb,0x5d,0x7f,0x78,0x1f, +0x6b,0x7e,0xec,0xd7,0xf5,0xdd,0xe4,0x82,0xb8,0x5d,0xd1,0x2a,0xd0,0x39,0x71,0xf2,0x6e,0x7e,0xfe,0xd4, +0x63,0x1a,0x22,0x95,0xd0,0x56,0x48,0x02,0x8d,0xeb,0x1a,0x6b,0x14,0x90,0x41,0x9f,0x32,0xd4,0xd3,0x4f, +0x3b,0xa2,0xca,0x32,0xaa,0xef,0x6e,0xf7,0x5e,0x93,0x03,0xe4,0xd0,0xa8,0xec,0x27,0x01,0x7c,0xc9,0x09, +0xdd,0x95,0x14,0x88,0x69,0xe4,0x3a,0xbe,0xf5,0x97,0x94,0x30,0xed,0x7e,0xdd,0xfd,0x02,0xe3,0xf6,0xe6, +0x85,0x9b,0x43,0xa0,0xdf,0xa7,0xbd,0x7a,0xfe,0x21,0xc1,0x27,0x0f,0xf4,0xd0,0x8e,0x07,0x64,0xf7,0xd8, +0x4f,0x24,0xa1,0x0b,0xe5,0xbc,0xfa,0x68,0x9c,0x0a,0x18,0x13,0xbf,0x14,0xc4,0x2f,0xf7,0xe2,0x97,0xe2, +0x37,0x20,0x75,0x30,0xda,0xde,0x15,0x3c,0x7a,0x57,0x52,0x38,0xfb,0xd9,0x47,0x3e,0x04,0x78,0x05,0xe8, +0x4e,0x0e,0xeb,0x33,0x26,0x37,0x52,0xbb,0x33,0xd1,0x75,0xe1,0xb2,0x87,0xf4,0x33,0x9b,0xe6,0x2e,0x82, +0x07,0xc5,0x24,0xaf,0x01,0x34,0x0a,0xfc,0xfc,0xfd,0x7b,0x0f,0xbd,0x61,0xe0,0x82,0x09,0x4d,0x11,0x89, +0xbb,0x13,0xd4,0x94,0xf0,0xb1,0x1e,0x61,0x7c,0xe2,0x22,0x48,0xe3,0xfa,0x48,0xd1,0x59,0xfc,0x05,0xab, +0x0d,0x97,0xc9,0x2e,0x28,0x70,0xe9,0x09,0xc8,0x50,0x2c,0x3e,0xc3,0xc9,0xe0,0x1a,0x9a,0xd2,0x8d,0x2b, +0x1c,0x47,0x72,0x4a,0xb6,0xae,0xf8,0x1b,0xf6,0x6f,0x3d,0xef,0x73,0x67,0x7c,0x8e,0x70,0xf0,0x90,0x51, +0x18,0x34,0x90,0x82,0xf0,0x6a,0x24,0x1e,0x10,0x8a,0x3a,0x91,0xfb,0x06,0x7a,0xf7,0x18,0x33,0x20,0xfe, +0x7f,0xe2,0xda,0x85,0x45,0xc8,0x5c,0xbc,0x64,0x7f,0xa6,0x6e,0x22,0x3e,0x68,0x01,0xf8,0x9d,0x87,0x09, +0x1b,0xef,0x88,0xcb,0x31,0x49,0x76,0xa2,0xcc,0x34,0xe3,0x2a,0x58,0x00,0xb5,0x1c,0x4a,0x9f,0x84,0xc9, +0x62,0xd3,0xa0,0xb3,0x24,0xe8,0xc6,0x31,0x62,0x46,0x4e,0x3d,0x9b,0xc8,0x50,0xc9,0x0b,0x74,0xfa,0xba, +0x95,0xad,0x6f,0xde,0x60,0xcd,0x14,0x1d,0x33,0x80,0x82,0x75,0x0b,0x83,0x23,0x5b,0xfe,0x1a,0xdb,0x5e, +0x75,0xc8,0x26,0x98,0x37,0xf9,0x9b,0x90,0x80,0x21,0x02,0xeb,0x06,0x34,0x0d,0xe6,0x61,0xe7,0xa6,0x77, +0x9a,0x38,0x03,0xda,0xea,0x40,0xb5,0x2c,0x55,0x49,0xa6,0x22,0xe8,0x95,0x8d,0x09,0x80,0xae,0xab,0xe3, +0x17,0xc4,0x94,0xf0,0x11,0x99,0x6b,0x8a,0x7b,0x8e,0x05,0x83,0x62,0x08,0xa2,0xeb,0x1f,0x64,0x23,0x59, +0xd0,0xcb,0x75,0x8a,0x29,0xd4,0x1c,0xb6,0xf4,0xac,0xc2,0x72,0x0f,0x82,0x88,0x47,0x2f,0x6b,0xce,0x7b, +0x43,0x74,0xac,0x7a,0x21,0x63,0xa1,0x46,0x0e,0xfd,0xe9,0x54,0xfe,0xba,0xbe,0x09,0xf9,0xcf,0xe9,0x9e, +0x8c,0xde,0x00,0xf6,0xfe,0xab,0xfc,0x2d,0x6b,0x4d,0x0d,0xc1,0xe1,0x6d,0x67,0xc9,0x45,0x84,0x8b,0x3b, +0xcd,0xc1,0x6d,0xf2,0xaf,0x3a,0xa0,0x1f,0xf1,0x61,0xfe,0x09,0x2a,0xd3,0xf4,0xa3,0xb1,0x8b,0x3c,0x22, +0x5c,0xa8,0x90,0x46,0x84,0xda,0x40,0xc0,0xb6,0xa9,0xcf,0x54,0xf7,0x03,0xbc,0x7c,0x57,0x48,0xbd,0x00, +0x7b,0xb0,0x79,0xfc,0x43,0xf2,0x19,0xc1,0x01,0x46,0x3c,0x66,0xf0,0xb3,0x77,0x7d,0x4e,0x36,0x98,0x90, +0x39,0x71,0xc4,0x97,0xa0,0x29,0x3f,0x93,0x14,0x6d,0xb2,0xde,0x91,0x31,0xa3,0xc0,0x2e,0x14,0x53,0xe9, +0x2f,0x12,0xe2,0x81,0xbf,0xdc,0x0d,0x92,0x17,0x40,0x5d,0xda,0xb4,0xb4,0x21,0xa6,0x6e,0x65,0x96,0xb3, +0xa6,0xda,0xbb,0x8e,0xfb,0xf2,0x9c,0x9c,0x36,0xb0,0x77,0x8d,0xb4,0x0b,0xd1,0x5d,0x0e,0x72,0x0e,0xc5, +0x5d,0xc3,0x05,0x68,0x9a,0xc2,0x09,0xf7,0xcd,0xd8,0x86,0x07,0x3e,0x7b,0x16,0xa7,0x60,0x5a,0xc1,0x68, +0xc8,0x47,0x98,0xb7,0x9e,0xc5,0x59,0x10,0x65,0x94,0xfe,0xcc,0xfa,0xa6,0x40,0xb4,0x24,0xf6,0x81,0x4e, +0xc0,0x05,0xbf,0x8b,0x30,0xb2,0x0f,0x61,0x01,0x7e,0x5f,0x60,0xa0,0xe6,0x44,0xe8,0x91,0x7b,0x1a,0xd1, +0xe1,0xe9,0x7c,0x0c,0x60,0x18,0x14,0x40,0x81,0xb0,0x88,0xdd,0x4b,0x5d,0xa5,0x20,0x52,0x5d,0x97,0xa8, +0x8b,0x7d,0x83,0x62,0xbf,0x20,0xf8,0x83,0x93,0xeb,0xe3,0x36,0x0d,0xe6,0x41,0x6a,0x17,0xdf,0xa3,0x94, +0xf5,0xbd,0x4e,0x63,0xc7,0x2f,0x2f,0x28,0x02,0xf5,0x2c,0xc1,0x33,0x19,0xc5,0x67,0x74,0x21,0x45,0x00, +0x24,0xe5,0x57,0x04,0xc5,0xd5,0xe6,0xea,0x84,0x7a,0x5a,0x4d,0xf3,0xda,0xae,0x6a,0xe8,0xf7,0x49,0xe2, +0xe6,0xb0,0x39,0x41,0x6e,0x13,0xd0,0xd1,0x74,0xb2,0x02,0x79,0xd7,0xe4,0x43,0x8a,0x94,0x45,0xa4,0xc1, +0xf0,0x1e,0x17,0xac,0x6e,0x9a,0xf8,0x87,0x84,0x10,0x22,0xf4,0xa5,0xf5,0xd3,0x7f,0xa5,0xa9,0x76,0x2f, +0xf4,0xc5,0xa7,0x81,0x44,0x04,0x3d,0x8a,0x8f,0x3c,0x50,0x07,0xea,0x90,0xc9,0x0f,0x2d,0xe2,0x59,0xa3, +0x25,0xf8,0x33,0x8b,0xfd,0x61,0x6f,0xf1,0x37,0x1f,0x1e,0xcf,0xe7,0xc1,0xa2,0x82,0xd8,0x5b,0xfc,0xed, +0xf2,0x18,0xf6,0x8e,0x3e,0x79,0x76,0xc7,0x58,0xb5,0x92,0xde,0xca,0x99,0xa6,0xf9,0x42,0x93,0xc1,0x90, +0xd9,0xd5,0xd3,0x64,0xde,0xbb,0x22,0x10,0x50,0x7c,0xe8,0x1e,0xf3,0xbb,0xf5,0xbc,0x0e,0x81,0xa8,0x29, +0x97,0x3e,0xb2,0xdf,0x4d,0xcd,0x48,0x26,0x24,0xd1,0x3c,0x3b,0xf0,0xed,0x57,0x88,0x1b,0x14,0x68,0x64, +0xf9,0x37,0x2c,0x48,0x08,0x29,0x6e,0xf7,0x27,0x12,0x5e,0x03,0xf7,0xc1,0x10,0x2a,0xa0,0x02,0xbf,0x08, +0x71,0xc8,0xc6,0x2c,0xf1,0x16,0x3b,0x69,0x61,0xf1,0x81,0x3d,0x08,0x3c,0xaf,0xed,0xb8,0x41,0x3f,0x56, +0x12,0x3d,0xbc,0xbb,0xd0,0xe2,0x56,0xe2,0x6b,0x62,0x2b,0xbc,0x75,0xa6,0x71,0x5d,0x48,0xfb,0xe8,0xec, +0x26,0x12,0x60,0x6b,0x7e,0x4b,0x00,0x9b,0xc7,0xce,0x5f,0xbc,0x8d,0x47,0x68,0x94,0xd7,0x7c,0xe7,0x6d, +0x0f,0xc3,0x28,0x2c,0x22,0x1c,0x69,0x39,0x77,0x0b,0xc2,0x49,0xf1,0xfe,0x34,0xd2,0x9c,0xf3,0x55,0xfb, +0x46,0x5b,0x74,0xa7,0x78,0x29,0xc0,0xc8,0xcf,0x3c,0xc2,0x61,0x7b,0x24,0x90,0xc2,0x49,0x3b,0x75,0xdc, +0xb1,0xdf,0xe3,0x56,0x16,0x32,0xf6,0x40,0x10,0x62,0x00,0x47,0xe8,0x8f,0xf7,0xd1,0xa8,0x91,0x22,0x0e, +0xe3,0x3d,0x0b,0x04,0x9f,0x46,0x36,0x3d,0x13,0x5b,0x24,0x88,0x02,0xa4,0xef,0x47,0xdc,0x2c,0x74,0xad, +0x75,0x12,0x1d,0x90,0xa6,0x82,0x58,0x25,0x0a,0x37,0x1a,0x71,0x53,0xc3,0x1e,0x69,0x03,0x07,0xdb,0xd9, +0x0d,0x7e,0x23,0xfc,0xc9,0x10,0xf5,0x86,0x63,0x0f,0x8b,0x42,0xa8,0xa5,0x4d,0x71,0x3a,0xe5,0x14,0x64, +0x7e,0xb5,0x31,0xe0,0x2e,0xed,0x86,0xe7,0x91,0x3d,0x6e,0x7f,0x5c,0x1e,0xab,0x03,0x32,0x23,0x49,0x1b, +0xe4,0xa1,0xa1,0x41,0x50,0xe9,0x60,0xc2,0xfc,0xfc,0x69,0x82,0x98,0xf9,0x28,0x7f,0x7c,0xf7,0xff,0x31, +0xf7,0xae,0xdb,0x6d,0x1b,0xd9,0xba,0xe8,0xff,0xfd,0x14,0x22,0xe2,0x56,0x00,0x13,0xa4,0x28,0x39,0x49, +0x77,0x40,0xc1,0xdc,0x8e,0x2f,0xb1,0xbb,0xed,0xd8,0x6d,0x2b,0x71,0xd2,0x14,0x93,0x01,0x91,0x90,0x84, +0x98,0x02,0x18,0x00,0xb4,0xe4,0x88,0xec,0x37,0x3b,0xe3,0x3c,0xd2,0x79,0x85,0x33,0xbf,0x39,0xab,0x0a, +0x85,0x0b,0x95,0xac,0x5e,0x7b,0x8d,0x73,0x32,0x62,0x11,0x97,0x42,0xdd,0x2f,0xf3,0xfa,0x4d,0xe2,0x09, +0x96,0x44,0xa3,0x31,0x4f,0x11,0x40,0x41,0x12,0x95,0x11,0x28,0xfd,0xc2,0x34,0x84,0x56,0x64,0x82,0x3f, +0x38,0x92,0xe8,0x38,0xde,0x8a,0xb6,0x81,0x0e,0x94,0x28,0x84,0x15,0xd9,0xfb,0xd2,0x18,0xe5,0xd8,0x9f, +0x83,0x56,0x21,0xb6,0x22,0x63,0x16,0x16,0x5a,0x3e,0x8c,0x4b,0xd5,0xee,0x7f,0x34,0xe8,0x25,0x4c,0x97, +0xe8,0xd7,0xe8,0xe6,0x9d,0x0a,0xf4,0x3d,0x3c,0x5f,0x46,0xa5,0x3a,0x76,0x36,0x1b,0xa5,0xba,0x92,0x48, +0x8a,0x9e,0x11,0x73,0x82,0x28,0xa1,0x71,0x77,0x13,0xfa,0x9d,0xc4,0x41,0x0e,0x23,0x18,0xaa,0x98,0xe7, +0x81,0x58,0xc1,0xcb,0x4a,0x5e,0xc7,0xe2,0xea,0xea,0x74,0x04,0x43,0x64,0xeb,0xe7,0xfe,0xd5,0xc1,0xa2, +0x1b,0x79,0xb3,0x8e,0xe3,0x4d,0xa4,0x41,0x5c,0x35,0x70,0x4c,0x0d,0xa2,0xe6,0xb3,0xc1,0x80,0x47,0x44, +0x21,0xf7,0x95,0x57,0x19,0x8a,0xb0,0x1d,0x14,0xb6,0x04,0xda,0x61,0x44,0xaa,0x5e,0x62,0x59,0xbf,0x8d, +0x8b,0x15,0xb5,0x29,0x7e,0xce,0xb0,0xca,0xae,0xa3,0x24,0xfd,0x83,0x13,0x51,0x4a,0x62,0x3e,0xe6,0x9e, +0x59,0x24,0x05,0xf6,0xf6,0x82,0x56,0xc6,0xfe,0x3e,0xfe,0xca,0x8a,0xa5,0xed,0xf6,0x76,0x6d,0x86,0x27, +0xf1,0xc6,0x67,0x74,0xc8,0x7e,0x60,0xfe,0x8e,0x2a,0x43,0x5f,0xa5,0x5e,0x26,0xf5,0xc2,0x8a,0xbb,0x35, +0x99,0xa5,0x62,0x7b,0xbd,0x16,0xe5,0x29,0x9a,0x05,0xc0,0x6f,0x1c,0x17,0xb4,0x02,0xf7,0x9c,0x3e,0x5e, +0xcc,0x40,0x22,0x26,0x2a,0xc7,0x88,0x59,0x77,0xa2,0xfa,0x98,0x5d,0x8d,0x50,0x42,0xa6,0x0f,0x11,0x58, +0x8d,0xe1,0x03,0xa2,0x6f,0x4c,0x55,0x88,0xa2,0x4c,0xe9,0xe8,0xa9,0xba,0x35,0x8e,0x5b,0xd6,0x76,0x5a, +0xf4,0x80,0x09,0x34,0xb7,0xfb,0x53,0x2d,0x2b,0xb1,0x71,0x23,0xaa,0x97,0x3b,0x21,0x62,0x4a,0xd4,0xaa, +0xaa,0xb7,0x9c,0x46,0xf5,0xf5,0x3c,0x0b,0x6b,0x4d,0x89,0x66,0x4a,0xa6,0x3a,0xd7,0x43,0x32,0xce,0x44, +0x82,0x8c,0xa3,0x98,0xbb,0xfe,0x19,0x23,0xc9,0x8a,0x9d,0x52,0x3a,0xed,0x78,0x4e,0x93,0x87,0xa6,0xfb, +0x7a,0x7f,0x3f,0xc7,0x5e,0x8c,0x1a,0x3e,0xe3,0x73,0x15,0x8a,0xb5,0xd0,0x7e,0x00,0xcd,0x8a,0x69,0x01, +0x73,0x15,0x99,0x6f,0x95,0xcc,0x06,0x33,0x3c,0x4b,0x32,0x8c,0x88,0xd9,0x28,0xe9,0x19,0x7a,0x8f,0xba, +0x0e,0xb6,0x76,0x3c,0x2a,0x51,0xb8,0x9c,0xae,0x79,0x14,0x60,0xcb,0xb5,0xa4,0x55,0xc3,0x97,0x7e,0x2f, +0xf2,0x2c,0xa5,0x2d,0x26,0x43,0x98,0x10,0x19,0xb0,0x4c,0x4a,0x3e,0x4f,0xfc,0x82,0x7a,0xca,0x68,0xff, +0x75,0x16,0x58,0x7c,0x55,0x2e,0x7c,0x47,0x53,0x26,0x82,0x08,0x6f,0x34,0x89,0x58,0xe1,0x1b,0xe0,0x4f, +0x0f,0x0f,0x58,0x40,0x89,0x34,0xfe,0xdc,0x0c,0x24,0x72,0xf5,0xac,0x79,0x15,0x71,0x4a,0x14,0x4f,0x0c, +0x45,0x3c,0x75,0xd8,0x9e,0xb7,0x70,0x66,0x5e,0x19,0x46,0xae,0xde,0xd9,0x61,0xba,0x21,0xf7,0xca,0x52, +0xe3,0xdc,0xe8,0x1b,0x80,0x66,0x15,0x07,0x0e,0x4b,0x36,0xf3,0x18,0x66,0xfe,0x8e,0xcf,0x3f,0x41,0x34, +0x39,0x0f,0x9c,0xef,0x18,0xf1,0x1b,0x5e,0xc1,0x6c,0x61,0x8b,0xc8,0xaf,0x34,0x1b,0xfb,0x8c,0x72,0x4e, +0xbd,0xb0,0xdd,0x6e,0xeb,0xf9,0x14,0x6b,0xe6,0x26,0x1d,0x1f,0x5d,0x1f,0x94,0x5b,0xde,0x92,0xca,0x18, +0xc6,0x4c,0x69,0x6c,0xb9,0x1f,0xf8,0x79,0x1c,0x8a,0x34,0x43,0xe9,0x84,0x44,0xec,0xf9,0xfa,0xdc,0x4f, +0xe2,0xb0,0x54,0x1a,0x07,0x3f,0xe3,0x6b,0x91,0xbd,0xf9,0x11,0xdf,0x60,0x9f,0xf6,0x0b,0xbe,0x64,0xe7, +0x35,0xfa,0x62,0x1d,0x63,0xc6,0x2e,0xe3,0x70,0x0d,0x1d,0xf5,0x3b,0xde,0x53,0xfd,0x39,0xdf,0x5e,0x46, +0xc5,0xeb,0xeb,0x54,0xab,0x9c,0xfc,0xf3,0x38,0x9c,0x5b,0x69,0x56,0x71,0x78,0xae,0x6c,0xba,0xa5,0x2e, +0x60,0xbb,0x58,0xa2,0x06,0xb0,0x83,0xe1,0xd1,0xf0,0xd0,0xa1,0x7d,0xdd,0xb2,0xda,0xb0,0x2c,0x25,0x20, +0xaf,0x05,0x9d,0x92,0x1a,0x49,0x2d,0x71,0x05,0x1f,0xe3,0xf0,0xe0,0xe7,0xe9,0x69,0x71,0xba,0x7e,0xf6, +0xf4,0xd9,0xb3,0xd3,0x9b,0x47,0xa3,0x59,0x7f,0xd3,0xb8,0xbf,0x77,0x70,0xe1,0x7f,0x42,0xba,0xc1,0x55, +0x31,0x38,0xf0,0xaf,0xe8,0x72,0xe0,0x4e,0xa3,0xc1,0xef,0x33,0x8f,0xde,0xdc,0xec,0x28,0xae,0xac,0x0b, +0xa6,0xb7,0x63,0x2e,0x5b,0x91,0x7b,0xd2,0x77,0xe1,0xed,0xaf,0x6c,0x26,0x17,0x5c,0x12,0x61,0x49,0x8b, +0xa6,0xcc,0xd7,0xb0,0x96,0x23,0xae,0xc2,0xd7,0xb4,0xb4,0x5f,0x66,0x4c,0x7f,0x74,0x68,0x98,0x92,0xb8, +0x52,0x49,0x53,0x4b,0x6a,0x6a,0xa8,0xea,0xa8,0x17,0x59,0x49,0x3c,0xa9,0xa5,0x0e,0xe2,0xe3,0xd1,0x04, +0x57,0xd3,0xb8,0xcf,0xea,0x28,0x29,0x6e,0x16,0xc8,0xb3,0xd9,0xd6,0xc7,0xa0,0xbd,0x2b,0xa3,0xf9,0x87, +0x5a,0x9e,0x46,0x1f,0x24,0x3a,0x63,0xfe,0xd4,0xaa,0x38,0xed,0xd8,0x96,0x61,0x07,0x48,0xe8,0x8f,0x32, +0x4a,0x22,0xfa,0x2c,0xc1,0x36,0xcd,0x2f,0xbb,0x6a,0x69,0x48,0x2b,0xa4,0x8b,0xa9,0x35,0x44,0xf5,0x76, +0xa5,0xe3,0x12,0x4d,0xdd,0x5c,0x45,0x1e,0xd7,0x9d,0x50,0x6c,0x57,0x08,0x63,0xff,0x9f,0x62,0xb4,0x3d, +0xca,0x98,0xa7,0x69,0x47,0x6f,0x36,0x72,0x4e,0xe2,0x6e,0xdd,0x1c,0xe5,0xc0,0x7a,0xfb,0x5d,0x39,0xc4, +0xbf,0xb9,0x23,0x30,0x9b,0xd1,0x9d,0x49,0x06,0x60,0x48,0xe3,0xdf,0x3a,0xfa,0xd6,0x1a,0x0d,0xe2,0xbf, +0xfb,0x71,0xdf,0xe5,0xa1,0x0a,0x46,0x55,0xbf,0xd6,0x2b,0x9a,0x3e,0x0c,0x69,0xd7,0x49,0x8f,0xcb,0xc9, +0x94,0x07,0x2f,0x9d,0xcd,0x88,0x55,0x44,0xf6,0xe9,0x62,0x67,0x33,0xcd,0xc0,0xd0,0x39,0xda,0x1a,0x43, +0x19,0xfc,0x20,0x22,0x2a,0x28,0xcb,0xcb,0x00,0x4b,0x9b,0x7e,0x7d,0xec,0x93,0xd4,0x73,0xb8,0xe5,0xab, +0xad,0x6f,0x88,0x80,0x50,0x96,0x95,0xba,0xb1,0x0a,0x45,0x9b,0x2c,0x93,0x32,0x22,0x72,0x4c,0x4f,0xf2, +0xb9,0x09,0xa3,0x96,0xf0,0x90,0x76,0x7a,0xf3,0x58,0xb7,0x7d,0xa9,0x05,0x63,0xce,0x59,0x96,0x41,0x7b, +0x5e,0xd1,0xcf,0x11,0x8b,0x3b,0xa3,0x5a,0x66,0x85,0xca,0x8c,0xf8,0x37,0xbf,0x45,0x70,0x8b,0x8e,0xda, +0xa2,0x4a,0x23,0xd1,0x4a,0x13,0x71,0x43,0xc5,0xf3,0xd9,0x41,0x77,0x3c,0xd0,0xc5,0x60,0xe0,0x8d,0x8b, +0xe3,0xf5,0xb8,0x10,0x4e,0x50,0x69,0xf8,0xe3,0x5a,0x51,0x42,0xd3,0x96,0x22,0x8b,0x4a,0xc3,0x08,0xca, +0xf0,0x9c,0x9d,0x45,0xfc,0x48,0xc9,0x93,0x96,0x7c,0xdc,0x09,0x31,0xfc,0x66,0x19,0x25,0xa9,0x92,0x03, +0xe7,0x28,0x39,0x09,0xeb,0xac,0x05,0x2c,0x86,0x27,0x2e,0x1b,0xbc,0xf5,0x0e,0xe9,0xc8,0x4b,0xf7,0xf7, +0xeb,0x09,0x52,0x6f,0x92,0x62,0x50,0x03,0x7e,0xd7,0xca,0x94,0x5f,0x83,0x84,0xa4,0x1a,0x58,0x16,0x43, +0x90,0x9b,0x50,0xd6,0x81,0xa1,0xf0,0x50,0x23,0x4e,0x93,0xdb,0x7a,0x3f,0xdf,0x12,0x7f,0x28,0xb7,0x88, +0x40,0xb9,0x8d,0x11,0xf7,0x72,0x19,0xf7,0x59,0x08,0x94,0xe3,0xf9,0x15,0x9d,0xc2,0xc6,0x8a,0xe2,0xe0, +0xf4,0x09,0xed,0x7e,0x30,0xa4,0x48,0x38,0xce,0xf0,0xa7,0x00,0xa4,0x20,0x9f,0x43,0xf6,0xb4,0x6e,0x7a, +0xa4,0x61,0x71,0xa7,0x99,0x2d,0x4e,0xf0,0xa8,0xea,0xd5,0xe0,0x74,0xac,0xfa,0xca,0x1f,0xcb,0x32,0x94, +0x45,0x3e,0xda,0x29,0x6b,0xd7,0xbe,0xd7,0x83,0xb7,0x46,0xcc,0x32,0x77,0x71,0x57,0xc3,0x27,0xdf,0xd1, +0x38,0xe6,0xc9,0xbc,0x7b,0x5f,0xab,0x3b,0x82,0xb9,0x95,0x4f,0x17,0x7b,0x78,0x19,0x13,0xa3,0x10,0x0c, +0x77,0x8f,0xf2,0x8a,0xbe,0x73,0xe3,0x81,0xa5,0x63,0x8c,0x3d,0xae,0x96,0x35,0x3a,0xed,0x72,0x7c,0x6d, +0x98,0xdd,0x73,0x7b,0xb0,0x7f,0x99,0xca,0x7c,0xdd,0x93,0xf4,0x33,0x76,0x5b,0x88,0xb5,0x7f,0x0e,0x2b, +0xd9,0x73,0xd4,0xc9,0x63,0xa6,0x6c,0x6e,0x76,0x34,0xc7,0x5a,0xb4,0x90,0x9c,0x97,0xf6,0x2a,0xf6,0x6d, +0x1f,0x36,0x6d,0x23,0xb9,0xd9,0xe8,0xb3,0x33,0x85,0x97,0xd7,0x4a,0x55,0xd6,0xd2,0x53,0xb4,0x2b,0x3b, +0xb6,0xa6,0xba,0x11,0x15,0x1a,0x1b,0x54,0x1f,0x59,0xdf,0x75,0xea,0xc0,0x29,0x26,0x68,0x2d,0x48,0x76, +0xa8,0xa9,0xc6,0x54,0x3f,0x9d,0xac,0xe3,0x69,0xd5,0xf2,0x59,0xc5,0x00,0x07,0x3a,0x05,0x4d,0x56,0x63, +0x1a,0x54,0x2b,0x36,0xe5,0xf9,0x60,0xe4,0xc1,0x5d,0x55,0x8a,0xcd,0xcc,0xfd,0x14,0xfb,0x0e,0x9d,0xe4, +0x4e,0x35,0x97,0xaf,0x62,0x3a,0xc6,0xbd,0xd6,0xf1,0x54,0x31,0x55,0xc4,0xfc,0x32,0x33,0x81,0x71,0x10, +0x63,0x3f,0x63,0xe2,0x9d,0x1c,0xa7,0xdc,0xf9,0x62,0x2b,0x45,0x6c,0x77,0xe2,0xe3,0x07,0x3d,0x4c,0x5d, +0x45,0x4c,0x6f,0x93,0x57,0x67,0xf9,0x75,0xc7,0x07,0x62,0x9d,0xca,0x64,0x62,0x65,0xc0,0xe3,0xd3,0x94, +0xbb,0xba,0xab,0x87,0xa9,0x7f,0xc1,0xf9,0x5b,0x8d,0xf9,0x18,0x8b,0x07,0xcd,0x55,0xf4,0x21,0x6e,0xd0, +0x0e,0xb6,0x95,0xa0,0x2d,0x54,0x31,0x8b,0x85,0x5a,0xa8,0x36,0x15,0x6a,0x68,0x65,0x91,0x9f,0xb6,0x1d, +0x88,0xe2,0x09,0xd1,0x09,0x41,0xec,0xd1,0x39,0xa1,0x35,0xa6,0x20,0xda,0x53,0x9a,0x51,0x69,0xbb,0x54, +0xeb,0x48,0x96,0x8a,0x97,0x93,0xc1,0x61,0x60,0xb9,0xe7,0xc1,0xc6,0xd5,0xe7,0xc2,0x1a,0xb5,0xad,0x6c, +0xfe,0xfb,0xda,0x8e,0x45,0x69,0x9f,0xcd,0x08,0xe4,0xc7,0xb4,0xa2,0xa8,0x9f,0x63,0x16,0x31,0xb0,0x1a, +0xc0,0x74,0xa0,0x4a,0x13,0x52,0x1f,0xd3,0xe4,0xa1,0x2e,0x6a,0x55,0xcb,0xd6,0x23,0xb7,0x1c,0x4d,0xe8, +0x7c,0xe8,0xa5,0x96,0xb7,0x49,0xaf,0x64,0x67,0x0c,0xd8,0x89,0xe6,0x34,0xc0,0x85,0x31,0x19,0x8f,0x6d, +0x7f,0x8c,0xa4,0x49,0xbf,0x54,0x02,0x7d,0x39,0x05,0xa1,0x8b,0x15,0x71,0x89,0x99,0x51,0x56,0x7b,0xa2, +0xe3,0x84,0x45,0xe4,0x59,0x88,0xd2,0x22,0x3a,0x55,0x20,0x53,0x96,0x31,0x22,0xde,0xa4,0xa8,0x1b,0xa9, +0x57,0x9c,0xdd,0x1f,0x7f,0xa0,0xd9,0x4d,0xcb,0x08,0x93,0x49,0xc7,0x75,0xb2,0x08,0x0e,0x7d,0x16,0x43, +0x76,0x4e,0x7f,0xea,0x1b,0xb6,0x02,0x6f,0x4e,0x02,0x11,0x14,0xf1,0xc9,0x07,0x5f,0xdd,0x38,0x4c,0x9b, +0x42,0x4a,0x4b,0x87,0x1e,0x6a,0x12,0xd4,0x9c,0xa5,0xfe,0x91,0x67,0x9b,0x58,0x5b,0x4b,0x55,0xd1,0x5e, +0x42,0x9e,0xc0,0x12,0x51,0x94,0xf9,0xad,0x1c,0x3c,0xaf,0xb2,0xb5,0x8e,0xed,0x1f,0x3e,0xf2,0xc5,0xd4, +0xda,0x4f,0x70,0xe2,0x5c,0x07,0x4f,0x88,0xdd,0x81,0x51,0x83,0x5f,0xac,0x57,0xf0,0xad,0x0e,0x16,0x31, +0x11,0x00,0x1d,0x1b,0xd1,0xbb,0x4f,0x57,0x67,0xd9,0x52,0x8e,0xef,0xf3,0x74,0x2a,0xb7,0xc3,0xa4,0x44, +0x40,0xdc,0x2c,0xa7,0x09,0x16,0xb7,0x9e,0x79,0x46,0x9a,0xe4,0x7c,0x23,0xf4,0xca,0x9e,0x38,0xa3,0xed, +0x09,0xe3,0xb2,0xa7,0x3b,0x64,0x8f,0xd7,0xc6,0x1e,0xea,0xb2,0xf7,0x36,0xbe,0x78,0x7a,0xb3,0x52,0xfb, +0xbe,0x1c,0x8f,0xaa,0x6c,0xc7,0xe6,0x4a,0xeb,0x03,0x42,0x7b,0xa4,0x39,0x31,0x9c,0x7e,0x09,0x69,0xdf, +0xac,0xe5,0x1a,0x2c,0x32,0xbb,0x33,0x9b,0x33,0xb1,0x44,0x62,0xe5,0x4e,0x39,0x82,0x3f,0x87,0x99,0x02, +0xb6,0xb3,0xba,0x19,0xdf,0x25,0xad,0xd7,0xca,0xa6,0x3a,0xf8,0x1a,0x93,0x41,0xfb,0xc8,0xb5,0x44,0x62, +0xb4,0xbb,0xe3,0x5c,0x83,0x19,0xe4,0xe5,0xfe,0xfe,0xd7,0xf2,0xc3,0x66,0xe9,0x97,0x7a,0x2e,0xa4,0xc8, +0xa0,0x07,0x7a,0xc4,0x45,0xc6,0xb5,0xc2,0x68,0xcc,0x83,0x7b,0xd8,0x3d,0x9f,0xef,0xef,0xbf,0x84,0x42, +0x9d,0x5d,0xc6,0x9f,0xfb,0xaf,0x45,0x81,0x7f,0xa8,0xf2,0x75,0xd7,0xe1,0x27,0x65,0x49,0x46,0x73,0x4c, +0x0c,0x05,0xe0,0x8f,0xc2,0x89,0xbe,0x0e,0x51,0x18,0x0b,0x68,0x40,0xd9,0x59,0x2e,0x4d,0xdf,0x7c,0x7a, +0xb1,0x80,0x0d,0x41,0xad,0x26,0xd1,0x90,0xe6,0x4d,0x18,0x1a,0x9d,0x85,0xf2,0xc1,0x80,0xdb,0xc6,0x56, +0x8b,0x19,0x56,0x4c,0x24,0xae,0x3a,0xb2,0xda,0xdf,0x7f,0x41,0x3b,0x18,0xdb,0x2c,0xfe,0x41,0x3e,0xb7, +0x2c,0x55,0x3a,0x9a,0xe9,0xf7,0xff,0x50,0xf3,0x9c,0x98,0x93,0x6e,0xaf,0x2b,0xde,0x51,0x51,0x43,0x6e, +0xdc,0x03,0x38,0x23,0x9f,0xd4,0x53,0x3e,0x86,0x34,0x5b,0x5c,0xc7,0xcb,0x1d,0x6f,0xfe,0xb0,0x34,0x93, +0x12,0xad,0xa1,0xaa,0x52,0x79,0x27,0xc3,0xdf,0x0a,0xa2,0xb3,0x7b,0xdf,0x13,0x83,0x48,0x93,0x10,0xc2, +0x9d,0xde,0x1b,0x1a,0xd7,0x37,0x5a,0xcc,0xad,0x06,0x83,0x07,0x95,0xa6,0x0c,0xa4,0x4f,0x95,0x44,0xa6, +0x92,0x5b,0xef,0xb2,0x45,0x97,0x6d,0xd7,0x2d,0x64,0x6c,0x2c,0xe9,0x7c,0xb2,0x70,0xe8,0xe0,0x29,0xc2, +0xc2,0x1c,0x67,0xd7,0xb1,0x7f,0x12,0xc3,0xdd,0xb8,0x68,0x26,0xa4,0x4d,0xf4,0x3d,0xcc,0x15,0xde,0x89, +0x79,0xda,0xd2,0x98,0xf4,0xc0,0xcc,0x71,0xc9,0x4e,0x77,0x9f,0x39,0xfd,0x82,0xa5,0x38,0x44,0x0e,0xf3, +0x0e,0x0d,0x6d,0x90,0x28,0x10,0x7c,0x07,0x2e,0x5b,0x57,0xc6,0x5b,0x77,0x7f,0x1f,0xa7,0x72,0x65,0x54, +0x4b,0xa4,0x57,0x89,0xae,0x98,0x7b,0x96,0x1b,0x4d,0xd5,0x87,0xab,0xb6,0x87,0xdb,0x9c,0x7b,0x4f,0xc4, +0x35,0x17,0x44,0xd5,0x9e,0x43,0xdb,0xb7,0xfc,0x74,0x0b,0x1e,0xe3,0x3d,0xc6,0xa7,0xe5,0xdb,0x80,0xd6, +0x1a,0x81,0x0c,0xfc,0x1a,0x4c,0xab,0x0b,0x3a,0xc4,0xef,0x1d,0xc2,0xb1,0xb2,0x21,0xc6,0xc7,0x36,0x59, +0x89,0x04,0xdd,0xb4,0xf2,0xe3,0x07,0xc7,0xcd,0x22,0x71,0x34,0xd8,0x7b,0xf8,0x98,0xb6,0xca,0xf9,0x65, +0xfc,0x52,0x59,0x92,0x1b,0xed,0x74,0xa9,0x05,0x6a,0x33,0x22,0x3b,0x38,0x2d,0x31,0x04,0x22,0xf4,0x09, +0x2b,0x8a,0xc0,0x12,0xe7,0xe6,0x36,0x11,0x35,0x7d,0xcf,0xb2,0x73,0xeb,0x75,0x52,0x11,0xd0,0xcf,0x9b, +0x1e,0x67,0x3a,0xcc,0x94,0xe3,0x8d,0xab,0x4e,0xec,0xf5,0xe2,0x4a,0xac,0x95,0xda,0xbe,0x4b,0xaa,0xbf, +0xca,0x9a,0x75,0xf4,0x5d,0x9e,0xa5,0x65,0x88,0x43,0xce,0xd2,0x00,0x64,0x4d,0xa2,0x21,0xd6,0x3b,0xe9, +0xc6,0x61,0x4d,0xb5,0xa1,0x19,0x30,0x47,0x1e,0x0f,0x23,0x1a,0x8b,0xe7,0xec,0xda,0x31,0x4d,0x89,0x6c, +0xa0,0x9d,0xb4,0x05,0x06,0xa0,0xc8,0x25,0x62,0x24,0xf0,0x7d,0xdb,0x86,0xa7,0xee,0x6f,0x02,0x19,0x67, +0x91,0xad,0xf3,0x79,0xfc,0x02,0xb2,0xae,0x41,0x69,0xdf,0x89,0x3c,0x5a,0x9f,0x87,0xbc,0x89,0x8a,0x7a, +0x84,0xf6,0xd2,0x61,0x4a,0x4c,0xd8,0xbb,0xe4,0x6c,0x49,0x7b,0x29,0x8b,0x58,0x53,0x66,0x33,0x24,0xf1, +0xe0,0xd0,0x8c,0xcb,0xe4,0x30,0x18,0x58,0x6e,0x5e,0x45,0xa7,0x56,0xe7,0x0f,0x5d,0x41,0x14,0x63,0x65, +0x3b,0x84,0x94,0xe2,0x86,0x43,0x8d,0xb3,0xfa,0x73,0xfd,0xdf,0xc9,0xde,0xb5,0xf2,0x27,0x62,0x5e,0x61, +0x4e,0xe3,0xce,0xdb,0x51,0xdc,0x72,0x47,0x71,0x9a,0x0f,0xcc,0xf2,0x2b,0x36,0xa6,0x9b,0x34,0xa7,0xc8, +0x22,0x29,0x18,0x3f,0x9b,0x69,0xe8,0x89,0xc3,0x30,0x36,0x92,0xd2,0xba,0xb6,0xbe,0xa9,0x65,0x60,0x7f, +0x1d,0x07,0x65,0xf3,0x36,0x29,0x9e,0x58,0x0f,0xa0,0x79,0xa8,0x9e,0x80,0xc8,0xa7,0x0a,0x3c,0xc5,0x8c, +0xee,0xfa,0xda,0x2a,0xbd,0x51,0x4d,0xbb,0xd9,0x73,0xab,0xd9,0xb9,0xdb,0x6e,0x38,0x2d,0xad,0x7e,0xe9, +0x5b,0x6f,0x44,0xc5,0xa5,0x26,0x39,0x2c,0x44,0x62,0x90,0x72,0x7a,0x72,0xc3,0x86,0x01,0x0e,0x26,0x9a, +0x9c,0xc4,0x54,0x4f,0xa7,0x30,0x7f,0x89,0x66,0x22,0xa6,0x4f,0x68,0x11,0xbb,0x39,0x7e,0x70,0xed,0xb1, +0xdd,0x9d,0x8d,0xca,0x60,0x2d,0xf8,0xfd,0xfd,0x3f,0xef,0x3c,0x4c,0x93,0xbf,0x0e,0xbb,0xd0,0xc0,0x55, +0xa8,0x4c,0x4c,0x46,0x7e,0xc5,0x41,0xd1,0xb2,0x72,0x9c,0x71,0x49,0x44,0x7c,0x09,0x1a,0xbb,0xcf,0xf4, +0xe4,0x90,0x11,0x1b,0x8d,0xc6,0x69,0x17,0x72,0x42,0x88,0x3e,0x65,0x0f,0x0d,0x5e,0x3f,0xd4,0x13,0xc9, +0x66,0x03,0xd3,0x13,0x38,0x2c,0x55,0x23,0xcc,0x5a,0x03,0x3a,0x2e,0x7e,0xe9,0xf7,0x2b,0xf1,0x24,0xcb, +0xf1,0x2a,0x90,0x02,0x6c,0xb4,0xca,0xed,0x77,0x5c,0x0a,0x13,0xe1,0xb5,0xdc,0xc9,0x36,0x9b,0xc8,0xd8, +0xca,0xab,0x2f,0xc6,0x66,0xff,0xaa,0xd9,0x64,0xf8,0x6b,0xa5,0x42,0x84,0x59,0x28,0x7c,0x83,0xbf,0xf1, +0x8b,0x99,0xb8,0xd0,0xb4,0xca,0xe8,0x28,0x84,0xba,0x52,0xe5,0xa2,0xd9,0xee,0xd1,0xd6,0x90,0xfb,0x77, +0xd7,0x8f,0x1e,0x9e,0xd3,0xfb,0xf7,0x70,0x63,0xc7,0x0f,0xcb,0xb9,0xe6,0xe1,0x39,0xed,0xfc,0xeb,0x34, +0xa1,0x53,0xeb,0xc5,0x13,0xbc,0xaa,0xdd,0x73,0x1a,0x38,0xc0,0x86,0x77,0x9c,0xd9,0x52,0x2a,0x2d,0x80, +0xb1,0x26,0x66,0xdc,0x65,0x38,0xc7,0xb1,0xba,0xbf,0xbf,0x84,0x8f,0x54,0x18,0x7e,0x83,0x2b,0x56,0xa2, +0x14,0xba,0x9f,0x56,0x44,0xf0,0x84,0x4b,0xfa,0x23,0xba,0x28,0x3a,0x95,0x57,0x3e,0x3f,0x6b,0x37,0xd0, +0xf4,0x64,0x0d,0x61,0xc2,0x62,0x0b,0x64,0xc2,0x3c,0x3c,0xac,0x8f,0x9a,0xbd,0x16,0x2c,0xbe,0x5c,0x39, +0x3b,0xf4,0xc0,0x5c,0xab,0x74,0x1d,0x22,0x8c,0x00,0xee,0x13,0x35,0x10,0x85,0x66,0x8e,0x50,0x7b,0xa7, +0x15,0xb7,0x9f,0x31,0x4f,0x0f,0x1a,0x1a,0x6b,0xa7,0x52,0x0d,0xdb,0x93,0xf4,0xd3,0x0e,0x47,0x72,0x30, +0x98,0x05,0x9b,0x45,0xc5,0x95,0xa0,0x53,0xf8,0xb4,0xd2,0x88,0x1d,0xd9,0xc4,0xbd,0x98,0x79,0xe2,0x18, +0xd4,0x4b,0xe1,0xa0,0x4c,0xf9,0xb0,0x13,0xa9,0x62,0xe4,0xe0,0x56,0xac,0x4e,0xfb,0xa2,0xdb,0x88,0xff, +0x4a,0xd5,0x80,0x89,0xfb,0x4a,0x65,0x00,0xa9,0x15,0x4d,0x09,0xb6,0x86,0xbb,0x62,0x72,0x8f,0xf8,0xc3, +0x5e,0x26,0x8f,0xb2,0x10,0x4e,0x65,0x11,0xec,0x4f,0xaa,0xad,0x26,0x17,0xde,0xa0,0x31,0x99,0xe1,0xe8, +0x4e,0x7f,0x2e,0xe1,0xd6,0x2d,0xed,0xb8,0x80,0xe3,0xef,0x47,0xe5,0xe5,0xef,0x17,0x96,0xe3,0x7a,0x31, +0x0b,0x0a,0x7f,0x4a,0x2c,0xd2,0x55,0x08,0xbe,0x00,0x64,0x7f,0x39,0xb9,0x08,0x3e,0xb9,0x17,0xfe,0xca, +0x8f,0x39,0x73,0xff,0x26,0x4c,0x27,0x19,0x34,0xce,0x93,0x38,0xb8,0x84,0xb7,0xdb,0x64,0x3a,0x0b,0xa2, +0xe0,0x8a,0xcf,0xc8,0xfd,0xfd,0xd4,0xbd,0xf2,0x6f,0x24,0x65,0xc2,0x27,0xe6,0x32,0xfc,0xe4,0xde,0xf8, +0x0b,0x56,0xa6,0xfb,0xe8,0x54,0xbc,0x9a,0x57,0x24,0x20,0x3b,0x9d,0xd3,0x32,0x58,0x4e,0xe7,0xdc,0x91, +0x37,0xd3,0x05,0x5d,0x61,0xd7,0xbb,0x52,0x57,0xe7,0x5a,0x3d,0x8c,0x79,0x9c,0xc1,0x67,0x85,0x2f,0x64, +0xb8,0x96,0x21,0x83,0x7f,0xdc,0x34,0xb3,0xbb,0x91,0xec,0x96,0xd2,0xf5,0x57,0x74,0x47,0xf9,0x8c,0x33, +0x16,0x13,0x53,0x1b,0xa6,0x70,0x94,0x5b,0xd3,0x4e,0xca,0x3e,0x72,0xbb,0xbe,0xa6,0xdc,0xb3,0x49,0x1c, +0x53,0xcf,0x9e,0x7b,0xc1,0x0a,0xcf,0x1e,0x0e,0x0e,0x21,0x7a,0x99,0x2e,0x51,0xc1,0x08,0x3f,0xe7,0xb0, +0x87,0xe4,0xf5,0x7e,0x83,0x96,0x86,0xf0,0x42,0xba,0x51,0x82,0x77,0xf7,0xd2,0xd7,0x59,0x7b,0xc1,0x0d, +0x8d,0xe0,0x44,0x55,0x20,0xa2,0x3e,0x5a,0x7b,0x81,0xa6,0x51,0xe9,0xb6,0xb6,0xad,0xdf,0xd4,0xb6,0x60, +0x99,0x9d,0xd5,0x3c,0xcc,0xc2,0xc7,0x43,0xe0,0x4c,0xc3,0x47,0x7c,0x2a,0x2e,0x45,0x34,0x7a,0x33,0x1c, +0x24,0x9b,0x8d,0xf5,0x0a,0xf4,0x22,0xcd,0xe1,0x0c,0x96,0xfe,0x34,0x8f,0x2d,0xdf,0xc9,0x06,0x26,0xd4, +0x96,0xea,0x23,0xe6,0xc4,0xdd,0x69,0x62,0x8d,0xca,0xa4,0x13,0xce,0xc3,0xa9,0xc5,0xea,0x56,0xec,0x69, +0xd8,0x83,0x42,0x16,0x6e,0xe5,0x74,0xda,0xfe,0x8a,0x75,0x00,0xb0,0x12,0xaf,0x9a,0x61,0x6b,0x95,0x3c, +0x58,0xaa,0x8b,0x4a,0x57,0x12,0x0a,0xa6,0xc7,0x76,0x46,0xab,0x2b,0x31,0x42,0xfd,0x7a,0x53,0x0b,0xd5, +0x54,0x8f,0x2a,0x40,0x84,0x3b,0x91,0xf1,0x74,0xbc,0xcc,0xcc,0x1e,0x87,0xd4,0x62,0x09,0x66,0xa5,0xd5, +0x6c,0x00,0x72,0xe7,0xa7,0x6c,0x07,0x03,0x08,0x83,0x94,0xd6,0x92,0x74,0x73,0x1e,0xf6,0xfb,0xc5,0x38, +0x3f,0xc6,0xa2,0xab,0x15,0x98,0xab,0x4c,0x58,0x68,0xa5,0xab,0x7a,0xe5,0x16,0x70,0x63,0xe5,0xe2,0xf9, +0x8a,0x4e,0x4c,0xa5,0xc5,0x1f,0xf9,0xc5,0xe0,0xd0,0x38,0x34,0xdc,0xf2,0xc9,0x18,0xd0,0x38,0x80,0x8a, +0x98,0x16,0x83,0x23,0xc9,0x6e,0x42,0xeb,0x2e,0x70,0x9c,0xad,0x25,0xa2,0x37,0x4c,0x44,0xea,0x17,0xc7, +0x39,0x9c,0x08,0x75,0x96,0x85,0x98,0x99,0xa1,0x6e,0x37,0x00,0xba,0xaa,0xa0,0x9b,0xe4,0xe1,0x82,0x25, +0xd9,0x73,0x63,0x63,0xa3,0xd5,0x74,0x54,0xbb,0x3a,0x5c,0x86,0x16,0x6b,0x99,0x1d,0xf2,0x21,0xb6,0xcb, +0xb8,0xba,0x89,0xc2,0xe6,0x56,0xe2,0x2f,0xe5,0x1b,0xde,0x4a,0x14,0x60,0x86,0x33,0x72,0xb0,0x83,0xec, +0xef,0x03,0x1d,0x04,0xeb,0xe8,0x2a,0xfc,0x95,0x16,0x14,0x0d,0x3a,0x8d,0x3d,0xfa,0x3f,0x5d,0x0c,0x4f, +0x1e,0x7d,0x0b,0x65,0x3f,0x8c,0xda,0xce,0xc2,0x6f,0xfa,0xa1,0x08,0x12,0xaf,0x68,0x22,0xd6,0x34,0x14, +0x9b,0xcd,0xf0,0xd0,0xbf,0xae,0x56,0x1f,0xaf,0x67,0x9a,0x40,0xbf,0x86,0xd0,0xd9,0x3f,0x67,0x7b,0x9d, +0xa5,0x37,0xbe,0xa4,0xc9,0x74,0xad,0x2d,0xb1,0xb0,0x5a,0xa7,0x97,0xc4,0x1e,0x5e,0xd2,0x88,0xf0,0x3e, +0xb0,0xbf,0x3f,0x97,0x51,0x84,0x47,0x32,0x7d,0x31,0xaf,0x0b,0x29,0x24,0x27,0xf7,0x25,0x8f,0x56,0xd8, +0x7b,0xed,0x8d,0x57,0x34,0x16,0xe7,0x1a,0x60,0x02,0x16,0x7b,0x11,0x84,0x16,0x05,0x5b,0x97,0x70,0x2f, +0xce,0xb5,0x09,0x00,0x2a,0xf3,0x4d,0x78,0xe6,0x6d,0x81,0x64,0x40,0x25,0xf7,0x56,0x28,0x8c,0xfa,0x7c, +0x30,0x00,0xde,0xcf,0x85,0x4e,0xce,0x4e,0x06,0x8b,0x7e,0x78,0x89,0x83,0x19,0xd5,0x5d,0x98,0x1a,0x51, +0x69,0xa9,0x2a,0x6d,0x45,0xdb,0xe8,0x47,0x74,0xac,0xb5,0x9d,0x2d,0x1e,0x8e,0x84,0xaf,0xb8,0xc4,0xce, +0x73,0x41,0x2d,0xa3,0xad,0x99,0xff,0xba,0xf8,0x09,0xff,0x29,0x52,0x34,0x3a,0x7a,0xc7,0x1f,0x69,0x7b, +0xf9,0xe8,0x6d,0xf5,0x9e,0xb1,0xf6,0x3f,0xf2,0xe1,0x82,0x4d,0xfa,0xa3,0x19,0x44,0xaa,0x5b,0xdf,0x8c, +0xef,0x21,0x8e,0x1e,0x21,0x1c,0xde,0x65,0x79,0x49,0xb9,0xe8,0xd9,0xa1,0x1a,0xe6,0xff,0x1a,0x5e,0x79, +0xfe,0x85,0x81,0x41,0x4b,0x26,0xb9,0x1b,0x79,0x41,0xc4,0x3c,0xe6,0xb5,0x7f,0xe2,0x3f,0xf6,0x9f,0xfa, +0x1f,0xfc,0x77,0xfe,0x77,0xfe,0x13,0xff,0x57,0xff,0x91,0xff,0x9b,0xff,0xd2,0x7f,0xee,0x3f,0xf3,0x5f, +0xfb,0x6f,0xfc,0xb7,0xfe,0x2b,0xff,0x85,0xff,0x3e,0x74,0x8a,0xe4,0xf7,0xdf,0x97,0xb1,0xd3,0x3f,0xbc, +0x0f,0xdd,0x11,0xe4,0x65,0xfe,0x3d,0xdb,0x1a,0xe1,0x1b,0x1a,0x97,0x5f,0xe8,0xdf,0xef,0x21,0x71,0xc4, +0xfe,0x8f,0xfc,0xf7,0x7b,0xfe,0xfb,0x43,0xb7,0x3e,0x9e,0x37,0x25,0xaa,0xe1,0x6f,0x30,0xbe,0xf0,0x47, +0x5b,0xff,0x5b,0x22,0x7a,0x9a,0xe6,0x06,0x3f,0x61,0xee,0xfd,0x33,0xfc,0x69,0xb8,0xca,0x56,0xfe,0xdf, +0xf1,0x0b,0xe3,0x85,0x7f,0xe8,0x8b,0x7f,0xd1,0x85,0xd8,0x39,0xc4,0x4d,0xad,0xff,0x1f,0x60,0xd3,0xc0, +0x66,0x06,0x86,0x54,0x15,0x4f,0x67,0xf0,0x79,0xb0,0x03,0x96,0x3a,0x16,0x47,0xbc,0xd8,0x08,0x08,0x39, +0x5d,0x44,0x80,0x96,0xdf,0xc0,0xf9,0xed,0x9c,0x5a,0x5d,0xf0,0x15,0xfb,0x63,0xa8,0xf0,0xde,0xc5,0x66, +0x01,0xdb,0xef,0x8d,0xe6,0x2a,0x36,0x62,0xf2,0xbf,0x49,0x8a,0xab,0x68,0xb5,0x59,0x66,0xd9,0x6a,0x73, +0xb5,0x5e,0x96,0xc9,0x6a,0x19,0x6f,0xa8,0x7d,0xe9,0x46,0x43,0x6a,0x6e,0x74,0x2c,0xcf,0x4d,0x31,0xa7, +0x17,0x0b,0x07,0x96,0x1e,0xce,0xf4,0xf4,0xf4,0xe6,0x68,0x74,0x7a,0x5a,0x9e,0x9e,0xe6,0xa7,0xa7,0xe9, +0xe9,0xe9,0xf9,0xcc,0x81,0xd1,0x87,0xe3,0x4e,0x82,0x53,0xfa,0x6f,0xb8,0xa1,0x14,0xd7,0x83,0xd9,0x66, +0xfa,0xf3,0xe9,0x68,0x40,0x89,0xa3,0xd1,0xcc,0xeb,0x3b,0x30,0xff,0x70,0x4e,0x4f,0xa7,0x4e,0x3f,0x8d, +0xfb,0xce,0x7d,0xd7,0xe9,0xe7,0xf4,0xeb,0xd1,0x47,0xfa,0xc9,0xf4,0xfe,0xcf,0xf7,0x36,0xbd,0x7f,0xcf, +0x26,0xa1,0xa7,0x1f,0x4d,0x82,0xcf,0xdd,0x2a,0xdb,0x9f,0xf1,0xfb,0xf9,0xcc,0xbb,0xef,0x7d,0xbe,0x39, +0x75,0x9a,0x2f,0x4e,0x1d,0xbc,0x39,0x75,0x36,0x3a,0x6b,0x6f,0xa3,0xf3,0x39,0x3d,0xa5,0x2a,0x66,0x54, +0x7e,0x60,0x15,0x7b,0x7a,0xea,0xba,0xee,0x7f,0x3d,0x7b,0x6f,0xd3,0x7c,0xe3,0x7a,0xd4,0xe0,0xd9,0x6c, +0xe3,0xf4,0x13,0x64,0x7d,0xdf,0xdb,0x0c,0x29,0xe1,0x29,0x8a,0x87,0x69,0x0b,0xe6,0xa5,0x88,0x70,0x5d, +0x54,0x86,0x7a,0xc2,0xb9,0x80,0x11,0x51,0xed,0x8d,0xf3,0xb3,0x54,0xb5,0xcf,0xb9,0xff,0xac,0x72,0x9e, +0x79,0xba,0x28,0xca,0x55,0x25,0xb8,0xa7,0xbe,0x5f,0x77,0x7e,0x7f,0xdf,0x57,0xbf,0xc0,0x94,0xe9,0x4e, +0xe1,0x4e,0x1f,0xf6,0xff,0x8d,0xda,0xe2,0xce,0xab,0x92,0xcf,0xeb,0xc9,0x43,0x93,0x9c,0xaa,0x32,0xfb, +0x9c,0x9a,0x7f,0x7f,0x52,0xeb,0x4f,0xae,0xc5,0x79,0xed,0xa3,0x2c,0xf6,0x60,0x7a,0xd3,0x28,0x15,0x1d, +0x7e,0xcf,0x11,0x0b,0x9c,0x17,0x4f,0x82,0xda,0xdb,0xcf,0xf4,0x80,0xd0,0xfb,0xc7,0x2f,0x1f,0xbd,0x7b, +0x57,0x7f,0x4d,0x2d,0xb7,0x12,0xd0,0xce,0x5e,0x7f,0xad,0xde,0x6d,0xa6,0xf7,0x67,0x78,0xff,0xe8,0xe4, +0xe4,0x6d,0xd0,0x28,0x3c,0xa1,0x2a,0xbd,0x79,0xf7,0xf4,0xfb,0x27,0xaf,0x9b,0x6f,0x50,0xd9,0xc7,0xcf, +0x5f,0xbc,0x6c,0xd4,0x28,0x70,0x79,0xea,0x33,0xd7,0xb7,0x81,0x7d,0xc6,0x26,0x2d,0x2f,0xf1,0x6f,0x80, +0x1b,0x6f,0xe0,0x32,0xfc,0xc2,0x26,0x3b,0x1f,0x30,0x20,0x86,0x4c,0x24,0xdd,0x57,0xc0,0xef,0xd8,0x64, +0x8b,0x05,0x8d,0xe2,0xb4,0x4f,0x4b,0xc0,0x73,0x4f,0x4f,0x17,0xf7,0xbd,0x74,0x63,0xcd,0x68,0xf5,0x46, +0x3f,0xa0,0x04,0x7d,0x9a,0x2a,0x55,0xd7,0xf2,0xbc,0x71,0x12,0x6a,0x0f,0x4c,0x1a,0x1a,0x0d,0xc6,0x6a, +0x29,0xd1,0x1d,0xf7,0x54,0x9a,0x34,0x8e,0x17,0x05,0xdb,0x33,0xde,0x94,0xcd,0x16,0x72,0x86,0x32,0xda, +0x41,0x55,0xb5,0xf8,0xb7,0xcd,0x05,0xb5,0x4c,0xda,0x55,0x35,0xb3,0xd1,0x12,0xba,0xa3,0xb5,0xbb,0xf0, +0x26,0xdc,0x00,0xbb,0x72,0xee,0x24,0x9c,0xfe,0x4c,0x2d,0xb8,0xa7,0xaa,0xc9,0x46,0x55,0x07,0xa8,0x1a, +0x8b,0x71,0xd4,0xbe,0xb4,0xd1,0x30,0x20,0x1b,0x91,0xe7,0x78,0xf7,0x0e,0x12,0x98,0x5c,0x1d,0xfc,0x7c, +0x79,0xba,0xc0,0xb5,0x58,0x54,0xfd,0x7c,0x3b,0xeb,0x9f,0xde,0x9e,0x16,0xf7,0x4f,0xa7,0x29,0xd3,0x3d, +0x7b,0xa7,0xd7,0x07,0x62,0x45,0x45,0x19,0x7e,0xe6,0x4e,0xb1,0x93,0x50,0xff,0xb8,0xa7,0xd7,0xf4,0x97, +0x66,0x83,0x7a,0x40,0xd9,0xb1,0x81,0xd5,0x94,0x1a,0x77,0x00,0xdb,0x2a,0xbb,0xe9,0xbc,0x28,0x69,0x4d, +0x2e,0xa2,0xc1,0xf9,0xec,0xf6,0xd0,0xff,0x6a,0x2b,0xf5,0x9f,0x6c,0x54,0xf3,0x68,0x85,0x72,0xe5,0x31, +0x83,0xcf,0xea,0x3b,0x74,0x25,0x32,0x70,0x46,0x37,0xd4,0xd7,0x83,0xaf,0xbe,0xfc,0xf2,0xc1,0x57,0x86, +0x77,0x83,0x79,0x04,0x51,0x98,0x93,0x32,0xc8,0x8f,0x47,0x13,0x51,0xda,0x0c,0x61,0x3c,0xf7,0xf8,0x32, +0xca,0x1f,0x03,0x5e,0x2a,0xef,0xf3,0x17,0x5e,0xd0,0xf9,0xf2,0xe1,0xc3,0xc3,0xd1,0xe6,0xcb,0x2f,0x8f, +0xbe,0xfe,0xca,0x3f,0x1c,0x1d,0x3d,0xd8,0xcf,0x37,0x5f,0x7e,0xf5,0xe0,0x08,0xa6,0x40,0xd7,0xd4,0x1a, +0xaa,0x34,0xed,0x98,0x37,0x87,0xe7,0xa7,0x37,0x7f,0x3d,0x9f,0x6d,0x7e,0x1e,0x4c,0x68,0x04,0xe8,0xe7, +0x9e,0xda,0x4b,0xd5,0x9b,0xc1,0xe9,0xfa,0x19,0xfd,0x87,0x9e,0x38,0xb8,0xf0,0x4f,0x76,0x19,0x96,0x4d, +0x9c,0xd3,0x91,0xa0,0x4f,0x3a,0xff,0xcf,0xff,0xfd,0x7f,0x39,0x41,0x45,0x27,0x12,0x99,0xd8,0xa7,0x5e, +0x02,0x12,0xcf,0x5c,0x55,0xed,0x11,0x20,0x5e,0x34,0x54,0x87,0x67,0x2c,0xe9,0xdc,0xc3,0xaf,0x3c,0xc8, +0x7e,0x03,0x49,0xbe,0xf5,0x1f,0xd7,0x7c,0x40,0x5f,0xc2,0x0c,0xe8,0x69,0xbc,0x83,0x74,0xaf,0x89,0xdb, +0x60,0xf3,0x68,0x24,0x73,0xd0,0xec,0x1b,0x91,0x17,0x34,0xea,0xb7,0x8b,0x24,0x0f,0x6c,0x49,0x8c,0x0f, +0x39,0x4d,0xe0,0x48,0xb8,0x26,0xa2,0x56,0x59,0x30,0xac,0xc9,0x8f,0x9f,0xc2,0x7f,0x09,0x69,0x72,0xaf, +0x06,0x0b,0x56,0xbf,0xfb,0x69,0x7a,0xaf,0x0d,0x95,0x32,0xab,0x00,0x5d,0x45,0xb0,0xfc,0x81,0xea,0xfb, +0x8f,0xf0,0x96,0xf3,0x0d,0x7e,0x52,0xa9,0x26,0xf5,0x0e,0xfd,0xbb,0x2a,0x36,0xf6,0x55,0xb1,0xa5,0x67, +0x63,0x53,0xb6,0x44,0xc9,0x95,0xfa,0x79,0x0c,0xd1,0xb9,0xe8,0x9a,0x99,0x0a,0x1b,0x1b,0x45,0x33,0x8e, +0xf3,0xed,0xf6,0x24,0x2c,0x87,0x4a,0xcd,0x08,0xeb,0xc4,0x0f,0x21,0x84,0x86,0x3f,0xbe,0x7a,0x59,0x53, +0xc8,0x29,0x3f,0x07,0xb8,0x9f,0x35,0x55,0x60,0x0c,0xcc,0x2a,0x37,0x4a,0xca,0xa6,0x25,0x16,0x3d,0xa2, +0x61,0x1c,0xa0,0xaf,0xd5,0xd5,0x2b,0x5b,0xff,0x65,0xc8,0x2a,0x12,0x43,0xa1,0x76,0xd8,0x9d,0x80,0x30, +0x99,0xb4,0xcb,0x0a,0xee,0xd9,0xcb,0xe0,0xf9,0xfe,0xfe,0xd7,0xa1,0x8d,0x60,0xb2,0xbf,0x9f,0x37,0x2b, +0x33,0x71,0x9f,0x87,0xb9,0xff,0x2c,0x7c,0xde,0x7c,0xe1,0xbf,0x0e,0x7b,0x1f,0xdc,0xe7,0x34,0x64,0x92, +0x13,0x71,0x4f,0xcf,0x0d,0x2e,0x50,0x12,0x5f,0x33,0xc0,0x19,0x51,0x31,0x0a,0xa7,0x20,0x05,0x24,0x52, +0x0d,0xcb,0x75,0xd2,0x7e,0x04,0x14,0x6a,0x01,0x78,0x7d,0x1c,0xc3,0x6e,0x3e,0x48,0x21,0xb6,0x8f,0xe6, +0x97,0x9c,0x0a,0x19,0x5a,0xb7,0xae,0x93,0xa5,0x55,0x72,0x62,0x67,0x4e,0x58,0xc6,0xcf,0xfa,0x96,0x22, +0x4c,0xba,0xa7,0xf3,0x5c,0x2b,0xc0,0x42,0xda,0xf7,0xfc,0x5e,0xa7,0xc7,0x08,0x5e,0x3b,0xb0,0xd9,0x3f, +0xe9,0x14,0x7d,0xee,0xca,0xda,0x06,0x86,0xd3,0xea,0x91,0xc7,0xd9,0x95,0xa8,0x47,0xe0,0x86,0xda,0xdb, +0x01,0xb8,0x88,0xa3,0x5b,0xcd,0xa9,0x76,0xa1,0x46,0x63,0x17,0x7e,0x54,0xfa,0xab,0xe7,0xbb,0x94,0x7f, +0xf2,0x29,0x74,0x95,0xdd,0x55,0x7c,0x56,0xab,0x22,0xcd,0xbb,0x64,0x11,0xbe,0xf7,0x7b,0x8d,0xfc,0x90, +0xd5,0x66,0xd3,0xf5,0xd4,0x7d,0xdf,0xac,0x26,0xca,0x9a,0xb8,0x9a,0x69,0x1e,0xbe,0x78,0xd2,0x35,0xe9, +0x0d,0x8f,0x7a,0x13,0xd3,0x4e,0x5d,0x81,0xdd,0x75,0xf5,0x61,0x5b,0x43,0xc8,0xf2,0x05,0xda,0xb3,0x84, +0x31,0xac,0x95,0x80,0x35,0x0b,0x1d,0x64,0x87,0xd0,0xba,0xa9,0x07,0xde,0xdf,0x7f,0x5d,0x69,0x32,0x1a, +0x7a,0xdd,0xaa,0x4a,0xe9,0x84,0xe8,0xf6,0x60,0x3a,0xdb,0x6e,0xbd,0xe0,0xff,0x40,0xab,0xa4,0xbc,0x9d, +0x32,0x75,0xd3,0x50,0xd1,0x6c,0xb4,0x9f,0x49,0x07,0x98,0xca,0x61,0xfa,0x4b,0x70,0xa3,0xff,0xc3,0x5d, +0x22,0x16,0x9b,0x9d,0x1d,0x23,0x72,0x32,0x16,0x8d,0x64,0x3b,0xea,0xe7,0xd7,0x2a,0xa6,0x55,0xd0,0xd3, +0x6c,0xa6,0x5c,0x01,0xcb,0x8e,0x89,0x14,0x7b,0xbc,0xb3,0x66,0x61,0xa2,0x36,0xd5,0xff,0xac,0x04,0xc5, +0x8d,0xf2,0x80,0xf9,0x95,0xe8,0x20,0xec,0x5e,0xb7,0x93,0xae,0x23,0xf6,0x8f,0x7a,0xca,0xfa,0x7c,0xa7, +0xe2,0x3e,0x60,0x15,0x3a,0xbd,0x6f,0xa9,0x89,0x63,0x6d,0x92,0xd9,0x3c,0x6a,0x34,0xb4,0x06,0xb1,0xa0, +0xda,0xd9,0x6a,0x47,0xe6,0x63,0xe3,0x0b,0xa1,0x44,0x7a,0xe3,0xd4,0x38,0x58,0x41,0x21,0x90,0xd6,0x36, +0x6f,0x25,0xc4,0xa9,0xe4,0xd4,0xda,0x0c,0xc8,0x4c,0x17,0xa6,0xd0,0x9b,0x3d,0x64,0x99,0x16,0xfc,0x97, +0xe7,0x52,0xfd,0xf3,0xd7,0xc6,0x19,0x74,0x97,0xe1,0x01,0x48,0x85,0xb7,0x68,0xf8,0x1b,0xfc,0x11,0xf3, +0x03,0x6b,0x6f,0x6b,0xf6,0x21,0x1b,0x43,0xd6,0xb7,0xb3,0xf6,0x3e,0x66,0x60,0x4a,0x9d,0xe3,0x08,0x31, +0x1d,0x3e,0x77,0xfa,0xef,0xfb,0xce,0xe7,0x0f,0x8f,0x0f,0x22,0x13,0xa5,0xcc,0x3c,0x1e,0x80,0xe7,0xfd, +0x7c,0xef,0xaa,0x20,0x32,0x20,0xbb,0x9e,0x47,0x2b,0xaa,0x6f,0x1c,0x7e,0xfe,0xb9,0x89,0x20,0xa6,0xb9, +0x72,0x7e,0xd6,0x8a,0x18,0xe6,0xf8,0x1d,0x88,0xb7,0xce,0xb4,0x9e,0xdd,0xcf,0xf4,0xed,0xcc,0xec,0xea, +0xfb,0xfb,0x6f,0x64,0x64,0x1c,0xf0,0xc7,0xb3,0xd0,0x62,0x8d,0xc1,0xa6,0x9e,0x32,0x67,0xd4,0x99,0xab, +0xae,0xca,0xcc,0xc2,0xd1,0xd0,0x79,0x59,0x8c,0xf8,0x24,0xe0,0xd5,0xb1,0x51,0x8c,0xc5,0xae,0xdc,0x92, +0xc5,0xbf,0x43,0xe9,0x82,0xae,0xfc,0xe8,0x5d,0xf7,0x77,0x81,0x12,0x57,0x74,0x7c,0x53,0xbd,0xea,0xfc, +0x32,0xfa,0x8c,0x8b,0xeb,0xdf,0xef,0xf8,0x74,0xf8,0xd9,0xb0,0x0f,0xd2,0x9f,0x4f,0xda,0xfa,0x00,0xc7, +0x8d,0x11,0xbd,0xcc,0xe3,0x73,0xea,0xd0,0x3d,0x43,0x8b,0x7e,0xae,0xaf,0xea,0x43,0xdc,0xf9,0x5e,0xc6, +0xef,0xc0,0x1a,0xc0,0xf1,0x0e,0x13,0x06,0x51,0x61,0x13,0x99,0xda,0xb0,0x3f,0x61,0x58,0x4a,0x5f,0x23, +0x35,0xa0,0xa5,0x35,0xec,0x5d,0xaf,0x91,0x1c,0x31,0x4f,0x28,0xf9,0x93,0x5d,0xa3,0xc0,0x31,0x51,0x16, +0x5d,0xb3,0x83,0xbf,0x54,0x2c,0x9f,0x11,0xa4,0x50,0x36,0x47,0x0c,0xc6,0xd9,0x31,0x30,0xb1,0x0a,0x25, +0xdd,0xce,0xca,0xbc,0xf2,0x9d,0x40,0x77,0x06,0xe5,0xd4,0x5a,0x3a,0xa6,0xcb,0x7a,0xa3,0xdd,0xc5,0x54, +0x19,0xfc,0xd9,0x72,0xba,0xb2,0xb9,0xef,0x07,0x80,0xae,0xd1,0x5f,0xfa,0xc3,0xfb,0x81,0xc3,0x98,0x12, +0xb4,0x07,0x28,0x99,0xba,0x4e,0x6f,0xf6,0x83,0x57,0xe1,0x33,0xfd,0x6e,0xb3,0x79,0x36,0xbc,0x8e,0xcf, +0x3e,0x24,0xe5,0xab,0x7a,0x62,0xbc,0xb8,0xca,0x7e,0xef,0x78,0x9a,0x75,0xa5,0x2c,0x1a,0x0f,0x3d,0xc6, +0x52,0xb5,0xa7,0xdf,0xc9,0xb0,0x8a,0x78,0x18,0x2f,0x38,0x79,0xf8,0x4a,0xfb,0x59,0xb3,0x9c,0xa5,0xba, +0x9b,0x16,0x3d,0x2c,0x75,0x6e,0xd9,0x5b,0xd5,0xb2,0x5e,0x08,0x49,0x15,0xa6,0xf5,0x9b,0xf0,0x8d,0xe9, +0x31,0x8b,0xbb,0x7d,0xa3,0xec,0x92,0x36,0xa0,0x0a,0xdf,0x86,0x6f,0xbb,0xd2,0xbc,0xb5,0xd3,0x94,0xa6, +0x43,0x9e,0xb1,0xc7,0x36,0xf1,0x58,0x9a,0xa8,0x7f,0x93,0x15,0x09,0x2a,0xee,0xf9,0x2f,0x60,0x3c,0x67, +0xa7,0x13,0xa0,0x3e,0x6f,0xd2,0x71,0xf4,0x84,0x5f,0xd7,0x4c,0x5b,0x26,0x71,0x93,0xba,0x0f,0x60,0x02, +0x53,0xd6,0x4d,0x72,0xc6,0x96,0x78,0x95,0xfa,0xb2,0xe7,0xf6,0xf2,0x36,0x04,0x62,0x0f,0x48,0xf2,0xaa, +0xe8,0x49,0x6a,0x21,0x1d,0x02,0xd0,0x6a,0x47,0xdd,0xf7,0xf7,0x0f,0xbf,0xda,0xdf,0xf9,0x96,0x9d,0x26, +0x9a,0x07,0x28,0xec,0xaa,0x3d,0xad,0x20,0xb7,0xeb,0xc8,0x16,0xd7,0x16,0x99,0xd0,0x1b,0x55,0x5a,0x7b, +0xff,0x87,0xb0,0x9c,0xb4,0xf2,0x89,0x6d,0x49,0x2d,0x44,0xc6,0xfe,0x48,0xc5,0xac,0xe9,0xed,0xac,0xd3, +0x00,0x81,0x54,0xba,0x5f,0x55,0xc4,0x64,0x1a,0x10,0x51,0xd3,0xc5,0xf0,0x01,0xb2,0xa8,0x6d,0x0a,0xe9, +0x4d,0x76,0x77,0x41,0xe9,0x05,0x87,0xfe,0xe1,0x7e,0x4a,0xfd,0x7b,0xc2,0x3e,0x3a,0x4f,0x62,0xf0,0x41, +0x00,0x7e,0xda,0x59,0x11,0x97,0x0b,0x4a,0x27,0xb1,0x68,0x30,0xe2,0x96,0x5a,0xe3,0x1e,0xcc,0x19,0xef, +0xf9,0xb1,0x07,0x03,0x6d,0xa5,0xe7,0x28,0x77,0xa5,0xa2,0xea,0x1d,0x06,0x8f,0xa0,0xcc,0x7c,0x44,0x5f, +0x0c,0xf8,0x97,0x6a,0x35,0x0a,0xbe,0xd8,0x4f,0xf1,0xfd,0x61,0xd7,0x10,0xed,0xec,0x5a,0x63,0xd7,0x5d, +0x8d,0x1c,0x13,0x41,0xd6,0x6d,0x11,0x02,0xc6,0x66,0x1d,0x4e,0x4b,0x36,0x27,0xe8,0x01,0xc5,0xc9,0x78, +0xd1,0x72,0x9b,0x4c,0xb5,0xa9,0x66,0x09,0x6e,0xb2,0xee,0x2a,0x32,0x48,0x05,0x3b,0x93,0x6a,0xe5,0xb9, +0xf8,0xd5,0x2b,0xeb,0x7e,0x36,0xc9,0xb2,0x67,0x50,0x61,0xf9,0xa2,0xab,0x44,0x65,0x2b,0xd1,0xba,0x99, +0x68,0x0c,0xe0,0x18,0x76,0x6c,0x86,0xb9,0x46,0x5e,0x99,0x9f,0xe4,0x93,0xc8,0x65,0x50,0x19,0xbc,0xf1, +0x02,0x95,0xec,0x1e,0x2a,0xbc,0xd6,0xd7,0x87,0xc1,0x68,0xeb,0x3f,0xf7,0x82,0xe7,0x5b,0xbf,0xd4,0x1b, +0x9e,0x4d,0xd6,0x57,0x36,0xf5,0x6c,0x8d,0x00,0x5d,0xa4,0xfc,0xf1,0xac,0x0f,0xcc,0xee,0x59,0xff,0x10, +0xc6,0x1b,0x1d,0x93,0x50,0x9b,0xdd,0x02,0x00,0x94,0xda,0xa6,0xd9,0x99,0x39,0x6d,0x6a,0xe1,0xe7,0xf7, +0x0e,0x41,0xbe,0xf8,0xad,0x8d,0x99,0x08,0x3c,0x36,0x15,0x4d,0x8d,0xa9,0xe8,0x5b,0x1a,0x96,0xb7,0xb2, +0xe7,0xa4,0x4c,0xab,0xd9,0xc6,0xa3,0x88,0x77,0x00,0xa1,0x8f,0x48,0xe3,0xcc,0xae,0x99,0x8a,0x02,0x6b, +0xb3,0xe9,0xd8,0x69,0x31,0x51,0xf5,0x5e,0xa4,0x6c,0x89,0xab,0x07,0x15,0xd6,0xb3,0x21,0x70,0xab,0x78, +0x26,0xa6,0x83,0x52,0xff,0xb9,0xf4,0x0e,0x80,0x8e,0x8c,0x66,0x0b,0x1d,0xa5,0x77,0xa3,0x4e,0x69,0xdb, +0x1f,0x74,0xd2,0x0b,0xe5,0x46,0x5a,0xb2,0x94,0xa1,0x91,0xc3,0xdd,0xdf,0xaa,0xed,0xa4,0x66,0x83,0xd8, +0xb0,0xe3,0x9e,0x89,0xc5,0xe1,0xb7,0xd2,0x45,0x76,0x4a,0xbf,0x91,0xd2,0x9b,0x88,0x90,0xb3,0xf7,0x5a, +0x33,0x17,0x7a,0xa2,0x55,0xde,0x5f,0x93,0x3c,0xb0,0x85,0x21,0x34,0x20,0xaf,0x27,0x0d,0xe6,0x9a,0x16, +0x06,0xbb,0xd9,0xb7,0x58,0xae,0x12,0xa3,0x98,0x0b,0x64,0xca,0x79,0x12,0x2f,0x26,0xb9,0xb0,0x5e,0x8c, +0xc9,0x8e,0xe6,0xc7,0x05,0x11,0xb9,0x75,0x7b,0x73,0xdd,0x81,0x35,0xe7,0x14,0xb1,0xe6,0xe5,0x2f,0x60, +0xef,0x5e,0xfb,0xa0,0x15,0xbc,0xec,0xdd,0x27,0x1a,0x99,0x9b,0x3d,0x4e,0xe9,0xef,0xad,0x53,0x84,0x77, +0xbe,0x48,0x93,0xdf,0xe3,0xc5,0x5e,0x7c,0xb3,0x02,0x86,0x09,0x1c,0xc6,0xf6,0x9c,0xbe,0xe4,0x58,0x29, +0x25,0xbb,0x84,0x5f,0x60,0x2c,0x64,0x83,0xe1,0xb5,0x4f,0x1b,0x0f,0x4d,0xb4,0xb8,0xa4,0x49,0xf6,0x64, +0x0d,0xbb,0x0a,0x22,0xfa,0x0a,0xff,0x51,0xa8,0xb6,0xd2,0x77,0x0c,0xa9,0xce,0xd6,0x9c,0x22,0x5b,0x05, +0xf9,0x82,0x17,0xee,0x0f,0x9e,0xff,0x9b,0x31,0xe1,0x8a,0x15,0xd3,0xc5,0x87,0x0b,0x23,0x0b,0xb8,0x30, +0x32,0xe5,0x23,0x3f,0x51,0x50,0x76,0x62,0x6b,0xaa,0xdd,0x26,0x5d,0xd8,0x99,0xfa,0x87,0x46,0x69,0xfa, +0x48,0x4c,0x14,0xe2,0xad,0xff,0x54,0xf8,0x3d,0xc4,0x33,0xe8,0xac,0xbf,0xe3,0x98,0x0d,0xd2,0xc4,0xa2, +0x62,0xa4,0x9d,0x5b,0x65,0x06,0x46,0x9b,0xe1,0xd7,0xf2,0x73,0xc8,0xb7,0x0a,0xf0,0xae,0xe9,0xb8,0x63, +0x63,0x22,0x57,0x38,0xd8,0xd6,0x43,0xae,0x35,0x8c,0x02,0x2a,0x84,0xea,0x71,0x3c,0xc6,0x03,0xcb,0x46, +0xd5,0x4b,0xfb,0xe1,0x53,0xf0,0x6d,0xda,0xea,0xfb,0x81,0x14,0xfd,0x85,0x6d,0x00,0x2f,0x35,0x65,0xdc, +0xe0,0x9a,0xf5,0x5a,0xac,0x38,0x7c,0xce,0xa3,0x02,0x82,0x4d,0x89,0x21,0x65,0x29,0xa6,0xec,0x2b,0x45, +0x78,0x6b,0x99,0x30,0x07,0x5f,0x8e,0x14,0x00,0xd2,0x9b,0x22,0x5e,0x2f,0xb2,0x20,0xf7,0x79,0x1f,0x0a, +0xe8,0x58,0xa8,0x16,0x06,0xfc,0x20,0xc1,0xd2,0xe2,0x57,0x9b,0x5c,0x04,0xb7,0xce,0x43,0x27,0x68,0x4b, +0xa2,0xc5,0x91,0x17,0x6e,0x6c,0x10,0x84,0xb7,0xde,0xd3,0xe3,0xbe,0x79,0x9c,0xc7,0x1f,0x93,0x6c,0x5d, +0xa8,0xc6,0xd7,0xbe,0xfd,0xf7,0xae,0x44,0xdb,0xad,0x4f,0x8f,0x04,0xdc,0x20,0xb8,0x65,0x25,0x56,0x97, +0x7c,0x0b,0xf6,0x72,0xf8,0xd3,0x10,0x1e,0xf9,0xf1,0xf4,0xc1,0x8c,0x28,0x05,0x46,0xc0,0x8d,0xa7,0x5f, +0xf0,0xdf,0x2f,0x67,0x0c,0x98,0xdd,0x4c,0x0a,0x36,0x8d,0x67,0xe0,0x91,0x84,0x8b,0xa1,0x0f,0x61,0xf4, +0x8e,0x0b,0x36,0x06,0xf7,0x2b,0x15,0xc1,0x17,0xb4,0x54,0x44,0x3b,0x76,0x67,0x5d,0xea,0x91,0x40,0x9c, +0x54,0x30,0x73,0xf9,0x95,0xce,0xe9,0x81,0x37,0x51,0xb5,0x53,0x8b,0xd9,0x8d,0x19,0x53,0x0d,0x75,0x0d, +0xfb,0x2e,0x7e,0x26,0xa8,0x31,0x2e,0xbf,0xa2,0x54,0x87,0x5e,0x70,0x74,0xdf,0x75,0xa0,0xaf,0x92,0xbc, +0x18,0xda,0x37,0x5b,0x2c,0xf4,0x1d,0x62,0x22,0x50,0x7a,0xfe,0xf6,0xaf,0x33,0xaa,0xfd,0xdf,0x5a,0x09, +0x02,0xfc,0x80,0xbc,0xa9,0x15,0xb8,0xd5,0x9a,0xc0,0xae,0x65,0xd3,0x43,0xe9,0x00,0x5a,0x38,0x32,0xc6, +0xed,0x8b,0x78,0xc8,0x5d,0xa0,0x4c,0xff,0x91,0x89,0x04,0xf2,0xe2,0xf6,0x4c,0x62,0xb6,0x4e,0xac,0xf5, +0x78,0x40,0xdb,0xf0,0x79,0xac,0xcf,0x2f,0x86,0xae,0x78,0x47,0x47,0x4a,0x6f,0xe4,0xc9,0x4d,0xaa,0xa1, +0x0d,0x5c,0x62,0xb4,0x8d,0x21,0xee,0xa0,0xf4,0x06,0xfa,0xda,0xe3,0x81,0x19,0xcd,0x04,0xae,0x59,0xf7, +0x61,0x89,0x26,0x53,0x69,0xa9,0xf5,0xc4,0x1e,0xad,0x07,0xb0,0x03,0xf3,0xcf,0xd5,0x14,0x82,0x9e,0xf4, +0x8f,0xa5,0x8e,0xdd,0xa6,0xde,0x22,0x2d,0x6a,0xc7,0xe4,0xea,0xd5,0xc4,0x50,0xff,0xe5,0x98,0x84,0x10, +0x39,0xb2,0x7a,0xb7,0x5d,0xaf,0xdf,0x95,0xef,0x48,0x1d,0x30,0xda,0xd6,0xe7,0xb9,0x3f,0x57,0xda,0x6a, +0xfa,0xab,0x94,0x78,0x50,0x40,0xa2,0x5b,0x7f,0xef,0x8e,0xe9,0xa7,0x90,0x96,0x3a,0xf6,0xb5,0x79,0x25, +0x71,0xb2,0x6e,0x68,0xf8,0xfe,0x50,0xd8,0x7a,0x27,0x4a,0x14,0x8c,0xe8,0xfc,0xc6,0xea,0xf5,0x6d,0xbf, +0x0b,0xf3,0x58,0x83,0x51,0x87,0x42,0x0e,0xb8,0x89,0x05,0x87,0x20,0xd6,0x4a,0xd9,0x04,0x8c,0x20,0x28, +0xf0,0xa0,0x07,0x04,0xe2,0xac,0x8f,0x6d,0xdd,0x91,0x47,0x13,0x00,0x1f,0xd3,0x3e,0xd2,0xd3,0xb7,0x3d, +0xbe,0xfd,0x59,0xdd,0x12,0x9d,0xc5,0x91,0x83,0xcc,0x54,0x23,0xee,0xc9,0xb9,0x5f,0xbd,0xb4,0x5f,0x3c, +0x24,0x3a,0xd2,0xb9,0x67,0xbf,0x93,0x19,0x35,0xc8,0xf5,0x74,0x94,0xa2,0xfe,0xad,0x92,0xb8,0x0c,0x96, +0x62,0x26,0x52,0x14,0x63,0x4b,0x64,0x05,0xa1,0xd7,0xcc,0x75,0x13,0x6a,0xd4,0xeb,0x4c,0x58,0xbe,0xcc, +0xcc,0x56,0x9d,0x79,0xff,0x90,0xb3,0xef,0x3b,0x03,0x87,0xe7,0x6f,0x73,0xbf,0x69,0x60,0x77,0x87,0xbc, +0xbd,0x30,0x4d,0x57,0xcd,0x7b,0x80,0x4c,0x43,0x7d,0x6d,0x3f,0x1f,0x7c,0x01,0x6b,0x2b,0x47,0xa9,0xe8, +0xd9,0x0f,0xdb,0x8e,0xda,0xa2,0x3a,0x28,0x99,0xb4,0x67,0x4d,0xaf,0x67,0x73,0x15,0x77,0x5a,0x6a,0xfb, +0x0b,0x1f,0xd6,0xac,0x19,0x23,0xbf,0x3b,0xd6,0x89,0xe7,0x74,0x1c,0x03,0x1f,0xeb,0xec,0xc9,0x27,0x78, +0x84,0xee,0xb2,0x9f,0x86,0xfd,0xeb,0x9a,0x08,0xe5,0xc2,0xbf,0x61,0xcf,0xe0,0x73,0xf7,0xa3,0x6d,0x73, +0x3a,0xbe,0x18,0xcb,0xc5,0x8a,0x5a,0xb5,0x0a,0x57,0xd3,0x0b,0x11,0x81,0x17,0x93,0xd5,0xee,0xf5,0xf7, +0x29,0x40,0xc3,0x57,0x4d,0x12,0x98,0xb2,0xbf,0x0c,0x2f,0xa8,0xa7,0x10,0x3f,0x37,0xe4,0xa0,0x85,0xbd, +0x4b,0x00,0xaf,0x5a,0xad,0xd9,0x9a,0xf5,0x4f,0x85,0x5c,0x86,0xd3,0x68,0xf2,0xd1,0x3a,0xf0,0x83,0x8f, +0x55,0xbc,0x26,0x46,0xbe,0xbc,0xd2,0x95,0xfb,0xe8,0x9f,0x53,0xe5,0xd8,0xba,0x7c,0x65,0x5b,0x97,0xaf, +0x1a,0xd6,0xe5,0xab,0x86,0x75,0x39,0x4c,0xc5,0x63,0x01,0xda,0x5a,0x84,0x75,0x73,0x71,0xea,0x90,0x05, +0xae,0x8e,0x66,0xfe,0x0a,0x57,0x1f,0x2d,0xfd,0xee,0x74,0x31,0xa3,0xde,0xe8,0xf7,0xe9,0xf1,0x8a,0xfe, +0xa7,0x5e,0xa1,0xdc,0x29,0x7d,0x38,0xa2,0xc5,0x79,0x09,0x3b,0x2d,0xda,0xe3,0xb4,0x21,0xfc,0xca,0x92, +0x7b,0xf7,0xfb,0x37,0x94,0x9e,0x79,0xcd,0x5b,0x94,0x0c,0x0b,0xfc,0x85,0x7f,0x33,0x53,0x56,0x78,0x86, +0x6e,0xb9,0xa2,0x99,0x0c,0x0f,0xb6,0xff,0x91,0x56,0xc1,0xb2,0x99,0xdd,0xba,0x25,0xa4,0xe8,0x1f,0x35, +0x04,0x87,0xc4,0x1d,0x03,0xde,0xe3,0x01,0xef,0xd5,0x06,0x9c,0xc8,0x78,0x6a,0x29,0x65,0x85,0x76,0xfc, +0x67,0x6d,0xd0,0xbd,0x73,0x43,0x67,0x29,0x34,0xb3,0x88,0x8d,0x68,0xf6,0xad,0x9b,0x41,0x98,0x70,0x23, +0x68,0xa5,0xdf,0xfc,0x25,0xa7,0x8b,0xd1,0xfe,0xfe,0xcd,0x41,0xfe,0x30,0x1c,0x6d,0xb7,0x1d,0x07,0xaf, +0xe5,0xe8,0x4c,0xbc,0xcd,0x8a,0x49,0xb5,0x82,0xbb,0xe8,0x31,0x44,0xa2,0xcf,0x14,0xa6,0x62,0xa3,0x65, +0x16,0x11,0x41,0xbb,0xb5,0xd2,0x9c,0x13,0x99,0x2f,0xdf,0x0b,0x89,0x5f,0xe1,0xb7,0xbd,0x9f,0xc1,0xe4, +0xd9,0x0b,0x32,0x63,0x93,0x08,0xec,0x8c,0x69,0xec,0xc3,0xf3,0xdd,0x4f,0x67,0xbe,0x5d,0x54,0xc3,0xc4, +0xcf,0x6d,0x7a,0x35,0x4c,0x2c,0x63,0xf7,0x9a,0xc6,0x1f,0xe6,0xd1,0x19,0x37,0xc8,0xc6,0x0b,0x67,0x6f, +0x1a,0xe2,0x91,0x00,0xc6,0x95,0x4c,0x23,0xa6,0x77,0x72,0xd8,0x6d,0x0b,0x8e,0x2b,0x9e,0x6c,0xbd,0xae, +0x13,0x15,0x59,0x8d,0x38,0x4a,0x4c,0x90,0x81,0x32,0x94,0x9e,0x09,0x6e,0xd3,0xac,0x0c,0x72,0xb7,0x7d, +0x7c,0xc2,0xa1,0x47,0x54,0x41,0xdf,0x75,0xf8,0x05,0x56,0xbe,0xe6,0xe8,0x8d,0x7a,0x13,0xea,0x0e,0x0c, +0x70,0x3b,0x48,0xb4,0x7c,0x20,0x67,0x43,0xfc,0xa2,0xb2,0x4f,0x2c,0xd8,0x36,0x1d,0x31,0xc9,0xc4,0xe3, +0x20,0x66,0x70,0x3c,0x17,0x01,0xba,0x10,0x05,0xa3,0xd6,0x14,0xfa,0x3a,0xab,0x8e,0x61,0xa6,0x65,0xfc, +0x04,0x3b,0x28,0x72,0xce,0xd0,0x4f,0xfc,0x90,0x6f,0x7b,0xa9,0xcc,0x68,0x28,0xe1,0x68,0x00,0x1a,0x2d, +0x6c,0x1e,0x9c,0x0d,0x49,0x86,0xc5,0x9f,0x63,0x7a,0x2a,0xfe,0xbc,0x3b,0x8f,0xb8,0x45,0xff,0x58,0x48, +0x40,0x86,0x09,0x2d,0x6d,0xfe,0x86,0x5d,0xb8,0xa0,0x4b,0x00,0xab,0xb5,0xd9,0x80,0x03,0xa9,0x4e,0x39, +0x31,0x49,0xc7,0xca,0x8e,0xd2,0x8b,0xee,0x22,0x57,0xda,0x67,0x94,0xa9,0x83,0x1d,0x33,0x97,0x3f,0xe7, +0x79,0xeb,0xb7,0xab,0xd8,0x38,0x18,0x5a,0x9e,0x75,0x08,0x38,0xca,0xda,0xcf,0xd7,0x93,0x72,0xc8,0x39, +0x35,0xfd,0x64,0x6f,0xae,0x96,0x01,0x5e,0x48,0xf9,0xf5,0x77,0xf2,0xdc,0x98,0x99,0x86,0x69,0x2b,0x40, +0x2c,0xfb,0xb1,0x71,0xb0,0xd9,0xaa,0xdd,0x7c,0x5a,0xab,0x28,0xa6,0x6e,0x5d,0x7e,0xea,0x35,0xbd,0x1c, +0x2b,0xa7,0xa7,0xf1,0xff,0x42,0x5f,0x95,0x51,0x5e,0x83,0x9f,0x32,0x12,0x65,0x9a,0x67,0xd9,0x3c,0x12, +0x71,0x6e,0x75,0x8d,0x25,0x79,0x59,0x53,0x61,0x6b,0x14,0x48,0x2e,0x25,0x59,0x10,0xef,0x96,0x65,0x9d, +0x70,0x56,0x90,0xf1,0x3d,0x23,0x8a,0x18,0x06,0xb1,0xbb,0xde,0x3f,0xaf,0x87,0xba,0x83,0xf8,0xe9,0x39, +0x8a,0x7c,0xc6,0x56,0xb4,0x9b,0xea,0x9a,0x37,0xdc,0x5e,0x4f,0xc5,0xc0,0x82,0x90,0x09,0x4a,0xa5,0xcd, +0xe6,0xdf,0xf4,0x20,0x3a,0x63,0x97,0x4d,0x06,0x52,0x62,0x05,0x46,0xb0,0x74,0x69,0x1f,0xf7,0xb5,0x3e, +0x03,0xb7,0x70,0x5a,0x50,0xa1,0xb2,0xba,0xc8,0xf2,0x3f,0xe9,0x7b,0x59,0xa2,0x0e,0x26,0xe8,0x16,0x38, +0x9e,0x95,0xc2,0x23,0xd0,0xaf,0xb4,0xc2,0x6f,0xeb,0xeb,0xab,0x6e,0x92,0xdd,0xf6,0x89,0xb4,0xef,0x4c, +0x06,0xdc,0x24,0xbf,0xba,0x67,0xfb,0x2d,0x6a,0x21,0x10,0x60,0x6a,0x59,0xfe,0x29,0x09,0x00,0xe3,0x00, +0xea,0x29,0x71,0xfc,0x55,0x17,0x3e,0x8c,0xd4,0xa1,0xa3,0xb6,0x3d,0x73,0x36,0x48,0x00,0x00,0xd6,0x00, +0x5f,0x32,0x8c,0xe3,0x0e,0x7c,0x30,0x15,0xad,0x4c,0x77,0x2a,0x70,0x1f,0xd0,0x83,0x5d,0xa9,0x2f,0xbb, +0x52,0x8b,0xb9,0xe2,0x7f,0x73,0xa0,0x2c,0x27,0x56,0x3d,0x69,0xac,0x47,0x25,0x0c,0xb9,0x6f,0xba,0x50, +0x74,0x9a,0x59,0xed,0x2a,0x53,0x85,0x59,0xab,0xf2,0x87,0x91,0x14,0x73,0x0e,0x0c,0x96,0xd8,0x19,0x7a, +0x0f,0xb1,0x0f,0xd4,0x37,0x4d,0xa9,0xa0,0x06,0x2b,0x9b,0xbb,0x2d,0xbe,0x0f,0x5e,0x6f,0x9e,0xe0,0x94, +0xcd,0x1b,0x07,0xa0,0x4a,0x50,0x0e,0x0e,0x91,0x24,0xfe,0xad,0x91,0xa0,0x92,0x3b,0x4f,0xd3,0xe3,0xd1, +0x24,0xed,0x97,0x41,0xca,0x09,0x89,0xb1,0x6f,0xe5,0x65,0x99,0xc5,0x8f,0xd3,0xe3,0x72,0x9c,0x22,0xd6, +0x4e,0xdc,0x34,0x49,0x00,0x10,0x07,0xf1,0xf9,0x77,0x7c,0x7d,0xf8,0x07,0x5f,0x2f,0x9b,0xcd,0xa8,0x41, +0xbb,0x84,0xa6,0xa2,0xe3,0xc1,0x00,0xa4,0xcb,0x58,0xe7,0x92,0xd7,0x72,0xb9,0xf8,0xb3,0xb9,0xf4,0xfb, +0x39,0x55,0xa7,0x33,0x13,0x36,0xc2,0xd1,0x73,0x9b,0x18,0x9b,0xd0,0x9a,0xe9,0xbf,0xb1,0x68,0xed,0x7a, +0x2f,0x49,0x6f,0xf3,0x68,0x91,0x64,0x80,0xcc,0xe2,0x75,0x7f,0x96,0xdd,0xe0,0x9a,0x18,0xfd,0x18,0xbf, +0x2b,0x62,0x3e,0xaf,0xb3,0x7c,0x81,0xeb,0xe4,0x2a,0xba,0xc0,0xc3,0xad,0x57,0x91,0x53,0xd7,0xb3,0xb0, +0x70,0xaf,0xbd,0x2a,0xb7,0x62,0x7d,0x76,0x95,0x40,0x1e,0xe5,0xe7,0x31,0x91,0x3e,0xed,0xe4,0x6b,0x24, +0xd7,0xe7,0x97,0x85,0x40,0xa8,0x0d,0x9a,0x8a,0xaa,0x9a,0x35,0xea,0x89,0xf9,0xf5,0x95,0xff,0x8e,0xa7, +0xd6,0x87,0x18,0xc2,0xd7,0xb0,0x4d,0xec,0x35,0xe2,0xa0,0x84,0x3f,0x6a,0xd6,0x9f,0xf1,0x13,0x8c,0xee, +0x6b,0x14,0xcc,0x8d,0x38,0x55,0x02,0x83,0x12,0x05,0xb1,0x66,0xb7,0x39,0x14,0xaf,0x65,0x65,0xe3,0x82, +0x38,0x21,0x62,0xe6,0x7a,0x40,0xbd,0x50,0x18,0x1f,0x85,0xc7,0xc8,0x69,0x1c,0x23,0x51,0xa3,0x9b,0x26, +0x90,0xa4,0x28,0x76,0x76,0xb3,0x29,0x3c,0x5f,0x79,0xda,0x64,0x94,0x23,0x1c,0x99,0x80,0xa6,0x46,0x59, +0x2c,0xab,0x2c,0x58,0x3c,0x9b,0x18,0x78,0xd9,0x4c,0xd2,0x2b,0x8f,0xaa,0x5c,0xd0,0xac,0x38,0x57,0x9b, +0xdc,0xda,0x63,0x63,0x84,0xaa,0x58,0xc3,0x42,0x4b,0x1b,0x18,0x7b,0x47,0x77,0xa3,0x87,0x3a,0x2f,0x80, +0xc1,0x63,0x55,0x7b,0x49,0xb7,0xd2,0x1a,0x5c,0x41,0x2e,0xcc,0xd8,0xba,0x7f,0x50,0x8f,0xc8,0x57,0x6a, +0x96,0x20,0xd9,0x55,0x3c,0x83,0xa8,0x08,0x62,0x94,0x89,0x2e,0x3e,0x31,0x38,0xec,0xc5,0xc4,0x88,0xc9, +0xbc,0x00,0x21,0x15,0xd6,0x9e,0xe9,0xfd,0xad,0xff,0x5d,0x28,0x3a,0x42,0x9a,0x71,0x0d,0xb5,0x45,0xcd, +0x0c,0x89,0x11,0x91,0xbe,0xb7,0x86,0xb3,0xa7,0xd8,0x55,0x91,0xe6,0xbc,0x13,0x88,0x92,0xb0,0x1e,0x62, +0x2c,0x0b,0x6f,0x38,0xd0,0x32,0xb5,0x8c,0xa9,0x53,0xed,0xb6,0x1a,0x54,0xf1,0x75,0x29,0x53,0x2a,0xed, +0xcc,0x4d,0xd8,0x11,0x2d,0x33,0x32,0xdf,0x30,0xb6,0xac,0x93,0x9e,0x18,0x61,0x70,0xd8,0xa6,0x6f,0x9b, +0x10,0xb8,0x5d,0x88,0x62,0x00,0x6d,0x98,0x87,0x70,0x66,0xa2,0x8a,0x86,0x4b,0x53,0x0a,0x34,0x31,0x36, +0x86,0x0d,0xa8,0x9b,0xb9,0xee,0x57,0xe6,0xc9,0x89,0xad,0x23,0x52,0x76,0x6e,0xc9,0xea,0x50,0x4b,0x45, +0x96,0x1e,0xd1,0x36,0xfd,0xe2,0x09,0x36,0x5c,0x37,0x92,0x40,0x12,0x6a,0xaf,0xfe,0xba,0x81,0x05,0xf1, +0x1a,0xce,0x6b,0xc6,0xf3,0x2f,0x63,0xc1,0x2a,0xbb,0x1a,0xb2,0xaa,0x3a,0x74,0x8d,0xb5,0x9e,0x1b,0x69, +0xa5,0x9a,0x3d,0xfb,0x14,0x3d,0x5b,0x32,0xb8,0x36,0xc2,0xb4,0xfa,0x3d,0xcb,0x7b,0x68,0xc9,0x92,0xc7, +0x1a,0x9d,0xe6,0x57,0xae,0x7c,0x99,0x9e,0x5d,0xa2,0xa8,0xd1,0x8d,0xdb,0x8a,0xfd,0xdd,0x02,0x27,0x79, +0xe5,0x60,0x60,0xc0,0xcb,0x47,0x86,0xa7,0x82,0xff,0x34,0x43,0xde,0x64,0xf0,0x72,0xb6,0x1d,0x18,0x11, +0x46,0x48,0x5a,0x21,0xce,0xeb,0xeb,0x50,0x9a,0xa1,0x38,0x88,0x9c,0xf6,0x9a,0xbb,0x5a,0xa3,0x11,0x57, +0x32,0xed,0x63,0xda,0x89,0xbc,0xa2,0x00,0x66,0x32,0xad,0x35,0x49,0xfc,0x43,0xb4,0x2e,0x37,0x56,0x11, +0x1c,0x72,0xa3,0xd7,0x01,0x6e,0x93,0x03,0x32,0x47,0xf1,0xf8,0x8a,0xfa,0x5f,0x6e,0x36,0xc4,0x43,0x21, +0xe0,0xa3,0x9b,0xb3,0x92,0x8c,0x66,0x10,0x62,0x82,0xdf,0x0d,0xfe,0xc2,0x60,0x66,0xb6,0x36,0x28,0x7c, +0xaf,0xc1,0x44,0x1c,0x4f,0xeb,0x82,0x94,0x41,0x06,0x9b,0xa4,0xbe,0xf7,0xdb,0x1a,0xa5,0xb0,0xd7,0xfb, +0xcd,0x7f,0xe9,0x7a,0x7e,0x5d,0x43,0xdf,0x6d,0x8b,0x7b,0x78,0x87,0xbd,0xc3,0x1d,0xd8,0x2a,0x6d,0x5b, +0x29,0x43,0x11,0x76,0x99,0x4c,0x7d,0x26,0xf6,0x51,0x8e,0xef,0x7c,0x26,0x74,0x46,0x45,0xe4,0x35,0x08, +0x0c,0xa4,0xc7,0xe6,0xb7,0xd9,0x64,0x42,0x6d,0x6c,0x98,0x3e,0x96,0xa8,0x56,0x1b,0x89,0x74,0xe5,0x37, +0x8f,0x4b,0xec,0x11,0x69,0xa5,0x00,0xaa,0xeb,0x17,0x7d,0xc7,0x48,0xee,0xea,0xa4,0xca,0xe4,0x30,0x38, +0x12,0xeb,0xea,0x4a,0x43,0xd9,0x34,0xc1,0xe9,0x6c,0x15,0x93,0x54,0x07,0x6c,0x80,0x67,0xb5,0xa3,0x6e, +0x7c,0xc5,0x2b,0xc0,0x61,0xac,0x4a,0xe7,0x0f,0x9a,0x2c,0x49,0x75,0x9b,0xd5,0x87,0x9d,0x4d,0xa4,0x3d, +0xe0,0x0f,0xc9,0xb9,0xaa,0x1b,0xec,0xd8,0xb8,0xbb,0x86,0x4b,0x41,0xfa,0x35,0x6a,0x54,0x19,0x50,0x49, +0xa5,0xca,0xb8,0x55,0x1f,0x3e,0x82,0xc7,0xf5,0x8e,0x07,0xce,0x27,0x43,0x54,0x37,0x3a,0xfa,0x3f,0x52, +0xf0,0x22,0x9e,0x23,0x54,0xd6,0x1c,0x05,0x35,0x5d,0x84,0x67,0xb1,0xa0,0x86,0xae,0x72,0xba,0xac,0xf4, +0x75,0xfa,0xe1,0xd4,0x09,0x1c,0x85,0x44,0xba,0xca,0x0d,0x59,0x41,0xf7,0x96,0x86,0xd6,0xdc,0x21,0x87, +0xea,0xb9,0x7f,0x21,0xaa,0x48,0x3c,0x55,0xca,0x50,0x41,0x94,0xfb,0xf1,0xd5,0x4b,0x5a,0x13,0x78,0xcc, +0xd7,0xbe,0x15,0x75,0x14,0x0f,0xf5,0x35,0x57,0x81,0x75,0xd1,0xc6,0xf0,0xe1,0x4c,0x3f,0x61,0x8d,0xfb, +0x75,0xdb,0x2d,0xa8,0x22,0xf0,0xf8,0xc8,0x33,0x1a,0xf3,0x74,0x0c,0x1e,0x08,0x21,0x08,0x05,0xc6,0xcc, +0xd2,0xbb,0x6a,0x81,0x63,0xf5,0x48,0xc2,0xe9,0x00,0x9d,0x95,0x8d,0x4f,0x0b,0x98,0x39,0x28,0x2c,0x47, +0x85,0x10,0x68,0x54,0xbe,0xf9,0xb6,0xed,0xdb,0x53,0x51,0xba,0xd3,0x59,0x07,0xab,0xd5,0x04,0x1d,0x8a, +0x7b,0xcc,0x8e,0xa4,0x3a,0x6b,0x5b,0x83,0x1a,0x9b,0x9e,0xe7,0x4d,0xb8,0xb6,0xd1,0xc3,0xa7,0xe7,0xe0, +0xe7,0x63,0x41,0xaf,0x9e,0xfe,0x7c,0x7a,0x70,0x3a,0x7a,0x18,0xb0,0xdb,0x67,0x79,0x9a,0x9f,0xa6,0xa7, +0xe7,0xb3,0xfb,0xde,0xb4,0x7e,0x7f,0x7a,0x30,0x79,0xe8,0x4e,0x82,0x63,0x4a,0x7b,0xf8,0x70,0xc3,0xee, +0x5e,0x1f,0x90,0xc9,0x70,0xfa,0x73,0xf0,0xd9,0xe9,0xf4,0x74,0xe8,0xcf,0xee,0xdf,0x3b,0x18,0x9b,0x00, +0xb9,0x3b,0xbc,0xae,0x20,0x1a,0xb2,0x58,0x7e,0x17,0x4e,0x9b,0x69,0x46,0xfb,0x58,0x5f,0x19,0xa7,0x8a, +0x84,0x41,0x6f,0xef,0x87,0x35,0x17,0x94,0x89,0x9a,0x78,0x4d,0xcb,0x13,0xda,0xce,0xe9,0xd0,0x42,0xdc, +0xa9,0xe9,0x2c,0x68,0xa4,0x71,0x79,0x8e,0x02,0xf2,0xd1,0x0e,0xd8,0x61,0x6d,0xb6,0xb5,0x2e,0x65,0x6c, +0x68,0x1b,0xc6,0xd8,0xbd,0x65,0x35,0x72,0xb7,0x17,0x8d,0x8d,0xd2,0x9c,0xf0,0x9d,0x0d,0x8f,0x58,0x29, +0x99,0xec,0x20,0x38,0x35,0xd8,0x6a,0xcc,0x11,0x15,0x4f,0xd8,0xe2,0xcc,0x24,0x88,0xe2,0x68,0x5c,0x1e, +0xe7,0x8c,0x85,0x23,0xe1,0x34,0x8c,0xb5,0x5c,0xc2,0x38,0x8b,0xc0,0xef,0xae,0x20,0x53,0x3c,0x63,0x8c, +0x54,0x2f,0x02,0x62,0x3e,0x3b,0x2b,0xd5,0x39,0x2c,0xb4,0xa4,0x5c,0x2c,0x6b,0xf0,0x87,0x87,0x93,0xda, +0xca,0x84,0x60,0x35,0x35,0x3a,0xc7,0x2e,0x05,0x5c,0xbd,0xa4,0x4c,0x81,0x76,0x33,0x21,0xd5,0x3b,0xf4, +0x18,0xdf,0xb7,0x93,0x2d,0xbf,0xf3,0xc3,0x91,0x80,0xd1,0x76,0x89,0x0a,0x7a,0x2a,0x65,0x5b,0xe5,0x47, +0xe4,0x95,0x39,0xc2,0x27,0xdc,0xab,0x81,0xa9,0x87,0xf6,0x4a,0x51,0x60,0x88,0xef,0x62,0xff,0x3b,0xe5, +0x7d,0x78,0x5a,0xdc,0x77,0x8f,0xa7,0xa7,0xd7,0xa7,0xef,0x67,0xfd,0x87,0xde,0xf4,0xe7,0x87,0xb3,0xfb, +0x9b,0xcf,0x6c,0x07,0xc4,0x27,0x71,0x58,0x01,0xc5,0x77,0xcf,0x67,0x01,0xc4,0xec,0xd5,0x46,0xd8,0x50, +0x94,0x54,0x56,0xbb,0xae,0xbc,0x41,0xe4,0x74,0x62,0x89,0x36,0x1b,0xc1,0x1e,0x9c,0x87,0x72,0x5d,0x39, +0xe8,0xcd,0x58,0x4c,0x26,0x54,0x66,0xf8,0x60,0x32,0x15,0xdb,0x10,0x96,0xb0,0xce,0x82,0xef,0x0c,0xde, +0xa1,0x0f,0x23,0xcb,0x5e,0x3e,0x45,0x7a,0x4d,0x11,0xf6,0x58,0xae,0x29,0x80,0xf2,0x13,0x90,0xe7,0xa9, +0xa7,0x86,0xdc,0x0b,0x5a,0x20,0xdf,0xa5,0x79,0xc7,0x46,0x57,0x1a,0x37,0x91,0xc8,0x49,0xe2,0x66,0x8a, +0x32,0x4a,0xe7,0xa8,0xf3,0x45,0x3c,0xc1,0xb2,0x0d,0x24,0xa6,0xb7,0x81,0x7c,0xc7,0x1d,0x63,0x1e,0xe3, +0x08,0xe6,0x8f,0xfd,0x52,0xab,0xbd,0x78,0xb5,0x76,0x20,0x3a,0xa6,0x31,0x0f,0x31,0xed,0x3f,0x2a,0xec, +0x06,0x8a,0xec,0xc0,0xb2,0x2e,0x05,0x0d,0x55,0x85,0x7f,0xac,0x63,0x89,0x32,0xbe,0x79,0x3e,0xf3,0x26, +0xea,0x82,0x45,0xee,0xaa,0x71,0xac,0x6f,0x25,0xe2,0x0e,0x4f,0x6c,0xa4,0x74,0xbd,0xe7,0x26,0xc4,0xc8, +0x36,0x5d,0x59,0x10,0x6a,0x98,0x51,0x8f,0x24,0x67,0xa2,0xf7,0x13,0xdf,0x5a,0xd9,0xe1,0xa1,0x0a,0x22, +0x55,0x53,0x89,0x73,0x03,0xcd,0x07,0x71,0xfd,0x03,0x59,0x9d,0x41,0x13,0x02,0x75,0x52,0x1d,0x29,0x43, +0xf8,0xe0,0x7f,0x9a,0xa8,0x5f,0x9e,0xb0,0xb4,0xc6,0xf9,0x13,0x83,0xd7,0xeb,0x4a,0xae,0xde,0x76,0xfc, +0xc4,0x0e,0x16,0xc0,0x53,0xd2,0x7f,0x87,0x0b,0x37,0x55,0xf6,0x63,0xbf,0xaa,0x29,0x2d,0x94,0x6b,0xb1, +0x81,0x82,0x92,0x6e,0xbf,0x4f,0xcb,0x64,0xb9,0x61,0x47,0x89,0x03,0xff,0x51,0x1c,0xde,0xb2,0x3e,0x8d, +0x92,0xb0,0xc0,0x41,0x45,0x60,0xc1,0x35,0x3b,0x63,0x42,0xe0,0x40,0xdf,0x41,0x68,0x30,0xae,0x6f,0x81, +0x90,0xdc,0xb7,0x05,0x66,0x17,0xb1,0xae,0xa1,0xcd,0xf9,0xd9,0x8b,0xbc,0x7b,0x6b,0x63,0x34,0x78,0xf8, +0x4e,0x1e,0xa7,0xe3,0xb8,0xbd,0xbd,0x49,0x8c,0x00,0x0e,0x28,0x59,0x6d,0x6f,0x5b,0x7f,0xbe,0xcc,0x8a, +0xd8,0x46,0xd3,0x6f,0x85,0xa1,0xb5,0x77,0x63,0xf0,0xfe,0x50,0x1d,0xb7,0xb6,0x62,0x75,0x32,0xf3,0xaa, +0xad,0x36,0x0d,0x15,0x9d,0xe8,0x38,0x61,0x2c,0x96,0x6a,0x27,0x85,0xb9,0x26,0x04,0xd3,0x38,0x66,0x1b, +0x56,0x9e,0xec,0x96,0x54,0x89,0x3d,0x0f,0x01,0xdd,0x13,0x4d,0x22,0x11,0xa4,0xd3,0xe6,0x09,0x25,0x79, +0xd3,0x07,0x67,0xd7,0xf1,0xc5,0x98,0xc9,0xde,0x6d,0x66,0x24,0x5a,0x35,0x5e,0xbe,0xb9,0x5f,0x56,0xda, +0xae,0xfa,0x86,0x4d,0x8c,0x75,0xc6,0xe2,0x50,0xaa,0x40,0xa7,0x60,0x78,0xd2,0x81,0xde,0xac,0xb1,0x97, +0xb9,0x57,0x7c,0x35,0x9f,0xbd,0x0a,0x92,0x99,0xb7,0x66,0xbd,0x9d,0x60,0xbb,0x0a,0xd4,0x3e,0xc2,0x3b, +0x97,0xba,0xa8,0x21,0xea,0xc9,0xc0,0x13,0x5d,0x4d,0x3c,0x27,0xe6,0x13,0xec,0xfb,0xf5,0x2e,0x1c,0x30, +0xcc,0xcf,0x62,0xd1,0x18,0xc5,0x5d,0xc7,0xa3,0xdd,0xba,0x7a,0xb4,0x09,0x84,0x38,0x05,0xd8,0x2e,0x7f, +0xcf,0xe8,0xbf,0x94,0xeb,0x37,0xcd,0x38,0x15,0x76,0xbe,0xf4,0xde,0xd5,0x60,0xd9,0x8d,0xe8,0x07,0x41, +0xe3,0x5e,0xcf,0x5b,0x60,0xdf,0x6c,0x2b,0x48,0xdf,0xdb,0x0e,0x29,0xb6,0x16,0x1d,0xb7,0x0d,0xe1,0xb5, +0x81,0xaa,0xbd,0x1f,0x2a,0x33,0x49,0xb5,0x4a,0xbb,0xaa,0x7a,0xcd,0x21,0x37,0x2d,0xeb,0x33,0xcf,0x24, +0xe7,0xc5,0xbc,0x0b,0x4d,0xbb,0xf5,0x1d,0x5b,0xff,0xa6,0x4d,0x29,0xb4,0x65,0xe3,0x5c,0x33,0x02,0xf0, +0xb6,0xb2,0xf4,0x77,0xa5,0x6d,0x1a,0x3c,0xa8,0xbc,0x69,0x68,0x77,0x37,0xa2,0x23,0xff,0x3b,0xd3,0xef, +0x28,0xe3,0x4f,0x34,0xdb,0x2e,0x89,0xdb,0x8d,0xac,0xfe,0x4c,0x77,0x35,0xcd,0x38,0xf0,0x6d,0x21,0x37, +0x9d,0xa3,0x73,0x12,0xc3,0x5a,0xba,0xea,0x65,0x04,0x9b,0xf0,0x2c,0x26,0x92,0x43,0x96,0x98,0x3d,0xb6, +0x3b,0x03,0x9b,0xe9,0x44,0x6a,0xbd,0x0b,0x77,0xa4,0x86,0xba,0xd6,0x49,0xce,0x73,0x76,0x39,0x9e,0x98, +0x98,0x59,0xfa,0x3c,0x0d,0x5c,0x4e,0x50,0xc6,0x57,0xab,0x65,0x54,0xc6,0x1c,0x36,0x37,0xae,0x42,0x6b, +0x41,0xb8,0x55,0x1d,0xd8,0xb4,0x1d,0xc6,0xb6,0xfb,0x3c,0xdb,0x93,0xd5,0x16,0xa1,0xa0,0x5c,0xc7,0xb3, +0x8a,0xd0,0xb1,0xb0,0xb7,0xec,0x08,0x2b,0x65,0x45,0x36,0x3a,0xdc,0xcb,0x35,0xdb,0x9c,0x2f,0x45,0xf8, +0x93,0x7a,0x00,0x31,0x6a,0x6d,0x38,0x39,0x43,0xce,0x19,0x26,0xc1,0x85,0xf5,0x8f,0x67,0x1f,0x9e,0xc0, +0x15,0x72,0x1f,0xc5,0x6c,0x29,0x50,0xdf,0x00,0x12,0xcf,0xff,0xd5,0x12,0xd4,0x24,0x74,0x74,0x22,0xba, +0x11,0xd8,0x6b,0xbf,0x19,0xb5,0xc5,0xd3,0xe4,0xde,0x6f,0xc0,0x90,0xf8,0xb9,0xc6,0xc9,0xf4,0x0f,0x2e, +0x70,0xbc,0x3d,0xa6,0xed,0xed,0x8c,0x12,0x17,0x35,0x23,0xdb,0x38,0xec,0xda,0x23,0x5d,0x39,0x99,0xab, +0xe8,0xa0,0xea,0xd8,0x35,0x11,0x4d,0xe4,0xb4,0x99,0xc2,0x25,0x61,0x70,0xe8,0x2f,0xc3,0xc6,0x89,0x97, +0xc0,0x22,0x36,0x1e,0x66,0x44,0x4e,0x81,0x6b,0x40,0x04,0x4c,0x0d,0x98,0x37,0xc6,0x17,0xea,0xc4,0x89, +0x4c,0x74,0xad,0x7e,0x7f,0x7d,0x6c,0x24,0x70,0x5e,0x36,0x5d,0x1b,0xa4,0x31,0x48,0xff,0x52,0x10,0x4d, +0xa1,0x8e,0x1d,0x8f,0x68,0x91,0xaf,0xd3,0x67,0xd1,0xb2,0x88,0x19,0x0e,0x5b,0x7f,0x07,0x9b,0xc7,0x43, +0x20,0x12,0x5c,0xc5,0x57,0x59,0xfe,0x69,0xb3,0x71,0xf9,0x01,0xb1,0x03,0xbd,0x43,0xa6,0x76,0xb2,0x30, +0x05,0xbc,0x1e,0x07,0x09,0x98,0x87,0xb7,0xb5,0x6d,0xb9,0x32,0x57,0x50,0xf0,0x83,0x65,0x2d,0x6f,0x6a, +0x64,0xa4,0x0f,0xab,0x4a,0x6f,0xbd,0x07,0x82,0xf2,0xb6,0x33,0x3c,0x62,0xca,0xcf,0x6b,0x11,0x01,0x27, +0x7a,0x74,0xf7,0xf7,0xe7,0x50,0xc5,0xd2,0x23,0x18,0x91,0xa9,0x5c,0x03,0xd6,0x07,0x6b,0x36,0xb0,0x3a, +0xc7,0x4d,0x74,0x0e,0x98,0x5f,0xe6,0xc0,0x24,0xf3,0xb6,0x16,0x0e,0xbc,0xaf,0xea,0xba,0xd4,0xb3,0x02, +0xe6,0xbf,0xc0,0x0a,0xee,0x68,0x9a,0xae,0x68,0x85,0x43,0xbf,0x83,0x17,0x1f,0xbb,0x1c,0xaf,0x49,0x05, +0x34,0xa0,0xd6,0xc0,0xc8,0x01,0x07,0x3c,0x0d,0x8d,0x31,0xe7,0x86,0x60,0x32,0x3d,0xe6,0x00,0x64,0x83, +0xc1,0x56,0x17,0xde,0xa4,0x9c,0xcc,0x51,0x6c,0xe5,0x17,0xfb,0x19,0x53,0x0b,0x59,0x65,0xee,0xd0,0x54, +0xc5,0xd6,0x86,0x83,0xf5,0x1a,0x2a,0x7f,0x25,0x15,0xea,0x0a,0x0b,0x15,0x46,0x22,0xb5,0x67,0x8b,0xf1, +0x5a,0xea,0x8e,0x71,0xee,0x65,0x5b,0x1f,0x11,0xb1,0x77,0x67,0x44,0xfc,0x0c,0x64,0xfd,0x92,0x9f,0x2e, +0x1e,0x9f,0x74,0x66,0xd7,0x4b,0x58,0xe7,0xc8,0x21,0x5a,0x1b,0x56,0x41,0x3a,0x5b,0x9e,0x90,0x22,0x77, +0x4f,0x61,0xb4,0xa3,0xb4,0xff,0x13,0x6d,0x05,0x40,0x53,0x60,0x56,0xcd,0x32,0x9f,0x0a,0xb7,0x06,0x15, +0x79,0x77,0xd4,0x75,0x3e,0xd4,0x85,0x36,0x03,0x36,0x59,0xdf,0x75,0xd6,0x37,0xdf,0x1a,0xa0,0xb2,0x79, +0x2d,0x16,0x8e,0x8e,0xab,0xdc,0x61,0xd5,0x30,0x9d,0x3a,0x12,0xa1,0xd6,0xc1,0x59,0x22,0xd1,0x54,0x11, +0x77,0xac,0xda,0x5b,0x5c,0x47,0x16,0x9f,0xe3,0xed,0x7a,0x7c,0x34,0xf3,0xa7,0x8e,0x8a,0x67,0x4b,0xd9, +0x20,0xfc,0x6a,0x33,0x0b,0x6c,0x1b,0x7b,0x3b,0xf2,0xa9,0xbf,0x1b,0xf9,0x3a,0xab,0x85,0x23,0xf9,0x32, +0x00,0xbb,0xef,0x20,0x66,0xeb,0x7f,0x9e,0xed,0xa1,0xaf,0x72,0x42,0xb6,0xf0,0x9e,0x70,0xe0,0x26,0xca, +0x27,0x66,0x12,0xaa,0xf8,0x71,0xed,0xb1,0xc8,0x89,0x26,0xe3,0xb0,0xb0,0x5d,0xd3,0x58,0x62,0xce,0x56, +0xc3,0x23,0x11,0x66,0x5b,0xc3,0xe5,0xb0,0x07,0x8f,0xd3,0x79,0x38,0x0e,0xcb,0xcb,0x38,0x55,0xd0,0x8b, +0x38,0xf3,0x93,0x55,0xad,0x16,0xc2,0x65,0x98,0x1c,0xc7,0xd5,0xa2,0x37,0x91,0xb2,0xed,0x11,0xd5,0x9b, +0x41,0xea,0xef,0x38,0xfe,0x6c,0x4e,0x6e,0x9a,0x4f,0xbf,0x98,0x81,0x6f,0x55,0x57,0xe3,0x6c,0x0a,0x46, +0x76,0xe6,0xb6,0xca,0x07,0x96,0x6e,0x77,0x04,0xb1,0x71,0xac,0xd8,0xde,0x2a,0x5b,0x1d,0x97,0x1a,0x5b, +0xa3,0x0e,0x51,0xed,0x55,0x71,0x7b,0x4b,0x15,0x0f,0x59,0x05,0xec,0x2d,0x75,0x18,0x64,0xd5,0x79,0xa5, +0x0a,0x7f,0x4c,0xe4,0xf9,0x14,0x71,0x55,0xfb,0x0e,0xd6,0x81,0x33,0x93,0x82,0x13,0x8e,0xd3,0x52,0x15, +0x0f,0xa8,0x67,0x04,0xea,0x60,0xb1,0x6e,0x55,0xda,0xd6,0x47,0xaf,0xda,0x76,0xb1,0x82,0x5d,0xab,0x77, +0xf8,0xcc,0x35,0x36,0xbb,0x4d,0xe3,0x2d,0x69,0x72,0x21,0x01,0xb4,0xe6,0xa1,0xb5,0xab,0xb6,0x43,0x81, +0x9d,0x33,0xff,0xe5,0x96,0xc7,0x91,0xa7,0x7c,0x01,0x73,0xd5,0x4b,0xf4,0x29,0xea,0x05,0xbe,0xc9,0x54, +0xca,0xab,0x3c,0x82,0x40,0x47,0x2b,0xaf,0xa0,0x93,0x4b,0xb1,0xc8,0x81,0x4b,0xfe,0xf9,0x80,0xfb,0x62, +0xcd,0x7a,0x3f,0x6f,0x7c,0xce,0x68,0x3a,0x7f,0x36,0xde,0x10,0xc6,0x91,0x27,0x53,0x23,0x66,0xc9,0xb9, +0x37,0x49,0x26,0xe7,0xda,0x2f,0x2d,0x73,0x23,0xd8,0x08,0x53,0xcb,0xd5,0xe5,0x12,0xa4,0x4b,0xe0,0x46, +0xfd,0xbe,0x7f,0x77,0x22,0xf3,0x34,0xb5,0x02,0x5a,0x7b,0xf8,0x36,0x97,0x88,0x95,0x6e,0xa1,0x84,0xcd, +0xc0,0x3e,0x25,0xbe,0xd7,0x77,0x69,0x73,0x4c,0xed,0x28,0xd7,0x1e,0xf7,0x0b,0x68,0xb6,0x15,0xec,0x9a, +0xed,0x79,0x0e,0x17,0xba,0x73,0x57,0xe3,0xe8,0xc7,0x3c,0x93,0xf5,0x0c,0xa7,0x4d,0x6c,0x1e,0xb3,0x15, +0x11,0xa2,0x4f,0xf3,0x7c,0xeb,0x7e,0x45,0x55,0x5f,0x21,0x64,0xf4,0xfc,0xc3,0x49,0x1e,0xcd,0xc1,0x08, +0xf6,0x0f,0x1f,0x86,0x88,0xb0,0x86,0x2a,0x2e,0x3b,0xaa,0x98,0xda,0xc1,0xb6,0x55,0xe5,0xc6,0xe5,0x64, +0x05,0xcd,0x82,0x5d,0x0c,0x31,0x6a,0x4c,0x93,0x49,0x05,0x5c,0xbb,0x94,0x70,0x57,0x3a,0x57,0x7c,0x1f, +0x62,0x8e,0x89,0x9e,0xad,0x4b,0x77,0x85,0xdc,0x31,0x73,0xa2,0x70,0x74,0xe7,0x42,0x46,0x08,0x28,0x9a, +0xfa,0xd3,0x07,0x33,0xe6,0xf7,0x32,0x17,0x51,0x69,0xeb,0xa3,0x9a,0xd0,0xa8,0x06,0x6b,0x3f,0xae,0x8d, +0x05,0x93,0x54,0x77,0x7d,0x55,0x7a,0xc4,0xbd,0xad,0x39,0xe1,0xd1,0x9d,0x09,0x73,0x6f,0x92,0x07,0x4b, +0xe8,0xe2,0xec,0x45,0xa5,0x2e,0xef,0x08,0x50,0x36,0xa9,0x8e,0x1c,0x8e,0x1b,0x9d,0xd0,0x58,0x23,0x20, +0xf0,0xb8,0x41,0xac,0xa4,0x0d,0x22,0x45,0x7a,0xa5,0x84,0xcd,0x34,0x2d,0xbd,0xe9,0x97,0xb3,0x71,0x32, +0x2d,0xb1,0x17,0x11,0x35,0x49,0x75,0xf4,0x0b,0x84,0x23,0x41,0x65,0xed,0x4d,0x38,0xa4,0xad,0x35,0x9d, +0x3e,0x18,0xc4,0x33,0xfa,0x4e,0xbb,0xf9,0xfb,0xdc,0x73,0x74,0x8f,0x43,0xdd,0xf3,0xe5,0xab,0x12,0x6d, +0xc5,0xa1,0x09,0x1d,0x3e,0x84,0x80,0xb3,0xb0,0x63,0x37,0xe7,0x37,0xb5,0xfd,0x06,0x0e,0x0a,0x4a,0xdc, +0x15,0x74,0x9f,0xc4,0xf5,0x8f,0xa8,0xb6,0xfa,0xe4,0x86,0x5a,0xcc,0xf4,0x5d,0xe6,0x89,0x4c,0x91,0xd7, +0x57,0x86,0x78,0x4b,0x44,0xa8,0x5c,0x5f,0xc6,0x5d,0x06,0x5c,0xad,0xe0,0x81,0x88,0x5d,0x9d,0x87,0x3a, +0x76,0x1e,0x9d,0x55,0xed,0xb8,0x41,0xd4,0xc7,0xf6,0x44,0xf2,0x6c,0x9c,0xda,0x1d,0x06,0xaa,0x60,0x91, +0x64,0x4b,0xc5,0x55,0xb3,0xd0,0x87,0x87,0x93,0x76,0x29,0x41,0xea,0x0f,0x06,0x25,0xa8,0x5b,0x3b,0x72, +0x3d,0x76,0x51,0x5a,0x34,0x90,0xb7,0x1e,0x87,0x60,0x7b,0xe6,0xd8,0x41,0xd4,0xc9,0x08,0xc2,0x5a,0xa7, +0xf6,0x33,0xb5,0xd8,0xa0,0xf7,0xf7,0xcd,0xe1,0xcb,0x4e,0x20,0x7c,0xfa,0x02,0xed,0xb6,0x31,0xcb,0x39, +0x8a,0x33,0xfe,0xf2,0xce,0x56,0x05,0xba,0xc9,0xe4,0xd8,0x54,0x2e,0x89,0x6c,0xa6,0x31,0xe7,0xd4,0x3e, +0x8a,0x34,0x25,0x55,0x56,0xd6,0xd6,0x34,0x56,0x3c,0xd5,0x4b,0x16,0x36,0x22,0xf6,0xda,0xe6,0x05,0x71, +0x9a,0x79,0x4a,0x17,0x6f,0xa3,0xf4,0x22,0xde,0xbc,0x45,0x3f,0xc6,0x44,0x3b,0x6c,0xc4,0x65,0x73,0xc3, +0x6e,0xff,0xdf,0xbf,0x7d,0xe1,0xf1,0x9e,0x2d,0xea,0xa1,0xee,0x1d,0x28,0xb4,0x8e,0x9c,0x14,0x28,0x1f, +0x10,0x53,0x67,0xe2,0x7a,0xa9,0x2e,0x87,0xd7,0x51,0x8e,0x00,0x15,0xfb,0xfb,0x2f,0x15,0x1f,0x48,0x27, +0x22,0x8c,0xfa,0x9a,0x69,0x5c,0x15,0xb7,0xd0,0x14,0xb5,0x67,0x8a,0x82,0x29,0x6e,0x49,0x1c,0x51,0x51, +0x44,0x17,0xf0,0x9e,0xe5,0xdd,0x88,0xf9,0xfe,0x8b,0x58,0x64,0xb1,0x4f,0x75,0xd2,0xd0,0x26,0x0f,0x6a, +0xdb,0x91,0xbd,0xfb,0xf2,0xc9,0x54,0xd2,0x69,0xca,0x5d,0xf3,0x3c,0xae,0x4f,0x27,0x25,0x47,0xe5,0x8c, +0xbb,0x26,0xd6,0x73,0x39,0x78,0xe8,0xc9,0x54,0x91,0x3a,0xb3,0xda,0x66,0xd6,0xaa,0x94,0xcb,0xd0,0x11, +0xb2,0x82,0x2c,0x22,0xd5,0x84,0x5e,0x3c,0xf4,0x39,0xfd,0xfb,0x28,0x29,0x03,0x75,0x5d,0x5b,0x29,0xec, +0x75,0xdf,0x1b,0x4d,0x06,0x03,0x9d,0x35,0x27,0xe5,0xc9,0xc3,0x59,0xc0,0x26,0xa9,0xba,0xe3,0x18,0x2f, +0x2a,0xa2,0x71,0xfd,0x93,0x87,0xa3,0xcd,0xe6,0x79,0x5c,0x9b,0xcf,0x69,0xec,0x4f,0x2f,0x20,0xb6,0x55, +0x42,0x33,0x4e,0xcb,0xed,0x0b,0x55,0x3b,0x11,0xac,0xf0,0x6a,0x85,0x00,0x34,0xec,0x3b,0xa4,0x92,0xbc, +0xc3,0x14,0x06,0x60,0x60,0x16,0x2d,0x14,0x93,0x57,0x7b,0x05,0xf0,0xfb,0x16,0x06,0x05,0xdd,0xbf,0x9b, +0xe7,0xd9,0x72,0x39,0xa9,0x0d,0x8d,0x2e,0x96,0x4e,0x21,0xfa,0xa6,0x0d,0x21,0xf7,0xe4,0xf5,0x2b,0x65, +0xfd,0xfd,0x92,0x4a,0x03,0x34,0xc9,0x39,0x63,0xb6,0xb4,0x12,0x0a,0x74,0x1c,0xd0,0xe1,0x31,0xae,0xcf, +0x9a,0xda,0x62,0x1d,0xc8,0x54,0x13,0x3d,0x1d,0x58,0xfe,0x50,0x1c,0x5b,0x51,0x90,0x42,0x9b,0x75,0x25, +0xa2,0x07,0x42,0x00,0xb6,0x7d,0x93,0x28,0xe2,0xcf,0x24,0xaa,0x77,0x41,0xbb,0x70,0x01,0x35,0x18,0x67, +0x6e,0x9c,0x53,0x6a,0xb1,0x3b,0x13,0x15,0xb5,0xbe,0x76,0xea,0x70,0x24,0x53,0xc6,0x59,0x67,0x39,0xb4, +0x09,0x2c,0x08,0x3b,0x18,0x81,0x3f,0xa7,0x2e,0x59,0xd2,0x86,0xd8,0x56,0x64,0xa9,0xb9,0xb8,0xb4,0x05, +0xc1,0x29,0x83,0xaf,0x28,0x2d,0x8c,0x09,0x49,0x50,0xb2,0x5f,0x00,0xb5,0x3e,0xa2,0x43,0x2e,0xd7,0xa1, +0x0b,0x0b,0xe0,0xee,0xeb,0x57,0x56,0x18,0x82,0x64,0x12,0x07,0xcb,0x89,0xae,0x88,0x17,0xac,0x27,0xe2, +0x64,0xe9,0xc3,0x65,0x63,0xeb,0xbf,0xee,0x72,0x13,0x6f,0xa8,0x63,0xc5,0x93,0xd9,0xbe,0xef,0xf5,0x2d, +0x65,0xed,0x78,0x35,0x44,0xa8,0xb7,0x43,0xdf,0x36,0x65,0x14,0x67,0xe1,0x2e,0x29,0xed,0x54,0xe2,0xee, +0x4a,0x70,0xd3,0x86,0x3f,0xe2,0x2d,0x2a,0xc4,0x82,0x25,0xd7,0x86,0x3e,0xa9,0x7f,0x12,0x96,0x81,0x92, +0x10,0x8b,0x23,0x61,0xe5,0x4c,0xe2,0xdb,0xe9,0x7c,0x65,0xc1,0x57,0x42,0xba,0x77,0x9e,0x5c,0xac,0x73, +0xe6,0xd1,0x59,0x4d,0x0b,0x13,0x0a,0xbf,0x88,0xcb,0x96,0x64,0x52,0xbb,0x9a,0x88,0x8e,0x0e,0x4d,0xd0, +0x00,0x5f,0xad,0x68,0x7b,0x5e,0x32,0x85,0xc2,0x44,0x47,0xb9,0xa4,0xfd,0x69,0x46,0x73,0xcd,0x38,0x56, +0x2b,0xa5,0x59,0x23,0x51,0xee,0xd5,0x43,0x22,0x26,0xcd,0x50,0xd1,0x96,0x20,0x5e,0xa6,0x1b,0x14,0xf1, +0x93,0x5a,0x75,0x82,0x46,0x7f,0x80,0x01,0xaa,0x3d,0x68,0xd5,0x8b,0x78,0x40,0x0e,0x2b,0xbe,0x4b,0x10, +0x5b,0x95,0xb4,0xd9,0x94,0x1d,0xf2,0x42,0x7a,0x66,0x92,0xa4,0x13,0x23,0xfa,0x47,0x65,0x03,0xd1,0x04, +0x14,0xb1,0x8a,0x63,0xe7,0xf9,0x95,0x7a,0x6d,0x92,0x06,0x80,0x63,0x68,0x8a,0x7e,0x6a,0x2a,0xa3,0xe6, +0x74,0xa8,0x2d,0x33,0xe6,0x4a,0xcc,0x2d,0x7d,0x55,0x8f,0xc4,0x0b,0x72,0x30,0x2c,0x59,0x24,0x6a,0xb7, +0x18,0x75,0x0a,0x1b,0x5d,0xe0,0x8b,0x32,0x75,0x2f,0x9f,0x4c,0xcb,0x59,0xa0,0xf0,0x38,0xdc,0xdf,0x62, +0x31,0xe6,0xb3,0x35,0x67,0xd5,0x89,0xac,0x82,0x74,0xe5,0x53,0x58,0x4f,0xce,0xb6,0xae,0xdd,0x4b,0xbc, +0xf0,0xad,0x58,0xad,0x2e,0x83,0x14,0xdd,0x35,0x69,0x15,0x19,0x56,0x45,0xfe,0xb2,0xdf,0x82,0xa7,0xb8, +0x8c,0x8a,0x27,0x08,0xfa,0xfe,0xa7,0x97,0x4c,0xd5,0x31,0xb4,0x37,0xb7,0x2a,0x54,0x82,0xaa,0xc1,0xf7, +0x6f,0x62,0x65,0x36,0xfc,0x56,0x5f,0xbc,0xd2,0xaa,0xf7,0x5b,0xd1,0xbb,0xdf,0x3f,0xdd,0x6e,0x4e,0xa7, +0xfa,0x7a,0x06,0xa5,0xfb,0x0b,0x48,0x6c,0x1f,0x0d,0xfe,0x35,0x13,0x31,0xad,0xa5,0x82,0x6c,0xd5,0x51, +0xd5,0xe6,0x2d,0x47,0x8b,0xc7,0x5b,0x17,0xbd,0xfa,0xc6,0xbe,0xdd,0x4a,0x38,0xfb,0x1d,0xb3,0x8f,0xbe, +0x94,0xe9,0xa9,0x9e,0xeb,0x19,0xf3,0xa4,0xf9,0x8d,0x77,0xfb,0x56,0x07,0x1d,0x53,0x48,0x1f,0xbf,0xdc, +0x95,0xef,0x9b,0x56,0xbe,0xbf,0xec,0xcc,0xf8,0x4d,0x3d,0x63,0x39,0x3a,0x2d,0xdd,0x6b,0xab,0x9c,0x06, +0x30,0xa3,0x68,0xe0,0x10,0x9f,0x03,0xee,0xbd,0x95,0xd5,0x5c,0x35,0x9f,0x05,0x1e,0x10,0x64,0x66,0x25, +0x66,0xe7,0x63,0xe4,0x2d,0xab,0xc2,0x41,0x63,0x1f,0x32,0x2d,0x59,0x29,0x2a,0x7b,0x6f,0xd4,0x2b,0xdf, +0x51,0x7d,0x09,0x33,0xb1,0xc2,0xf1,0x54,0xc4,0xbe,0xd4,0x04,0x7c,0x91,0x09,0x1b,0x31,0xb9,0xe9,0xe6, +0x21,0x2e,0x98,0x28,0xf3,0x47,0x6c,0xb9,0x63,0xdc,0xe1,0xd1,0x8c,0x81,0x23,0xba,0x81,0xfa,0xbe,0xa4, +0xc4,0x7d,0x5f,0xd2,0xd6,0x78,0x29,0x31,0x6e,0xa0,0xbc,0xa7,0x63,0xe4,0x0d,0x9f,0xec,0xad,0x3a,0xc0, +0x80,0xc0,0xa8,0xf3,0xd5,0x45,0x9b,0xd5,0x97,0xbd,0x82,0x59,0x26,0x8b,0x5c,0x7b,0x2b,0x59,0xea,0xf0, +0xf2,0x5e,0xf0,0x2c,0x6e,0x46,0x8d,0xd7,0xae,0x57,0x12,0xfd,0xa1,0x5a,0x79,0x0a,0xe9,0x52,0xf7,0x19, +0x7d,0x6e,0xed,0x37,0xb5,0x30,0x92,0x69,0x78,0xb9,0xf3,0xbd,0x9c,0xe8,0x7f,0x5c,0x37,0x0e,0x53,0xbf, +0x15,0x97,0xba,0xd2,0x6f,0x33,0x19,0xf2,0x06,0x3d,0xd1,0x3d,0x65,0x1b,0xa1,0xe6,0xdb,0x45,0xa9,0x39, +0x67,0x7a,0x42,0x2b,0x3a,0xd5,0xac,0xfb,0x6d,0x1d,0xaf,0xe3,0xee,0x33,0x0a,0x4d,0xac,0xec,0x5d,0x42, +0x0e,0xf5,0x73,0x7e,0x03,0x2f,0x71,0xfe,0x08,0xe8,0x24,0x6a,0xf6,0xa0,0x15,0x10,0xc8,0x33,0x16,0x58, +0x2b,0x94,0x39,0x27,0xb3,0x96,0x49,0xcd,0xf4,0x81,0xe8,0x88,0x20,0xaf,0x34,0x0c,0x39,0x36,0x4b,0x5a, +0xcb,0x71,0xbb,0x5e,0x44,0x83,0x87,0xaa,0x0a,0x0a,0x4a,0xe7,0x82,0x01,0xe6,0xd6,0xb2,0xa0,0xac,0x60, +0x7f,0x3e,0xa2,0x93,0x18,0x5b,0x7b,0x24,0xfb,0x85,0xd3,0x81,0xfd,0x28,0x24,0x71,0x64,0x73,0xb7,0x17, +0x30,0xd7,0xac,0x72,0xda,0x8e,0x61,0xe8,0xa9,0xc5,0xbc,0x70,0x30,0xe7,0x45,0x54,0x65,0x99,0x0f,0x06, +0x62,0x46,0x82,0xba,0x84,0xca,0x30,0x4f,0x03,0x41,0xd9,0xdf,0x02,0x2a,0x9f,0xf7,0xe2,0x8c,0x95,0x37, +0xc4,0xee,0x2a,0x5a,0x2d,0x22,0x06,0x17,0x46,0x3d,0xb4,0x92,0xb1,0x98,0x59,0x09,0xc0,0x7c,0x31,0x64, +0x08,0x56,0x65,0xbb,0x23,0x39,0xab,0x01,0xe0,0x14,0xce,0xb8,0xda,0x8a,0x64,0x28,0x52,0xd9,0x1a,0x4d, +0x87,0xa7,0xfe,0xad,0x28,0x19,0xee,0x90,0xfc,0x36,0x05,0x07,0xf6,0x5e,0x35,0x2d,0xcd,0x80,0xa7,0x33, +0x89,0x1c,0xd7,0xda,0xbb,0xba,0x46,0x4b,0x2a,0x7b,0xa4,0x15,0x88,0x1d,0x06,0x1a,0xae,0xc4,0x10,0x46, +0x2f,0xfa,0x29,0xfa,0xb4,0x39,0xfd,0x8f,0xd3,0x89,0x19,0x63,0xbd,0xff,0x19,0x34,0x55,0x43,0xb5,0x04, +0x9d,0x53,0xbf,0x31,0x45,0xcc,0x6a,0x1b,0x37,0xa6,0x83,0x5a,0x18,0xbe,0x1a,0xcb,0x58,0xcc,0x7c,0xf5, +0x08,0x62,0x4d,0xb3,0x01,0x84,0x35,0x47,0xaa,0xa5,0xd4,0x31,0x4f,0xef,0x5e,0x8f,0xdd,0xb9,0xcc,0x97, +0x71,0x94,0xff,0xf3,0xce,0x8c,0xd4,0xec,0x94,0xe9,0x0f,0x9f,0xde,0x2e,0x51,0x93,0x4d,0xf4,0x1c,0xfa, +0x49,0x43,0xe0,0x21,0x47,0x08,0xcd,0x7c,0xdb,0x7c,0xa6,0xb0,0xd7,0xc1,0x60,0x40,0xcb,0x2f,0xa9,0xb1, +0x7a,0x99,0x8f,0x70,0x6f,0x5b,0x26,0x59,0xee,0x18,0x42,0x19,0x11,0x76,0x47,0x50,0x2b,0x54,0x82,0x10, +0xea,0x0d,0x02,0x91,0x08,0xfd,0xb8,0x36,0x6f,0x15,0xa0,0x2f,0x4f,0x4d,0x1c,0x16,0xfd,0xbe,0xaf,0xee, +0x78,0x32,0x16,0x15,0x7b,0x51,0xb8,0xb6,0xa0,0xa8,0x34,0xe2,0x89,0xf7,0x1c,0x36,0x60,0x30,0x9b,0x80, +0xce,0x58,0xdc,0x3f,0x1d,0x6e,0xbc,0xd3,0x45,0x9f,0x6e,0xa6,0xf1,0xd3,0x19,0xbf,0xa0,0xdb,0x8d,0x77, +0xa0,0x22,0x72,0xfa,0xf7,0x1a,0x01,0x2d,0x74,0x08,0x07,0x2f,0xdc,0x78,0xae,0xd3,0x7f,0xcf,0xe1,0x44, +0x60,0x28,0xfb,0x97,0xd9,0x7d,0x13,0x96,0xe1,0x9b,0x38,0x9c,0x3a,0x27,0xd9,0x8a,0x6e,0xdf,0xc2,0x0a, +0x9e,0x7e,0xbf,0xc9,0xca,0x32,0xbb,0xa2,0x8b,0x97,0xf1,0x79,0xe9,0xcc,0xfc,0x5f,0x76,0xe0,0xf6,0xc7, +0xd8,0xae,0x60,0xb5,0x00,0x55,0x0f,0x73,0x37,0x45,0xf9,0x69,0xc9,0x60,0xfa,0x1c,0xd5,0x65,0xe3,0x74, +0x3c,0xe5,0x99,0x66,0x4c,0xa4,0x1a,0x80,0x62,0x3e,0xd8,0x16,0x93,0x1f,0x12,0xf2,0x0a,0x77,0xd4,0xb7, +0xd0,0xef,0xfe,0xde,0x66,0x60,0xed,0x40,0xe4,0xb7,0x32,0x94,0x99,0x70,0x0e,0x11,0xa2,0xf3,0xa9,0x0a, +0x20,0xca,0x79,0x75,0x49,0x1c,0x04,0x28,0x65,0xda,0xf4,0x34,0x62,0x3e,0xef,0xcc,0x9e,0xfd,0xb5,0x95, +0x1a,0x19,0x59,0xfc,0xc6,0x8f,0x31,0x0a,0xaa,0x6f,0x11,0xc5,0x65,0x76,0xdd,0xa1,0x20,0x52,0xea,0x12, +0x3e,0xe0,0x2e,0x93,0x45,0x97,0x7e,0x49,0x92,0x40,0x63,0x91,0x5d,0x5c,0x2c,0xbb,0xd6,0x88,0x73,0x26, +0x31,0xb8,0x6d,0xb2,0x40,0x11,0x06,0x28,0xd6,0x55,0x36,0x83,0xc8,0x5f,0x5f,0x37,0x57,0xe6,0x2f,0xb2, +0x22,0xd9,0xaa,0x94,0x2f,0xf4,0x97,0xe6,0x5e,0xbe,0xde,0x9a,0xe9,0xf7,0xbd,0x22,0x71,0xb5,0x3f,0xdf, +0x86,0x3d,0xfc,0xd8,0x5e,0xfa,0x07,0x7a,0x55,0xb7,0xb9,0xae,0x1b,0x26,0x78,0x94,0xe6,0x5b,0x7c,0x7e, +0x6f,0x73,0x7a,0x40,0x59,0xfc,0x1a,0x7d,0x8c,0x36,0xf1,0xfc,0x2a,0xf2,0x8a,0x79,0x9e,0xac,0x4a,0x7a, +0xfd,0x13,0x75,0xa1,0xf8,0x05,0x07,0xd3,0x43,0xdf,0xd1,0xe0,0xb6,0x3a,0xbe,0x4f,0xf8,0xb9,0xbe,0xfa, +0x1c,0x8e,0x1d,0x15,0xac,0x2d,0x0c,0x86,0xe3,0x68,0x21,0x1f,0xb1,0x17,0x8b,0xbc,0x57,0x97,0x33,0xe2, +0x54,0x97,0xc1,0xf4,0xc8,0xbc,0x3c,0xa6,0xfb,0x8b,0x3c,0x5b,0xaf,0x24,0x99,0xb9,0xb3,0xbe,0x28,0xf3, +0xda,0x07,0xe5,0x59,0xb6,0xf8,0xa4,0x32,0xe5,0x4b,0x3b,0x29,0x15,0xfc,0xa0,0x99,0xf4,0xb8,0xcc,0x55, +0xf2,0xfc,0x61,0xc7,0x37,0xbf,0x28,0x57,0x89,0x60,0x3a,0x02,0x9a,0x83,0xe3,0xcc,0xb6,0xe3,0x9f,0x68, +0xe2,0xaf,0x4a,0xae,0x49,0x28,0xd7,0xd4,0x11,0xd4,0x27,0x43,0xfe,0x1a,0x8f,0xca,0xf3,0x2c,0x2b,0x71, +0xa1,0x6b,0xcc,0xd7,0x91,0x08,0xf4,0x7e,0x62,0x29,0x54,0xb4,0xe0,0x2f,0x2e,0xf9,0x76,0xc1,0x63,0xf6, +0x4f,0x0c,0xcc,0x66,0xff,0xb3,0xc9,0xe9,0x75,0x7f,0x7c,0x30,0xee,0xb5,0xd4,0x78,0x69,0xac,0xdc,0x6e, +0xf4,0x92,0x7b,0x96,0x47,0x17,0xec,0x7f,0x03,0xfe,0xae,0x8e,0xf9,0x6b,0xd2,0x1a,0x17,0x9d,0x45,0xf2, +0xd1,0x61,0xf7,0xb8,0xf6,0x2b,0x0d,0x2b,0x9c,0x76,0xc3,0x0a,0xf3,0xdc,0xc1,0x9e,0xd8,0x78,0xad,0x91, +0x95,0x7d,0x73,0xd5,0x4e,0xa3,0xa0,0x86,0x4b,0x28,0xeb,0xeb,0x15,0x04,0xfd,0x21,0x7e,0xe6,0x8f,0x97, +0xb4,0x69,0xc0,0x03,0x10,0xbf,0xec,0x00,0x42,0x2b,0xae,0x71,0x67,0xf0,0x5f,0xb4,0x6b,0xba,0x5f,0xd6, +0x3c,0x6f,0x74,0x20,0x98,0x87,0x37,0x34,0x7a,0xfa,0xda,0x41,0x11,0x69,0xc6,0xf9,0x3f,0x96,0xcf,0xc2, +0x5e,0xaf,0x59,0x50,0x95,0x75,0xcd,0x2f,0xc6,0x95,0x95,0xf4,0x77,0xee,0xf7,0x66,0x64,0x89,0x7f,0x60, +0x81,0x7c,0x88,0x3f,0x1d,0xf8,0xff,0x52,0x2b,0xed,0x2a,0x5b,0x17,0xf1,0x66,0x95,0x25,0x10,0x46,0x73, +0x50,0x2c,0xaa,0x05,0xa5,0x5d,0x6f,0x16,0x34,0x4a,0xf4,0x27,0x5b,0x79,0x9b,0x39,0x31,0x19,0x1f,0x0e, +0xfc,0xb8,0xc4,0x37,0xd3,0x9f,0x87,0xb4,0x97,0xe3,0x7c,0x18,0xba,0x43,0x44,0xe7,0x61,0xc1,0x34,0x1c, +0x97,0xcb,0xf0,0xf6,0x62,0x99,0x9d,0x45,0x4b,0x00,0xc7,0x35,0x0d,0x15,0x6b,0x68,0x45,0x95,0x9b,0x6b, +0x85,0x16,0xe4,0x7f,0x34,0xa4,0xaf,0x27,0xc0,0x3e,0xcc,0x2c,0x11,0x3f,0x0a,0x4c,0xba,0x5c,0x6c,0x6e, +0x7c,0x80,0xd9,0xab,0x27,0x80,0x16,0x31,0x7e,0x33,0x4c,0x3c,0xee,0x32,0x11,0xfd,0x3b,0xb4,0x42,0x34, +0xc6,0x17,0xeb,0x64,0x01,0xcb,0x09,0xbe,0xc0,0x5e,0x8f,0xdf,0x7e,0xdf,0xf3,0xdd,0x75,0xf8,0x51,0x9a, +0x50,0x40,0x18,0x58,0xdd,0x31,0xb6,0x8b,0x1b,0xd1,0xbd,0x14,0x2a,0xa2,0x42,0x7d,0x57,0x93,0x72,0xab, +0x6d,0xb3,0x03,0x27,0xeb,0x42,0xb4,0xd5,0x9c,0xe3,0x90,0xa6,0xd8,0xc5,0x45,0x9c,0x73,0xd8,0x63,0x89, +0xdf,0x3c,0x31,0xef,0x70,0xe6,0xb0,0xbb,0x89,0x3e,0x20,0x2c,0x2d,0x88,0x82,0xbb,0xc7,0x92,0x61,0xae, +0x81,0x08,0x4c,0x5b,0x1a,0x42,0x4b,0xdc,0x5f,0x56,0xe2,0x90,0x25,0x48,0x85,0x22,0x8c,0x4b,0xb1,0x76, +0x2f,0xa7,0xcb,0x99,0xa7,0x00,0x76,0x2e,0xc2,0x02,0xd6,0xe6,0x97,0xa1,0x5b,0x4c,0x8f,0x14,0x14,0x9e, +0x72,0xc9,0x1b,0x6a,0x9f,0x3c,0x9a,0xe4,0x8c,0x84,0x63,0x6a,0xc6,0x3e,0x4d,0xd1,0x72,0xba,0x98,0xc1, +0x6c,0x8f,0x72,0x71,0xa1,0xdd,0x05,0x25,0x7e,0x41,0x2b,0x12,0x7c,0x6f,0x70,0x3e,0x3c,0xa3,0xbe,0x57, +0x78,0x3a,0x0b,0x7f,0xf7,0xc7,0xf3,0xd0,0xe2,0x95,0xd8,0x4d,0x77,0xe1,0x67,0xd4,0x2b,0x9c,0xcb,0x85, +0xc8,0x1c,0x72,0x5f,0x0d,0x72,0x90,0xfa,0x18,0xa4,0x40,0xc6,0xcc,0xd7,0xc3,0x1d,0x24,0xf5,0xe0,0x4e, +0x32,0xf8,0x3b,0x1c,0x76,0x44,0xdf,0x81,0x19,0x40,0xab,0x9a,0x7a,0x78,0x1e,0x07,0x97,0xca,0xe7,0x70, +0x88,0xf3,0x9d,0x78,0x78,0x77,0x15,0xae,0xa9,0x7e,0x18,0x5e,0xb9,0x82,0xd5,0xce,0xca,0x34,0xf0,0x71, +0xb6,0x4e,0x11,0x70,0xf9,0x1c,0xdb,0xc4,0x7a,0xb5,0xbf,0xaf,0x2e,0x8c,0x9c,0x98,0x26,0x70,0x04,0x68, +0xa1,0xde,0x21,0x6c,0xd5,0x9a,0x12,0x71,0xe8,0x58,0x5a,0x52,0xf2,0x05,0x47,0x89,0x3d,0xc7,0x0b,0xf4, +0x35,0x7e,0x75,0x76,0x73,0xcf,0x9f,0xeb,0x59,0xae,0x27,0x6d,0xfd,0x41,0x28,0xfd,0x41,0x19,0x24,0x93, +0x95,0x36,0xa3,0x6a,0xd4,0x97,0xa8,0xbf,0x11,0x65,0x15,0xac,0x74,0x30,0x44,0xdf,0x8c,0x88,0x2c,0x53, +0x34,0xd3,0xe2,0x82,0xff,0x93,0xe5,0x5a,0x09,0x8b,0xf6,0xf7,0xeb,0xab,0x97,0x4d,0xde,0xcc,0xa2,0xd2, +0x7e,0x39,0x7f,0x76,0xea,0x02,0xb3,0xeb,0xbf,0x33,0x7b,0x75,0x94,0xc7,0x3b,0x26,0x70,0xfe,0x07,0x13, +0x58,0xe6,0x01,0x17,0x5b,0x84,0x05,0xa3,0x42,0x36,0x70,0xf7,0x10,0x7a,0xce,0xe9,0xeb,0xa9,0x84,0x70, +0x6c,0x93,0x60,0x78,0x1f,0xb1,0xef,0x3c,0x70,0xf4,0x2e,0xae,0x80,0xc3,0x07,0xf9,0x52,0xb8,0xd2,0xed, +0xcb,0x58,0x6d,0x18,0xae,0x40,0x1c,0xf6,0x30,0x6f,0x7b,0xf0,0x9b,0xd6,0x0b,0x60,0xb3,0x61,0x12,0x1e, +0x83,0xcb,0xcf,0x65,0xf4,0x0b,0x40,0x9e,0xc9,0x34,0x9e,0x0f,0xcd,0x2c,0xa6,0x6a,0xd2,0xd4,0xca,0x39, +0x5d,0x61,0x70,0x84,0x5d,0xe7,0xfe,0x7d,0x47,0x42,0x71,0xf5,0xaa,0xe7,0x3c,0xb3,0xf5,0x44,0xc9,0x60, +0x6f,0x67,0x7f,0xd3,0x98,0x39,0x83,0x01,0xcd,0x4b,0x99,0x14,0x98,0xea,0x72,0x55,0x4d,0x4e,0x6f,0x1c, +0x51,0x7d,0x56,0x95,0x34,0xec,0x9c,0xaa,0x16,0xe5,0x0b,0xa2,0xab,0x91,0x5c,0x5f,0xeb,0x0f,0x2e,0x7d, +0xb3,0x7b,0xaa,0x15,0x72,0xa1,0x59,0x61,0x09,0x37,0x14,0xd3,0x8c,0x32,0x49,0x34,0x7b,0xcf,0xcb,0xb1, +0x02,0x3c,0x5d,0x80,0x3e,0x5e,0x7b,0x66,0x3c,0x0d,0x2b,0xbd,0xe8,0x63,0x7a,0xf0,0x74,0xa5,0xb9,0x3c, +0x6e,0x09,0x54,0xd7,0x32,0x33,0x4d,0x7a,0x47,0xca,0xd9,0x93,0x89,0xe9,0x40,0x84,0xab,0xb7,0xdd,0x6e, +0x47,0x35,0x0d,0xcb,0x50,0xcd,0xa5,0xf3,0x04,0x61,0x80,0xfd,0x35,0xb3,0x3e,0x22,0x72,0x69,0x72,0xd9, +0x00,0x35,0x73,0xd5,0x82,0x10,0xbf,0x2b,0x5d,0x1e,0x9b,0x3d,0x4f,0x0b,0xf1,0xfe,0xe6,0xd9,0x35,0x6f, +0xcf,0x52,0xf3,0x5a,0xf1,0x16,0x6b,0xf8,0xc9,0x14,0xb4,0xf5,0x1f,0x8e,0xcb,0xe3,0x66,0x59,0xec,0x9b, +0xb6,0x86,0xaf,0xaa,0x79,0xa3,0xa0,0xbd,0x8b,0x6a,0x82,0x33,0xb8,0x90,0x70,0xab,0x34,0x2b,0x56,0x79, +0xfc,0x44,0x35,0x1a,0x61,0x59,0xad,0x5b,0xcb,0x73,0xa1,0x90,0xe1,0x92,0xa5,0x14,0x55,0x95,0x54,0xfb, +0x50,0x61,0x27,0x45,0x40,0x59,0xb8,0xca,0xb9,0x09,0xf1,0x2d,0x54,0x1f,0xd8,0x77,0xd1,0x8c,0x4d,0x0a, +0x68,0x79,0xa2,0x0b,0x46,0x2c,0x7a,0x57,0x66,0x44,0x48,0x41,0xb1,0xcb,0x27,0x3b,0x7d,0xbf,0xce,0x61, +0x0c,0xae,0xea,0x96,0x0c,0xa9,0xae,0x57,0x3e,0x40,0x4e,0xe8,0xa4,0x4f,0x4c,0x31,0x1c,0x24,0x4c,0x67, +0xf7,0xe2,0xea,0x2a,0x5e,0x24,0xc0,0xaf,0xed,0xcc,0xb7,0x18,0xe6,0x66,0x75,0xf0,0x17,0xd5,0xad,0x72, +0x98,0xaf,0xad,0x1e,0xb7,0x50,0xa5,0xd0,0x5c,0x41,0x6c,0xfb,0x21,0xce,0x1e,0x22,0x2a,0xf0,0x43,0xfc, +0xbe,0xeb,0xb6,0x06,0x26,0x33,0x0b,0x75,0x26,0xf6,0xeb,0xf2,0x3d,0xac,0x13,0x54,0x7d,0x3d,0x75,0x78, +0xab,0xe6,0xac,0x2d,0x49,0x26,0x16,0x67,0x01,0x51,0x00,0x11,0x6b,0x61,0xae,0x8d,0x90,0x5d,0xf1,0x9e, +0x80,0x6d,0xba,0xd0,0x71,0xb4,0x7f,0x15,0x2c,0xdb,0xb2,0xda,0xe8,0x5a,0xaa,0x41,0x1a,0xaf,0xac,0x28, +0xf5,0x80,0xc1,0x00,0xd8,0xbe,0xaf,0x0d,0xa0,0xaf,0x8b,0x63,0x45,0x85,0xf4,0xe7,0x6e,0x79,0x38,0xcf, +0x72,0xb6,0xc3,0x2e,0xeb,0xfb,0x01,0x4d,0x66,0xa0,0x39,0x61,0x98,0x30,0xad,0xd6,0x88,0xde,0x6d,0xc9, +0xbb,0x81,0xf4,0x49,0x04,0xa2,0x0d,0xb8,0x13,0x0f,0x05,0xd1,0xe7,0x61,0x78,0xa8,0xd4,0xa0,0x4b,0xd0, +0x3b,0x70,0xf0,0x5b,0x86,0xcb,0x9a,0x17,0x00,0x73,0x82,0xca,0x15,0xd8,0xce,0x56,0xe7,0xda,0xab,0x60, +0x82,0x96,0x26,0xaa,0x06,0xab,0xd5,0xd5,0xa9,0xa2,0x0c,0xc8,0x69,0x57,0x17,0x80,0x9c,0x35,0x07,0x8b, +0x85,0xb7,0x6c,0x0a,0x57,0xe4,0xdc,0xec,0x73,0xc0,0xdf,0xf0,0x8d,0xa0,0x2b,0x12,0xa8,0x69,0xfc,0xc0, +0x4b,0xd6,0xa2,0x17,0xc0,0xaf,0x8a,0x17,0x9b,0x82,0x34,0x73,0x97,0x6c,0x63,0xac,0xfd,0x3f,0xe5,0x9d, +0x42,0x42,0x5f,0x1a,0x24,0x74,0xda,0xe7,0x39,0xcb,0xcc,0x00,0xda,0x64,0x66,0x8b,0x2c,0x14,0x3a,0x09, +0xe6,0x44,0xb0,0xac,0xc6,0x22,0xdb,0x56,0x91,0x7f,0x65,0x75,0xae,0x8f,0xcb,0x3f,0xfc,0xaa,0x54,0x62, +0xff,0x35,0x83,0xaa,0x30,0xad,0x8d,0xb9,0xd2,0x18,0xd9,0x6e,0x6d,0x2b,0x35,0x82,0x77,0xdd,0x4a,0xe9, +0xeb,0xc7,0xfe,0x2d,0x11,0xfc,0x57,0xb1,0xd6,0xb4,0x36,0x35,0xaf,0xac,0xeb,0x6c,0x99,0x74,0x59,0x7c, +0x9e,0x56,0x8f,0x60,0x6d,0x24,0x69,0xb4,0xe4,0x02,0x8c,0x94,0xbb,0xeb,0xdd,0x36,0xf8,0xd3,0x9f,0xb7, +0xde,0x4c,0xe3,0x59,0x43,0x0b,0xbc,0xb3,0xb1,0x4a,0x42,0xf9,0x07,0xcd,0xbb,0xce,0x93,0x52,0x5f,0x2b, +0xe5,0x33,0x8b,0x24,0x60,0xf1,0xdc,0xed,0x0c,0x36,0x15,0x62,0x93,0xf5,0x7a,0x93,0x98,0x43,0x8b,0xea, +0x7e,0x65,0xbd,0x99,0xda,0x2e,0x82,0x5b,0x58,0x42,0x00,0x9e,0xf0,0x9b,0xf5,0x99,0xd2,0x62,0x2b,0xd8, +0xb5,0x5b,0xc5,0x07,0x74,0xf4,0x03,0xcd,0xed,0xc7,0x40,0xb1,0x12,0x67,0x30,0xa4,0xae,0xf5,0xc5,0xb9, +0xa0,0xae,0xf9,0x08,0xbb,0x51,0xa3,0x5f,0x1c,0x7e,0x93,0xa4,0xce,0xd6,0x3f,0x5b,0xae,0xf3,0xbb,0x8a, +0x08,0xed,0x22,0x90,0xb8,0x56,0x02,0x1e,0xec,0x2e,0x20,0x23,0x9e,0x1b,0xf2,0x55,0x5a,0x9b,0xbb,0x8a, +0x70,0xb4,0xfc,0x86,0x25,0xf9,0xc8,0x52,0x76,0x05,0xd1,0x87,0xe3,0x4b,0xc0,0x52,0xc8,0xb9,0xa8,0x98, +0xf8,0x5a,0x05,0x38,0x89,0xaa,0x81,0x11,0x65,0x74,0xfb,0xf8,0xa8,0x6d,0xc9,0x77,0x22,0x3e,0xca,0xcf, +0x62,0xda,0x11,0x62,0x09,0x5e,0x18,0xdc,0xda,0xfb,0x62,0xfd,0x70,0xd7,0x5b,0x72,0xac,0xf6,0x47,0xec, +0x58,0xb5,0x49,0x26,0x01,0x25,0xed,0x27,0x43,0x29,0x94,0xb9,0x6c,0xf3,0x1d,0x95,0xb9,0x55,0xe6,0x4f, +0x86,0x9e,0x69,0xd9,0x88,0xd4,0xde,0xda,0x3c,0x41,0xc7,0x63,0xb7,0xd4,0x06,0x55,0xed,0xbc,0x6a,0xe2, +0xeb,0xba,0x8f,0xb1,0xa4,0x9e,0xb8,0xbc,0xf5,0x96,0xc6,0xbd,0xb6,0xde,0x02,0xed,0x69,0xcb,0x96,0x1e, +0x92,0x4c,0x1e,0x24,0x85,0x3a,0x7a,0xde,0xc8,0x41,0x14,0x2f,0x42,0x83,0xb6,0x61,0x1e,0x6d,0x36,0x95, +0x46,0xb4,0xf5,0x52,0x5a,0x53,0xf5,0x0f,0x8e,0xb6,0xc9,0x75,0x70,0xa2,0x0a,0x94,0x13,0x5e,0x0f,0xd6, +0xfe,0xfe,0x03,0x39,0x2a,0xf8,0xce,0x8e,0x95,0xa3,0x9e,0x54,0xc7,0x43,0x60,0x06,0x58,0x26,0x46,0x8d, +0x62,0x88,0xeb,0xf7,0x92,0x84,0x21,0x76,0xe2,0x85,0x49,0x52,0xbb,0x57,0x82,0x4a,0xe9,0x00,0x18,0x47, +0x56,0x9c,0xa8,0x78,0x67,0x29,0x6f,0xa8,0x32,0xb9,0x8a,0xdf,0x95,0xd1,0xd5,0x2a,0x94,0x0e,0xd5,0xb7, +0x4c,0xba,0xa6,0x90,0x61,0xf2,0x39,0x22,0x0a,0x14,0x6b,0x2f,0xe0,0x43,0xa9,0xb1,0x1d,0xb0,0xc6,0xbb, +0xbd,0xeb,0x86,0xb7,0x96,0x2f,0x79,0xa0,0xdf,0xfb,0xed,0xa1,0xa0,0x4e,0xec,0x22,0xa3,0xf8,0xf1,0x1d, +0xe4,0x10,0xbf,0x7f,0x97,0x5c,0xad,0xb9,0xf9,0x30,0x6e,0xab,0x53,0x19,0x6d,0x73,0xff,0xf6,0x84,0x19, +0xef,0x9a,0x1d,0xd7,0x3e,0xce,0x7d,0xf5,0xd6,0x14,0xc2,0x80,0x89,0x0d,0x52,0x86,0xf6,0xc2,0x3a,0x25, +0xf3,0x5f,0x29,0xb7,0xdd,0xaa,0x3b,0x4a,0x6e,0x51,0x4c,0x52,0x74,0x57,0x17,0xfd,0x57,0xea,0x70,0x47, +0x17,0xff,0x41,0x65,0xba,0xbe,0x74,0xd5,0x04,0x6b,0x57,0x76,0x5b,0xb9,0xca,0x46,0xcb,0xf2,0x1f,0xf1, +0x27,0x9c,0x46,0x67,0x7c,0x6c,0xb0,0x97,0xf9,0x1c,0x6b,0x7d,0x69,0x8e,0xb0,0x4b,0xd8,0x8c,0x2e,0x4e, +0xb2,0x35,0xa3,0x9b,0xe1,0x49,0x99,0x2f,0xd5,0x57,0x8b,0xb8,0x8c,0x92,0x25,0xae,0x78,0x2c,0xde,0x10, +0x93,0xce,0x1f,0x5d,0xd1,0x73,0x95,0x84,0x0a,0x8e,0x7f,0xd4,0x17,0x3f,0xe1,0x82,0x35,0xad,0xea,0xed, +0xc7,0x24,0xbe,0xc6,0xaf,0x83,0x00,0xc8,0x8e,0x2a,0x8f,0x23,0x21,0xe3,0xfa,0x83,0x24,0xa2,0x1f,0xfd, +0x44,0xc1,0x42,0x9a,0x2b,0xa9,0xd0,0x32,0xa1,0xc2,0x7f,0xac,0x2e,0xb9,0x98,0xec,0xfc,0x9c,0x8e,0xee, +0x1f,0xab,0x4b,0x7e,0xaa,0xe4,0x8f,0x2f,0x16,0xd6,0x0d,0x1f,0x39,0xa8,0xd8,0x3c,0x8f,0xe3,0xf4,0xc7, +0xea,0x92,0xbf,0x90,0x5d,0xc1,0x6a,0x7f,0x99,0xe9,0x00,0x5b,0x7c,0x63,0x9e,0x5f,0x5f,0x26,0x5d,0x9c, +0x5d,0xa8,0x29,0xd4,0x3a,0xb6,0x7c,0x3c,0xe4,0xf4,0xfb,0xfb,0xff,0x30,0xf0,0x97,0x0c,0xaf,0x35,0x51, +0x96,0xe6,0x26,0x24,0xf4,0xa4,0xba,0xa4,0x3d,0xca,0xf4,0x85,0xf9,0xde,0xb6,0xf2,0xf9,0x57,0x23,0xaf, +0xc3,0xfd,0x12,0x50,0x4d,0xf4,0xf7,0x41,0xf0,0x05,0xfd,0x3d,0x0a,0x46,0x81,0xfa,0x50,0x4d,0x03,0xde, +0x2a,0x14,0x55,0x67,0xf9,0x50,0xb3,0xb0,0x16,0x2b,0x30,0x0f,0x1c,0xbe,0xa6,0xd3,0x23,0x77,0x7c,0xbe, +0x5c,0xc6,0xd1,0xc7,0x58,0x3f,0xa6,0x13,0x55,0x77,0xa3,0x4a,0xae,0xee,0xe4,0x03,0x75,0xa3,0x3e,0xd1, +0xaf,0xf8,0x5c,0x6f,0xb9,0xb5,0xd6,0xf9,0x9f,0x78,0x16,0xde,0xd6,0x68,0x82,0xd2,0xd7,0x42,0x13,0xba, +0x14,0x02,0xb5,0xd5,0xd9,0x1a,0xed,0x85,0xc3,0xae,0xd4,0xf6,0x63,0x3f,0x0b,0xe3,0x8a,0x05,0x33,0xda, +0x2e,0xb6,0x0d,0x60,0x09,0x86,0xad,0xb8,0x63,0x77,0x57,0x62,0xda,0xa4,0x1b,0xc3,0x8a,0x15,0xb3,0x85, +0xc2,0xdd,0xfe,0x42,0xbe,0xfa,0x86,0xf1,0xc8,0xda,0xaa,0xf6,0x1a,0xa4,0xa9,0x56,0xf0,0xa9,0xca,0x3c, +0x35,0x6a,0x6e,0x7e,0xbc,0xf5,0xb3,0xb4,0x2d,0x29,0xdb,0x91,0x1a,0x21,0x6a,0x30,0xcd,0x77,0xd9,0x2b, +0xb2,0x35,0x48,0x7b,0xd7,0xc4,0x13,0xd3,0x29,0x26,0x20,0x93,0xdd,0x53,0xec,0xbc,0xdf,0x60,0xee,0xbd, +0x21,0x95,0xe4,0xe6,0x15,0x83,0x3b,0xc9,0x4d,0x0f,0xf5,0x9d,0xa1,0xd3,0xb7,0x5e,0x05,0xd5,0x2b,0xbf, +0xe2,0x8f,0xe8,0x52,0x33,0xb1,0xbc,0x4b,0xd5,0x2d,0x70,0x2b,0x0c,0x16,0xf6,0xca,0xdd,0xe3,0xe0,0xe4, +0xb2,0x6f,0x52,0xb9,0xc4,0x13,0xf9,0x08,0xde,0xd3,0x09,0x1c,0x52,0xf6,0x84,0xd5,0xad,0x1c,0x88,0xaa, +0x80,0xa7,0xec,0xc9,0x08,0xcb,0x5a,0xad,0xef,0x4e,0x35,0x5f,0x9c,0x86,0x27,0x6a,0xb7,0xec,0x50,0xf9, +0xd7,0xa4,0x40,0xaa,0xcf,0x53,0x31,0xfc,0x51,0xba,0xc4,0xb2,0x84,0xc2,0x70,0xd2,0x83,0x4a,0x65,0x73, +0x06,0xdd,0xc6,0x72,0x13,0x5f,0x9d,0x11,0x21,0x73,0x99,0x6f,0x92,0xab,0x8b,0x0d,0x53,0x9e,0x9b,0x65, +0x92,0x7e,0xd8,0x60,0x67,0xdc,0x10,0xd5,0x11,0x5d,0x79,0xee,0x6e,0x1d,0xe3,0x7d,0xc1,0xb9,0xf1,0x4e, +0x0f,0x1e,0x1e,0x5c,0x24,0x7e,0x8a,0x02,0x44,0xb9,0xb8,0x39,0x66,0xb5,0xed,0xe6,0x18,0xb9,0x1d,0x24, +0x7e,0x4e,0xaf,0x94,0xc2,0x07,0x40,0x39,0x93,0x60,0xfa,0x73,0x38,0xdb,0x84,0x74,0xad,0xf5,0x40,0x43, +0xe8,0x2b,0x13,0x28,0x54,0x88,0x04,0x88,0x4f,0x0f,0xdc,0xe1,0x7d,0xef,0xc0,0xcf,0xf0,0x80,0x52,0x1d, +0xf7,0xa0,0x5f,0x99,0x3e,0x7e,0xf2,0xe8,0xe4,0xd1,0xe9,0x74,0x33,0x18,0x78,0x1b,0x3c,0x98,0x9d,0xce, +0x70,0xfd,0x90,0x52,0xdc,0x6b,0x58,0xf8,0x95,0x57,0x4b,0x3a,0x9e,0x77,0x03,0x0e,0x55,0xe8,0xd9,0x25, +0x11,0xcb,0xc7,0xf7,0x0e,0x1f,0x1e,0x1f,0xdc,0x3b,0x7a,0xe8,0x08,0x38,0x48,0x6b,0x4a,0x37,0x20,0x49, +0xb1,0xf1,0xd9,0xca,0x27,0x7f,0x1d,0xde,0xad,0x52,0x17,0x17,0xb5,0x96,0x1a,0x4b,0xa2,0x1a,0xda,0x20, +0x5c,0x87,0xf5,0x07,0xca,0x44,0x53,0x80,0xca,0x80,0x21,0xe1,0x89,0xf0,0xe9,0xca,0x65,0x77,0x93,0x2b, +0x57,0x02,0x1c,0xb3,0xea,0x47,0x63,0xa6,0x28,0xe0,0x91,0x5f,0xdd,0x0c,0x51,0xa1,0x22,0x46,0xaf,0xe1, +0x28,0x86,0x30,0x27,0xf3,0x44,0x42,0x90,0x6d,0x36,0xfc,0x6d,0x14,0x46,0xb8,0x2a,0x76,0xe6,0xf2,0xc4, +0xce,0x85,0xa5,0x8f,0x4f,0xa8,0x47,0x0a,0x33,0xad,0xb9,0x2a,0xbe,0x23,0xc3,0x0e,0xd1,0xae,0xf1,0x3b, +0xde,0xdf,0xbf,0x71,0x23,0x1f,0xd1,0x29,0xae,0x20,0x66,0xd4,0x29,0x98,0x41,0x87,0x35,0x4a,0xda,0xb2, +0x32,0xd3,0x3e,0xd2,0x4a,0xcc,0xd8,0x12,0x03,0x52,0x83,0x47,0x63,0x73,0x90,0x20,0xa2,0x1e,0x0c,0x47, +0xc6,0x99,0xc0,0xbe,0xbc,0x56,0xb6,0xf0,0x40,0x1c,0x4a,0xa7,0x6f,0x2a,0x22,0x54,0x9e,0x69,0xc9,0x7b, +0x65,0xd4,0xac,0x9f,0xc0,0x44,0x70,0xd2,0x5c,0x42,0xd8,0xc4,0x82,0x86,0x5c,0x16,0xb5,0xd2,0xe2,0x3e, +0x6f,0x5c,0x2b,0x43,0xad,0xd6,0x6d,0x3a,0x7d,0x1b,0x5b,0x16,0xcd,0x6e,0xed,0x5e,0x2f,0xe9,0x8e,0x1d, +0x77,0xc1,0x18,0x8e,0x5d,0x13,0xf5,0x37,0xbd,0x9a,0xbb,0x35,0x13,0xad,0x74,0x5e,0x07,0x5e,0xb4,0x4a, +0xd2,0xb2,0x4f,0x8c,0xdb,0xa6,0xd3,0xac,0x7d,0x43,0x06,0x62,0x91,0xa0,0x0c,0x19,0x18,0x4d,0xdb,0x6b, +0xed,0x3b,0x87,0x4a,0x86,0xd5,0x9c,0xbf,0x8d,0x67,0x5f,0x37,0x1f,0xc1,0x48,0x9e,0xd9,0x8a,0x0a,0x3f, +0x3f,0x8c,0x2b,0x1b,0xc5,0xb8,0x65,0xa4,0x05,0x48,0x16,0x56,0x3a,0x77,0x98,0x6e,0x3c,0x6a,0x1c,0x6e, +0xb5,0xe6,0x29,0x39,0x5a,0xa3,0xf4,0xc3,0x8e,0x67,0x5f,0x37,0x1f,0x69,0xc2,0xe8,0x83,0xee,0xd8,0x71, +0xd9,0x88,0x69,0xcb,0x22,0x11,0x3a,0xaa,0xfe,0x7f,0x50,0x31,0xda,0x75,0xe2,0xbc,0xfc,0x86,0xb9,0x7d, +0xec,0x58,0x35,0x5c,0x10,0x54,0x53,0x04,0x01,0xff,0xd5,0x5a,0x72,0xb9,0x36,0x02,0x7c,0xe3,0x41,0xab, +0x5c,0x36,0xa2,0xc1,0x68,0x9d,0x97,0x75,0x29,0xc8,0xff,0x54,0x69,0x35,0x44,0x43,0x94,0xdc,0x02,0x3b, +0x30,0x28,0x51,0x2c,0x90,0x17,0xba,0xd5,0x15,0x0e,0x07,0x30,0x8c,0xac,0x2d,0x68,0x42,0x21,0xb2,0x21, +0xbe,0xde,0xa3,0x5c,0xec,0x5f,0x00,0xa1,0xf3,0x6b,0x11,0xed,0x80,0x5c,0x50,0x3b,0xdc,0x3b,0x0e,0x0e, +0xcb,0x4e,0x4b,0x11,0xcc,0x40,0x0f,0x2e,0x95,0x4b,0x4f,0x39,0x01,0x91,0xc8,0xad,0x80,0xf1,0x7f,0xbb, +0xb7,0x2e,0xd4,0x31,0x53,0xb7,0xe3,0xc5,0xd9,0xf6,0x67,0x97,0xb7,0xcc,0x14,0x65,0xd7,0xc8,0x9a,0xbe, +0x94,0xf6,0xf9,0x1a,0xd8,0x60,0xdd,0xb6,0xbb,0x15,0x95,0x41,0xb7,0xb0,0x32,0xd3,0xe8,0xf4,0x23,0x61, +0xc7,0x2a,0x83,0xf9,0x4b,0x37,0x3f,0xc5,0x53,0xf7,0x07,0x03,0x2e,0xc7,0x6a,0x4d,0xd8,0xdd,0x78,0xad, +0x40,0x70,0x33,0x00,0xa5,0x50,0x4b,0x6b,0x47,0x36,0xb4,0xa6,0xec,0x8c,0xcc,0x6e,0x0c,0xc7,0x39,0x8b, +0xb0,0x55,0x43,0x52,0x69,0x48,0xbd,0x9e,0xed,0x31,0x2b,0x65,0xcc,0x6c,0xfb,0x12,0x64,0x1a,0x8e,0xac, +0x48,0x9e,0xdb,0x52,0x4d,0x32,0xbd,0xc3,0xc9,0x02,0x67,0x99,0xe5,0xee,0xad,0x48,0x51,0x0e,0x75,0x38, +0x0a,0xcd,0x39,0x4f,0x8d,0x91,0xda,0xee,0xb9,0x5e,0x99,0xd9,0xd6,0x27,0xf8,0xd8,0x06,0x06,0x91,0xb5, +0x7d,0x3c,0xea,0x68,0x19,0x56,0x99,0xb2,0x6d,0x54,0x75,0x51,0x11,0xc2,0x15,0x92,0x23,0x30,0x65,0x6a, +0x60,0x53,0xd2,0xac,0x93,0x2c,0x70,0xe4,0xca,0xd1,0xdb,0x16,0x1e,0xa9,0x4b,0xc7,0xb7,0x97,0x57,0xe0, +0xc8,0x8e,0xa1,0x9f,0x3e,0xe2,0x05,0xed,0xf0,0xba,0x76,0x74,0x0f,0x00,0x82,0xc9,0xb1,0x7a,0xa3,0x8b, +0x5f,0x6a,0xc0,0x00,0x59,0xc7,0xbc,0xc1,0x03,0x17,0x2f,0xb1,0xcc,0xc4,0xc3,0x01,0x20,0x0c,0x2d,0xd5, +0xe8,0x38,0xcc,0xc6,0x11,0x8d,0x7b,0x1a,0x46,0xec,0x02,0x5c,0x19,0xe2,0xca,0xaa,0x00,0xe1,0x05,0xd5, +0x04,0xe2,0xe3,0xd0,0x52,0x86,0x2d,0x52,0x14,0x2b,0x86,0x27,0x87,0x79,0x0b,0x70,0xbd,0x6a,0x6b,0xd4, +0x82,0xf5,0xc9,0x0d,0x61,0x1c,0x81,0xc8,0xbc,0xa2,0x61,0x4a,0xd2,0x03,0xbf,0x28,0x1b,0x06,0x9c,0xda, +0x68,0x73,0xd2,0x5b,0xdd,0x78,0x62,0xb9,0xd9,0xd7,0x86,0x9b,0xeb,0x32,0xec,0x18,0xd3,0x3a,0xf1,0x67, +0xac,0x91,0x92,0xf8,0xba,0x16,0x9e,0x24,0x5b,0x41,0xbe,0xc9,0xe4,0x7e,0xec,0x49,0x6d,0x1f,0x67,0x57, +0x44,0x82,0xc7,0x8b,0x77,0x20,0x9e,0x61,0x85,0x5a,0xb3,0x1e,0x33,0xb8,0x09,0xa5,0x48,0x95,0x0b,0xef, +0xb6,0x50,0x86,0x9d,0xf3,0xa2,0xe0,0x10,0x9f,0xce,0x59,0x76,0x33,0x28,0x92,0xdf,0x69,0x69,0x06,0x67, +0x59,0xbe,0x88,0xf3,0x01,0x3d,0x19,0xaf,0x14,0xe0,0x73,0xa0,0xf1,0xb6,0xc7,0xca,0x9a,0x33,0x38,0x83, +0x7f,0xf6,0x58,0x1a,0x1f,0x44,0xeb,0x32,0x1b,0xcb,0x67,0xc1,0xe1,0x8a,0x3e,0x23,0x8e,0x1b,0x39,0xe1, +0xba,0xcc,0x56,0xc1,0xe1,0x5f,0xc6,0x8c,0xcf,0x1c,0x7c,0x39,0xfa,0x8b,0xe3,0x17,0xb6,0xe1,0x96,0xe3, +0xff,0xbd,0x6e,0xb8,0x16,0x29,0xa6,0x43,0x70,0x81,0xeb,0x0d,0x23,0xea,0x31,0x0d,0x9d,0xc3,0xbf,0x38, +0x62,0xf2,0x93,0xad,0x68,0xec,0x9d,0xa3,0x95,0x48,0xc7,0x87,0x52,0x19,0xd8,0xbd,0x02,0x1c,0xe4,0x0b, +0xfd,0x98,0x4b,0xf6,0x75,0x83,0x25,0x11,0x5b,0xcb,0x86,0x0e,0x57,0x27,0xb1,0xd2,0x5a,0x6f,0x51,0x2f, +0x21,0x9a,0x74,0xbd,0x88,0x7a,0x67,0xa0,0x8a,0x6d,0x4d,0xd3,0xd7,0x61,0x4e,0xc7,0x96,0x76,0x48,0xdd, +0xfd,0x66,0xac,0xea,0xc2,0x1a,0x4b,0xa9,0x15,0xcc,0xde,0x61,0x28,0x48,0x9d,0xb0,0x4c,0x56,0xa1,0xa3, +0xc0,0xb3,0x30,0x06,0xe8,0xaf,0xba,0xbd,0x5a,0xf7,0x27,0x6c,0xeb,0xc6,0xc6,0xda,0xcc,0x27,0x70,0x87, +0xd5,0x33,0xa2,0x16,0x76,0x97,0x47,0xb4,0x77,0x6b,0x36,0xf0,0x58,0x8e,0xd4,0xb0,0xfd,0x8d,0xc6,0x51, +0x70,0xb6,0xe9,0x11,0x06,0x74,0x34,0x5e,0x52,0x37,0x07,0x83,0xaf,0xe9,0x3f,0x6b,0xbc,0x47,0x6a,0x42, +0x0c,0x78,0xd0,0x57,0xd6,0xfc,0x89,0xce,0x18,0x18,0x83,0x76,0x85,0xa8,0x36,0xda,0x85,0xed,0xef,0xb1, +0x88,0xfd,0xdb,0x55,0x72,0x13,0x2f,0x35,0xce,0x78,0x07,0x1d,0x00,0xa5,0x6e,0x4a,0x54,0x49,0x76,0xf3, +0x8e,0x67,0xeb,0xdb,0x78,0x99,0xec,0xc0,0x1e,0x62,0x6f,0x08,0x40,0xb2,0x50,0x8e,0xaf,0xaa,0x81,0xdd, +0x91,0x32,0xe1,0x10,0xad,0x9c,0xd7,0x2b,0x33,0x91,0x76,0xa4,0xcd,0xb6,0x40,0xe8,0x55,0x06,0x82,0x4b, +0x36,0xe6,0x83,0xdd,0xf3,0x86,0x95,0x5f,0xb4,0xe0,0x07,0xf3,0x69,0x4c,0x5b,0xcb,0xb0,0x4f,0x9c,0xe8, +0x1c,0xaf,0x07,0x83,0x03,0xff,0xbc,0x0c,0x6f,0x4d,0x7f,0x38,0x55,0x87,0x7c,0x4c,0x8a,0xe4,0x2c,0x59, +0x26,0x44,0x7c,0x38,0x97,0xc9,0x62,0x11,0xa7,0x8e,0xaf,0x57,0x99,0xc3,0xcb,0x8c,0x76,0xc7,0x15,0x7d, +0xbc,0x8c,0x4b,0xda,0x43,0xdf,0xad,0xa2,0x39,0xfa,0xda,0x19,0x39,0xfe,0x39,0x8d,0xee,0x7b,0x19,0x17, +0xe7,0x8b,0xd1,0x88,0xd2,0x2d,0xca,0x70,0xea,0xbc,0x8f,0xcf,0x3e,0x24,0x30,0x06,0x7f,0x95,0xfd,0x4e, +0x7f,0xaf,0x0a,0x67,0x46,0x67,0xff,0xae,0xe9,0x28,0x63,0x6f,0xf3,0xc0,0x34,0x0d,0xc4,0xc1,0xe3,0x36, +0x43,0x61,0x54,0xb1,0xdb,0xb6,0x93,0x26,0x33,0x86,0x6a,0xe7,0x7a,0x09,0x3e,0x4d,0xa5,0x35,0xb4,0x8d, +0x23,0xc1,0x1c,0x9d,0x43,0x27,0x48,0x59,0xb9,0x43,0xd9,0x7e,0xb7,0xbe,0x3a,0x43,0xec,0xd0,0x28,0x4d, +0xae,0x58,0x5e,0xfb,0x82,0x5a,0xc4,0x17,0xac,0x1e,0x17,0x9d,0xe2,0x72,0x7d,0x55,0xdd,0xd2,0xa9,0xbe, +0x7c,0xad,0x6a,0x81,0xdb,0x65,0x7c,0xf3,0x6d,0x9e,0x5d,0xeb,0xeb,0x77,0x97,0x44,0x4d,0x7c,0xe0,0xbb, +0xaa,0x2f,0xe8,0x8e,0xa8,0xba,0xf8,0xb9,0xb9,0xcb,0xaa,0x0c,0x64,0x62,0xf3,0xc5,0x8a,0x18,0x32,0x91, +0x6c,0x26,0x8b,0xec,0x9a,0xaf,0x7e,0xe7,0x98,0x44,0x7c,0x95,0x65,0x57,0xac,0x65,0xa4,0x5a,0x43,0x78, +0x48,0x9d,0xe1,0x9c,0x2f,0xb3,0xa8,0x74,0x02,0x87,0x1e,0x3d,0xe3,0x4b,0x08,0xc6,0x3f,0x2d,0xbb,0xa4, +0x58,0x22,0x8f,0x7a,0xd0,0xe0,0xd1,0xff,0xd6,0xb8,0x57,0x46,0xea,0xb5,0xf0,0x11,0x2d,0xcf,0xcd,0x75, +0x38,0x57,0xe4,0x51,0xe9,0xb1,0xfd,0x80,0x8c,0x97,0x9a,0x8e,0x6b,0xf6,0x1b,0x7e,0x0d,0x97,0x04,0x3f, +0x52,0x56,0xf7,0x3c,0x78,0x74,0xae,0x89,0xd4,0x4f,0xdf,0x17,0x33,0xdf,0x72,0x62,0x8d,0xf6,0xf7,0x1d, +0x1a,0x54,0x87,0xf8,0xd9,0xc8,0x12,0xb2,0xc2,0xec,0x44,0xb9,0xea,0xc0,0x8b,0xdf,0x03,0x4c,0xc9,0x12, +0x2e,0xa3,0xc4,0xfa,0x2b,0x82,0x2d,0xb5,0xe0,0x84,0xe1,0xee,0x47,0x9f,0xdc,0x53,0xe4,0x1a,0x87,0x57, +0x4f,0x18,0x08,0x18,0x60,0x65,0xdc,0x1d,0x40,0x9e,0x09,0x9d,0x94,0x07,0xde,0x51,0xda,0x1e,0x21,0x69, +0x71,0x98,0xa9,0xe0,0x9d,0xfa,0xbd,0xca,0x31,0xed,0x33,0x64,0x11,0xc7,0xe4,0x75,0xa5,0x0d,0x32,0x73, +0xa8,0x11,0x13,0x07,0xa1,0x2f,0x6f,0x20,0x0f,0x68,0x6f,0x78,0x30,0x63,0x83,0x57,0xcc,0x66,0xc3,0x12, +0xe3,0xca,0xf1,0xaf,0xda,0xf0,0x60,0x38,0xe4,0xa2,0x49,0x21,0xb5,0xfd,0x32,0xce,0x13,0x16,0x3e,0xc0, +0xe9,0xb7,0xd1,0x1b,0x21,0x4b,0x0a,0x22,0xe5,0xda,0x8b,0x51,0x65,0x8b,0xd6,0x09,0x02,0x77,0x94,0x95, +0x62,0x1c,0x7e,0xe4,0x9c,0x5d,0xca,0x08,0xa0,0x3c,0x61,0x3a,0x26,0xc4,0x9f,0x1d,0xe1,0xff,0x68,0x5c, +0xeb,0x83,0x69,0x8f,0xe1,0x08,0xbe,0xf0,0xd5,0xb8,0x8b,0x40,0xf8,0x25,0x57,0x0b,0xa1,0x4e,0x9c,0x34, +0xcb,0xaf,0xa2,0xa5,0xf2,0x23,0x63,0x0f,0xe1,0x55,0xc9,0x69,0x56,0x25,0x78,0x1c,0x89,0x50,0x40,0xdd, +0x99,0x4e,0x68,0x02,0x30,0xcc,0x32,0xcf,0x7c,0xb6,0x01,0x65,0x0c,0x87,0xcd,0x26,0x29,0x9e,0x01,0x99, +0x1a,0x28,0x29,0x93,0x8c,0x3a,0x3e,0x10,0xdc,0x98,0x8a,0x88,0x9c,0x3a,0x72,0x76,0xd0,0x56,0x24,0x51, +0x1a,0x66,0x6d,0x62,0xcf,0x6a,0x5d,0xd8,0xdc,0x6c,0xf4,0x82,0xd2,0xfe,0x8b,0xbd,0xa5,0xea,0xad,0x0e, +0x1f,0x13,0x0f,0x56,0xa3,0xa0,0x1b,0x58,0x51,0xf2,0x36,0x9e,0x97,0x85,0x81,0x7d,0x55,0x71,0x75,0xbf, +0xc1,0x2c,0xa0,0xe9,0x5b,0x25,0xa1,0x14,0x5c,0xb1,0xc9,0x2b,0xd5,0x31,0xc1,0xef,0xe0,0x12,0xcf,0x2d, +0xec,0x40,0xb3,0xfd,0xeb,0x24,0xe0,0xa7,0x8a,0x8e,0x7a,0xea,0x68,0x93,0xf9,0xfe,0xfe,0xba,0x14,0x41, +0x19,0x5d,0xbe,0x95,0xaf,0x7c,0xa7,0xa2,0xad,0xea,0x4e,0x32,0xe6,0x3c,0x73,0xb0,0xee,0x00,0x30,0x53, +0x09,0xca,0xda,0x4b,0x0c,0xf3,0x9f,0x17,0xab,0x44,0xad,0xc6,0x6a,0x60,0x35,0xbb,0xb8,0xbc,0x60,0x2e, +0xfa,0x69,0x95,0x39,0x82,0x38,0xbf,0xe1,0xfa,0x45,0x46,0x82,0xa4,0xfb,0xdb,0x22,0x9a,0xc2,0xe7,0x10, +0x33,0xb6,0xcf,0x40,0xbf,0x63,0xdf,0x97,0xaa,0xb9,0xd6,0x84,0xe0,0x23,0xa0,0xca,0xac,0x1a,0x89,0xce, +0xee,0x06,0xf5,0x30,0xe0,0x4e,0xbe,0xad,0xbe,0x09,0x46,0xdb,0x8e,0x0e,0xbf,0x3b,0x13,0x3a,0x87,0xfb, +0x68,0xbf,0xcd,0xb3,0x28,0xa2,0x94,0x28,0x22,0x4d,0x97,0xd0,0xa5,0xa2,0x68,0x9c,0xf7,0x3c,0x03,0x3b, +0xd8,0x0d,0x33,0x03,0xe3,0x3e,0xe6,0xa0,0x88,0xde,0xaa,0xe1,0xad,0x45,0x66,0x80,0xc0,0xf3,0x16,0xc8, +0x48,0x2d,0x6e,0x36,0x05,0x70,0xa0,0x58,0xd1,0xee,0x39,0x5e,0x40,0x2c,0xe7,0x38,0x3f,0xfe,0x82,0x05, +0xa2,0x09,0xe5,0xfc,0x0d,0xc2,0x73,0x22,0x7f,0xc8,0x46,0x37,0x1b,0xfa,0x3b,0x38,0xe2,0xdf,0x91,0xe5, +0xa1,0xb4,0xf5,0x23,0xc3,0x0f,0x9b,0xfd,0xaf,0xaa,0x1b,0xb6,0x9f,0xf0,0x8d,0xd7,0x92,0x04,0x36,0xf7, +0x9d,0x3b,0xd8,0xfb,0x86,0x60,0x1a,0x6d,0x01,0xdf,0x44,0x43,0xdb,0x04,0x18,0x90,0x56,0xe7,0xa1,0xcc, +0xe5,0xa4,0x32,0x2e,0x8e,0x8e,0x13,0x66,0xb1,0x00,0xa0,0x14,0xcd,0x66,0xd6,0x6c,0x83,0x37,0x1d,0x9f, +0x1d,0x06,0xbc,0x67,0x5b,0x93,0x15,0x32,0x32,0xc2,0x85,0x9a,0xac,0xaa,0x2e,0x81,0x35,0x59,0xb7,0xac, +0xf4,0x69,0x3b,0x1b,0x6b,0xc6,0xf4,0xe4,0x3a,0x8e,0xd3,0xf0,0x85,0xff,0x62,0x97,0x09,0xc0,0x0b,0x1f, +0xfb,0x51,0x87,0x11,0x36,0x02,0x86,0x0a,0xd3,0x4e,0x04,0x90,0xb6,0xe1,0xa0,0x4c,0x56,0xb4,0x5e,0x94, +0x7a,0xa4,0xa0,0xd1,0x04,0x4c,0x2a,0x4f,0x26,0xdc,0x0c,0xb5,0xd9,0x8c,0xa4,0x10,0x67,0x9f,0x22,0x2c, +0xb5,0xf6,0x39,0xca,0x4b,0x2d,0x64,0xbb,0x0e,0xb5,0x69,0x85,0x56,0x4e,0xd3,0xb8,0x84,0xb9,0x5c,0xae, +0x01,0xde,0x9f,0x35,0x8f,0xb3,0xb4,0x3a,0xce,0xe8,0xe0,0x58,0xe7,0x6d,0xa9,0x00,0xb7,0x72,0xa5,0xf6, +0x46,0x5d,0x5f,0x33,0x59,0x74,0xac,0xf0,0x49,0x65,0x20,0xeb,0x05,0xd6,0x27,0xa6,0xf6,0xd5,0x6b,0x22, +0x73,0xd7,0x1d,0xb8,0x55,0xb4,0x61,0xdc,0x5d,0x94,0xdd,0xfc,0xe1,0x62,0x2d,0x54,0x9b,0xc2,0xb0,0xce, +0xa8,0x43,0x42,0xd3,0x65,0x53,0xab,0x2f,0x67,0x5a,0xda,0xd6,0xfc,0xf2,0x3e,0xe2,0xcf,0x1e,0x76,0xbf, +0x53,0xc2,0x63,0xc9,0x56,0x4b,0xeb,0xa8,0x7b,0x5d,0xdd,0xa9,0x83,0xaa,0xf3,0xbd,0xfb,0x65,0xbf,0xba, +0xab,0xe7,0x57,0x94,0xf1,0x4a,0x89,0x69,0xec,0x47,0x95,0x61,0xa7,0x58,0x96,0xea,0xfc,0x35,0x8a,0x3c, +0x87,0x00,0xa5,0x3e,0x4d,0x8d,0x27,0xfc,0x8e,0x3e,0x35,0xaf,0x05,0x0a,0x69,0x6b,0x4f,0x49,0x8e,0xd6, +0x60,0xcd,0x50,0xeb,0x95,0x6f,0x65,0x16,0xde,0x1a,0xb3,0xac,0xc6,0xb1,0x57,0x0f,0xd6,0xb8,0x27,0xda, +0x1d,0xd4,0xd7,0x92,0xf0,0x6a,0x5d,0x3a,0x1e,0x4f,0x63,0x19,0x2f,0xaa,0xbd,0x52,0xc0,0x73,0x62,0x39, +0x12,0xd4,0xbb,0x49,0x3d,0xa9,0xc6,0x09,0xc1,0xc2,0x93,0xae,0x90,0x17,0x1c,0x9b,0x08,0x38,0x28,0xe0, +0xeb,0x99,0xd7,0x9e,0xd0,0xe6,0xdc,0x3a,0xf0,0x44,0x4a,0x73,0xc3,0x7d,0x6a,0x4a,0x68,0x3f,0x02,0x62, +0xcb,0xce,0xda,0x37,0xea,0x29,0xb5,0x61,0x7a,0x5b,0x7f,0x3e,0x13,0x70,0x8f,0x6a,0x0b,0xec,0x6c,0x0a, +0x93,0xd5,0xd7,0x41,0xb5,0xad,0xd4,0xda,0xc3,0x2f,0xfb,0x8c,0xf4,0xcb,0x86,0x66,0xf6,0x08,0x0c,0x0b, +0x06,0x70,0x3a,0xa1,0x5d,0xa0,0xfd,0x94,0xcf,0xc5,0xdb,0x66,0xb3,0x1b,0x2d,0xc1,0x1a,0xe4,0x07,0xb6, +0x04,0xda,0xed,0xaa,0x9f,0x31,0x4e,0xe1,0x6d,0xe6,0x16,0x4c,0x4a,0xd4,0xad,0x64,0xa4,0xbe,0xbe,0xc6, +0xc9,0xd5,0x7e,0x37,0xfc,0x72,0xf0,0x2a,0x2a,0x2f,0x87,0xf3,0x8c,0x46,0xed,0x3e,0x5f,0xbe,0x79,0xe1, +0x1d,0x1c,0x59,0x06,0x7e,0x0e,0x7f,0xeb,0x48,0x08,0x99,0x9b,0xb0,0x39,0x2b,0xfd,0x6a,0x8c,0xe0,0x09, +0x8b,0x69,0x76,0x51,0xfa,0x1f,0x4b,0xff,0x53,0x29,0xae,0x6c,0xe2,0xca,0xba,0x81,0x8f,0xe9,0x06,0x8e, +0xa5,0x00,0x44,0xb9,0xa2,0x77,0x95,0x83,0xb4,0x60,0xa8,0x3d,0xd2,0x5c,0x9c,0xe5,0x87,0xf4,0xbd,0x7f, +0x5b,0x62,0x83,0x86,0x75,0xed,0xad,0x73,0xdf,0x09,0xa6,0x9d,0x28,0x01,0xbc,0x51,0x32,0x33,0xca,0xdb, +0x39,0xbf,0x33,0x20,0x83,0x6e,0xaa,0x46,0xcf,0xd7,0x54,0x0f,0xcc,0x0d,0xc0,0xf7,0xcf,0xb6,0xbe,0xca, +0x3d,0x68,0x1d,0xe0,0xf5,0x20,0x17,0xca,0xf7,0x7b,0x4a,0x55,0x98,0x79,0x01,0xac,0xf4,0x8c,0xa7,0xcc, +0xd8,0x96,0x23,0x8e,0xd8,0x96,0xa2,0xa1,0xbb,0x84,0x82,0x30,0x9f,0xf9,0xdf,0x0f,0x75,0x53,0x68,0xa3, +0x0e,0x6b,0x77,0xec,0x6f,0x50,0x7b,0x62,0x30,0x16,0x4a,0xd1,0xed,0xa8,0x48,0x96,0xc1,0xf4,0xf7,0x59, +0x75,0xdb,0xc4,0x8c,0x98,0x7c,0x3f,0xac,0x92,0x9a,0x1c,0x68,0xc1,0xd4,0x9e,0xeb,0x88,0x4d,0x72,0xfe, +0x15,0xab,0x38,0x5e,0xec,0x88,0x6b,0x04,0x80,0x80,0x36,0x08,0x89,0xc5,0xda,0x43,0xc0,0x1b,0xdc,0x6a, +0xfc,0xb3,0x80,0x28,0x7b,0xc4,0x0e,0x2b,0x9b,0xc0,0x7d,0x8c,0x66,0xe7,0xeb,0xfd,0x38,0xa0,0x9e,0xe4, +0x29,0x1b,0x48,0x5a,0x83,0xb7,0x63,0x59,0x31,0xd3,0x0b,0x1b,0x25,0x92,0xe6,0x57,0x76,0x7e,0x3e,0xc9, +0xcd,0x9e,0x1e,0x8e,0x02,0xcd,0xe4,0x19,0x93,0x85,0xea,0x2d,0x7c,0xec,0xcd,0x0d,0xb8,0x10,0x35,0x45, +0xd1,0xd4,0xc2,0xce,0xc5,0x7e,0x3e,0xad,0x9e,0xcf,0x82,0x1d,0x69,0xcc,0x66,0xed,0xf9,0xb2,0x59,0xe6, +0x82,0x56,0x80,0xf0,0x62,0x7c,0xd1,0x13,0x26,0xc6,0x55,0xb7,0x8c,0xf7,0xe0,0xf9,0xf9,0x30,0x5b,0xd2, +0xd1,0x3d,0xd4,0x1d,0xe5,0x57,0x97,0x0d,0x6c,0x0e,0x1b,0xfe,0x0c,0x1f,0x71,0xe0,0xb2,0x6c,0xb9,0xa8, +0x0e,0x19,0xe4,0xa6,0xca,0x6c,0x22,0x2c,0xa8,0x17,0x38,0x93,0x5b,0xf1,0x9e,0x22,0xda,0x5c,0xb2,0xdd, +0xf6,0x31,0x62,0xc1,0x2c,0x2a,0x8f,0x5f,0x62,0x8f,0x37,0x71,0x23,0x82,0xf1,0x47,0xda,0x43,0x1c,0xa7, +0x25,0xb4,0x13,0xbc,0x52,0x63,0xd7,0x08,0x74,0xca,0xad,0x1f,0x2b,0x33,0x1c,0xf5,0x6e,0x17,0xcf,0x1a, +0xb6,0x1c,0x81,0x62,0x85,0x5b,0xc2,0x3d,0x2c,0x48,0xbc,0x0d,0xcc,0x12,0x11,0xe0,0x7e,0x6f,0xe2,0xf3, +0xd8,0xb3,0x0f,0x9c,0x0d,0x90,0x65,0x6b,0x3e,0x3d,0xe7,0xb4,0x2f,0x15,0x97,0x0e,0x78,0x9b,0x92,0x4d, +0xfa,0x20,0xe8,0x34,0xf3,0x09,0xa8,0x9c,0x78,0x1f,0x46,0x7e,0x02,0x07,0x11,0x19,0x29,0xb6,0x8c,0xad, +0xac,0x5a,0x22,0x45,0x35,0x48,0xf7,0xaa,0x44,0xe0,0x78,0xd8,0x78,0xb1,0xdb,0x2a,0x23,0x6c,0x1d,0xb7, +0xa1,0x58,0x1c,0x8e,0x35,0x4c,0x95,0x40,0xa3,0x20,0x0c,0x95,0xae,0x4d,0x27,0xd0,0x84,0x18,0xde,0xd8, +0x60,0x13,0xa5,0x84,0x45,0x83,0xfd,0x8d,0x55,0x2d,0x0b,0x23,0xa3,0xdb,0x20,0x47,0x2a,0x81,0x40,0xbb, +0x96,0xde,0xaf,0x06,0x4d,0x21,0x7d,0x0f,0x43,0xda,0x1c,0x78,0x19,0x56,0x37,0xb2,0xb1,0x46,0xe2,0x89, +0xef,0x04,0xfe,0x72,0xe5,0x81,0x4f,0x1f,0xb1,0x3d,0x91,0x71,0x01,0x63,0x93,0xa3,0xa8,0x9d,0xf0,0xca, +0x38,0x78,0x56,0x1f,0xfd,0x2f,0x89,0x1c,0x60,0x07,0xe2,0x44,0x28,0x00,0xfa,0x06,0x5b,0xb4,0xd2,0xcf, +0x1b,0x4a,0x64,0x7f,0x9f,0x5f,0xe9,0x95,0x05,0x7d,0x3d,0x3f,0xc0,0x1c,0x93,0x71,0x4d,0x15,0xf4,0x7f, +0x2d,0x96,0xa6,0x37,0x06,0x5a,0x7e,0xca,0xbb,0x50,0x07,0x04,0x89,0x8c,0x7e,0xe7,0x39,0xa9,0xba,0xd8, +0x35,0xd8,0x1e,0x77,0xf5,0xab,0x9f,0xd6,0xba,0x0b,0xa0,0x3c,0x53,0xdd,0xb9,0x0e,0xf4,0x49,0xd5,0xad, +0xf4,0xf5,0xac,0xd1,0xd9,0xf9,0x44,0x87,0xfc,0x0c,0x04,0x36,0x31,0xd5,0x33,0x53,0xb0,0x10,0x6b,0x68, +0x2e,0x53,0x09,0xe7,0x94,0xa8,0xce,0x95,0x5f,0xcb,0xa9,0x08,0x4a,0xa8,0xb2,0xea,0xd8,0x52,0x3a,0xb6, +0x94,0x8e,0x55,0xaa,0x7b,0xf4,0x67,0x39,0x33,0x13,0x1e,0x53,0x8d,0x1f,0x54,0xfd,0xd9,0xe3,0x50,0xb0, +0xaa,0x2f,0x4b,0xee,0xcb,0x2a,0x70,0x5b,0xc4,0x3a,0xea,0x9c,0xbe,0xa0,0x01,0xc5,0x77,0x52,0xdb,0xda, +0x8d,0xb5,0x51,0xe9,0x59,0xaf,0x5b,0xb5,0xf5,0xea,0x62,0x1c,0xa1,0x0b,0x1c,0xdf,0xc1,0xde,0x42,0x3f, +0xa0,0x0d,0x5a,0xc2,0x1c,0x03,0x6d,0x73,0x9e,0xc2,0x79,0x4e,0x5f,0xd8,0x07,0x96,0x0d,0xdc,0xad,0x88, +0x40,0x44,0x2e,0x6f,0xc3,0x64,0xa4,0xdd,0x46,0x88,0x2a,0xc8,0x96,0xda,0xd3,0xbe,0x81,0xc2,0x16,0xf0, +0x2e,0xbe,0x40,0xd9,0x5a,0x92,0x80,0x62,0x49,0x35,0x7c,0x92,0x5d,0xa7,0xc1,0x37,0xae,0x54,0xda,0xf3, +0xf9,0xd9,0xf7,0x2b,0x3c,0xe1,0xfa,0xab,0x27,0x27,0x02,0xdf,0x41,0x4f,0x55,0x2b,0x3d,0x1f,0x5b,0xf0, +0x8b,0xb4,0x12,0x82,0x4b,0x0e,0x5b,0x7e,0xfe,0x7a,0x5d,0x5a,0x2f,0x38,0x23,0x79,0xa1,0xf2,0xa9,0xde, +0xa9,0xec,0xfe,0x44,0xa0,0x93,0xb8,0xbd,0xc1,0xeb,0x36,0x96,0x7a,0xb7,0x96,0xd6,0xc9,0x84,0x84,0x0e, +0x54,0xce,0xba,0x32,0x99,0x7f,0x68,0xc3,0x9b,0xc3,0xcf,0x3a,0xad,0xe6,0x2f,0xcf,0x8b,0x0b,0xa6,0xf8, +0xd9,0xf0,0x9e,0xe6,0x47,0x6a,0x3b,0x3e,0xc6,0x21,0xc6,0xc9,0x67,0x18,0x5f,0x5c,0xc9,0xfa,0x4d,0xcd, +0xdc,0x1a,0x0c,0x68,0x76,0x8d,0xf5,0x27,0xbc,0x56,0x99,0x8a,0xa4,0x49,0x88,0x58,0xe5,0xda,0x90,0xc9, +0x54,0xe9,0x2a,0xae,0xef,0xaf,0xa6,0x22,0x9a,0x9e,0x31,0x74,0x68,0x94,0xb3,0x7d,0xbb,0xdc,0xb2,0x09, +0xed,0xc7,0x68,0x19,0x1e,0x3e,0xb0,0x13,0xd4,0x9a,0x07,0x5b,0xc7,0x8f,0xbc,0x4d,0xbe,0x77,0x3d,0xf3, +0x25,0xaa,0x52,0x4f,0x26,0xfa,0x3a,0xdf,0x26,0x08,0x88,0x9a,0x5f,0x12,0xa3,0xf0,0xd5,0x68,0x44,0xc3, +0x55,0x94,0xc1,0x11,0x5d,0x18,0xc2,0xf9,0x8b,0xd1,0x48,0x9f,0xc3,0xb4,0x14,0xa2,0x4f,0x0d,0x38,0x60, +0x83,0x36,0xc6,0x19,0x4e,0x6a,0xb4,0x08,0xa4,0xb2,0x65,0x80,0x2d,0x26,0x55,0x7b,0xbc,0xb5,0xed,0x77, +0xc3,0xf9,0xd7,0x20,0x63,0x53,0x26,0x80,0x5b,0x6d,0x50,0x12,0x6e,0x9d,0x2a,0x51,0xde,0x54,0x3b,0x91, +0x41,0x9a,0x90,0x1e,0x7e,0x97,0xda,0x47,0xcc,0x54,0x1d,0x81,0x7d,0xbc,0x1b,0x34,0x44,0xf8,0x66,0x3a, +0x98,0xc7,0xca,0xf0,0xb7,0xf2,0x48,0x32,0xf0,0x1d,0xaf,0x11,0x8b,0x83,0x19,0x3c,0xf6,0xf9,0xc2,0x73, +0xfa,0x4c,0x30,0x23,0x62,0x38,0xd8,0x17,0xea,0xd2,0xbf,0xab,0xa2,0xea,0xeb,0xd0,0x29,0x1d,0x6d,0x64, +0xac,0x80,0x47,0x90,0x21,0x5f,0xfd,0xa0,0x13,0x84,0xa6,0x30,0xad,0x84,0xbb,0x29,0xfd,0xb3,0xd2,0x84, +0x1a,0x05,0xfe,0xe0,0x73,0xb6,0xb6,0x6b,0xe0,0xfc,0xe0,0xc5,0xdd,0x42,0x32,0x4a,0x8f,0x44,0x3b,0x65, +0x50,0xca,0x90,0xee,0x51,0x3d,0xa3,0x3f,0x04,0xd6,0xaa,0xbe,0xda,0x09,0x76,0xd7,0xaa,0x5b,0x5d,0x46, +0x67,0xc5,0x7b,0xa5,0x63,0x1e,0x9a,0xa5,0x4c,0x14,0x4a,0xf4,0x73,0x84,0x1f,0xaf,0x85,0x9b,0x51,0xed, +0xa4,0xb5,0x10,0xbb,0x98,0xb8,0x60,0x4a,0xb5,0xec,0x8d,0xed,0xc9,0x32,0x15,0x07,0xc2,0x18,0x8c,0x22, +0xc0,0x7d,0xa8,0x7a,0x43,0xc9,0x84,0x1a,0xe6,0x37,0x22,0xcc,0xb2,0xa0,0x22,0xb0,0x83,0x6b,0x3d,0xc6, +0xe4,0xa6,0x54,0x48,0x1b,0x5e,0x1d,0x12,0x95,0xf7,0x7b,0xba,0xe0,0x67,0xf5,0x9e,0x61,0x3c,0xd5,0xa4, +0x52,0xc5,0x24,0xb6,0x62,0x8a,0x43,0xd4,0x2b,0x55,0x4c,0xe9,0x01,0xdf,0xde,0x8d,0xeb,0x58,0x33,0xd4, +0x98,0x3e,0x24,0x17,0xa9,0x64,0x72,0x61,0x32,0x11,0xe2,0x44,0xb2,0xd0,0x88,0x80,0x9c,0x41,0x1e,0x2a, +0x4f,0x54,0x89,0xd8,0x28,0x48,0x81,0x5c,0xc1,0x5c,0xc3,0xc6,0xe7,0x1c,0xd5,0x4d,0xf7,0x41,0x20,0x30, +0x1b,0xb7,0x4d,0xe8,0x5c,0x89,0xc2,0x5c,0x9b,0xa7,0x54,0x03,0x99,0xbe,0x82,0xc1,0x27,0x51,0xb4,0x94, +0x8b,0x9e,0x3a,0x24,0xd5,0x1c,0x36,0xc2,0xbc,0x4e,0xf4,0x1d,0x05,0x5e,0xa8,0x97,0x07,0xe8,0x27,0xd6, +0xa6,0x76,0xce,0xc3,0x56,0x30,0x42,0xd0,0xd3,0x15,0xe3,0x2b,0x41,0x81,0x1b,0xa1,0x82,0xc5,0x52,0x2a, +0x4c,0xa6,0xc4,0xf9,0xce,0xc6,0x9e,0x3d,0x22,0x52,0x91,0x94,0x67,0xea,0x4d,0x4b,0x10,0xe2,0xd7,0x37, +0x45,0xa6,0xc7,0x9b,0x03,0x4a,0x63,0xd1,0x68,0x56,0x2a,0x9c,0x7c,0xe5,0xa5,0xd3,0x31,0x87,0x04,0x13, +0x4d,0x55,0xfc,0xe0,0xf4,0xba,0x7f,0x70,0xe1,0x75,0x92,0x19,0x67,0xa5,0x56,0x87,0x99,0x51,0x1c,0xf3, +0xb3,0x3a,0x7f,0x5c,0x43,0x19,0x6b,0x4c,0x63,0x13,0x7a,0x96,0xc3,0x0a,0x9d,0xb1,0xf8,0x9a,0xff,0x86, +0x89,0x21,0xc2,0x75,0x2e,0x93,0x88,0x63,0xe7,0xa9,0xf7,0x19,0x8c,0x06,0x94,0x29,0xd0,0xb5,0x12,0x9d, +0x88,0x29,0xbc,0x6c,0x78,0x1b,0x8d,0x43,0xb4,0x11,0x07,0x19,0x46,0xdf,0x3a,0x51,0x09,0xa3,0x0d,0xde, +0xe0,0x51,0x63,0x8b,0x5a,0xb5,0xfd,0x8f,0xdb,0x5b,0x94,0x48,0xb5,0xee,0xde,0xa2,0x1a,0x8e,0xcc,0x77, +0x6f,0x51,0x8a,0xf2,0xd3,0x6e,0x78,0xc8,0xff,0x59,0x72,0xc3,0xf1,0xcf,0xe2,0x59,0x6b,0xab,0x6a,0xd5, +0xf1,0x3f,0xdb,0xaa,0xf6,0x76,0x6d,0x3c,0xbc,0x93,0xeb,0x3a,0xf0,0xc9,0x2a,0x46,0xdd,0x96,0x7c,0x7a, +0x56,0xdb,0x55,0xfe,0xfc,0xa6,0x21,0xea,0xb1,0x3f,0xb9,0x41,0x20,0x31,0x63,0x19,0xae,0xf4,0xea,0x8f, +0xce,0xc4,0x42,0xa0,0x5b,0x58,0xdb,0xdc,0x4e,0x1c,0x4a,0xcf,0x2a,0x68,0xcb,0xe0,0x73,0xc2,0x4a,0xb3, +0x17,0x74,0xf4,0x11,0xb5,0x3e,0xf2,0x82,0x6b,0xad,0xeb,0x31,0x51,0xdb,0xa9,0x07,0x4e,0xda,0x0f,0xd9, +0x97,0x25,0x8f,0xcf,0x27,0x23,0x84,0xaf,0xdc,0x4a,0xb5,0xa8,0x7f,0x60,0x97,0x90,0xe5,0x0e,0x11,0xa1, +0xe5,0xd5,0xf2,0x19,0x5d,0xf9,0xce,0x7c,0x19,0x15,0x05,0xec,0x14,0xf0,0x8b,0x8f,0x1d,0x8c,0x5f,0xfd, +0x58,0x96,0xcd,0xdb,0x92,0x77,0xea,0xe3,0x7a,0x47,0xcb,0x76,0x04,0x97,0x2c,0xeb,0x66,0xb7,0xb6,0xcd, +0xad,0xce,0x91,0x3b,0xcc,0x17,0x4a,0xac,0x29,0x49,0xed,0xc8,0x1b,0xea,0xe7,0xb2,0xf1,0xf1,0x9f,0x2d, +0xa5,0xc9,0xaf,0xa8,0xd1,0x02,0x84,0x59,0x1c,0x2d,0x5e,0xa7,0x4b,0xc4,0xc8,0xba,0x8a,0x6e,0x5e,0xf2, +0x5a,0x41,0x4f,0xc5,0xcb,0xa5,0x32,0xa8,0x51,0x77,0x6f,0x44,0x65,0x88,0x4f,0xb2,0x6b,0x7a,0x95,0xe2, +0x79,0xb6,0x54,0x57,0xeb,0x22,0x7e,0x15,0x01,0x6e,0x91,0xc3,0x21,0x7e,0xc3,0x3a,0x45,0x4e,0xc0,0x56, +0x56,0x4f,0x17,0xe2,0xfe,0x6e,0xf3,0x44,0x7c,0xfa,0x9b,0xa9,0xcc,0xb6,0xeb,0xb5,0x43,0x94,0xd9,0xbc, +0x96,0x2e,0x8f,0x2a,0xf1,0x18,0x83,0x77,0x37,0x90,0x8a,0xbf,0x16,0xa5,0x5d,0x53,0x76,0xe7,0xed,0x5e, +0xe9,0xcc,0x78,0x28,0xcc,0x40,0x5d,0x88,0x6b,0x47,0x59,0x2d,0xfd,0x6f,0x95,0x99,0x28,0x51,0x28,0xbb, +0x0c,0x76,0xe5,0xd0,0x28,0x6d,0xa1,0x2a,0x04,0xa2,0x63,0x15,0xae,0x76,0xcd,0x07,0x09,0x0e,0x9b,0xf0, +0x5b,0x30,0xfc,0x79,0xd8,0x0c,0x41,0xeb,0xec,0x39,0xfd,0x1f,0x88,0x9a,0x05,0x90,0x84,0x86,0x45,0x19, +0x8d,0xb3,0x90,0xf6,0x55,0xfe,0xd6,0x82,0x6c,0xa6,0x94,0x19,0x27,0x63,0x83,0xd6,0xbc,0x1f,0xca,0xdd, +0xb8,0x08,0x7f,0x70,0x01,0x56,0x46,0xeb,0xb6,0x50,0x1a,0x1b,0x1b,0x9a,0x8e,0x97,0x80,0x5f,0x78,0x76, +0xf8,0x5a,0xbd,0x33,0xfe,0xcf,0x77,0xad,0x55,0xce,0xdd,0xbd,0xdb,0x6b,0x59,0x09,0xd7,0xd8,0x3f,0x6c, +0x23,0xba,0x2d,0xb0,0x1a,0xff,0xff,0x6a,0x3c,0x24,0x30,0x71,0xc7,0xa0,0x70,0xf8,0xc1,0x3c,0xcc,0x8d, +0x53,0x94,0x79,0xe5,0xff,0xe7,0xa3,0x24,0x2c,0x73,0x73,0x94,0x2c,0xed,0x81,0x6a,0xb8,0x96,0xc8,0xb5, +0x24,0x07,0x35,0x0c,0x7f,0x20,0xf6,0x8b,0x9c,0xb0,0x9a,0xf0,0x4a,0x84,0x50,0x1b,0xa6,0x8e,0x48,0xd9, +0x1d,0xa3,0x9c,0x5a,0xa3,0x6c,0xd5,0xb3,0x36,0xca,0xa9,0x1e,0x65,0x76,0x83,0x07,0xc0,0xf7,0x1d,0x32, +0x29,0x9e,0x72,0xf5,0x81,0x0d,0xc5,0xd9,0x4a,0x48,0x38,0x5d,0x1a,0x9f,0xab,0xf5,0xe1,0x2d,0x61,0x46, +0x20,0x63,0x0b,0xfc,0x30,0xa9,0x08,0x91,0xde,0x49,0xad,0x61,0xa0,0xa9,0xab,0xa6,0x97,0x4a,0x0e,0x58, +0x01,0x37,0x50,0x5f,0xe9,0x0e,0x14,0xd3,0x29,0x9a,0x48,0xa6,0xfe,0x0c,0xf9,0x64,0x00,0xc1,0x9d,0x5f, +0x7e,0x31,0x67,0xca,0x2f,0xbf,0x38,0xc6,0xcb,0xdf,0x1e,0x55,0x25,0xef,0xec,0x1c,0x68,0x40,0xce,0x0a, +0x9d,0xe8,0x38,0x41,0x4d,0x00,0x5c,0xcf,0xd8,0x63,0x38,0x32,0xf1,0x59,0x50,0xed,0xea,0x5e,0xae,0xe1, +0x48,0x49,0xbb,0x30,0xed,0x62,0x4c,0x3b,0x3d,0xe7,0x73,0x3d,0xe7,0x9b,0x53,0xdd,0x95,0xb9,0x8e,0x85, +0x20,0xd3,0xdd,0xcc,0xea,0x12,0xb3,0xd9,0x44,0xfb,0x56,0x93,0xab,0x77,0xa8,0x49,0xbc,0xc7,0x44,0xb9, +0x9d,0xe6,0xe2,0x2d,0x68,0x6d,0xd5,0x44,0x96,0xef,0xda,0x4a,0xb4,0xfb,0x84,0x91,0x63,0x37,0xd6,0xfa, +0x44,0x18,0x90,0xda,0x9c,0xeb,0x12,0x5f,0x2a,0xe2,0x2a,0x19,0xb7,0x9c,0x7c,0xd8,0x34,0x28,0x9f,0xd4, +0x27,0x9f,0x99,0x9f,0x54,0x35,0xd7,0x23,0x02,0x5c,0xf1,0x34,0xc9,0x24,0x09,0x61,0x6d,0x60,0x6c,0xed, +0xd4,0x6a,0xa1,0xe7,0x7d,0xbc,0xa8,0xdb,0x7e,0x40,0x08,0xec,0x9a,0x90,0xbb,0x49,0x97,0xef,0x97,0x0e, +0x64,0x4d,0xdf,0x52,0xdf,0x3b,0x1c,0xab,0x05,0x1f,0x50,0xb9,0x96,0x15,0x81,0x02,0xe0,0x6a,0x3d,0xd7, +0x94,0x4d,0xe3,0x40,0x64,0xc5,0xb3,0xa2,0xe5,0x4a,0xdb,0xfd,0xbb,0x9a,0x86,0x89,0xef,0x30,0x33,0xc4, +0xa6,0x7c,0x9c,0x97,0xf0,0x46,0x09,0xa6,0x0c,0xcd,0xf6,0x89,0x5b,0xaf,0x46,0xd2,0x55,0x87,0xe4,0xce, +0x0a,0x5c,0xb4,0x2b,0x00,0x21,0x3f,0xcf,0xd8,0xaa,0x78,0x6f,0x92,0x06,0xf4,0x38,0x51,0x82,0x8f,0x4e, +0x6b,0x20,0xbd,0x31,0x3e,0x2e,0xb1,0x83,0x07,0x2a,0x26,0x0f,0xfa,0x0c,0xe8,0xf2,0x4a,0x68,0x56,0x27, +0xaf,0x75,0x1d,0x03,0x0d,0xb2,0xfb,0x27,0xa9,0x4d,0x55,0x2b,0xdb,0x11,0xbf,0xc7,0xba,0xfb,0x1f,0x5c, +0xed,0x9b,0xc7,0xc1,0xc4,0x15,0xf8,0xe3,0x8e,0x6c,0x79,0xee,0x62,0xb3,0x51,0x36,0x13,0xc4,0x32,0x35, +0x89,0xb9,0x22,0x54,0x32,0xa4,0x81,0x41,0x8f,0x66,0xc3,0x86,0x75,0x58,0x30,0x83,0x1f,0x4c,0x01,0x44, +0x58,0x4c,0xa2,0x7e,0x15,0x4f,0x76,0x2c,0x5b,0x5a,0x74,0x3c,0x9a,0x2c,0x03,0x7a,0x15,0xd0,0x0a,0x3b, +0x5e,0x8a,0x65,0x14,0xa2,0x15,0xb0,0xb7,0xa8,0x5b,0x09,0x8b,0x36,0x9b,0x1c,0xd8,0x52,0xf0,0x08,0x4a, +0x0d,0x5a,0x15,0x50,0xfc,0x53,0x9b,0xfc,0xd3,0x2f,0x36,0x9b,0x5e,0xe2,0xda,0x6f,0x60,0x8f,0x2c,0x18, +0xbd,0x1c,0x2e,0x82,0x9d,0x3b,0x2f,0xc0,0xcb,0xca,0xaa,0xf0,0x2b,0x28,0xa0,0xf1,0x5a,0x04,0x92,0xa5, +0x39,0x85,0xd6,0xed,0xd8,0x38,0x35,0xe7,0x13,0x56,0x19,0xeb,0xde,0xc9,0x64,0x89,0xe8,0x90,0x01,0x0c, +0xdf,0x9f,0x18,0x2b,0x29,0x08,0xf5,0x89,0xab,0x00,0x53,0xe9,0xe6,0x15,0x91,0x6d,0xb9,0xeb,0x58,0x73, +0x52,0xe5,0xc9,0x93,0x2c,0xf7,0x24,0x1e,0xaf,0xc7,0xda,0x25,0x00,0xf2,0xe9,0x51,0x65,0xc7,0xfd,0xda, +0x78,0x20,0x76,0x33,0x6c,0xd2,0xeb,0xd4,0xaf,0x12,0x9e,0x55,0x02,0xbb,0x26,0x55,0x5a,0x5b,0x8e,0xb3, +0x36,0x77,0xcf,0xbd,0xd6,0x32,0x08,0x33,0xc2,0x0a,0xe5,0x7f,0x5d,0x6f,0x0b,0xcd,0x22,0xd5,0xc1,0xbc, +0x9b,0x02,0x13,0xd0,0x48,0x09,0x85,0xe5,0xa8,0x97,0x8a,0xb6,0x76,0x05,0x60,0x52,0x62,0xa2,0xba,0xe0, +0xca,0xd5,0x33,0x7c,0xe2,0x64,0x29,0xed,0x39,0x4a,0x04,0xe8,0xa9,0x0d,0xfa,0xa9,0x62,0xad,0x15,0x7a, +0x13,0xff,0x6c,0x34,0xd2,0x12,0x63,0x33,0x89,0xd9,0x82,0x5a,0x66,0x17,0xca,0x71,0xd7,0x6f,0x63,0x2f, +0xdd,0x05,0xda,0x19,0x4e,0xf3,0xcd,0x26,0x8d,0x01,0xa1,0x39,0xd7,0xfb,0xae,0x2f,0xb2,0x1a,0x22,0x18, +0x78,0x1d,0x00,0xc0,0xc1,0x7e,0x69,0xd0,0x02,0x38,0x45,0x05,0xa4,0x57,0xc1,0x6d,0xd2,0x82,0x01,0x09, +0x10,0x85,0x45,0x98,0x87,0x9c,0xbf,0x0f,0xb6,0x39,0x6f,0xd8,0x8e,0xdb,0xf7,0xbd,0xa7,0x8a,0x47,0x5c, +0xf4,0x75,0x4b,0x2a,0x48,0x5c,0xcc,0x9a,0x45,0x45,0xae,0x0d,0x99,0x50,0xa3,0x67,0x97,0xe1,0xc2,0x2a, +0x95,0x9a,0x70,0x69,0xe2,0x46,0x5c,0x2a,0xc8,0x4f,0xd8,0x98,0x5b,0x9f,0x06,0x42,0x78,0xa3,0xc3,0xfb, +0x0b,0x44,0x11,0xaa,0x21,0x76,0x95,0x75,0x88,0x9e,0x85,0xdf,0xb2,0x3e,0x00,0x8f,0xc8,0xbe,0x74,0xc5, +0x89,0x54,0x8e,0x8e,0xa1,0xa3,0xe0,0x81,0x6f,0xf5,0x43,0x68,0x61,0xc8,0xd2,0xf3,0x0a,0x69,0x30,0xb4, +0x12,0x4d,0xfe,0x43,0xc8,0x50,0x11,0xd8,0x94,0x1a,0x2c,0x50,0x85,0x0f,0x2d,0x15,0x18,0x12,0xce,0x0f, +0x0d,0xaa,0x94,0x33,0x36,0xb6,0xda,0x9e,0x61,0xf2,0x5e,0x5b,0xd7,0xa9,0xcf,0x32,0x87,0xdd,0x40,0xa8, +0x09,0x6d,0x41,0xe7,0x7a,0x04,0x36,0x1b,0x73,0x59,0xb9,0x95,0x29,0x14,0x48,0x91,0x15,0x26,0x34,0x82, +0xe7,0x43,0x8d,0x67,0xa6,0x0d,0x2a,0xde,0x53,0xbf,0x67,0xd7,0x08,0xa6,0xc4,0xfb,0xcd,0x3a,0xac,0x23, +0xab,0x02,0x4d,0x55,0x0f,0xfb,0xba,0xbf,0x10,0x0c,0xe5,0xc8,0xda,0xf5,0xbc,0x71,0x34,0x6e,0x3c,0x51, +0xa0,0xb5,0xec,0xbe,0x14,0x8d,0x01,0x58,0x06,0x43,0x05,0xdb,0xd1,0x0c,0xd3,0xcd,0x03,0x74,0x29,0x27, +0x2c,0x6c,0xbf,0xb3,0xcd,0x46,0x7b,0x19,0x4a,0xcd,0x88,0x7e,0xf3,0xb6,0x02,0x04,0x30,0x1a,0x53,0xd9, +0xab,0x69,0xa6,0x30,0x25,0xcb,0x9d,0x10,0x95,0xa5,0x42,0x19,0x79,0x78,0x38,0x59,0x5b,0xd0,0xb0,0x68, +0xcb,0xdc,0x40,0x7b,0x46,0x4d,0x5c,0x4f,0xf9,0x6a,0x66,0xc0,0x70,0x23,0x8d,0x37,0x4a,0x73,0x64,0x0e, +0xa8,0x46,0xe9,0xd6,0x08,0xd2,0xc4,0x39,0x62,0xc3,0x46,0x00,0x2f,0xad,0xde,0xec,0xef,0xbf,0x8e,0x5d, +0x9c,0x1e,0xae,0x19,0xfb,0xda,0x37,0xe6,0xa9,0xd2,0xd1,0xb7,0xf0,0x96,0x2a,0x89,0x8d,0xd4,0x7f,0x81, +0x11,0x2e,0x3b,0x30,0x9c,0xa0,0x2f,0x3b,0x37,0x76,0x26,0xc0,0x6d,0x35,0x06,0x82,0x52,0x1e,0x75,0x2c, +0xeb,0xc8,0x52,0x8d,0xda,0xda,0x7b,0x1d,0x73,0xac,0xbc,0x65,0x33,0xf6,0x72,0x0e,0xa8,0xd6,0xd6,0x5c, +0xe0,0xb8,0xb7,0x39,0x9a,0x57,0x80,0x03,0xa6,0x0b,0x89,0xa4,0xe7,0xb7,0xd7,0x3d,0xd5,0x12,0x79,0xb8, +0x9d,0xef,0xd4,0xdc,0x37,0x99,0x14,0x5e,0xd5,0x0d,0x20,0x0a,0x14,0xec,0xd2,0x0e,0x53,0x89,0x6a,0xe3, +0xb4,0x97,0x3c,0x02,0xb8,0xf0,0x96,0x17,0xd7,0x41,0xb2,0x46,0x5b,0x6f,0xdc,0xac,0x02,0xd6,0x00,0x2f, +0xc6,0x8e,0x18,0x2d,0xad,0x2d,0xb8,0x89,0xd2,0xb6,0x1b,0xba,0x44,0xe7,0x8e,0xda,0x1a,0x37,0x72,0xf5, +0xf0,0xb9,0xc2,0xbe,0xde,0x65,0x83,0x06,0xe2,0xdc,0xf2,0x2c,0xd8,0xeb,0xcc,0x34,0x65,0x04,0x05,0xeb, +0x6c,0x75,0x70,0x98,0xec,0xf1,0xc9,0xb2,0xa7,0x0e,0x9b,0x3d,0x7d,0xce,0xec,0x51,0x7f,0x26,0xbf,0xc7, +0x7b,0x62,0x3f,0xb8,0xc7,0x00,0x7c,0x7b,0x8b,0xb3,0xa5,0x5c,0x30,0xa8,0x10,0x20,0x7d,0xe5,0x6a,0xbd, +0x92,0x5f,0x30,0x6b,0x7b,0x06,0x87,0x68,0x4f,0x43,0x0f,0xed,0x55,0x30,0x45,0x7b,0x15,0x34,0xd1,0x9e, +0x40,0x56,0xed,0xa9,0xa0,0x0b,0xc5,0xfa,0xec,0x2a,0x29,0xf7,0x3e,0xc4,0x9f,0x38,0x5f,0xfa,0x5d,0x21, +0x38,0x0c,0x2e,0x28,0x7b,0x0b,0x87,0xde,0xb1,0x0c,0xd3,0x3b,0x75,0xd1,0x75,0xa9,0x79,0x25,0xda,0x6f, +0xc9,0x98,0x47,0xc2,0x18,0xcb,0x41,0x29,0xce,0xce,0xda,0x66,0x57,0xf7,0x5b,0xd7,0x18,0x5f,0xa2,0x71, +0x77,0x8c,0x70,0xd5,0x5a,0x50,0x11,0x55,0x83,0x01,0x75,0x2d,0xd6,0x6f,0x44,0x4b,0xa8,0xfe,0x0e,0xe9, +0x40,0xd2,0x28,0x8d,0xc0,0xcf,0xb1,0x5e,0x29,0xe3,0x6d,0xe8,0xff,0x05,0x21,0xd2,0xc0,0x39,0x0a,0x9a, +0xa3,0x0d,0xbe,0xd8,0x35,0x2f,0x1a,0x3a,0x6b,0xb5,0xd9,0xab,0xa9,0x0d,0x4d,0xbc,0x06,0xd2,0x33,0x2f, +0x05,0xc6,0xd8,0xdb,0x8e,0x5b,0x67,0x43,0x29,0x64,0xd5,0xda,0x92,0x96,0xeb,0xf5,0x24,0x5d,0x58,0xdf, +0x87,0x85,0xbb,0xb1,0x22,0x52,0xe5,0xd0,0x0a,0xd3,0xbe,0x93,0xb7,0x51,0xcf,0x63,0x99,0x97,0x7e,0x2d, +0x31,0xc7,0xec,0x1e,0x79,0xfd,0x43,0x06,0xf3,0x10,0xf4,0xe8,0xff,0x46,0xd1,0x83,0xc3,0x71,0x32,0xa9, +0x17,0x90,0x20,0x60,0x78,0x27,0xde,0xa2,0x55,0x21,0x05,0x85,0x82,0x2c,0xd8,0xe3,0x84,0xc9,0xb3,0x0f, +0x90,0x4f,0x2d,0xb3,0x39,0x9f,0x0f,0xfe,0xbb,0xca,0x10,0xc1,0xff,0x0e,0x9c,0xf5,0x84,0x69,0x33,0x16, +0x8e,0xff,0xf8,0xea,0x65,0xdb,0x3d,0x9a,0xa5,0x65,0x00,0x3d,0x6f,0xda,0x5f,0x19,0x80,0x53,0x4c,0x45, +0xf6,0xfd,0x4f,0x43,0xde,0xa3,0xe2,0xe1,0x93,0xd7,0xaf,0xde,0x20,0xc3,0xdc,0x93,0x8c,0x9f,0xe5,0xd9, +0xd5,0x3b,0xfe,0x9c,0x69,0x36,0x9a,0x98,0x07,0x37,0x57,0x4b,0x47,0x87,0x2d,0xcf,0x3d,0xfa,0x54,0x71, +0x64,0x95,0xc3,0x75,0x8f,0x09,0x72,0xa5,0xaf,0x2e,0xbe,0xf9,0x74,0x12,0x5d,0x80,0x6b,0x74,0x1d,0xce, +0x32,0x8f,0x11,0x53,0xd8,0xf1,0x6c,0xb3,0x88,0x58,0x42,0xc3,0xbf,0x48,0x89,0x3e,0xa5,0xcc,0xa8,0x39, +0x1c,0xf0,0x17,0x6a,0x2f,0xee,0x89,0x27,0x68,0xef,0xf4,0x74,0x76,0xef,0xc0,0xff,0x95,0x85,0x0a,0x93, +0xd3,0xf4,0xe0,0xc2,0x7f,0xa4,0xe8,0x57,0x59,0xd1,0x4a,0x69,0xb4,0x49,0xae,0xa2,0x8b,0x78,0x43,0x4b, +0x9a,0xa8,0x95,0xf3,0x64,0x19,0xb3,0x16,0xe9,0xb7,0x3b,0xd5,0x4d,0xb4,0xf4,0x2f,0xe2,0xd4,0x13,0xd5, +0x12,0x83,0x31,0x85,0x1d,0x13,0x5d,0x3b,0xd1,0xef,0x30,0xdf,0x69,0x84,0x1d,0x47,0x68,0x9a,0x71,0x3e, +0xd5,0xf6,0x4f,0xb3,0x30,0x4e,0xe7,0x44,0x5e,0x7c,0xff,0xf6,0x05,0x5c,0xb4,0x89,0x0d,0x64,0xc0,0xd6, +0xbe,0x13,0x3a,0xfd,0x8e,0x37,0x35,0xae,0x77,0xdb,0x76,0x34,0x89,0xd9,0x5b,0xe8,0xd7,0xdf,0x10,0x2f, +0x59,0x9f,0x7f,0x6f,0x96,0x51,0x92,0x1a,0x93,0x43,0x4f,0x2f,0xea,0xd8,0xe6,0x62,0x04,0xfb,0x54,0x42, +0xfd,0x55,0x82,0x00,0x30,0x04,0xc6,0xd8,0x2d,0x15,0x7c,0xad,0x9f,0x88,0x82,0x8b,0x81,0x63,0x8c,0x29, +0x6c,0x74,0x7e,0x8a,0x7c,0xdc,0x77,0xbc,0xa6,0xfd,0x25,0x8d,0x2c,0x2d,0x60,0xda,0xc9,0x3b,0x5c,0x80, +0x75,0xaf,0xea,0xd0,0x9f,0x2a,0xa5,0x34,0xc5,0x63,0x3b,0x79,0xfb,0x51,0x97,0x13,0xf1,0x65,0x1b,0xba, +0x43,0x2c,0x39,0xb4,0x6e,0x5e,0xc4,0x62,0xb1,0x9a,0x75,0x15,0x53,0xcf,0xda,0xfb,0x8a,0x2c,0x55,0x92, +0xcc,0xad,0xa7,0x6d,0x41,0x5b,0x19,0x1a,0x19,0x4c,0xcd,0x91,0x03,0x3d,0xc6,0x1d,0xad,0x44,0x45,0x49, +0x41,0xa4,0xbe,0x66,0xa6,0xe1,0x4d,0xf6,0x9b,0xf6,0x48,0xb4,0x45,0x35,0xa0,0x4d,0x1e,0x59,0x58,0x1d, +0xf2,0x76,0xae,0x21,0xa9,0x7a,0xdf,0x6b,0x7c,0x3e,0x8e,0x3c,0x5f,0x6b,0x5f,0x6d,0x72,0xd9,0xf2,0xa9, +0x3a,0x6c,0xa0,0x18,0x08,0x04,0xad,0x30,0x7d,0x4a,0x0c,0x95,0x76,0x88,0xa1,0x6e,0xd1,0x96,0x40,0x58, +0x05,0x85,0x3a,0x5c,0x61,0x74,0xfd,0x4a,0xeb,0x1c,0xa0,0x63,0xc0,0xb4,0xf5,0x82,0x8e,0xa4,0x69,0x77, +0x52,0x81,0x80,0x30,0xb1,0xc6,0x69,0xb9,0xfd,0xe5,0x68,0x44,0x6b,0xf4,0x39,0x5d,0x7d,0x36,0xbc,0x4f, +0x2b,0xf7,0x19,0x5d,0xb9,0xd3,0xc9,0xfe,0xcc,0xfb,0x25,0x9c,0xfe,0xbc,0x3f,0xbb,0x7f,0xe0,0xbf,0xe6, +0x55,0x39,0xbc,0x3f,0x21,0x7e,0x6e,0xef,0xb4,0x9c,0xdd,0x77,0xa7,0x3f,0x23,0x47,0x44,0xec,0x3a,0xb8, +0xb8,0xf2,0xdf,0x68,0xdd,0xef,0x19,0x9d,0x3e,0x1b,0x22,0x0e,0xf1,0x6f,0x50,0x94,0x59,0x8e,0x25,0x3e, +0xec,0x0f,0x78,0xf2,0x15,0xd4,0x3a,0x5e,0xe9,0x58,0xf4,0x9b,0xeb,0x64,0xc1,0x70,0xab,0x54,0xe2,0x5b, +0xf5,0xf9,0xb7,0x4f,0x4f,0x36,0xcf,0x9f,0x3e,0x7a,0x02,0xb3,0xfc,0x57,0x8c,0x73,0x76,0x70,0x7a,0x70, +0xe0,0xbf,0xe0,0xf0,0xc1,0xef,0xf9,0xef,0xbd,0x32,0x74,0xee,0x1f,0x38,0x00,0x16,0xa3,0xad,0xcd,0x75, +0xee,0x23,0x58,0x58,0x97,0x29,0x50,0x44,0x33,0xeb,0x9b,0x92,0x15,0x8b,0xe1,0x07,0xf9,0xad,0xd9,0xa4, +0xcc,0x01,0xfa,0x10,0x8c,0x7c,0xc4,0xc8,0x79,0x95,0x2d,0x92,0xf3,0x84,0xa8,0x3b,0xd8,0xf1,0x96,0xd1, +0x05,0x07,0xa7,0xf9,0x35,0xba,0x79,0x17,0x97,0x25,0x6d,0xa9,0x45,0x70,0xbb,0xce,0x97,0x81,0xce,0x85, +0xa9,0x42,0x87,0xea,0xea,0x10,0x65,0xf8,0x92,0xb6,0xfd,0x65,0xf0,0x46,0x4d,0x9d,0x0f,0xca,0x31,0x66, +0x9e,0x81,0x88,0x95,0x40,0x37,0x80,0xab,0xcc,0x33,0x1c,0x33,0x8c,0xee,0x45,0xb7,0x51,0xf1,0x29,0x9d, +0x2b,0x94,0x69,0x68,0xd7,0x04,0x39,0x19,0x24,0x75,0x22,0x67,0xc8,0xc1,0xcd,0xe0,0xfa,0xfa,0x7a,0x40, +0x8b,0xfc,0x6a,0x40,0x25,0xcb,0xb6,0xb3,0x18,0x83,0x3e,0xca,0xe1,0x23,0xf7,0xfd,0xc9,0xb3,0xc1,0xdf, +0x1c,0x0e,0xfd,0xbb,0x2a,0x95,0x37,0xc2,0xbd,0x52,0x40,0xae,0x64,0xf3,0x5f,0x61,0x8b,0x71,0x04,0x17, +0x47,0x9e,0xe0,0xd2,0xf1,0x6f,0x70,0x5f,0x2b,0xe9,0x6a,0xe9,0xef,0x99,0xf3,0xc2,0xff,0xb5,0x60,0x17, +0x7e,0x2b,0x01,0x9e,0xa8,0x14,0x88,0x97,0xa5,0x30,0xcb,0xb6,0xba,0xee,0x54,0x3a,0xf2,0x3c,0x38,0x3d, +0xa3,0x9f,0xd3,0xb3,0x03,0x29,0x92,0x6e,0xf1,0x7b,0x20,0xf9,0xd1,0x1d,0x7e,0xe9,0x2d,0xf4,0x54,0x05, +0xed,0x9c,0x74,0x60,0x25,0xf1,0x72,0xa1,0x3e,0x76,0xf4,0x43,0x3a,0x4f,0x1c,0xd5,0x0a,0xfd,0x08,0xa8, +0x0d,0xba,0x5a,0xfa,0xd9,0xdf,0xdf,0xbd,0xfe,0x4e,0x6a,0x40,0x64,0x57,0xa9,0xfc,0x31,0xb8,0x8a,0x4e, +0x20,0x87,0xa0,0x1c,0x81,0x7b,0xdc,0x66,0x46,0x29,0xe5,0x5b,0xe4,0xe2,0x04,0xf8,0x5a,0x0e,0x4d,0xf5, +0x18,0x0d,0x0f,0xac,0x13,0x9a,0x88,0x27,0x22,0x88,0x5e,0x8b,0x14,0x4c,0x86,0x5e,0x8f,0xd5,0x0d,0x9c, +0xee,0xb7,0x66,0x72,0xac,0x77,0xd8,0x33,0x94,0x93,0x7f,0xb8,0xff,0xa0,0x27,0x30,0x32,0xb2,0xa6,0x11, +0x44,0x47,0xc1,0x3f,0xdc,0xc6,0x53,0xc6,0x27,0xc3,0x83,0x0a,0x9c,0xef,0x9f,0xee,0x0b,0x48,0xdb,0xe8, +0xd9,0x49,0x1e,0xa5,0xd4,0xe8,0xbc,0xa4,0x67,0xef,0xd5,0xb3,0x9a,0x34,0xc7,0xc2,0x40,0xc9,0x95,0x74, +0xa7,0x90,0xed,0x68,0xc9,0x61,0x58,0xce,0xfc,0x6b,0xff,0x24,0x4c,0xc7,0x73,0xc4,0x87,0x81,0x5d,0xe1, +0x1a,0xea,0xf6,0x9a,0xe9,0xdd,0x1a,0xee,0x8f,0x8a,0x85,0x8a,0xc2,0x02,0x1a,0x0a,0xff,0xb1,0x15,0x1f, +0x3e,0x2c,0x89,0x2e,0xfe,0x02,0x4b,0x85,0xae,0xc2,0xa3,0xd1,0x88,0x98,0xca,0xe3,0x07,0xa3,0xd1,0x66, +0xf3,0x60,0xf4,0x05,0xe4,0xf6,0x3e,0xa2,0x16,0x9c,0x85,0xff,0x72,0x2f,0xfd,0xc7,0xec,0x7c,0x7d,0x16, +0xc6,0x31,0xdd,0x9c,0xd1,0x2d,0xad,0x83,0xe5,0xc4,0xbd,0x1c,0x26,0xe7,0x7a,0x91,0x51,0xda,0xeb,0xf0, +0x31,0xb6,0xa1,0xb7,0x6a,0x3c,0x9f,0x53,0x51,0x88,0x09,0xff,0x92,0x96,0xe2,0x40,0x27,0xa3,0x75,0x7d, +0x2d,0x38,0x3f,0xf6,0x0a,0x45,0xd4,0xbb,0x6b,0x7a,0xd3,0x9d,0x01,0xd6,0x6e,0xf5,0x1d,0xee,0x24,0xbd, +0xe7,0x1f,0x49,0x4d,0xa9,0x6d,0xd8,0x5f,0x20,0xd8,0xbd,0x14,0x0c,0xec,0x93,0xd0,0x49,0x33,0x35,0x97, +0x9d,0x40,0x35,0x48,0x9e,0x96,0x57,0xba,0x2a,0x81,0x7b,0x12,0x9e,0xc1,0x5a,0xb3,0x8c,0xfd,0x15,0x5d, +0x71,0xf0,0x87,0x05,0x5d,0x30,0x3d,0x44,0xfd,0xd2,0x5b,0x78,0x44,0x3d,0x2e,0xc2,0x13,0x1f,0xe6,0xde, +0x27,0xd4,0xd7,0x94,0x83,0xd0,0x4f,0x7e,0xc9,0xea,0xdd,0x32,0x1c,0x21,0x38,0xf9,0x63,0xce,0x65,0x0d, +0x37,0x4b,0x7d,0xc9,0xb8,0x24,0x2e,0x11,0xf7,0x27,0x44,0x64,0x38,0xd4,0x5b,0x9f,0x6a,0xa1,0x20,0x2f, +0xfc,0xe9,0xca,0x3f,0xf1,0x1f,0xcf,0xbc,0x00,0x2f,0x40,0x34,0xe8,0xe7,0x8f,0xe9,0x39,0xb1,0xd5,0x26, +0x27,0xe0,0xcb,0xba,0x37,0x9e,0x7f,0xa3,0x47,0xf3,0x7c,0x7f,0xff,0xa3,0x61,0x5c,0x96,0x13,0x87,0xa7, +0xdd,0x9a,0x49,0x5e,0x27,0xe0,0xbb,0xa7,0x52,0x47,0xca,0xea,0x92,0x4a,0x5e,0x05,0xc8,0xee,0x8a,0x43, +0xa2,0x5a,0x85,0x40,0x3a,0x44,0x4d,0xa8,0xb2,0xe2,0x4f,0x1f,0x2b,0xb7,0x11,0xf9,0x9a,0xd2,0x0c,0x06, +0x98,0xd9,0xbc,0xaf,0x0a,0xb1,0x58,0xe3,0x37,0xa5,0xf0,0x32,0x63,0xa9,0xf5,0xb6,0x4b,0x9a,0xd6,0x02, +0xf9,0x4c,0x39,0xb8,0x89,0x85,0x2f,0x50,0x0f,0x30,0x14,0x56,0x0b,0x69,0xbd,0x82,0x0b,0x46,0x4a,0x7b, +0x6e,0x78,0x39,0x54,0x2b,0x75,0xb3,0xb9,0xf4,0x3f,0x56,0xb7,0x98,0x13,0x36,0x86,0xa4,0xa2,0xc9,0x38, +0xf2,0xe0,0x05,0x6b,0x48,0x45,0x66,0xfa,0xa9,0x11,0xbf,0xf3,0x0a,0xf7,0xbb,0x02,0xb8,0x52,0x57,0x5f, +0x5a,0x9d,0xcf,0xe2,0xb2,0x33,0x1c,0x53,0xd7,0xf8,0x43,0x73,0x40,0x20,0x99,0x11,0x49,0xee,0x71,0x78, +0x5b,0x2d,0xa9,0x80,0x03,0x26,0xd4,0xa7,0x6f,0x87,0x87,0x24,0x51,0x93,0x73,0x11,0xaa,0x09,0x48,0x23, +0x62,0x8d,0x8d,0xcb,0xf0,0xb5,0x0a,0x7a,0x14,0x21,0x9c,0xc9,0xb4,0x6c,0xc3,0x87,0x85,0xe5,0xf4,0x08, +0x4a,0x54,0xf8,0x14,0xd6,0xdf,0x6c,0x6b,0x34,0x49,0x29,0x34,0x49,0xc9,0xa1,0xea,0x1f,0x2d,0x97,0xf5, +0x0a,0x15,0x1d,0xe4,0xdd,0x5c,0x19,0x8a,0xb1,0x1e,0xe1,0x2d,0x1c,0x1a,0x8a,0xb2,0x55,0x7d,0x6b,0x27, +0x94,0x72,0xe6,0xec,0xc7,0x70,0xdd,0xaa,0x4d,0xc7,0x23,0x84,0xee,0x3c,0x83,0xf1,0xb8,0xd2,0xc0,0x6e, +0x7d,0xf0,0xd6,0x79,0xb2,0x88,0x5f,0xd1,0x76,0x25,0x41,0x98,0x76,0x09,0xd5,0x51,0xcc,0xe5,0xf0,0x4a, +0xa5,0x0b,0x63,0x9d,0x43,0x35,0x40,0xdd,0x7d,0x1c,0x7b,0xdc,0xd1,0x8f,0x87,0xd1,0xf2,0x3a,0xfa,0x54, +0xb8,0xf1,0x54,0xaf,0x28,0xdb,0xb7,0xa4,0x14,0x72,0x1b,0x66,0x5b,0xe1,0xf4,0x86,0x0d,0xca,0xe9,0x4f, +0xcd,0x8b,0x98,0xf6,0xf1,0x33,0xec,0xd5,0x1d,0x86,0x40,0xb4,0xbc,0x2d,0xec,0xe2,0x64,0xc8,0x09,0xa1, +0x53,0xc9,0xdd,0x91,0xd6,0x36,0x6f,0x99,0x7f,0xf8,0x64,0x62,0xae,0x3e,0x86,0xec,0x9a,0x8e,0x9f,0xd0, +0x85,0xa0,0x80,0x2f,0x37,0x1b,0x45,0x85,0x60,0xaf,0xf0,0x0c,0x85,0xf7,0xaa,0xf4,0x2d,0xd2,0xa3,0xef, +0x1c,0x1c,0x38,0xf8,0x96,0xc5,0x7b,0xe9,0xf0,0x2a,0x2e,0x2f,0xb3,0x05,0x2c,0xde,0x25,0x46,0xca,0xa5, +0x79,0x22,0x49,0x28,0x25,0x76,0x34,0x74,0x5a,0x11,0xba,0xd5,0x0d,0xed,0x96,0x44,0x5e,0xd5,0x07,0xa8, +0x15,0x96,0x4b,0xba,0x9e,0x96,0x59,0x9e,0x11,0x85,0x93,0x5d,0x11,0xdd,0xe1,0xdd,0x2e,0x77,0x10,0x64, +0xe0,0x61,0x97,0x42,0x93,0x71,0x73,0x7c,0x75,0x23,0x3f,0x7e,0x2d,0x97,0xf0,0x9b,0x46,0x8b,0xfa,0x20, +0xe7,0xb2,0xa2,0xec,0x21,0x3a,0x4c,0xed,0xc5,0x92,0x9f,0x2b,0x5e,0xf7,0xa9,0x77,0x5b,0xcf,0x07,0x27, +0x36,0xf5,0xab,0xb4,0x6b,0x7f,0xff,0x72,0x68,0x91,0x63,0x95,0x39,0x85,0xe1,0xba,0x75,0x3a,0xf5,0x41, +0x68,0x78,0x22,0xb9,0x47,0xb7,0x42,0x01,0x85,0xf1,0x8d,0x96,0xb4,0xa5,0xff,0x9d,0x0e,0x6b,0xda,0x3d, +0x53,0x9f,0x86,0x6b,0xae,0x39,0xf6,0xc7,0xe3,0x4a,0x9e,0x8e,0x22,0x85,0x16,0xc4,0x36,0x3a,0x12,0x74, +0x0c,0xd9,0x27,0xfb,0xfd,0x76,0xd8,0x3f,0xbd,0x53,0x46,0x79,0x59,0x0d,0xa2,0xfc,0xd0,0x50,0x7c,0xbf, +0x5a,0xe9,0xa1,0xa0,0x77,0xb0,0x21,0x50,0xd0,0x8b,0xbd,0xb7,0x8a,0x06,0x95,0xa4,0xb0,0xaa,0xe0,0x2e, +0x36,0x73,0xe4,0x39,0xab,0x69,0x6b,0x1f,0x4d,0x76,0xf4,0x09,0x23,0xc4,0xa8,0x9d,0x53,0xc8,0x53,0x89, +0xa2,0x66,0x74,0x27,0x7f,0x48,0xab,0x32,0x6c,0x87,0xea,0x40,0xf9,0x31,0xf5,0x78,0x49,0xf5,0xe8,0x3b, +0x72,0x5e,0x4a,0x0d,0x25,0x52,0x8d,0x56,0xa7,0x7a,0xbe,0x19,0x81,0xac,0x1f,0xba,0xdf,0xa9,0x66,0x65, +0xde,0x84,0xb8,0xd9,0xc0,0x99,0x38,0x5e,0x5f,0x8d,0x84,0x32,0xed,0x94,0x3b,0x7c,0x36,0x27,0x36,0x3a, +0xd6,0xd8,0xd0,0x59,0x98,0x99,0x32,0x9f,0x51,0x99,0xf7,0x0e,0x59,0x2f,0xd4,0x99,0xa1,0xf3,0x0b,0x71, +0xf7,0xef,0xca,0x7e,0x7f,0xaf,0xbf,0xd0,0x8b,0x2e,0x93,0xcb,0x1a,0xe1,0xd2,0xa6,0x46,0xf6,0xf7,0x1f, +0x0f,0x9b,0x5b,0xa1,0xeb,0xbc,0x38,0x37,0x74,0xcc,0xe0,0x5d,0x42,0x47,0x80,0xe3,0xb7,0x3f,0x15,0xb1, +0xab,0x10,0x29,0x3b,0xb3,0xf9,0x2e,0x4b,0x63,0xb8,0x45,0xcf,0x2f,0x1d,0x2b,0x39,0x4d,0x3b,0x6b,0x3e, +0x57,0x03,0x8a,0x3b,0x6b,0xd4,0x94,0x54,0x3e,0xb5,0x9f,0x79,0xdd,0x45,0xa9,0x0c,0x06,0x27,0x6c,0x9a, +0x5d,0xcb,0x85,0x09,0x8c,0xd6,0x07,0x8f,0x98,0xf9,0x70,0xec,0xbd,0x83,0x23,0x76,0x5f,0x0e,0x15,0x5b, +0x32,0xad,0xbf,0x99,0x4d,0x76,0xbe,0xe9,0x83,0x8d,0xeb,0x85,0x61,0xfd,0xf1,0xc4,0xf1,0xf7,0x9c,0xfe, +0xbd,0xb2,0xef,0x8c,0xf7,0x7e,0x0b,0x47,0xc3,0xd1,0x21,0x0d,0x96,0xe3,0x05,0x55,0x36,0xec,0x01,0xcd, +0xda,0xf7,0x15,0xf6,0x66,0xea,0x08,0x39,0xb7,0xbc,0x8e,0xfa,0xae,0x7c,0xf3,0x7a,0xba,0x12,0xf4,0xe7, +0xcb,0xa1,0xe0,0x39,0xbe,0x23,0x86,0x90,0x67,0x6b,0x75,0x2b,0x0a,0xce,0x0b,0xa2,0x5f,0x2f,0x25,0x06, +0xd7,0x66,0x33,0x37,0x0a,0xe2,0xc7,0x6a,0xf3,0xe6,0x4c,0xe8,0x8c,0xe7,0x3b,0x87,0xc8,0x26,0x04,0xd0, +0xbe,0x34,0xee,0xb4,0xe8,0xb6,0x05,0x30,0x19,0x89,0x42,0x10,0x9a,0x0b,0x4f,0xce,0xa3,0x64,0x49,0x4f, +0x98,0x36,0x04,0xdd,0xfd,0x77,0x22,0xeb,0xd5,0xee,0xc1,0x27,0x7d,0x8d,0xf0,0x3e,0xac,0x53,0x70,0xb2, +0x31,0x30,0x2e,0xa5,0x22,0xb9,0xac,0xfd,0x86,0xba,0x05,0x6c,0x25,0x06,0xa0,0x14,0xc2,0x1e,0x78,0xd3, +0xee,0xba,0xee,0x8c,0x63,0x9d,0xe8,0xba,0x19,0x8e,0x4a,0x0e,0x49,0x91,0xf9,0xd6,0x93,0xbd,0x7a,0x0e, +0x87,0x46,0x18,0x1a,0x13,0xcb,0x7c,0x06,0x2f,0x2d,0xb3,0xc5,0xf2,0x89,0x59,0x5e,0xe6,0xd9,0xf5,0xde, +0xd3,0x71,0xee,0x0e,0x0e,0xfd,0xa7,0xde,0x56,0x62,0xf1,0xf1,0x9d,0xf3,0x5d,0xb6,0x67,0x98,0x97,0x4a, +0xc8,0xf3,0x98,0x49,0x0d,0xb0,0x60,0xbb,0xcc,0xed,0x2f,0x62,0x6d,0xa8,0x4c,0x9d,0xe2,0x30,0xcb,0xc6, +0x22,0xac,0xf2,0x1d,0x73,0x9e,0xdd,0x64,0x86,0xf9,0x48,0x6b,0x41,0x2b,0x70,0xed,0xba,0xe9,0x00,0xac, +0x5f,0x7c,0x07,0x41,0x7f,0x3a,0xb0,0x9a,0x9a,0x7a,0x82,0x9a,0x3b,0x5f,0x5d,0x70,0x98,0x8a,0x0d,0x11, +0x04,0xd8,0x70,0xb6,0xf4,0x53,0x43,0xb6,0x2a,0xa2,0xd4,0xb5,0x64,0x0d,0xe0,0x20,0x63,0x5f,0xe9,0xcf, +0xf5,0x14,0x0f,0x12,0x09,0x63,0x9a,0xfa,0x6a,0x76,0x04,0xe2,0x29,0xdd,0x14,0x0f,0xc2,0x06,0x52,0x37, +0xe2,0x97,0xf8,0x63,0xb4,0xfc,0x9e,0xf6,0xa6,0x0e,0x12,0x48,0x97,0x6b,0x97,0x26,0x42,0x0a,0x53,0xa2, +0xee,0x13,0x9f,0xf7,0x49,0x4b,0x12,0x71,0x68,0x64,0x15,0x34,0x70,0x3c,0xa8,0x85,0xc3,0x9a,0xae,0xa6, +0xec,0xf0,0x3a,0x8f,0x56,0x40,0x38,0xdd,0x09,0xe6,0xa1,0x54,0x4f,0xb2,0x55,0x36,0xdc,0xee,0xe1,0xca, +0x5a,0x99,0x73,0x51,0x2a,0x65,0x55,0xa5,0x80,0x54,0xe8,0x41,0x5d,0x74,0xef,0x0d,0xe3,0xdf,0x5c,0x1d, +0x2e,0x99,0xdd,0x41,0x75,0xb2,0xba,0xcd,0x74,0x0d,0x02,0x59,0x67,0xed,0x97,0x4d,0x29,0xa4,0x01,0x3d, +0x96,0x18,0x73,0xb1,0xe0,0x41,0x2b,0xf2,0x84,0xbd,0xc4,0xc6,0x1e,0x6a,0xd8,0x7e,0x6c,0xa0,0x2b,0x0c, +0x18,0xae,0x58,0xba,0x2a,0xf2,0x12,0x9d,0xf2,0x22,0x4d,0xbb,0x41,0xf5,0xff,0x8c,0xe5,0xa5,0x6d,0x5f, +0x6b,0x32,0xab,0x5b,0xd7,0x7a,0x77,0xdb,0x5a,0x56,0xb6,0x94,0x30,0xe5,0xd2,0xa2,0x19,0xb7,0x72,0x6a, +0x9c,0xa4,0x43,0x35,0x76,0x2c,0x58,0xb5,0x40,0x7d,0x3d,0x69,0x40,0xb7,0x11,0x56,0xad,0xea,0xe3,0xdd, +0x4a,0xcb,0xb4,0xd1,0x02,0x94,0x53,0xd6,0x8d,0xf7,0xbc,0x40,0x0a,0x5b,0xa7,0xad,0xe2,0x6a,0x68,0xb4, +0x3c,0xb4,0x50,0x93,0x11,0x03,0xee,0x3a,0x88,0x3b,0xee,0xb4,0x51,0xd6,0x2d,0x73,0x64,0x83,0xb4,0xab, +0x45,0xb6,0x34,0x62,0x98,0x19,0xd0,0x96,0x2a,0xaa,0x5a,0x99,0xa0,0xad,0xf2,0xe1,0xaa,0x88,0xd7,0x8b, +0x8c,0x43,0xd2,0x2f,0xe2,0xb4,0x63,0x19,0xf5,0x9a,0x29,0x19,0xc3,0x12,0xb0,0x2b,0xb2,0x16,0xba,0xde, +0x75,0x65,0xd3,0x43,0xc0,0x03,0x0e,0x5e,0xc3,0xb0,0x5c,0xd0,0x01,0xc8,0xad,0x40,0x37,0xde,0x81,0xe5, +0x26,0xe5,0xd8,0x02,0xa2,0xe1,0xcd,0x65,0x6e,0x7b,0x4e,0x62,0x63,0xd6,0x7c,0x0f,0x2b,0x83,0x7e,0x7c, +0xf5,0xf2,0x79,0x59,0xae,0xd4,0x81,0xa7,0xf6,0xe8,0x12,0x58,0xce,0xcc,0x3c,0xff,0x52,0x86,0xb7,0x23, +0x76,0x07,0x3d,0x3c,0x3a,0x7a,0x40,0x17,0x5f,0x6c,0xfd,0xdf,0xcb,0xb0,0xa3,0x14,0x17,0x8e,0xd2,0x34, +0x7d,0xf2,0x22,0xec,0xf5,0x7e,0x87,0xb5,0xe0,0x35,0x75,0xec,0x63,0x62,0x84,0xa9,0x9a,0x49,0xb4,0x2c, +0xa0,0x91,0xfc,0xbd,0x84,0x4a,0x12,0x9f,0x86,0x94,0x0b,0xd2,0xe9,0x0a,0x9b,0xfd,0xbe,0x36,0xb5,0x95, +0x4a,0x06,0x87,0xa5,0xca,0x7c,0xb3,0x41,0xde,0x88,0x56,0x6e,0xf1,0x0c,0x4a,0xe4,0x5d,0xd4,0x80,0xe3, +0x19,0xd8,0x8a,0x01,0x89,0xfd,0x82,0x26,0xb6,0x54,0x91,0xa3,0x96,0x02,0x2a,0xd8,0x2d,0x55,0x80,0x32, +0x66,0x25,0x4a,0x39,0x00,0x71,0x47,0xfb,0x81,0x28,0x4e,0x68,0x3a,0x15,0xc5,0x75,0x96,0x2f,0xb0,0x1f, +0xd0,0xd7,0x22,0x75,0x94,0x80,0x11,0x12,0xf0,0xa0,0x7a,0x58,0xc0,0xf3,0xc9,0x7a,0x40,0xb7,0xe3,0xd2, +0x70,0x93,0x88,0xb4,0xd8,0xe4,0x44,0xbb,0x9e,0xb9,0xd5,0x27,0x28,0xd3,0x6a,0xe1,0x66,0x93,0x4c,0x9d, +0x1f,0x07,0x6a,0x94,0x88,0x30,0x64,0x78,0x68,0xf8,0x1a,0x76,0x3e,0x0f,0x9d,0xfa,0xb0,0x3a,0x42,0xe9, +0x70,0xb5,0x13,0xaf,0x68,0x53,0x38,0x91,0xcf,0xa8,0xcf,0xe3,0xae,0x69,0xbd,0x67,0x4d,0x9f,0x94,0x25, +0x2d,0x79,0x08,0x35,0x37,0x62,0xe0,0xf1,0x05,0xd3,0x23,0x7c,0xc5,0x44,0x01,0x5f,0x31,0x21,0xc2,0x42, +0x2f,0xd1,0xcb,0xb3,0x6d,0x86,0xaf,0xc8,0x1d,0x8e,0x8b,0x50,0x68,0x4a,0x28,0x50,0xc2,0x2e,0x7e,0xda, +0x42,0x43,0x29,0x14,0x37,0x3d,0xc9,0x88,0xd7,0x55,0x29,0xbd,0x20,0x73,0xf5,0x73,0xbf,0xb0,0x44,0x61, +0x78,0xf1,0x4b,0x39,0xd5,0x8f,0x66,0x30,0xd4,0xe9,0x48,0x26,0xb2,0x5b,0xc6,0x0a,0xe4,0x00,0xa6,0x22, +0x2c,0x16,0x4e,0x85,0xdf,0x78,0x1d,0x0a,0x55,0x2b,0x25,0x62,0x78,0xde,0x9e,0x25,0x69,0x94,0x7f,0x0a, +0xaa,0xc7,0xdb,0xe0,0x96,0x85,0xbc,0xf5,0x84,0x5b,0x2a,0xb9,0x53,0x40,0xe2,0x0a,0x2e,0xa6,0xe9,0x49, +0xc4,0xee,0xca,0xad,0xfe,0x4c,0x5d,0xdd,0xda,0xca,0xa5,0xcb,0xf4,0xf1,0xa4,0xea,0xed,0x3c,0xe8,0xec, +0x6f,0x6b,0xd0,0xbe,0x60,0x88,0xe3,0x8a,0x34,0x64,0x84,0xd5,0x4e,0xb2,0x2e,0x05,0xc2,0x85,0x04,0x8e, +0x40,0xf9,0x32,0x5a,0x42,0xcd,0x15,0x42,0xc8,0x95,0x35,0x86,0xa1,0x64,0xa2,0x5b,0x70,0x2e,0x34,0x7d, +0xb7,0x56,0xa8,0x94,0x42,0xdf,0xad,0xb7,0x2d,0xb9,0x06,0x17,0xc3,0x21,0xc9,0xb6,0x86,0xe2,0xa9,0x80, +0xea,0xed,0xf9,0x17,0xdb,0x4b,0x80,0xbd,0x3d,0xf5,0xe9,0x34,0x14,0x6a,0x04,0x86,0x63,0x55,0x26,0x4a, +0x96,0x67,0x34,0x1b,0x92,0x46,0xa9,0x30,0x2a,0xfd,0x83,0xbf,0x57,0x53,0x51,0xec,0x78,0x1e,0xcf,0xaf, +0x3a,0x9f,0xdf,0x0c,0xaa,0x37,0x35,0x4d,0x86,0x2a,0xed,0xe0,0xf4,0xcc,0x9d,0x04,0xc8,0x75,0x83,0x84, +0x9e,0x3c,0x66,0xf5,0x45,0x4d,0xe7,0xc0,0xda,0x03,0x95,0xcd,0x8e,0xb3,0x5f,0xe8,0xaa,0xa7,0xd0,0x07, +0xd2,0x7e,0x10,0x77,0xf7,0x97,0xa1,0xcb,0x6a,0x67,0x70,0x15,0x49,0x91,0xc9,0x35,0xe9,0x3b,0x66,0x70, +0xa9,0xc7,0xfc,0x76,0xbf,0x8a,0x63,0x39,0x28,0x3e,0xab,0x3f,0xab,0x2d,0xb9,0xb3,0x10,0x08,0xbf,0xea, +0x92,0x1b,0x65,0x28,0x3d,0xee,0xdc,0x8e,0x99,0x1e,0x66,0x62,0xc3,0x51,0x51,0x8d,0x1e,0xd2,0xe1,0xcc, +0x5a,0xdd,0x5b,0xa5,0x9b,0x82,0x53,0x2c,0xbf,0x79,0x2c,0xf7,0x7e,0x91,0xcf,0xe9,0x19,0x6d,0xd0,0x44, +0x3d,0x51,0x16,0x0e,0x16,0xca,0x9e,0x12,0x8d,0xd7,0xb7,0x2a,0x13,0x68,0x46,0x9b,0x2b,0x22,0x74,0x5e, +0xe2,0x5a,0x5b,0x8b,0x88,0xec,0xbf,0x18,0x7d,0xc1,0xa7,0x99,0x8a,0x95,0x46,0x8d,0x4d,0x63,0xe6,0xed, +0x6a,0x1e,0xff,0x25,0xa8,0xc0,0xbb,0x26,0xae,0xd2,0x80,0xfe,0x58,0xc2,0x58,0xe0,0x7b,0xe8,0x3c,0x43, +0xef,0x74,0xe2,0x4e,0xc2,0xfd,0xcd,0x3d,0x6f,0x73,0x3a,0x51,0x76,0x1b,0xd6,0xac,0x04,0x33,0xb2,0x0a, +0x9c,0xb9,0x12,0x06,0x8b,0x5a,0x6a,0xa5,0x65,0xc3,0x6d,0x84,0xc3,0x1f,0x4b,0xb1,0x8f,0x13,0x69,0xb8, +0x58,0x96,0xf6,0x9d,0x5f,0x44,0xd2,0x50,0xa3,0x98,0x63,0x84,0x85,0xc4,0xf4,0xe8,0x9a,0x1d,0x28,0x85, +0x75,0x57,0x2b,0x6b,0xf0,0x3a,0xc0,0x77,0xcb,0x21,0x27,0xd2,0xc0,0x31,0xdf,0x6b,0x3d,0x37,0x3a,0xdf, +0x9b,0x38,0xf4,0x97,0x18,0xe8,0xa6,0x21,0x7f,0xa9,0xa4,0x08,0x2c,0xf2,0x29,0xff,0xbb,0x22,0x9f,0xaa, +0x4c,0x16,0xc9,0x10,0x01,0x81,0x5f,0x71,0xef,0x82,0x62,0x49,0x5a,0xc1,0x80,0xf4,0x36,0xab,0xaf,0xb9, +0xd8,0x44,0xb7,0x41,0xf7,0x69,0xd3,0x5c,0xa3,0xfe,0x16,0x36,0xc7,0xb5,0x07,0x30,0xe5,0xa8,0x3f,0xf1, +0x8b,0x09,0x9d,0x25,0x10,0x88,0x17,0x33,0x23,0x0d,0xfa,0x5e,0xa4,0x41,0xfd,0xc4,0x24,0xd7,0x7d,0xc6, +0x7d,0x65,0x09,0x9d,0x74,0xdf,0x69,0x39,0x91,0x4a,0xce,0xc6,0x20,0x09,0x9f,0xef,0x66,0x3f,0x98,0xaa, +0x05,0x26,0x5a,0xc6,0x99,0xbd,0x79,0x6b,0x6b,0x33,0xcb,0x84,0x26,0xe9,0x3b,0x7b,0xd7,0x51,0xb1,0x47, +0x44,0xee,0x1e,0xe6,0x13,0xeb,0xbb,0x22,0xea,0x8a,0xad,0x5f,0xef,0x9a,0x50,0x58,0x60,0xb8,0x2d,0x4c, +0x93,0x19,0xc7,0x4f,0xb3,0xb3,0x8e,0xaa,0x80,0xee,0x5b,0x3f,0xd7,0x02,0x6c,0x7b,0x26,0xea,0x6d,0x24, +0x9b,0x88,0x0d,0x7b,0xe5,0x42,0xed,0x52,0xfb,0x39,0xbf,0xcc,0x2f,0x25,0xd0,0x75,0xb3,0xf7,0xd3,0x46, +0x67,0x62,0x42,0xc3,0x92,0x36,0xf1,0x18,0x58,0xba,0x3e,0x38,0x19,0x0d,0x77,0xe6,0x46,0xcc,0x7c,0x45, +0x61,0xa6,0xcd,0x8d,0x3c,0xc3,0x8a,0x8b,0x99,0x9b,0x48,0x85,0x11,0x22,0x41,0xf3,0x79,0x2d,0x7c,0x17, +0x08,0x8f,0x13,0x08,0x51,0xf0,0x96,0x67,0x5c,0xc7,0x57,0x2e,0x66,0x27,0xd8,0x83,0x0a,0x64,0xc0,0x0a, +0xbe,0x70,0x8c,0xc9,0xf9,0xf0,0xf8,0x40,0x7e,0xec,0x1b,0xc7,0x3f,0x92,0x4d,0xd5,0x30,0x0a,0x8a,0xf0, +0xde,0x8a,0x6d,0x29,0x6b,0x81,0x39,0x93,0xa6,0x80,0xc2,0xf2,0x64,0xab,0x20,0xb2,0xd4,0xd4,0x9d,0xce, +0xc6,0x9d,0xce,0x7a,0x4a,0xa7,0x45,0x3b,0xf6,0xd8,0x78,0x91,0x9b,0x95,0x4f,0xa4,0x5f,0x67,0x87,0xc0, +0xcb,0xe7,0xcf,0x76,0x01,0x02,0xd7,0x34,0x25,0xed,0x67,0x51,0x11,0x33,0xd4,0x1b,0xcb,0xd6,0xd3,0xca, +0x36,0x4d,0xd9,0x2f,0xb4,0x37,0xcb,0xdc,0xa3,0xa5,0x40,0x29,0x21,0x91,0x7a,0x6a,0x22,0xd4,0xd0,0xa4, +0x03,0x86,0x1e,0x6c,0xa9,0x26,0xd3,0x66,0x29,0x00,0x43,0xf7,0x66,0x81,0x9b,0x84,0x67,0x2e,0xed,0x5f, +0xd4,0x45,0xc0,0x76,0x46,0x68,0x73,0x8d,0x49,0x7d,0x01,0xe0,0xec,0x6a,0x4f,0x87,0xa1,0x4b,0x9c,0xd3, +0x43,0xe4,0x67,0xf3,0x69,0x46,0xca,0xc0,0x34,0xd4,0x4e,0xe7,0x7b,0x00,0x1c,0x80,0x93,0x44,0xa4,0x39, +0xcb,0x5d,0xd3,0xb0,0xa5,0x85,0x38,0x02,0xe4,0xe1,0x0f,0xf0,0x22,0x61,0x91,0x32,0x78,0x74,0xb0,0xf4, +0x72,0x37,0xf2,0x71,0xdf,0x32,0x00,0xab,0xab,0x1d,0x83,0xb2,0x03,0x68,0x50,0x80,0xc3,0x9d,0x37,0xaf, +0xdf,0x9d,0x34,0xe2,0xb9,0x75,0x49,0x5d,0x12,0xda,0xe8,0x1a,0x82,0x17,0x31,0xbe,0x60,0x79,0x4f,0x49, +0xe7,0x20,0x8b,0x05,0xed,0xa3,0x2f,0xab,0x96,0x31,0x65,0x8f,0xd4,0x6e,0x3e,0xe1,0x63,0x76,0x91,0x7c, +0xc4,0x19,0xab,0xf8,0x75,0x7b,0x7e,0xc2,0x16,0x49,0x82,0xcd,0xe7,0xc2,0x5b,0xeb,0xd5,0x4f,0x43,0x56, +0x97,0x6c,0x45,0x2d,0xfe,0xb9,0x06,0x3f,0x95,0x6d,0x36,0xd3,0xb8,0x46,0xf0,0x22,0x50,0xe3,0x0c,0x34, +0xa4,0x16,0x71,0x54,0x02,0xb4,0x4a,0x37,0xe1,0x57,0x1a,0x5d,0xbf,0xa1,0x10,0xb6,0x55,0xcb,0x35,0xa5, +0xb3,0x5f,0x89,0x30,0x3b,0xa0,0xd2,0x9b,0x96,0xba,0x75,0xc9,0x00,0x1f,0x7a,0x55,0x10,0x1f,0x9b,0x11, +0x57,0x60,0x52,0x8b,0x1d,0x72,0xb1,0x0b,0xda,0xf8,0xd9,0x4f,0x4c,0xd0,0xce,0x6c,0xe6,0xd4,0xc4,0xa2, +0xc2,0x91,0x04,0x9b,0xb4,0xad,0xe6,0xc3,0xb9,0xd1,0xc2,0xb3,0xb3,0xa5,0xeb,0x6b,0xbe,0xbc,0x3b,0x06, +0xa2,0xe8,0xa7,0x2d,0xf0,0x73,0x1d,0x15,0xc3,0x11,0xff,0x0a,0xac,0x29,0x46,0x26,0x75,0x40,0xe6,0x27, +0x73,0x9c,0x84,0xf3,0x0a,0xdf,0x7c,0xa8,0x93,0x87,0x8e,0x8e,0x4a,0xc3,0xd1,0x56,0xce,0x55,0x3d,0x5c, +0x05,0x4f,0xa8,0x32,0x67,0x5d,0xba,0x0a,0xb3,0x28,0x4f,0x96,0x8c,0x4f,0xee,0x2f,0x43,0xb7,0x8a,0xc2, +0x81,0x22,0x80,0xdb,0x94,0xdc,0x30,0x6e,0x4e,0x38,0xf7,0x58,0xb1,0xb2,0xb6,0x8f,0x76,0x80,0xe1,0xc2, +0x8f,0x06,0xa6,0x1a,0x39,0x15,0xa7,0xeb,0xe1,0x32,0xc8,0x3b,0x87,0xa3,0x49,0xc2,0x9c,0x61,0xc9,0xbd, +0x00,0xce,0x11,0x15,0x3a,0x7a,0x46,0xd4,0x0d,0x9c,0x79,0xad,0x47,0x6b,0x3c,0x6a,0x2f,0x34,0x36,0x82, +0x28,0x45,0x24,0x14,0xb3,0x33,0xa7,0x85,0x9e,0x08,0xbf,0x71,0x85,0x63,0xc9,0xe1,0x6f,0x28,0xf1,0x0a, +0xbf,0x72,0x37,0x00,0xb6,0xc0,0xaa,0x1f,0x55,0x49,0x50,0x15,0x4e,0x83,0x0b,0x75,0x3f,0x28,0xf8,0x07, +0x07,0xb3,0x03,0xab,0xe8,0x0b,0xf6,0x73,0x9c,0x40,0x1e,0x00,0xb8,0x6b,0x55,0xf0,0xca,0x0b,0xce,0xb9, +0xbf,0x56,0x0a,0xb3,0xd6,0x0e,0xf2,0xda,0x1c,0x63,0xde,0xf4,0x77,0x39,0xb2,0x5b,0x01,0x02,0x31,0x41, +0xbb,0xc4,0x73,0x32,0xaf,0x25,0xdb,0xa1,0x99,0x44,0x76,0xec,0xb2,0x71,0xcd,0x41,0xbf,0xe9,0x57,0x9b, +0x4d,0xb2,0x5d,0x72,0x22,0x8c,0x54,0xb6,0x0b,0x41,0x9e,0xe1,0xf7,0xea,0x01,0x37,0x21,0x18,0x5c,0xa8, +0x1b,0xb5,0x87,0x33,0xea,0xb9,0xe5,0x13,0xe3,0xdf,0x02,0x4f,0x92,0xc7,0xbb,0x9f,0x0c,0x39,0xec,0xb3, +0x54,0x78,0x40,0xa7,0x0f,0xe7,0x7e,0x42,0x33,0x81,0xe3,0xe3,0xe4,0xaa,0xab,0x39,0xd5,0x8f,0x8d,0x54, +0x2f,0x19,0xbe,0x3e,0xe0,0xdc,0x46,0x92,0x7e,0xb4,0xd5,0x0e,0xa1,0xfe,0xaa,0x23,0x04,0x0e,0x9c,0x18, +0x95,0xe0,0x56,0x43,0xbc,0xf9,0xc6,0x8f,0x81,0x8e,0xb8,0x7a,0x56,0xda,0x3b,0xde,0xcc,0x69,0xb5,0x02, +0x52,0x7b,0xc1,0x4d,0xe8,0x3c,0xdb,0xd5,0x3d,0x81,0xab,0xc3,0x79,0x73,0xc5,0xdf,0x88,0xcc,0x11,0xbd, +0x66,0x3d,0x45,0xe4,0x1c,0x37,0x46,0xf9,0xb2,0x7d,0xc3,0x27,0x09,0x11,0x76,0xf5,0x5b,0x4f,0xd7,0x4b, +0x3a,0x4c,0xaf,0x42,0xfe,0x40,0x30,0xf9,0xa9,0xb7,0x04,0x94,0x9f,0xad,0xc7,0xed,0x7e,0xeb,0x48,0x8c, +0x4e,0xab,0x52,0xd3,0x26,0xc7,0x79,0xcb,0xf4,0xe7,0x12,0x06,0x55,0x2b,0x25,0x0e,0xc0,0x09,0x76,0x5e, +0x93,0xb3,0x5a,0x03,0x52,0x40,0x2b,0x2d,0x47,0x2e,0x10,0x6f,0x8f,0xad,0x6f,0xb7,0xfa,0x4f,0x9a,0xf0, +0xd6,0x85,0xe7,0xb5,0x7e,0x1b,0xc7,0xac,0x44,0xd7,0xfb,0x59,0xd7,0xe6,0x27,0x82,0xf5,0xda,0x47,0x7a, +0xe3,0xdd,0x6c,0xfe,0x1e,0x6f,0x1b,0xd8,0x86,0x06,0xa0,0x3a,0x70,0xac,0xe9,0xe5,0xf8,0x06,0xcf,0x5a, +0x9e,0xab,0xc9,0xb9,0xc3,0xd9,0xa1,0x96,0x04,0xdb,0xbb,0x02,0x6c,0xb4,0x71,0x09,0xf3,0x16,0x02,0x91, +0x95,0x53,0xe5,0x26,0x39,0xb6,0x65,0xf9,0xca,0x79,0x29,0xf6,0x26,0x44,0x92,0x07,0x5f,0x37,0xa3,0x24, +0xc2,0xbd,0xd8,0x5a,0x52,0x76,0x84,0x92,0x09,0xad,0x66,0x78,0xdf,0x01,0x81,0x99,0x57,0x83,0x8b,0xe5, +0xad,0x1b,0xe5,0xa6,0x74,0x63,0x35,0x37,0x48,0xfc,0x74,0x92,0x04,0x99,0xbd,0x0e,0x3d,0xfe,0x36,0x4c, +0x38,0x92,0x40,0xde,0x11,0x87,0xaf,0x01,0x6a,0x89,0x93,0x99,0xcf,0x84,0x3f,0x88,0x4b,0xc2,0xf1,0x31, +0x6a,0xf1,0xa9,0xec,0xf4,0x69,0x2d,0x46,0x89,0x0a,0x8a,0x54,0xd2,0xc1,0xa4,0x98,0x24,0x36,0x62,0x06, +0x6b,0x51,0x1d,0x1b,0x94,0x29,0x07,0xaf,0x08,0xd2,0xad,0x67,0x0f,0xad,0x0a,0x5c,0x64,0xc2,0xa6,0xf0, +0x84,0x0f,0x54,0xf0,0x94,0xae,0xc8,0xe2,0xfc,0x95,0x09,0x76,0xc1,0x04,0xbe,0xd3,0x8f,0xb5,0xc8,0x26, +0x80,0x49,0x43,0xe0,0x64,0xeb,0x92,0x1f,0x6f,0x1b,0x60,0x84,0x32,0xe0,0xb9,0x35,0xe0,0x95,0x3c,0x39, +0x6c,0x76,0x1e,0x68,0x75,0x0b,0xab,0xd3,0x50,0xf8,0x09,0xc7,0x3b,0x63,0xcc,0x38,0x01,0x54,0xce,0xf8, +0x77,0xa2,0x16,0x16,0x15,0x2f,0xeb,0xb7,0x22,0x44,0x5b,0x73,0x09,0xdb,0xfb,0x5d,0x73,0x89,0x88,0x50, +0x4c,0x10,0x0b,0x85,0x44,0x5a,0x44,0x7b,0xd8,0xd4,0xb4,0x79,0x16,0x54,0xfb,0x77,0x73,0x23,0x9f,0x3a, +0xb2,0xf1,0x72,0xb2,0xaf,0x6b,0x41,0x20,0x11,0xdd,0xa6,0xbd,0xf1,0x33,0xb8,0xfa,0x15,0xd1,0xad,0x25, +0xb3,0x50,0xcc,0xbb,0xd2,0x2c,0xc4,0xf7,0x7e,0x56,0xbb,0xd3,0x09,0x64,0xf1,0xea,0x04,0xf5,0xbb,0xaa, +0x70,0xe3,0xf7,0xcf,0xf3,0x5d,0x6d,0x05,0x68,0x7e,0xe1,0x55,0x08,0xf6,0xdc,0x1d,0x80,0x46,0x01,0xca, +0x17,0x4d,0x71,0x6d,0x7a,0xca,0x08,0x90,0x4d,0xd7,0x2a,0x38,0x55,0xee,0x44,0x66,0x53,0x64,0x61,0xac, +0xbc,0xef,0xe8,0x15,0xf4,0x4b,0xad,0x4f,0x1a,0x1f,0x9c,0x9f,0x9b,0x2f,0xbc,0xad,0xaf,0xbd,0x50,0xff, +0x00,0x90,0x5a,0xa8,0x4f,0x05,0x2c,0x0d,0x20,0xc2,0xce,0xaf,0xcc,0x37,0xc0,0xb3,0x68,0x81,0x62,0x58, +0xc5,0x3b,0xf7,0xef,0x3b,0x4a,0xa1,0x87,0x07,0x94,0x35,0x8c,0xad,0xee,0x3b,0x7e,0xaa,0x17,0xf1,0x65, +0xb6,0x5c,0xbc,0x85,0xbc,0xb7,0x46,0xd4,0xc6,0x82,0x43,0x47,0x8f,0xdf,0x47,0x49,0xd9,0xef,0x07,0xfa, +0x96,0x61,0xa5,0x85,0xe6,0x62,0xd7,0x82,0xb0,0xe6,0x68,0x60,0x18,0x5b,0x68,0xde,0x43,0xcb,0x02,0xfa, +0xa2,0xc2,0xe1,0x0a,0x13,0xbf,0x0a,0xbe,0x6e,0x58,0x1f,0x41,0x5c,0xdc,0xdf,0x97,0xdf,0x61,0x74,0xb5, +0xd0,0xd7,0xae,0x23,0x16,0x94,0xc0,0x7c,0xee,0x88,0x6e,0x73,0x11,0x2b,0xe2,0xe6,0x07,0x78,0x47,0xfd, +0xfa,0x4f,0x24,0xf5,0xbf,0xc5,0xf5,0x3d,0x6b,0x0d,0xa4,0xd9,0xe3,0x2c,0x3d,0x27,0xf6,0xac,0x1e,0x36, +0xd2,0xb0,0xf4,0xf7,0xf8,0x44,0x61,0x12,0xf9,0x5e,0xf8,0x6d,0x29,0xe8,0xd3,0x2a,0xb7,0xea,0x95,0xba, +0xff,0xa1,0x44,0xcf,0x6d,0x01,0xc6,0x52,0x3d,0xc4,0x87,0x17,0x0c,0xc9,0xba,0xf5,0x8e,0x0f,0x94,0xd4, +0x52,0x4b,0x2f,0x8d,0xed,0x34,0xa2,0x07,0xbc,0x22,0x0e,0x85,0xf6,0x5a,0x22,0xb1,0x6e,0xaf,0xd4,0x25, +0xf1,0xb8,0xa1,0xf3,0x96,0x38,0xd8,0x32,0xde,0x5b,0xaf,0x88,0x7f,0x8b,0xf7,0x18,0xb3,0x15,0x06,0x9d, +0x26,0xcd,0x11,0xa5,0x79,0x7d,0xf2,0x68,0x77,0x82,0x07,0x94,0x80,0xb9,0xa1,0x3d,0x74,0x3a,0xed,0x64, +0x7b,0xe0,0x04,0x7b,0x56,0x8a,0x2f,0x28,0xc5,0xc9,0x65,0xbc,0x07,0xe7,0x88,0xbd,0x45,0x16,0x8b,0x60, +0x68,0x49,0xfb,0xf3,0xde,0x32,0xf9,0x10,0xef,0x45,0x7b,0xe2,0x70,0x45,0xfb,0xde,0x79,0x72,0xa1,0xd1, +0xe4,0x21,0x93,0x59,0xaf,0xf6,0x28,0xdf,0xa4,0xa0,0x57,0x79,0xbe,0x5e,0xd5,0x0b,0xfe,0x92,0xb2,0x7d, +0xcc,0xba,0x84,0x62,0xaf,0x88,0x3e,0xc6,0x8b,0xe1,0xde,0x4f,0xd9,0x7a,0xaf,0xb8,0xcc,0xd6,0xcb,0xc5, +0x5e,0x1e,0xd3,0x66,0x57,0xee,0x7d,0xca,0xd6,0xf9,0xde,0x59,0x16,0xe5,0x0b,0x2a,0xf4,0xda,0xfa,0xfa, +0xaf,0xf4,0xf5,0x1b,0xa5,0x3f,0x2b,0xa8,0x56,0x5c,0x27,0x36,0xff,0xb3,0xab,0xfe,0xb7,0x66,0x19,0xd6, +0xbb,0xaf,0xe9,0xdd,0x77,0x99,0x72,0xeb,0xa4,0x2c,0x88,0x39,0x9c,0xd7,0x6b,0x78,0x08,0x09,0xd7,0x3b, +0xba,0x43,0x7b,0x88,0xa7,0x4b,0xf2,0x78,0xe1,0xef,0x11,0x1b,0x19,0xc1,0x90,0x24,0x66,0x61,0x31,0x0e, +0xc0,0xe1,0x70,0xe8,0x6c,0xcd,0x68,0x15,0x71,0x5a,0x64,0x39,0xfb,0xaa,0x19,0x6d,0xf5,0xd4,0x79,0xf2, +0x9c,0x78,0x6e,0xe7,0x09,0x91,0xf9,0x11,0x38,0xcd,0xa7,0x57,0x94,0xf2,0x51,0x1a,0x2d,0xb3,0x0b,0x73, +0xf7,0xe4,0xf1,0xe1,0xd1,0x61,0x75,0xf7,0xee,0xf0,0xc7,0xc3,0x2f,0xe9,0xf6,0xf9,0xcb,0xf7,0x7f,0x1b, +0x1d,0x1e,0xd1,0xd5,0x0f,0x5f,0x1f,0x7d,0x75,0xf8,0x0c,0x29,0x1e,0x3f,0x3f,0xfc,0xf2,0xab,0x11,0x5d, +0x99,0x2c,0x9e,0x24,0x17,0x49,0x19,0x2d,0xf1,0x52,0x1c,0xba,0x7d,0xe7,0xcd,0xab,0x77,0x3f,0x8e,0x46, +0x0f,0xe8,0xea,0x9b,0x57,0x3f,0x1e,0xfd,0x0d,0xc9,0x5f,0x3d,0xff,0xd7,0xe1,0xd7,0xf4,0xfb,0xee,0xc5, +0x5f,0x47,0x5c,0xd6,0xbb,0xe7,0x27,0x0f,0x7e,0xdc,0x7b,0x71,0xf4,0x18,0xa9,0x9e,0x1f,0xfe,0xf5,0x4b, +0xa4,0x7a,0xf3,0xaf,0xa7,0xaf,0x46,0xa3,0x2f,0x50,0xe1,0x47,0xaf,0x8e,0x1e,0x1c,0x8d,0x54,0x82,0x6f, +0xbf,0xff,0xe1,0xd1,0xbb,0xc3,0xa3,0x77,0x4f,0x1c,0xc3,0x03,0x1c,0x1e,0x33,0x54,0xfa,0xb1,0x89,0x50, +0x44,0xa7,0x42,0x3c,0x38,0x9c,0x89,0x1d,0xae,0xe9,0x93,0xab,0xe8,0x82,0xe6,0xf0,0x5a,0x76,0x7d,0xbb, +0x5b,0x4e,0xe2,0xab,0x15,0x42,0x11,0xae,0x11,0xb9,0xd6,0x79,0xbe,0xbe,0x4a,0x16,0x8c,0xa4,0xef,0xbc, +0x81,0x37,0xad,0x3c,0x7d,0xbc,0xce,0x41,0x95,0xa1,0x03,0xb2,0x65,0x49,0xfd,0x8d,0x7a,0xb1,0x85,0xe2, +0xde,0x1b,0xd8,0x7e,0xe2,0x76,0x25,0xaa,0x79,0xf3,0x80,0x36,0xa7,0x7a,0x0a,0xfe,0xdd,0x7b,0x16,0x21, +0x54,0x11,0x3a,0x89,0xce,0xad,0x8b,0x4f,0xe6,0x62,0x8f,0x68,0x0c,0xca,0xda,0xfb,0xc3,0x1e,0x3d,0x1c, +0x72,0x07,0xbd,0x3a,0x1a,0x7e,0x29,0xf7,0xb8,0x7d,0xfc,0x1a,0xa3,0xf3,0x72,0x0d,0xc4,0xbb,0xef,0x7f, +0xf8,0xcf,0x3b,0x87,0x97,0xa1,0xdd,0x3b,0xaf,0xff,0x41,0x39,0xbe,0x5e,0x97,0x7b,0xb4,0xd7,0xbd,0xc5, +0x34,0xa5,0xdb,0xf7,0x51,0x7e,0x85,0x55,0xfa,0x3d,0xe8,0x27,0xa5,0x62,0xc3,0xe3,0x3c,0xa3,0x87,0x2f, +0x9e,0xf0,0x24,0x2b,0xa3,0x3d,0x2d,0xe0,0xa0,0x91,0x33,0xd7,0xdf,0xbe,0x79,0xf1,0xda,0xdc,0x3c,0xa6, +0x35,0x7b,0xa6,0xd6,0xaa,0x28,0x31,0x4c,0xbd,0x47,0xaa,0xde,0x76,0xb5,0x67,0x81,0xda,0x25,0x40,0xca, +0x98,0xaa,0x7f,0x88,0xe3,0x15,0xea,0x20,0x5c,0x12,0x83,0x42,0x66,0xd7,0x95,0xa0,0xf4,0x7a,0xef,0x09, +0x9c,0x77,0x0f,0xe3,0x07,0xf7,0xf1,0x1c,0x58,0xfd,0xc4,0x2c,0xbc,0x78,0xf7,0x5a,0x79,0x83,0x7a,0xc3, +0x62,0x7d,0x26,0x52,0x4b,0x77,0xe4,0x1f,0x7e,0x5d,0xd9,0x0c,0x3b,0x27,0x0a,0xc9,0xec,0x9e,0x82,0x2a, +0x9e,0x42,0x67,0x1e,0x7e,0x4e,0xd9,0x7c,0x3e,0x73,0xc4,0x87,0x8d,0x36,0x54,0x7a,0x5b,0xac,0xa2,0xb4, +0xf1,0x92,0x45,0x54,0xad,0xb7,0xd1,0x45,0x56,0xbd,0xa5,0x1b,0xe2,0xd7,0xb3,0xeb,0x7e,0xdf,0xa7,0xcb, +0x7e,0x7f,0x5b,0xb5,0x89,0xfd,0xea,0xf4,0xd6,0x52,0x8d,0x06,0x7c,0xc0,0xc2,0xe1,0xfd,0xe9,0xa3,0xc1, +0xbf,0x4e,0x17,0x33,0x4f,0x6e,0xa2,0xc1,0xef,0x33,0x6f,0x7a,0x7a,0xfd,0xef,0xde,0xff,0xfe,0xec,0xde, +0x5f,0x7e,0xde,0xbf,0x7f,0xea,0x9e,0x7a,0xc7,0x0f,0xfd,0xe1,0xe9,0x64,0x1c,0xdc,0x6e,0xe1,0x6d,0x7a, +0x7a,0xba,0x99,0xdd,0x7e,0xe9,0x6f,0xef,0x1d,0x58,0xc8,0x06,0xe2,0xad,0x57,0x95,0xf9,0x7b,0x9c,0x67, +0x6f,0xa2,0x45,0x03,0xf2,0x06,0xd1,0x65,0x25,0xb0,0x72,0x29,0x71,0x95,0xfb,0xa1,0x33,0x72,0x54,0x36, +0x6e,0xda,0x27,0xaa,0x56,0x84,0x87,0x83,0xd2,0xca,0x0b,0xdb,0x12,0xc6,0xe4,0x5f,0x59,0xca,0xc7,0x85, +0x0a,0x73,0x3d,0xf8,0xeb,0xd1,0xc8,0x1f,0x7c,0xf5,0x15,0xfe,0x8c,0xe8,0xcf,0x97,0x7f,0xc5,0x9f,0x2f, +0xe8,0xcf,0x17,0x7f,0xc3,0x1f,0xbc,0x7d,0x80,0xb7,0x0f,0xf0,0xf6,0x08,0x2f,0x8e,0x0e,0xe9,0xcf,0x21, +0xde,0x1e,0xf2,0xb7,0x23,0x7f,0xe4,0x7f,0x05,0x43,0x0d,0xfa,0x47,0x4f,0xf1,0x1a,0xe9,0x8e,0xfe,0xdf, +0xea,0xbe,0x85,0xbd,0x6d,0x1b,0x59,0xf4,0xaf,0xc8,0x6c,0xe2,0x88,0x16,0x25,0x4b,0x4e,0x9c,0x76,0x25, +0xd3,0x3a,0x8e,0xf3,0x3c,0xdb,0x3c,0x6a,0xbb,0xcd,0xed,0x75,0x7c,0x7d,0x28,0x8b,0x96,0xd8,0xc8,0xa4, +0x96,0xa4,0xec,0xa4,0x8e,0xef,0x6f,0xbf,0xf3,0x00,0x40,0x80,0x04,0x25,0xdb,0xe9,0xee,0xdd,0xed,0xf7, +0xa5,0x16,0x81,0x01,0x30,0x18,0xbc,0x06,0x83,0x79,0x40,0x4d,0x58,0xe8,0xf1,0x63,0xf8,0xf7,0x64,0xdb, +0xc3,0x5a,0x1e,0xff,0xad,0xeb,0x61,0x95,0x58,0xf7,0x36,0xc0,0x6d,0x6f,0x6d,0x7b,0xd8,0x18,0xb6,0x8a, +0xcd,0x3f,0x05,0x58,0xc4,0x05,0x91,0xfa,0xf1,0xe9,0xb6,0xf7,0x23,0xc0,0xfd,0xf4,0xa4,0x7b,0xa2,0x82, +0xc4,0x08,0x85,0x76,0xe9,0xb2,0x17,0x35,0xda,0x03,0x3f,0xde,0xf5,0xbb,0xc3,0xb8,0xdf,0x8e,0x31,0x06, +0xdc,0xab,0xb7,0x47,0x4e,0xab,0x49,0x49,0x4e,0x0b,0x38,0xdd,0xb6,0xe3,0xb6,0x24,0x1d,0x95,0xb3,0xcf, +0x60,0x13,0x11,0x86,0x6b,0xe8,0x96,0x8b,0x30,0x0a,0x20,0x78,0x08,0xe9,0x5b,0x34,0xbd,0xd8,0x8c,0x57, +0x4e,0xa1,0x7c,0xce,0xb7,0x19,0x9a,0x2a,0x42,0xb0,0x09,0x40,0x3b,0xec,0x23,0x68,0x77,0x67,0x53,0xfc, +0xc0,0x5c,0x72,0x13,0xc8,0xbe,0x71,0x80,0x35,0x62,0x4f,0x4f,0x09,0x3e,0xba,0xab,0xb1,0xa0,0x83,0x11, +0x96,0xc0,0xcb,0x24,0xbd,0x28,0xa6,0x52,0x69,0x4e,0x07,0x63,0x58,0xd0,0x38,0xe1,0xa0,0x4d,0x2f,0x74, +0x59,0xc7,0xab,0x29,0xed,0x34,0x51,0xfa,0xa0,0xc9,0x74,0xd7,0xcc,0xe9,0x59,0x78,0x06,0x0a,0x66,0x21, +0x3e,0xa4,0xe2,0x41,0x2d,0xf5,0x4a,0xf0,0xf4,0x6c,0x4c,0xd1,0x77,0x01,0x99,0xf7,0x87,0x63,0x3c,0x8a, +0xf1,0x9c,0x24,0xb4,0xbc,0x46,0x04,0x27,0xe6,0x22,0xcb,0x19,0x24,0x80,0x23,0x1d,0x0e,0xb6,0xbc,0xb1, +0x4d,0x96,0x7c,0xb0,0x51,0xa2,0x38,0xb2,0xd1,0x83,0xf9,0x04,0x3b,0xe7,0x19,0x1e,0x79,0x41,0x3c,0x86, +0xef,0x05,0x2a,0x79,0xd3,0x37,0xec,0x06,0xac,0x4d,0xb1,0xe6,0xb8,0xde,0x5a,0x6f,0xc0,0x63,0xb5,0xa2, +0x77,0xa8,0x67,0xdc,0x2c,0x19,0xa1,0xe6,0x1c,0xed,0xb2,0x29,0xfa,0x50,0x9c,0xeb,0xb0,0xa9,0x37,0xc6, +0xd1,0xf9,0x79,0x88,0x9b,0x3b,0xb7,0xa2,0xcd,0xf4,0x49,0x98,0x93,0xcf,0xe7,0x42,0xa0,0x5a,0x78,0x5d, +0x82,0x1a,0x1f,0xe0,0xb5,0x8f,0x07,0x49,0xf8,0x09,0xa2,0x14,0x7a,0x11,0x76,0x84,0xf5,0x2c,0x24,0xf6, +0xfa,0xdd,0xbe,0xf0,0xda,0xb4,0x66,0x29,0xf4,0xed,0x9b,0xb5,0xd4,0x03,0xe5,0x72,0xa9,0xb4,0x9b,0xc3, +0x75,0x50,0x20,0x55,0xc8,0x60,0x03,0xd8,0xcb,0x81,0x8b,0x18,0xa3,0x76,0x27,0x3b,0x95,0x9c,0x5c,0x91, +0x4b,0xd4,0xec,0x33,0x46,0x26,0xc7,0x9d,0x7c,0x1c,0x63,0x72,0x76,0x36,0x7d,0x11,0xb3,0x31,0x30,0x7d, +0x1c,0x5e,0x45,0xa4,0x5c,0x8d,0xbf,0xf7,0x98,0xf9,0xa5,0xdf,0xac,0x08,0x8d,0xbf,0x5e,0x03,0x83,0xc4, +0xbf,0xde,0x46,0x31,0x05,0x36,0xc6,0xdf,0x1f,0x9f,0x67,0xe4,0x7d,0x75,0x16,0x7c,0x7d,0x06,0x6c,0x94, +0xfc,0xfd,0x61,0x31,0xcb,0x42,0xf9,0x81,0x7b,0x04,0x22,0xf1,0x8f,0x3c,0x7f,0x45,0x0e,0xbb,0xb4,0xdf, +0x6f,0xe2,0x4b,0x09,0xf6,0x3e,0x7e,0x1e,0x65,0x67,0x88,0xe1,0xd9,0x9f,0x07,0x98,0xf0,0x66,0xfc,0x85, +0xbf,0xde,0xca,0x73,0x0b,0x3e,0xf3,0x6c,0xfe,0x99,0x72,0xc5,0x6f,0x3d,0x0f,0x7a,0xf3,0x36,0xa1,0x5f, +0x6a,0x2e,0xc0,0x59,0xc3,0x0b,0x7c,0x58,0xb6,0x48,0x47,0x77,0x3f,0xc8,0x21,0x93,0x21,0x0b,0xd9,0xb0, +0xb8,0x64,0xca,0xc2,0x6f,0x7b,0xe8,0xbb,0x2d,0xd0,0x3d,0x07,0xc2,0xe2,0x67,0xd0,0xf8,0x44,0xf8,0x18, +0x36,0xa6,0x07,0x1e,0x85,0xc5,0xba,0xbb,0x56,0xa1,0x6d,0xe4,0x1c,0x15,0x2e,0xd9,0x68,0x62,0xda,0x74, +0x17,0x43,0x98,0x0f,0xc2,0x6b,0x2b,0x4d,0x09,0x9c,0xd2,0xe4,0xc0,0x5f,0x4d,0x3d,0x8e,0xe0,0x21,0x3c, +0x19,0xc7,0xeb,0xeb,0x6a,0xf8,0xe9,0x42,0x86,0x57,0x46,0x2d,0x45,0x1f,0xd2,0xad,0xed,0x6d,0x57,0x1a, +0x09,0xa0,0x2f,0x0b,0x58,0xe2,0xa8,0x15,0xa6,0x52,0xd2,0xf3,0x11,0xbe,0x5c,0x7b,0x79,0xd1,0xa1,0x14, +0xdf,0xe8,0xa5,0x27,0x05,0x5d,0x9a,0xe4,0x0c,0xb4,0x98,0xbb,0x81,0xf4,0x3a,0x85,0xc7,0x45,0x30,0x3a, +0x83,0x0b,0xd1,0x64,0x1a,0xfd,0xf1,0x79,0x76,0x11,0x27,0xf3,0x7f,0xc0,0xd6,0xb2,0xb8,0xbc,0xfa,0xf2, +0xf5,0x4f,0x72,0xfa,0xa4,0x0a,0xed,0xe9,0x85,0xf6,0x9e,0xed,0x3f,0x7f,0xf1,0xf2,0xd5,0xeb,0x37,0xff, +0xfd,0xf7,0x9f,0xdf,0xbe,0x7b,0xff,0xe1,0x97,0x83,0xc3,0xa3,0x5f,0x7f,0xfb,0xf8,0xbf,0x7e,0xff,0xdf, +0x66,0xa1,0x1f,0xf4,0x42,0xc0,0xa2,0xc2,0xe6,0xff,0xf4,0xc7,0x9f,0xfe,0x66,0x02,0xfd,0x57,0xb5,0x66, +0x13,0x60,0x4d,0x07,0xf8,0xbf,0xff,0x23,0x4f,0xd2,0xa6,0x7b,0xda,0x6a,0xc3,0x90,0x1d,0x9f,0xf4,0x3f, +0x39,0x83,0x47,0x3b,0xbb,0x43,0xaf,0xb3,0xf9,0xed,0xd3,0x27,0xa7,0x08,0x24,0x16,0xe0,0x01,0x99,0xf8, +0xe1,0x20,0xd9,0xed,0x0e,0xda,0xed,0xc4,0x0d,0x5a,0x7e,0x7c,0x4c,0xe2,0x03,0x8a,0x39,0xdc,0xe4,0x9f, +0x44,0xb5,0xa6,0xbb,0xd1,0x94,0xfa,0xa6,0xed,0x9e,0xeb,0x16,0xde,0x21,0xf5,0x09,0x13,0x23,0x2f,0x1a, +0xee,0x7d,0x78,0xf3,0xf7,0xf0,0xab,0x9a,0x02,0x06,0xd9,0x7b,0x4f,0x3d,0xe7,0xbf,0x7e,0x28,0xe4,0x29, +0xe5,0xdd,0x6e,0x1e,0x41,0x51,0xc5,0xa2,0x84,0xb8,0x63,0x19,0x33,0xf2,0xbf,0x33,0xa1,0x4f,0x52,0xe8, +0x49,0x16,0x37,0x5e,0xe4,0x09,0x94,0x82,0x64,0xe1,0x08,0x53,0xbf,0x5a,0x8c,0xf7,0x34,0xc9,0xc1,0x55, +0x38,0xca,0x92,0xb3,0xcf,0xac,0x39,0x45,0xd5,0x30,0x33,0x15,0x9d,0x7f,0x65,0x23,0x72,0x0a,0x22,0x26, +0x5f,0xf6,0x5c,0xb3,0xa2,0x7d,0xba,0xae,0x21,0x2e,0x4b,0xab,0xe1,0x5b,0x5d,0x3f,0x34,0xca,0x93,0x5f, +0x8c,0xf7,0x29,0x70,0xc8,0xc0,0x2d,0x23,0x93,0x51,0x5e,0x50,0xd5,0xe5,0x64,0x2e,0xa4,0x44,0x94,0x75, +0xbc,0x07,0x86,0xd7,0x4c,0xd4,0x97,0x59,0x5c,0x1c,0xd0,0xe5,0xcf,0xa7,0x5f,0xd0,0x7e,0x0c,0x35,0xf2, +0x07,0x3d,0xc2,0x76,0x0b,0x3c,0xc6,0x09,0xa7,0x61,0x2f,0xec,0x1a,0x67,0x57,0x24,0xc5,0x2a,0x1e,0x99, +0xf9,0xda,0x86,0x71,0x4f,0x14,0x8f,0x10,0x22,0x83,0xe0,0x96,0x18,0xc0,0x97,0x51,0x7a,0x71,0x05,0xc7, +0x8f,0xbe,0xce,0x90,0xa1,0x7d,0x09,0x0b,0xf5,0x80,0x14,0xec,0x06,0xb1,0xd0,0xac,0xc3,0x70,0xa3,0xba, +0x18,0xa4,0x00,0xe9,0x3c,0x7f,0xff,0xee,0x05,0xeb,0x06,0x91,0xef,0x1a,0x43,0x47,0x2e,0x6f,0x6e,0x3d, +0x7e,0x6c,0x66,0xa2,0x4b,0xa8,0x0e,0x9e,0xbe,0x68,0x0e,0xb8,0x97,0x37,0x11,0xad,0x01,0xcf,0xf4,0xe2, +0x9d,0x98,0xa2,0xc1,0x60,0x45,0x7b,0xd9,0x33,0x52,0x11,0x14,0x33,0x33,0x70,0x75,0xba,0xfc,0x3a,0x9f, +0xc0,0x99,0x16,0x6a,0xfb,0x98,0x31,0x4f,0x17,0x9c,0x8d,0x13,0x15,0x75,0xd6,0x71,0xfb,0xd1,0x9e,0x76, +0xac,0xc1,0x2c,0x86,0xf2,0x78,0x7e,0x89,0xfc,0x49,0xc1,0x5b,0xe4,0x89,0xf4,0x89,0x14,0xb0,0x88,0xe0, +0x3c,0x4d,0x2e,0xf8,0xe2,0x8e,0xd6,0x26,0x28,0x2c,0xec,0xf0,0xc9,0x8d,0xea,0x57,0xd1,0x9f,0xe1,0xee, +0x79,0x1a,0x86,0xa7,0xf8,0x4b,0x55,0xf9,0x06,0xbd,0xad,0x20,0x3f,0x92,0x27,0x49,0x63,0x86,0xe4,0xc0, +0x6a,0xcf,0x23,0x3a,0x24,0x72,0x60,0x69,0x82,0xcb,0x20,0x9a,0xe1,0xc1,0xd8,0x20,0xb7,0x74,0x68,0xf3, +0xd8,0x78,0x7f,0xb4,0xd7,0x69,0xc0,0x2c,0x86,0x83,0x15,0xae,0x76,0xe3,0x04,0x6f,0x45,0x41,0x23,0xbf, +0x4a,0xda,0x18,0x63,0x51,0x48,0x3c,0x64,0xd3,0xcd,0xf2,0xa8,0xea,0x12,0x1d,0xf2,0x71,0x63,0xbc,0x9c, +0x69,0xdc,0x94,0x29,0xf6,0xc8,0xc2,0xf0,0x02,0x71,0x1b,0x15,0x72,0x8f,0x73,0x51,0x69,0x83,0x7c,0xc6, +0x40,0x83,0x03,0x6d,0xba,0x00,0xe3,0x87,0x87,0xd0,0x20,0x96,0xcc,0xa4,0x23,0x48,0x0f,0x67,0x0e,0x06, +0xc8,0x44,0xf7,0x1e,0xde,0x03,0xed,0x9d,0x1e,0x16,0x23,0xda,0x0b,0xb6,0x0a,0x38,0xb6,0x96,0xa0,0xf7, +0x7d,0x69,0x95,0x21,0x6c,0x24,0x7a,0x86,0x93,0x06,0xf8,0x34,0x7c,0x39,0xf4,0x94,0xf1,0x86,0x29,0x6f, +0x84,0xa9,0xf0,0x83,0xa8,0xbc,0x0d,0x05,0x26,0x78,0xd3,0xc6,0x7b,0x51,0x84,0xd3,0xc5,0xc3,0x4b,0x27, +0x3d,0x1b,0x6a,0xc3,0xad,0x75,0x0f,0xc8,0x4a,0x73,0x1e,0xd8,0x47,0x16,0xcb,0xb0,0xa0,0x06,0xdf,0x30, +0x1b,0x47,0xb0,0x90,0x49,0x20,0xd2,0xb8,0x8a,0x66,0x33,0x24,0x51,0x1a,0xc2,0x58,0x67,0x53,0xe4,0x3c, +0x63,0x60,0x2c,0x33,0x5c,0xcd,0xe3,0x8c,0x7c,0x20,0xca,0x95,0xbb,0x1d,0x3e,0xc6,0x43,0x5d,0xd1,0x1b, +0x1a,0x42,0xdd,0xa3,0x40,0xdc,0x4a,0x1b,0xb0,0x51,0xe2,0xc0,0x02,0xc9,0xb9,0x65,0x9a,0x0e,0x48,0x5a, +0x42,0x47,0x89,0x63,0x00,0xac,0x11,0x4c,0x02,0x68,0xa6,0x49,0x2e,0x89,0xdd,0x0e,0x5a,0xe6,0x7c,0x99, +0x1a,0xb1,0x7d,0x6b,0x7a,0xce,0x31,0xfb,0x06,0x62,0xa1,0xd8,0x14,0xcf,0x95,0xc8,0x8f,0x71,0x40,0x31, +0x1f,0xff,0xaa,0x3a,0x88,0x72,0x54,0xc5,0x86,0x32,0xa3,0x0c,0x76,0xb9,0x4f,0x4b,0x22,0x20,0xe7,0x83, +0x0f,0x34,0x60,0xb1,0x31,0x5e,0x4b,0x4f,0x2a,0x82,0xc8,0x17,0xc1,0x97,0x3e,0xde,0x99,0xf3,0x60,0x86, +0x5a,0xb3,0xa4,0x5c,0xc9,0x92,0x68,0x83,0x13,0xc6,0xf5,0x8e,0xb3,0x5d,0xdd,0x0b,0xb4,0x65,0xff,0x03, +0x6a,0x16,0xc9,0x8c,0xe2,0xe0,0x2a,0x5d,0x4d,0xd6,0xd7,0xb5,0x33,0xa1,0xe0,0x9c,0xcc,0xf3,0x8b,0xd6, +0x90,0x90,0xa7,0xf1,0x95,0x1f,0x76,0x65,0xfe,0x1e,0xef,0x76,0x65,0x9b,0x62,0xcb,0xa5,0x83,0x03,0xea, +0x76,0x0e,0x93,0x8b,0x50,0x89,0xd9,0x68,0xcb,0xc0,0x85,0x34,0x0a,0xc3,0x98,0x65,0x72,0x8d,0xaf,0x61, +0xee,0xa1,0x08,0x0f,0xb7,0x94,0xab,0x20,0xce,0x69,0x4b,0xa1,0xad,0x65,0x0a,0x8b,0x8d,0xae,0x43,0x43, +0x40,0x1c,0x05,0xbd,0xa2,0x9f,0x4d,0xfd,0x92,0x35,0x4e,0xf4,0x33,0x11,0x6d,0xbf,0x0c,0x34,0x3d,0x75, +0x8b,0x2c,0x21,0xc6,0x91,0x59,0xd6,0xc8,0x53,0xb5,0xab,0x4e,0x5b,0xa9,0xb0,0x53,0x1c,0xb5,0xb9,0x77, +0x7d,0x53,0x9a,0xb0,0x06,0x55,0x30,0x07,0xd7,0x40,0xc1,0x68,0x5a,0xf7,0xcf,0x6f,0xdf,0xa8,0xa9,0x90, +0xcd,0xda,0x9d,0x3d,0x98,0xe8,0xd8,0x61,0x14,0x72,0x19,0x3d,0x17,0x92,0x4f,0x9c,0xe6,0xe3,0xf0,0x12, +0xf6,0xfc,0xa1,0xbc,0xf2,0x17,0x3d,0xcd,0x91,0x39,0x47,0x30,0xc7,0x35,0xf1,0x10,0xa7,0xe5,0x5f,0x84, +0xca,0x18,0x38,0x7f,0xae,0x90,0x37,0x75,0xc4,0xe9,0x8c,0xa5,0x71,0x8d,0x8f,0x6f,0x5e,0xbe,0x81,0x75, +0x08,0xdb,0x6d,0xfa,0xb9,0x0e,0x43,0x51,0xd8,0xb1,0xcc,0xd4,0xca,0x0c,0x3d,0x0c,0x2e,0xc3,0x25,0xb3, +0xb3,0x59,0x33,0x3d,0xa1,0x78,0x67,0x7e,0x95,0xbe,0xf8,0x32,0x67,0xe9,0x2d,0x33,0x12,0x5d,0xca,0xd0, +0x0f,0x3c,0x80,0x39,0x40,0xa6,0x45,0x13,0x7b,0x11,0x97,0x66,0xde,0xeb,0x70,0x55,0x75,0xa2,0xf9,0x34, +0x89,0xc3,0x43,0x7a,0x0e,0x72,0xc4,0x0e,0xe6,0xd4,0x56,0xf8,0xe2,0x8e,0xd5,0x14,0x2b,0xc6,0xef,0x7a, +0x76,0x9e,0x85,0x08,0x33,0x50,0x7c,0xd0,0x6e,0x77,0xd8,0xac,0xae,0xaa,0xdf,0xb5,0xf3,0x57,0x9b,0x32, +0xbc,0x29,0xe3,0xf1,0x48,0x83,0x25,0x56,0x1d,0xc0,0xe4,0xc1,0xe7,0xb0,0x11,0xc2,0x55,0xfa,0xac,0xba, +0xd8,0xe0,0x13,0x8e,0xda,0x38,0xb9,0x1a,0x62,0x44,0x30,0x5c,0x66,0x62,0x46,0xa3,0xdf,0xd4,0xbe,0xce, +0x86,0xdd,0x06,0x17,0x39,0x65,0xb0,0x51,0xc0,0xe1,0x63,0xf4,0x32,0xfa,0x2b,0x10,0x92,0x53,0xbb,0xc0, +0x09,0x97,0x23,0xd9,0x91,0xae,0xc2,0x48,0x9d,0x1b,0x74,0x38,0x11,0xc3,0x42,0x9b,0x4b,0x03,0x7d,0x0b, +0x02,0x2f,0x23,0xb0,0xba,0x15,0x1a,0xb4,0x07,0x60,0x74,0xad,0x32,0x0f,0x7c,0xe3,0xf5,0xf0,0x28,0x2b, +0x6d,0x0d,0xcf,0xe8,0x19,0xa4,0x78,0x79,0x52,0xaf,0xa4,0x85,0x73,0xbb,0x67,0x70,0xa5,0x6e,0x3a,0xe8, +0x33,0x90,0x36,0x7d,0xf4,0x6a,0x97,0xa5,0x67,0xbe,0x62,0x04,0x98,0x03,0x77,0x8c,0x8a,0x93,0x18,0xb9, +0xcc,0x5f,0xe7,0x92,0xf9,0xe5,0xc5,0x4e,0x4f,0x76,0xc4,0xcb,0x51,0x64,0x03,0xcb,0xd2,0xcf,0xbf,0x7d, +0xeb,0xd2,0x13,0xac,0xa1,0x2d,0xa4,0x64,0x37,0xa8,0x97,0x3e,0xd0,0x5c,0xbb,0xc3,0xcd,0x92,0xd9,0xcf, +0x32,0x7d,0xd1,0x0d,0x6b,0x94,0x2c,0x32,0xa0,0x24,0x1f,0x94,0xea,0xc8,0x47,0x9b,0xa3,0xab,0x34,0xca, +0xe1,0x34,0xec,0x34,0x96,0x6c,0x72,0xe8,0x7f,0x8b,0x23,0x1f,0xa9,0x3a,0x86,0x8e,0x08,0xce,0x61,0xa2, +0x95,0x94,0x19,0x6f,0xa9,0x7f,0x24,0x2d,0x5b,0x6c,0xd6,0x81,0xe2,0xa2,0x65,0xf2,0xd6,0xee,0x20,0x1f, +0x6a,0x1b,0xbb,0x23,0xb0,0xc0,0x88,0x0a,0x82,0x03,0xd1,0x1e,0xbd,0x60,0x40,0x13,0xc1,0x67,0xa3,0xda, +0x21,0x46,0xf0,0x28,0xef,0xfa,0x54,0x5a,0x8d,0x6d,0xf1,0x40,0xa9,0xde,0xe7,0x05,0xd9,0x0a,0xdc,0x87, +0x65,0xc6,0x12,0xee,0x9a,0x8d,0x28,0x8b,0x1f,0xa1,0x47,0xd1,0x39,0xda,0x42,0xc0,0x79,0x98,0xf0,0xb3, +0x6e,0x63,0x84,0xb6,0xae,0xc0,0xda,0xc2,0xf9,0x88,0xce,0xaa,0x99,0x7b,0x51,0x73,0x84,0x5c,0x9b,0x36, +0xcb,0x48,0xf1,0x0b,0xc7,0x57,0xda,0x9a,0x9a,0x75,0x27,0xf2,0xea,0x61,0x01,0xe6,0x9b,0x2b,0x32,0x06, +0xa7,0x1c,0xfe,0xf5,0x36,0xf7,0x4f,0x47,0x54,0x74,0x4a,0xeb,0x85,0xa2,0x15,0x94,0x8f,0x52,0xe3,0x78, +0xe0,0x48,0x9e,0xfa,0xb5,0xab,0xb8,0xa8,0x89,0xcb,0x23,0xab,0xed,0xe3,0xad,0x6d,0x50,0x3d,0xad,0x59, +0x6a,0xe5,0x78,0xd7,0xd1,0x18,0xcd,0x97,0xc9,0x26,0xab,0x9f,0xa3,0x94,0xef,0xa6,0x4c,0xab,0xc3,0xb3, +0x40,0x7b,0x13,0x46,0xf2,0x66,0x90,0x72,0x40,0x53,0x45,0x3e,0x14,0x38,0xbc,0xf3,0x8f,0xa3,0xcb,0x0e, +0x66,0x12,0x5f,0x86,0xda,0x7b,0x92,0x6b,0xf4,0xf4,0xa6,0x11,0xc2,0x21,0x6e,0xc1,0x6c,0xe8,0xf5,0x9e, +0x38,0xb9,0x8c,0xc6,0xa6,0x01,0xa7,0xea,0x4d,0xe9,0xb5,0x4d,0x03,0xb9,0xf2,0xab,0x35,0x3e,0x0f,0x47, +0x8b,0x09,0xf0,0x92,0x17,0x41,0x3c,0xae,0xbb,0xe3,0x8d,0x47,0x93,0xb3,0x8b,0x31,0x1e,0x4f,0xf4,0xf0, +0x62,0x48,0x5c,0xf9,0xb3,0xdc,0x22,0x97,0x80,0xf6,0xce,0xb8,0x66,0x94,0x1a,0x58,0x1b,0x46,0x0f,0x5d, +0x66,0x67,0x60,0x2a,0xe0,0xb3,0x99,0x90,0x82,0x3b,0x8e,0x59,0x8e,0x43,0xb6,0xbc,0x0d,0xe3,0x85,0xe0, +0xc2,0x61,0x88,0xc8,0xaa,0xdf,0x08,0xe6,0xe2,0xf0,0x9b,0x1d,0x53,0xfc,0x07,0x72,0x9e,0xbb,0x0a,0xe0, +0xe7,0x28,0xfe,0x5c,0x07,0xa4,0x89,0x3b,0x60,0xa8,0x3e,0x04,0x71,0x38,0xe3,0xd6,0x3b,0x73,0xfc,0x5d, +0x5c,0x79,0x0c,0x84,0x54,0x44,0x17,0x55,0x0f,0x5c,0xd5,0x35,0xf4,0x09,0xda,0x69,0x99,0xe2,0x0c,0x9e, +0x91,0x2a,0xfa,0x37,0x29,0x3e,0x8b,0xb1,0x20,0xcb,0xa7,0x47,0x52,0x2e,0x4d,0xcc,0x82,0xc1,0x19,0x9c, +0x05,0xb3,0x33,0xf2,0x71,0xfb,0x1c,0x18,0x01,0xf2,0x42,0x98,0x95,0x41,0x14,0xf3,0xa0,0x71,0xe2,0xa4, +0xee,0x4e,0x12,0x57,0xbc,0x79,0x98,0xc2,0x66,0xc6,0x10,0x4e,0x8e,0xc6,0x2e,0xb0,0x70,0x97,0xca,0x4b, +0x2a,0x6d,0xae,0xcd,0x00,0x38,0x75,0xa1,0xef,0x23,0x20,0x63,0x80,0xec,0xcc,0x61,0x2f,0x68,0x4f,0x48, +0x1c,0xa0,0x04,0x6c,0xa9,0xf6,0x08,0x13,0x01,0x70,0x22,0xed,0xdd,0xe9,0xe1,0x04,0xee,0xe3,0x40,0x45, +0x2f,0x92,0xd3,0x97,0x25,0x9b,0x0d,0xa8,0x30,0x2d,0xb8,0x25,0xca,0x17,0xf2,0x74,0x19,0x95,0xcd,0x7b, +0xd2,0x05,0x10,0x16,0x90,0x1d,0xa7,0x18,0xaf,0x59,0x5c,0x8e,0x8f,0x12,0xc6,0x1c,0x15,0x01,0x4b,0x5d, +0x55,0x02,0xe5,0x7f,0x59,0x77,0x11,0x35,0x2f,0xab,0xeb,0x75,0x26,0x7a,0x6d,0xbe,0x6c,0x73,0xc8,0x13, +0x0c,0x70,0x03,0xed,0xa8,0x7d,0x2b,0x62,0x61,0x27,0x89,0x99,0xe4,0x66,0x32,0x8d,0xf0,0x45,0x5b,0xd6, +0x12,0x49,0x41,0x80,0xa2,0x5a,0xb6,0x8c,0x6a,0x50,0xe1,0xf8,0x0b,0x2c,0xdd,0x65,0x64,0x1b,0x87,0xb3, +0x77,0xcc,0x9d,0x57,0x64,0xd9,0xfc,0x56,0x9e,0xe1,0x42,0x10,0x54,0x18,0x3c,0x28,0xac,0x6f,0x9a,0xda, +0x34,0xbb,0x80,0xc3,0xe0,0x4e,0xb5,0x38,0x9d,0x0b,0x3a,0x50,0x43,0xb9,0x26,0xf5,0xda,0xe0,0xae,0x5c, +0xad,0xcc,0xf9,0x41,0x5c,0x22,0x32,0xcb,0xf0,0x85,0xbb,0x3e,0xdc,0x80,0x45,0xa1,0xac,0xf4,0xd4,0xf5, +0x36,0xf8,0x22,0xde,0x9f,0xf0,0x7d,0x5c,0xd5,0x92,0xa2,0xe4,0x91,0x4c,0x96,0xc8,0x33,0x31,0x33,0x04, +0x5b,0xdd,0x6e,0xab,0xd7,0xdd,0x08,0xbd,0x58,0x6f,0x12,0xb5,0x0f,0x70,0xe9,0xe1,0x69,0x8a,0x26,0x1f, +0xd0,0x21,0xd2,0x2a,0x7f,0x80,0x01,0x4b,0xe4,0x98,0xab,0x4d,0x2e,0x30,0x16,0xf6,0x4a,0x11,0x67,0x31, +0x70,0xa8,0x19,0xd4,0x6a,0xdd,0xe0,0xf0,0xaa,0x3a,0x3a,0xec,0x67,0xb8,0x0d,0xc3,0xd4,0x16,0xd8,0x38, +0x6c,0x83,0x48,0xe7,0x3a,0x46,0xdf,0x95,0xe3,0x67,0x2d,0x87,0x64,0xb6,0x17,0xd4,0xc6,0x0c,0xcd,0x41, +0xb4,0x29,0x22,0x49,0x84,0x66,0x22,0xc6,0x44,0x39,0x44,0x82,0x2d,0x66,0xe1,0x77,0xce,0x94,0xbb,0x55, +0x43,0xcb,0x60,0xf9,0x6c,0x51,0x15,0xea,0x4f,0xa7,0x70,0x48,0x73,0xaa,0x6d,0xc2,0xe4,0x34,0x61,0x64, +0xb9,0xe5,0x33,0xa6,0xa8,0xc7,0x32,0x65,0x62,0x39,0x65,0x72,0x2f,0x30,0x5a,0xb5,0xcf,0x19,0xdc,0x5b, +0x02,0x35,0x67,0x44,0xa8,0xb7,0x90,0x7d,0x4b,0x89,0xf7,0xba,0xa1,0x93,0xf1,0xd3,0x4f,0xdf,0x99,0x91, +0x82,0xe9,0x40,0x6e,0x5d,0x69,0xcb,0xe1,0x13,0xd8,0x5a,0x37,0xfb,0x18,0xf4,0x83,0xea,0x84,0x4c,0xe4, +0xa4,0xf8,0x41,0x3d,0x0f,0x3e,0x27,0x7a,0x08,0x51,0x63,0x84,0x33,0x27,0xb9,0xef,0xac,0xc5,0x78,0xb1, +0x62,0xd6,0x26,0xb6,0x59,0x2b,0x09,0x52,0x99,0xb6,0x92,0xfa,0xd6,0x92,0x34,0x6f,0xed,0x45,0xf5,0x29, +0x04,0x14,0xd5,0x67,0xae,0x1a,0x2a,0xa7,0xda,0x25,0xdb,0x09,0xbb,0xea,0x3a,0x7e,0x97,0x43,0xd7,0x4b, +0x8a,0x39,0x89,0x9a,0x78,0x74,0xe8,0x66,0xe6,0x94,0x24,0x9e,0xd3,0x36,0x1f,0xd7,0x60,0x46,0xc2,0xae, +0x5f,0xa8,0x74,0x28,0x68,0x39,0xd6,0xc6,0x51,0x24,0xd4,0x3d,0xa4,0xb0,0x90,0xb4,0x3e,0xd4,0xd9,0x15, +0x1b,0xe7,0x50,0x07,0x9f,0x93,0x13,0xa9,0xce,0xa2,0x1f,0x1d,0x89,0xc1,0x95,0x78,0x41,0x89,0x96,0x8c, +0xab,0x2e,0xd0,0xa8,0x10,0x10,0xab,0xd0,0x89,0x71,0x9d,0xc4,0x2c,0xb3,0xe8,0x4b,0xfe,0xdc,0xe3,0x78, +0x0b,0x68,0xa8,0x1f,0x44,0xe8,0x5d,0x66,0xad,0x2b,0x92,0x38,0xf6,0x03,0x39,0xa5,0x65,0xf2,0xff,0x8c, +0x07,0x66,0xdf,0x79,0xff,0xce,0xf1,0x80,0x8c,0x66,0xda,0xcb,0x97,0xce,0x0d,0xeb,0xe9,0xd0,0x0b,0x42, +0x27,0xca,0x98,0x7b,0xbf,0x93,0xea,0x45,0x20,0x99,0xce,0x82,0x01,0x09,0x0c,0x2d,0x0c,0x35,0x6a,0xc5, +0x93,0x57,0x69,0xe8,0x38,0xa3,0x7e,0xfc,0xae,0x4b,0x83,0xc7,0xf0,0xf6,0xe5,0xaa,0x5e,0x27,0x35,0x6e, +0x02,0x0d,0x64,0x03,0xb4,0x15,0x29,0x8f,0x22,0xea,0x27,0x75,0x26,0x73,0x8c,0x18,0x95,0x8a,0xb1,0x4c, +0xe8,0x5b,0x69,0x2f,0xd1,0x38,0xa7,0xc6,0x38,0x1b,0x1a,0x2b,0xea,0x91,0x1f,0xc7,0x4d,0xf0,0x07,0x09, +0xc6,0x73,0xce,0x6b,0x60,0x49,0x09,0xc0,0x00,0x9e,0x63,0x4a,0x45,0xc0,0xa5,0x94,0x04,0xaa,0xb0,0xa7, +0x17,0x59,0x05,0x5c,0xe9,0x0e,0x18,0xe0,0x14,0x0a,0xac,0x8a,0x88,0xae,0x68,0x50,0x85,0x3f,0x8d,0xe2, +0xcb,0x1a,0xe4,0x59,0x1b,0xc1,0x28,0x92,0xc4,0xa7,0x28,0xa7,0x44,0x93,0xd2,0xf2,0x2c,0x97,0xf7,0xab, +0x9b,0xd2,0x64,0x50,0xcc,0x64,0x69,0x19,0x2b,0x66,0x6e,0xc9,0x52,0xd6,0xa6,0x42,0xc1,0xfb,0x7d,0xff, +0x44,0x10,0x6c,0x65,0x6a,0x65,0x2b,0x13,0x2b,0x5b,0x99,0xd4,0xb2,0x95,0xc5,0x54,0x82,0xae,0x90,0x32, +0x31,0xf4,0x5c,0xdf,0x22,0xd2,0xca,0x16,0x61,0x10,0xaf,0xa0,0x43,0x95,0x76,0xfb,0xc9,0x2c,0x49,0x0f, +0x5e,0x3d,0x33,0x58,0xb8,0x33,0x4c,0xb4,0x13,0x2d,0x54,0x44,0xcb,0x0b,0x50,0x28,0x6f,0x3f,0x47,0x91, +0xb0,0x79,0x41,0x98,0x58,0xc7,0x8a,0x1b,0xa9,0x4a,0x62,0x29,0x9d,0xb6,0xfd,0xab,0x69,0x18,0xce,0x08, +0xc1,0x0f,0x70,0xa2,0x84,0x69,0xf3,0x3a,0x9b,0x45,0xe4,0x2c,0xd6,0xb9,0x4a,0x27,0xa3,0xb9,0x23,0xdc, +0x3c,0x70,0xea,0x42,0x77,0x59,0x50,0x66,0x59,0x2a,0x55,0x39,0x52,0xe5,0x03,0x63,0xbb,0xe1,0x83,0xcd, +0x40,0xbf,0x39,0x13,0x0e,0x70,0x71,0x86,0x56,0xfa,0x14,0x1a,0x8d,0xae,0x89,0xa3,0x14,0x0f,0xfa,0x98, +0xdf,0x77,0x08,0x6c,0xca,0x5a,0x9c,0xb5,0xcd,0x8a,0x20,0x6e,0x2b,0x38,0xa7,0x8c,0xc2,0x22,0xe7,0x62, +0x94,0x43,0xf3,0x12,0x2f,0x51,0x29,0x1a,0x67,0x8c,0x6c,0xe3,0xf8,0xfa,0xf0,0xb7,0xef,0x1a,0x47,0x28, +0xff,0xaf,0x1e,0xc7,0x69,0x76,0xf9,0xdd,0xe3,0x48,0xdf,0x24,0x16,0x51,0x8b,0xe9,0xf1,0x53,0xb8,0x1d, +0x74,0xa6,0xb8,0x98,0x5a,0x30,0xc2,0xc5,0x2a,0xeb,0x75,0x31,0x23,0xab,0xcb,0xb8,0x24,0x31,0x94,0x8d, +0xfe,0x80,0x28,0xca,0x4f,0xaa,0x84,0x87,0x39,0x10,0x87,0xb3,0xd2,0xd6,0x73,0x26,0x52,0x4b,0xb4,0xdf, +0xed,0x72,0x00,0x16,0x7d,0xdb,0xb1,0x0e,0xd2,0x2e,0xba,0x47,0x0f,0x07,0xe8,0x6d,0x05,0xfe,0x3e,0x7c, +0xec,0xc1,0xbd,0x77,0x7d,0x91,0x85,0x1f,0xa7,0x11,0x2a,0x1b,0x04,0xed,0x76,0xb1,0x21,0xc1,0x26,0xd4, +0x0e,0x3c,0xc3,0x6d,0x97,0x4d,0xd9,0x49,0x88,0xd9,0xf2,0xd2,0xe4,0x8c,0x6f,0x35,0x39,0xe3,0x42,0x0d, +0xd7,0x20,0x0e,0x77,0x93,0xc5,0x73,0x32,0x18,0x04,0x52,0xc9,0x8b,0x74,0x32,0xd8,0x27,0x55,0x06,0xcc, +0x51,0xb6,0x13,0x0c,0x32,0xc9,0x15,0x8d,0xfd,0xa4,0x95,0x79,0x33,0x28,0x19,0x55,0x4f,0x55,0x9e,0x1c, +0x8e,0x37,0x33,0xf7,0xba,0x71,0x31,0xef,0xea,0x41,0x8c,0xc5,0xca,0xd2,0x0c,0xb1,0x41,0xcf,0xa4,0xb4, +0x43,0x0c,0x63,0xc3,0x69,0x35,0xc7,0xad,0x1e,0xc6,0xef,0x33,0xe6,0xb8,0x18,0x4e,0xc7,0xe4,0x6e,0x98, +0xf1,0xbf,0x13,0x77,0x93,0x49,0xee,0x46,0x36,0x08,0x47,0x40,0xe6,0x1a,0xdb,0x72,0x7a,0x3e,0xfa,0x39, +0x0c,0xd2,0xea,0x38,0x56,0x07,0xc8,0x2b,0xb4,0x4f,0xf1,0xda,0x65,0x4c,0x5c,0xa8,0x06,0xe5,0x7a,0x31, +0x0f,0x4e,0xae,0x91,0xa4,0x1d,0xe1,0xdd,0x48,0xca,0x52,0xf5,0x0c,0x4e,0x43,0xa7,0x3e,0x06,0x3a,0x2f, +0x93,0x74,0xa2,0xc9,0x9f,0xef,0x8d,0xcf,0x39,0xd5,0xf3,0x17,0x20,0x84,0x56,0xfd,0xdf,0x8f,0x4e,0x46, +0xee,0x4d,0xef,0x8e,0x8c,0x50,0xc1,0xe2,0x05,0x74,0x63,0xde,0x71,0x0f,0xce,0x47,0xe8,0x69,0xc2,0xd8, +0x86,0x53,0x4e,0xc3,0x35,0x3e,0x0b,0x27,0xd8,0xa8,0x5c,0xe6,0x5e,0xae,0x03,0xdc,0x66,0xef,0x85,0x6d, +0x41,0x45,0x55,0x6e,0x54,0x96,0x27,0xb1,0x7d,0xf1,0xdd,0xae,0x88,0xb2,0xcf,0x1e,0x15,0xae,0xe4,0x88, +0x4e,0x7b,0x01,0xca,0xdc,0xa9,0xf9,0xe0,0xc6,0x68,0x45,0x5e,0x05,0xa1,0x13,0x6d,0x9e,0x70,0xc6,0x35, +0x50,0xce,0xe6,0xda,0x32,0x62,0x52,0x94,0x0b,0xf1,0x9c,0xab,0x2d,0x95,0x31,0x19,0xcd,0x32,0x38,0x2d, +0x80,0x60,0x06,0xe3,0x28,0x68,0x8f,0xc2,0x80,0x62,0xa0,0x34,0xc5,0x1a,0x61,0x78,0x8f,0x3e,0x87,0x4e, +0x49,0x1f,0xb4,0xe0,0xee,0x50,0xcc,0x2e,0x93,0x07,0x04,0x70,0x09,0x87,0x16,0xbe,0x08,0x21,0x0c,0xfb, +0x18,0x68,0x51,0xcc,0xec,0x8e,0x96,0x59,0x32,0x54,0xc8,0xa3,0x7c,0x16,0xea,0x66,0x0c,0x0e,0x3e,0x03, +0xaa,0x96,0xa8,0x96,0xb0,0x23,0xd3,0xa0,0xba,0x36,0x47,0xb9,0x52,0x2f,0x8b,0x54,0x81,0x9f,0xdf,0xb0, +0x97,0xd6,0xce,0xe7,0x90,0x6e,0xab,0x1d,0x20,0xdb,0x8b,0xb2,0xfd,0x3f,0xbb,0x3f,0x0c,0x48,0xbf,0x9e, +0x9e,0x0c,0xaf,0xc2,0x11,0xe9,0xea,0xe2,0x1b,0x21,0xba,0x30,0x10,0x6a,0x23,0x24,0xce,0x08,0x4c,0x41, +0xb9,0x90,0xd8,0xac,0x15,0x5e,0x0c,0x31,0x7b,0xae,0x14,0x4d,0x24,0x80,0xca,0x87,0x9e,0x04,0xd2,0x80, +0xcf,0x37,0xd4,0xad,0xe8,0xd1,0x26,0x09,0xc6,0xec,0x47,0x8b,0x81,0xb4,0xd7,0x56,0x7a,0x57,0x25,0xf4, +0x60,0x70,0xf6,0x93,0x45,0x4c,0xae,0xf5,0x72,0x57,0xa4,0xa4,0xc1,0xd5,0x6f,0xec,0xec,0x52,0xa0,0x5d, +0xbe,0xc8,0xb0,0x26,0x2d,0x6b,0xf4,0xd3,0x3c,0xbd,0x08,0xbe,0xf0,0x72,0x72,0xbc,0x5e,0xef,0x29,0xe0, +0x05,0x20,0x5c,0x25,0x8d,0x2c,0x30,0x71,0x06,0x8a,0x26,0xdb,0xcb,0x46,0xd3,0xb7,0xe7,0x5d,0x30,0xaa, +0xb6,0x60,0x1c,0x03,0x34,0x72,0xa7,0x8e,0x00,0x73,0xc0,0x4d,0x5c,0x9b,0xbc,0x98,0x78,0xf0,0x0c,0x64, +0xd0,0x40,0x8f,0x9c,0xe8,0x14,0x8a,0xcb,0x69,0x67,0xea,0x27,0xc7,0xdd,0x93,0x4d,0x34,0xb2,0x48,0x3b, +0x19,0x7c,0xf4,0x4e,0x36,0x81,0x15,0x81,0x8f,0x4b,0xf8,0xd8,0xe2,0x8f,0x7b,0xa0,0xc8,0x3c,0x11,0x5a, +0x90,0x22,0x7e,0x1a,0xe7,0xaa,0x53,0xa2,0xc2,0xd4,0xe2,0x8e,0x16,0x14,0x24,0xa1,0x83,0xd7,0x00,0x10, +0xd7,0x56,0xea,0xb4,0x3a,0x14,0xd5,0xe8,0x49,0x06,0x45,0xce,0x37,0xf9,0xed,0x43,0x9d,0x8e,0x76,0x0b, +0x29,0xc6,0x46,0x13,0x07,0x57,0xa7,0x91,0x96,0x59,0x70,0x76,0x01,0x5d,0x90,0x78,0x76,0x47,0xe7,0x91, +0x56,0x97,0xf6,0xda,0x27,0xda,0xb7,0xbf,0xf4,0x69,0xcf,0x44,0xc6,0x03,0x21,0xbf,0xf6,0xe0,0x02,0x95, +0x2f,0x79,0x6a,0xfe,0xe9,0x8f,0x7b,0xe2,0xa1,0xd0,0xd1,0x05,0x93,0x56,0xdc,0x55,0xae,0x15,0x79,0x25, +0x34,0x2b,0xe6,0x3e,0xde,0x71,0x0f,0x79,0xbb,0xd5,0x66,0xaf,0x76,0xf3,0x2d,0xb7,0x52,0x11,0x85,0x88, +0x91,0x91,0xba,0xfd,0x55,0xbc,0x2a,0x2f,0x4d,0x81,0xa7,0x41,0x17,0xbf,0x8b,0xa3,0x48,0x56,0x58,0x5c, +0xb2,0xeb,0x2a,0x35,0xdf,0x74,0x02,0xaf,0x54,0xca,0xfc,0x2e,0x35,0xa0,0x0c,0x0e,0x6e,0x87,0xb2,0x06, +0xae,0x7d,0x58,0xea,0xbc,0x07,0xd6,0xa5,0x62,0xa5,0x84,0x52,0x1b,0x42,0xc1,0xa0,0x5a,0xb9,0x78,0xa3, +0x2f,0xa9,0x21,0x04,0x27,0xae,0xdc,0x97,0xf1,0x79,0xb5,0xbc,0x16,0xd5,0xab,0xab,0x60,0x28,0xb5,0xa5, +0x58,0xe4,0x29,0xcf,0x14,0x4d,0x3d,0x19,0xd5,0x7e,0x39,0x87,0xfd,0x1e,0xb4,0x8d,0x32,0xec,0x03,0x01, +0xc3,0xf1,0xb2,0xf4,0x59,0xd3,0xd7,0x97,0x7b,0x2d,0x21,0x16,0xa1,0xfe,0x4d,0xa4,0x69,0x94,0xb4,0x7f, +0xe4,0x53,0x05,0x3d,0xfe,0x49,0x8d,0x65,0x48,0x1b,0xe8,0x5d,0xe5,0x67,0x22,0x9c,0xcb,0x6d,0xe4,0x65, +0xc5,0x02,0xa1,0x7d,0x07,0x8d,0x7c,0xf5,0x4e,0xc2,0x37,0x9c,0x3b,0xb4,0x40,0x82,0x49,0xe2,0x8b,0xdd, +0x53,0x29,0x10,0xcb,0x75,0xab,0x12,0xca,0x2b,0xc7,0x73,0xe4,0x1b,0x89,0x80,0xc4,0x30,0xcd,0x46,0xa0, +0x0a,0x97,0xcd,0x5e,0xc4,0x32,0x2a,0xa0,0x86,0xce,0xfe,0xfb,0x77,0xef,0x5e,0xec,0x1f,0xbd,0x78,0xee, +0xf4,0x9d,0x77,0xef,0x8f,0x1a,0xc5,0x37,0x56,0x9b,0xcf,0x2d,0x45,0x0e,0x7f,0x7f,0xb7,0xff,0x48,0xc2, +0x8b,0x0f,0x74,0xf8,0x33,0x47,0xef,0x4e,0x62,0xc3,0xe7,0x8b,0x8b,0x89,0x27,0x06,0xff,0x7e,0xf8,0xb4, +0x3b,0xd0,0xd2,0xc7,0xc2,0x72,0x8d,0x06,0xe0,0x6c,0x79,0xf6,0x02,0xb2,0xb7,0x9e,0x98,0xd9,0x5b,0x4f, +0x54,0xf6,0xdc,0x1f,0x0f,0x02,0x7f,0xde,0x72,0xc6,0x8d,0xc2,0x02,0x6e,0x41,0x26,0x71,0x53,0x2d,0xe5, +0x8c,0x52,0x2e,0xb4,0x94,0x19,0xa5,0x64,0xce,0x0d,0xd6,0x32,0xf5,0xce,0xbd,0x49,0x59,0xc7,0x80,0xde, +0x5d,0xf1,0x50,0x19,0x4c,0x34,0x23,0xb5,0xa6,0x61,0x87,0x35,0x31,0x8d,0xb0,0x26,0x65,0xe9,0x7c,0x50, +0x27,0x79,0x97,0x66,0x59,0xb6,0x3a,0xf0,0xe0,0xc1,0xd5,0xd1,0x6f,0x4e,0x55,0xe6,0x3c,0x25,0x7b,0x2d, +0xc7,0x41,0x7f,0x56,0x32,0x0d,0xbd,0xe9,0x73,0x22,0x17,0x9a,0xb6,0x82,0xd6,0xb9,0x2b,0x26,0xf6,0x85, +0x6f,0xda,0x88,0x16,0xdd,0xb9,0xd0,0xbb,0x33,0xf5,0x2f,0x2c,0x4d,0x5c,0x54,0x9b,0xb8,0xe0,0xf3,0x4e, +0xb4,0x41,0x4d,0x5c,0xfa,0x65,0x41,0x66,0xd1,0xc8,0xa5,0xd6,0xc8,0xa5,0x38,0x4b,0x29,0x4c,0xc1,0xb5, +0xf6,0xd0,0x10,0xf0,0xc5,0x9d,0x02,0x6a,0x06,0xae,0xba,0xbb,0x6a,0x9b,0xff,0x31,0x45,0x32,0x79,0x44, +0x2f,0xdb,0xb6,0xe7,0x8f,0xe0,0x38,0x3e,0xa9,0x55,0x29,0x30,0x23,0x76,0x06,0x3c,0x3f,0x61,0xd1,0x61, +0xd0,0xce,0x91,0xaf,0x3f,0xc2,0x5d,0xd3,0x2d,0xe6,0x5a,0x3c,0x67,0xf5,0xaf,0xe4,0xc3,0x16,0x3a,0x2b, +0xd5,0x79,0xcd,0x2b,0x0b,0xaf,0xa9,0x18,0xe4,0xab,0xe3,0xf0,0xa4,0x6c,0xd4,0x4b,0x0a,0xdb,0x28,0xd3, +0x1d,0x19,0x86,0xbd,0x26,0xcd,0x0a,0x10,0xee,0x9d,0xb8,0x1e,0xe7,0x4b,0xde,0x31,0x6a,0xcb,0x2a,0xca, +0xe4,0xb5,0x64,0x91,0xf1,0x22,0xca,0x94,0xf9,0xca,0x94,0xf9,0xec,0xeb,0x8f,0xd9,0x46,0xf7,0xbf,0xda, +0xbb,0x5f,0xd7,0xe7,0xcf,0xdc,0xe7,0xaf,0xc7,0xec,0xd4,0xae,0x18,0x7e,0x4d,0x48,0x6d,0x4e,0x01,0xc2, +0x63,0x9f,0xf1,0x38,0xf2,0xf7,0xd9,0x69,0x27,0x3a,0x3a,0x3b,0xf0,0x51,0xb7,0xef,0x68,0xb8,0xcf,0xda, +0x7b,0xad,0xfd,0xce,0x02,0x6a,0xc8,0xfa,0x25,0x03,0xf3,0xa3,0x8a,0x55,0xb5,0x02,0x00,0x9a,0x95,0xe7, +0x12,0x22,0x77,0x20,0xf1,0xc2,0x96,0x0f,0xfd,0x40,0x09,0x06,0x75,0x71,0xd6,0x61,0x15,0xc9,0x2f,0x84, +0xa4,0x6a,0x4d,0xc8,0x5b,0xb8,0x09,0x6a,0x41,0x36,0xf0,0xa5,0x78,0xee,0xb0,0xc2,0xd0,0xaa,0xfa,0x82, +0x42,0x0f,0x85,0xc6,0x1e,0xdc,0x14,0x80,0x6d,0x2f,0xda,0xdc,0xe3,0x36,0x3f,0xf8,0x7b,0x7a,0x9b,0xe5, +0x3b,0xc0,0xb1,0xb8,0xbd,0x62,0x17,0x3f,0x74,0xa2,0x31,0xf6,0xf2,0x58,0xbb,0xb7,0x72,0x3a,0xff,0xd6, +0x28,0xf0,0x81,0x7d,0xc6,0xea,0x93,0xc2,0xef,0x0e,0xe2,0x1d,0xc2,0x81,0xae,0x24,0x83,0xb8,0xd5,0x72, +0xf5,0xfb,0x3c,0x3a,0x17,0x65,0x33,0xd4,0x65,0x16,0x62,0x2e,0xba,0x69,0x34,0xcd,0xce,0xaa,0x9a,0xa5, +0xea,0x1a,0x8a,0xca,0x49,0xac,0x3c,0x2c,0xe5,0x05,0x5e,0x3e,0x28,0x19,0xbd,0x1a,0xaf,0xb9,0xbc,0x5d, +0x36,0x35,0x99,0x87,0x69,0xc7,0xea,0xe5,0xbe,0x93,0xa7,0x8b,0xb0,0x5a,0x54,0x59,0x68,0xb9,0xe4,0x91, +0xac,0x4e,0x40,0x5d,0x01,0x56,0x56,0xc0,0x7a,0x7e,0x81,0xb7,0x43,0x2e,0xf9,0x82,0x52,0xbe,0xb8,0x17, +0xba,0x03,0x4d,0x67,0x75,0x4d,0x73,0x70,0x09,0x7c,0x41,0x4c,0x8c,0x2f,0x06,0x5e,0x42,0xf5,0xc3,0x9c, +0x9c,0x12,0xa1,0xb4,0xb3,0xd5,0x2a,0x34,0xaa,0x75,0x9d,0x73,0x9f,0x80,0x29,0x57,0xe9,0x0e,0x2b,0xad, +0x79,0x23,0x13,0x53,0x3c,0xed,0x06,0x5a,0xe4,0x60,0x8a,0x57,0xdb,0x13,0x0f,0x15,0x91,0x7b,0x02,0x8d, +0x76,0x7b,0x19,0x1a,0x94,0x5b,0x87,0x86,0xc8,0xb4,0xa0,0x21,0x72,0x56,0xa0,0x81,0xb1,0x1b,0x35,0x75, +0xa9,0xc2,0x02,0xa0,0x46,0x07,0x38,0x1c,0x2a,0x8d,0x50,0xc3,0xf7,0x6a,0xe1,0x7c,0xe1,0x07,0x07,0xa3, +0x71,0xf5,0xd1,0x9f,0x83,0xe6,0xce,0x74,0x9a,0xe7,0x73,0x87,0x43,0xa5,0xd0,0xef,0x3e,0x46,0x37,0x83, +0x5d,0x0c,0xc3,0xb9,0xb1,0x2f,0xf0,0x12,0x28,0x59,0x16,0xa2,0xa0,0xc2,0x0f,0x07,0x52,0x3c,0xa2,0xda, +0x20,0x10,0xcf,0xb9,0xca,0x30,0xc2,0x15,0xfc,0x7f,0x20,0x94,0x49,0xd7,0xd7,0xa5,0x56,0xe9,0xd9,0x2c, +0xa1,0x48,0x62,0xe2,0x9b,0xb4,0x80,0x3f,0x86,0xa3,0x43,0xf8,0x1d,0x52,0x74,0x3a,0x09,0x98,0xc4,0x82, +0x5f,0x5e,0xaa,0x09,0x6c,0xc4,0xf9,0xda,0xa4,0xd0,0xdf,0xce,0x27,0x8c,0x3b,0xac,0x25,0xa6,0x9c,0x98, +0x1a,0x89,0x39,0x27,0xe6,0x38,0xb9,0x61,0x2e,0xea,0xa2,0x20,0x52,0xb9,0xa2,0x03,0x93,0x51,0xf1,0x94, +0xc0,0x64,0xad,0xe7,0x69,0xb7,0x51,0x4f,0xbf,0xdd,0x29,0x07,0x33,0xe8,0xdb,0xbb,0xe0,0x5c,0xbb,0x9e, +0xa0,0x96,0x69,0x22,0x50,0x98,0x43,0x8a,0xdf,0xd2,0x20,0x52,0x7c,0xb2,0x49,0xa4,0xa7,0x6e,0xce,0xd0, +0x30,0x32,0xcd,0x5d,0x8f,0xf8,0xe5,0xc1,0x03,0xf2,0x57,0x85,0xe6,0xff,0xb0,0xb7,0xd4,0xba,0x2f,0xad, +0x13,0xf8,0xc1,0x81,0x36,0x47,0xf3,0xa4,0xaa,0xd0,0xa8,0x60,0x35,0xe9,0xd8,0x43,0x83,0xc5,0xa1,0xfa, +0xd5,0xcf,0x3b,0x57,0xd3,0xe8,0x8c,0x5e,0x53,0x90,0x61,0xef,0x3d,0xd6,0x6c,0x6c,0x1a,0x66,0xec,0x18, +0x7a,0x21,0x81,0xd9,0xad,0x3b,0x5c,0x34,0xfc,0x1d,0x79,0x25,0x87,0x16,0x68,0x34,0xf1,0x06,0x3b,0x84, +0x5b,0x91,0x56,0xaa,0xf0,0x45,0xc2,0x9a,0xf8,0x65,0x1d,0x51,0x4d,0xf2,0x57,0xe8,0x73,0x0a,0x03,0x12, +0x14,0xff,0x22,0x68,0x7b,0x56,0x81,0x55,0xba,0xa3,0x04,0x5a,0x63,0xaf,0xd5,0x25,0x43,0xad,0x5e,0xb7, +0xcb,0x0f,0x7f,0x52,0xee,0xc8,0x26,0x88,0x25,0x05,0x19,0x61,0x00,0x63,0x01,0x6c,0x6b,0xc2,0x33,0x4b, +0x89,0x0f,0xba,0xa0,0x4d,0x89,0x36,0x85,0x19,0x90,0x59,0x80,0x67,0x4c,0x09,0x50,0x59,0xe3,0x94,0x61, +0x45,0x86,0x01,0x8e,0xa2,0x92,0x36,0xe9,0x33,0x97,0xc0,0x51,0x69,0xda,0x80,0x9c,0x06,0xed,0x33,0x29, +0xea,0x30,0x20,0xa5,0xd6,0xb3,0x01,0x2d,0x54,0x8c,0x4b,0xa0,0xba,0x3a,0x73,0xe5,0x5d,0xb0,0xd0,0x63, +0x56,0x13,0xb9,0x69,0x29,0xa2,0xb5,0xd0,0xa6,0x10,0xc2,0xf6,0x46,0x30,0xc7,0x80,0x97,0x0a,0xee,0x6d, +0xf6,0x58,0x55,0x2e,0xc5,0x06,0x1c,0xf6,0x12,0xd2,0x7e,0xa0,0x42,0x51,0x4a,0xb6,0x97,0x11,0xea,0xf0, +0xe5,0x32,0xba,0xde,0x3e,0x4f,0x5d,0x4d,0xd5,0x5f,0x7f,0x8d,0xd2,0x2d,0x3f,0x4a,0x73,0x88,0xed,0x45, +0x2b,0x73,0x87,0x92,0x0d,0x50,0xe0,0x41,0x60,0xa5,0x9a,0x90,0x26,0x17,0x62,0xab,0xb9,0xcd,0xf6,0x08, +0x66,0xb1,0xaa,0x6f,0xb5,0xd2,0xe8,0x95,0x2c,0x8d,0x35,0xab,0x85,0xca,0x40,0x6b,0xa0,0x1d,0xee,0x6e, +0xd5,0x1f,0x43,0x61,0xe2,0x82,0x9a,0xbb,0xa5,0x0a,0xa4,0x17,0x85,0xc2,0xfe,0x9e,0xf5,0x6a,0xcd,0x55, +0x09,0x4c,0x9a,0x5d,0x73,0xd2,0xb4,0x14,0x15,0xba,0x89,0x3a,0x9f,0xaf,0xa9,0x29,0x56,0x6a,0x64,0xe5, +0xbe,0x1a,0xd5,0x36,0x75,0x69,0xea,0xdd,0x78,0xda,0x95,0xaa,0x52,0x09,0xbd,0x0f,0xae,0xaa,0x63,0xab, +0x52,0xc7,0x58,0x05,0x25,0xd3,0x67,0x8a,0x7c,0xc7,0x2a,0x78,0x85,0x25,0xb0,0xd2,0x11,0x86,0x0e,0x2c, +0x59,0x09,0xd7,0xe2,0x9c,0xae,0x32,0x32,0x5e,0x44,0x6e,0x71,0x8f,0x4f,0x58,0xec,0x33,0xe0,0xd0,0xab, +0x0f,0x3a,0xc2,0x88,0x85,0x9e,0x2d,0xe2,0xe0,0x32,0x9a,0x04,0x30,0xcf,0x29,0x0a,0xd2,0x1e,0x4c,0xb9, +0x9c,0xae,0xcd,0xec,0x0d,0xaf,0xb3,0x08,0x28,0x96,0x64,0xe5,0x14,0x8f,0x94,0xd5,0x82,0x5f,0x8a,0xc8, +0xeb,0x45,0xfe,0x66,0xf3,0x6c,0x9a,0x26,0x30,0xc8,0xc7,0x8d,0x4f,0x9b,0x27,0xcd,0xe3,0x4f,0x57,0x9d, +0x93,0x96,0xbb,0x29,0xdd,0xc4,0x7f,0xfb,0xb6,0x89,0x66,0x2a,0x9f,0xa3,0x7c,0x09,0x40,0x82,0x3e,0xc8, +0xdc,0xe6,0xb0,0xdf,0xd9,0x90,0x6f,0x32,0xc3,0x7a,0xe8,0x8b,0x2c,0x0a,0xdd,0x86,0x25,0x4b,0x63,0x80, +0xd0,0xf2,0x1d,0x38,0x2b,0x92,0x93,0x61,0x88,0x6d,0x28,0x95,0xfc,0x19,0xcd,0x66,0xa2,0x95,0x61,0x23, +0xbd,0xec,0xcb,0x1a,0xdc,0xa1,0x56,0x07,0xb0,0x04,0xd7,0x82,0x64,0x7d,0xf4,0x62,0x4f,0xf4,0x11,0x48, +0x41,0xc2,0x16,0x26,0x74,0x9d,0x1b,0x7c,0x2f,0x37,0xa9,0x86,0x0c,0x91,0x48,0x11,0xe5,0x31,0xca,0x73, +0xa4,0xd1,0xdf,0xcc,0x3d,0x56,0x59,0x14,0xde,0xc3,0xcc,0xec,0x88,0x16,0xfd,0xa8,0xa3,0x1e,0xa9,0x4a, +0x10,0x4c,0xd4,0x4a,0xb5,0x9d,0x2c,0x38,0x0f,0xd2,0x08,0xaa,0x24,0xf7,0xf6,0xba,0x97,0x5e,0xc9,0xa1, +0x86,0xcd,0x48,0x19,0xf7,0x78,0x99,0x17,0xb0,0xc1,0x19,0xfa,0x37,0xa7,0x87,0x7c,0xd4,0xb9,0xd7,0x9c, +0x6f,0x2b,0x8f,0xb5,0xc8,0x2c,0x14,0xb7,0xbd,0xcc,0x85,0x4c,0xb2,0xd6,0x78,0x7f,0x15,0x63,0xc0,0x88, +0x30,0xcd,0xbf,0x32,0x63,0x81,0xf1,0xdc,0x48,0x20,0x97,0xe1,0x0d,0x99,0xfc,0x34,0xc7,0x27,0x3e,0xdc, +0xa1,0x55,0x3b,0xc4,0x0b,0x72,0x48,0x36,0xfc,0xf5,0x8e,0xc2,0x71,0x51,0x84,0x3a,0x4a,0xc3,0x18,0x70, +0xfb,0xe2,0x4e,0x85,0xf1,0xae,0x9e,0x47,0x97,0xc8,0x77,0x50,0x1e,0x9c,0xfa,0xc0,0x2b,0xb1,0x47,0x38, +0x99,0x36,0x8e,0x32,0x34,0xe8,0x46,0x2b,0xb1,0x43,0x5a,0x46,0xd4,0x61,0xce,0xb3,0x68,0x9a,0xc2,0x7a, +0x52,0x9c,0x10,0x85,0x0a,0xa6,0xe8,0xb9,0x16,0x40,0x4b,0x98,0x0e,0xaa,0x53,0x57,0xb4,0x5c,0x5f,0x67, +0x3f,0x9d,0xa4,0x3c,0x00,0xbd,0xc7,0x13,0x04,0x32,0xf1,0x86,0x80,0xd9,0x8e,0x40,0xa3,0xa4,0xae,0x59, +0x5b,0xea,0x4c,0x42,0xc8,0x82,0xc8,0x8b,0x45,0x81,0x72,0xbd,0x8b,0xcc,0x95,0x8e,0x75,0x94,0x3d,0xe7, +0xce,0x8f,0xeb,0x90,0x25,0x7a,0x47,0x59,0xd3,0xe9,0x0b,0x32,0x8d,0x9d,0x52,0x1d,0x36,0x72,0x2f,0xad, +0x0d,0x0b,0x70,0xf4,0x81,0xc6,0x19,0x9a,0xea,0x90,0x48,0x8d,0x88,0x2d,0xb1,0x27,0x0b,0x1e,0xb8,0xbe, +0x37,0x36,0x77,0x65,0x47,0x54,0x9e,0x5f,0x54,0x34,0x57,0xbe,0xb5,0x85,0xaf,0x53,0xd2,0x4b,0xc5,0x1b, +0xfb,0x0e,0xa9,0x7b,0x94,0xaa,0xa7,0xb4,0xf7,0xe7,0xe7,0xb2,0xf6,0xdd,0x4f,0x71,0xa3,0xb1,0x83,0x52, +0x8b,0x5d,0x01,0x61,0x2a,0xb8,0xb6,0x1c,0xd8,0x36,0x31,0xf7,0x53,0xbc,0xb3,0x49,0x85,0x8b,0x68,0x09, +0x47,0x49,0xd3,0x44,0xab,0x40,0xe2,0x10,0x8a,0xf8,0x06,0x46,0xc5,0xeb,0x3e,0x3f,0xdf,0x4b,0xd8,0xf8, +0x36,0xf8,0xc6,0xf5,0xe8,0x7e,0x37,0xb2,0xf1,0x33,0x72,0x2f,0x4c,0x18,0x68,0xc3,0x11,0x7d,0x40,0x99, +0x1a,0x8d,0x29,0x03,0xbc,0x8f,0x1f,0xed,0x6c,0x1a,0xd1,0x22,0x96,0xf4,0xff,0xb6,0x75,0x9e,0x9f,0xdf, +0xba,0xd2,0x58,0xa3,0x69,0xbc,0x8c,0xa4,0xbc,0x6a,0xca,0x6d,0x0b,0x72,0x71,0xa6,0x22,0xe7,0x2d,0x1b, +0x17,0xa5,0x88,0x75,0x5d,0x5e,0x31,0x81,0xac,0xaa,0x9e,0x61,0x8d,0xba,0x0f,0x90,0x6b,0x58,0x5a,0x35, +0x41,0xdc,0xb6,0xe6,0xb5,0xae,0xb9,0x3e,0x6d,0xdb,0x9b,0xbe,0x3e,0xe1,0x82,0xa7,0x8e,0xf9,0x0e,0x1d, +0x8e,0x8a,0x13,0x3c,0xd6,0x2a,0x36,0x17,0x99,0x31,0x83,0x4b,0x2b,0xf4,0x44,0x4a,0x39,0x16,0x31,0xb3, +0x25,0xd8,0x3c,0xf0,0x28,0x1c,0x6d,0x57,0x47,0xed,0x14,0x5d,0x01,0xc8,0xfd,0x52,0x8f,0xce,0x52,0x84, +0xc6,0x65,0xd7,0x5c,0x74,0x0d,0x06,0x16,0x7b,0x11,0xcc,0x86,0xa1,0xf8,0x21,0x43,0x0c,0x95,0xf7,0xb4, +0xf2,0xae,0x58,0xe5,0x47,0xc8,0x07,0x33,0x9e,0x5c,0xd2,0xbc,0xc4,0x9c,0x5a,0xac,0x7c,0x22,0xa6,0x55, +0xc6,0x79,0x06,0xa6,0xf8,0x6a,0xc8,0x6e,0xc5,0x21,0xbf,0xa5,0xbd,0x05,0x91,0x4b,0x6a,0x47,0xb8,0x14, +0x6f,0x8b,0x88,0x19,0xf8,0x3e,0x54,0xde,0x10,0x8c,0x26,0x72,0x5b,0x13,0x71,0xd1,0x44,0xae,0x35,0x11, +0x9b,0x4d,0xa4,0xac,0x90,0x46,0x6d,0x68,0x9b,0x3f,0x3b,0x80,0x89,0xfc,0x6c,0x37,0x1f,0x66,0xfd,0xdc, +0x8b,0x5a,0xb6,0x26,0xf4,0xc1,0x95,0x8d,0xe9,0xb3,0xee,0x2d,0x39,0x21,0x2f,0x0d,0x2e,0xb5,0x7f,0x4d, +0xd0,0xfd,0xe8,0x06,0x85,0x8b,0x45,0x33,0x95,0xc5,0x60,0x00,0xb7,0x9e,0xdc,0x18,0xd3,0xde,0xcc,0xfc, +0x11,0x1d,0x54,0x99,0xe3,0x18,0xbf,0x4d,0x80,0xcf,0x7c,0x9e,0x5c,0x69,0x53,0x23,0x92,0x31,0x81,0xf1, +0xf1,0x12,0x80,0x29,0xba,0xfd,0x73,0xe6,0x30,0xf0,0x5a,0xc2,0x87,0x9e,0x3a,0xd5,0x9a,0xca,0xbf,0x62, +0xee,0x8b,0x78,0x12,0xdf,0xbe,0x45,0x1d,0x29,0xef,0x24,0x3e,0x40,0xdc,0x55,0x60,0x1d,0x2d,0x60,0x43, +0x15,0x31,0x80,0x01,0x10,0xc3,0xe0,0xb1,0xb3,0x8c,0xd9,0xd7,0x7d,0xe4,0xe1,0x81,0xe0,0xbe,0x4e,0x33, +0x58,0x5a,0x69,0x30,0xa1,0x80,0x35,0xf2,0x7c,0xf5,0x73,0x48,0xe5,0x6c,0x8c,0x31,0xc0,0xbe,0x28,0x35, +0x59,0x4b,0xa9,0xf7,0x4e,0x31,0x45,0xbe,0x7d,0xeb,0x96,0xbb,0xff,0x1c,0x6a,0x7f,0x9b,0x5c,0x6a,0x13, +0x58,0x67,0xbc,0x06,0x1c,0x5f,0xaf,0x82,0xa1,0xaf,0xe3,0x28,0x7b,0x9f,0xf9,0xcd,0xbc,0x55,0xc5,0xac, +0x6d,0xe9,0x82,0xbb,0xc9,0x1c,0x07,0xce,0xac,0xc3,0x68,0x1c,0x7a,0x19,0x32,0xc0,0xcd,0xcc,0x87,0x29, +0x96,0xed,0xf6,0xe8,0x67,0x0f,0x67,0x74,0xb6,0x51,0x02,0xac,0x8c,0x2d,0xc5,0x8e,0x88,0x6f,0xcc,0xe3, +0x4e,0x1f,0xf6,0xd8,0xd8,0xe4,0x82,0x71,0xb4,0xc8,0x6e,0xcc,0x83,0x94,0xa1,0xd9,0x1d,0x3e,0xed,0x82, +0xfd,0xb6,0x56,0x61,0x19,0x82,0xe2,0x3a,0xb4,0x9b,0xbd,0x76,0xe6,0x96,0x90,0xbb,0xa9,0x4c,0x2e,0xa4, +0xee,0x0b,0xdd,0xf5,0x57,0x41,0xdc,0x5b,0x91,0x16,0x83,0xcc,0xd6,0x4d,0x36,0x26,0x2b,0x20,0x34,0xc1, +0x08,0x50,0xcd,0xdb,0xd1,0x59,0xe3,0x6b,0xcc,0x57,0xae,0x78,0xd7,0xef,0x6c,0xab,0x2b,0xb5,0x2b,0x63, +0x35,0x5b,0x20,0xd7,0xea,0x72,0xdc,0xa2,0xb8,0x75,0x5a,0x73,0x44,0x44,0x85,0xb5,0x88,0xc6,0x5a,0xf5, +0xd8,0x2e,0xe8,0xce,0x57,0x4c,0xc9,0x85,0x8a,0xcf,0xa6,0x6a,0xbc,0xb6,0x2b,0xae,0xe2,0xb8,0xc7,0xa2, +0x4c,0x69,0x58,0xc4,0x03,0x5e,0x1d,0xfb,0x58,0x5b,0x4e,0x65,0x54,0x94,0xa2,0x8d,0xc0,0xd2,0xfa,0x58, +0x0d,0x4b,0xc7,0x3d,0xba,0x82,0x62,0xcb,0x71,0xfd,0x52,0xc0,0x49,0xc2,0x07,0x59,0xa9,0x00,0x5b,0x51, +0xd6,0x97,0x09,0xfd,0x5a,0x3a,0x0c,0xcd,0xb1,0x6f,0x6d,0xf5,0xbb,0xc6,0xf2,0x11,0x41,0xa7,0xc4,0x12, +0x42,0xb7,0xf3,0x54,0xbf,0x70,0xce,0x5e,0x5a,0x50,0x0a,0x98,0x17,0x55,0x68,0x59,0x54,0xf6,0xf2,0x62, +0x89,0xa9,0xf2,0xc6,0x32,0xab,0x6d,0xd4,0x56,0x86,0x16,0x5e,0xd8,0x2e,0xad,0xb9,0x52,0x05,0xa5,0x31, +0xd3,0xaf,0x62,0x95,0x61,0x43,0x71,0x44,0xe1,0x5e,0x96,0x64,0xcd,0xb6,0x58,0xd8,0x8d,0x1c,0x10,0x7a, +0x45,0x71,0x50,0x71,0xa7,0x94,0x91,0xc7,0x8a,0xe8,0x25,0xa4,0x99,0x5e,0x44,0xee,0xb0,0x94,0xfb,0x75, +0x6e,0x29,0x65,0x08,0x5a,0x38,0xea,0x43,0xd3,0xb9,0xc0,0xe3,0x08,0x87,0xbc,0x91,0xe3,0x51,0x81,0xbf, +0x84,0x12,0x6d,0x0d,0xec,0x62,0xce,0x90,0xa4,0x76,0x8c,0x81,0x55,0x8a,0xf9,0x50,0x95,0x8f,0x29,0xc4, +0xc4,0x1a,0x68,0xca,0x08,0x69,0xda,0x8c,0xd3,0xea,0x46,0x7f,0x33,0x5c,0x7b,0xc6,0x11,0xd3,0x6a,0x3a, +0xa9,0xce,0xd0,0x55,0xbd,0xbc,0x7d,0x1f,0x97,0xf4,0xb0,0x7c,0xbd,0x34,0x6f,0x9f,0x96,0x51,0xa6,0xad, +0xb6,0x96,0x39,0x51,0x3d,0xd7,0x98,0x21,0xf3,0x62,0x55,0x9c,0x25,0x62,0xf6,0xa9,0x22,0x3c,0xf3,0xdb, +0x4f,0x6e,0xca,0x37,0x13,0xed,0x6c,0x0a,0x29,0x3b,0xf2,0x6d,0x25,0x5b,0x3d,0xcf,0x64,0x8b,0xd7,0xd7, +0x1f,0x94,0xe5,0x2b,0x3b,0x3f,0x62,0xf0,0xbe,0xb6,0xff,0x58,0x5e,0xd3,0xe5,0xd4,0xf7,0x05,0x36,0xab, +0x19,0xae,0x76,0xa4,0xcd,0x0a,0xba,0x67,0x17,0x9b,0x44,0x85,0x5f,0x60,0xac,0xcb,0x27,0x5b,0xed,0xf1, +0x5a,0xda,0x64,0xee,0x71,0xd6,0x96,0x9b,0x92,0x9b,0xa0,0xa5,0xb1,0xee,0xaa,0x63,0xb9,0x52,0x97,0x57, +0xcf,0xac,0xdd,0x7e,0x67,0x2e,0x1f,0xeb,0xc5,0x66,0x60,0x67,0x1a,0xab,0x87,0x36,0x7a,0x03,0xac,0x9c, +0x88,0x4a,0x77,0xb8,0xca,0x63,0xde,0x83,0x93,0x5c,0x2b,0x8e,0xd6,0xf5,0x75,0xf2,0x6a,0x1b,0x8c,0xb2, +0xa6,0x85,0x29,0x68,0xe7,0xee,0x2e,0x77,0x12,0x72,0x8e,0xa6,0xb8,0x0f,0x24,0xb3,0x31,0xc5,0x21,0x51, +0x67,0x33,0x46,0xe5,0x12,0x84,0x96,0x1c,0x22,0xf1,0x2e,0x76,0x4a,0xfc,0x3a,0xb7,0xd3,0xe1,0x9f,0xde, +0x67,0x0d,0xc5,0x17,0x68,0xcc,0x8e,0xaa,0x3e,0x6b,0x3d,0x5d,0x20,0xe8,0x5f,0xcb,0xa3,0x81,0x42,0x2e, +0xdf,0xce,0xde,0xb8,0x62,0xa6,0x6c,0xb1,0x64,0x36,0x66,0x48,0xdf,0xd1,0x44,0x0f,0x72,0xd4,0x1d,0xcf, +0x94,0x35,0x19,0x40,0xaa,0x2e,0xc7,0xd3,0x45,0x30,0x06,0xcc,0xcf,0x9c,0x21,0x21,0x84,0x50,0xc9,0x02, +0x72,0x7e,0x8e,0x42,0x79,0x25,0x78,0x30,0x20,0xb8,0x13,0x2a,0xbf,0x90,0x1f,0x58,0xa0,0x38,0x57,0xc2, +0x16,0x02,0x01,0x0b,0xe8,0x01,0xc7,0xbf,0x32,0xa6,0x50,0x7f,0xdb,0xd3,0x2f,0x75,0xfd,0x9e,0xfc,0xe6, +0x9d,0xa0,0xff,0xc4,0x2b,0x6d,0x7f,0x50,0x40,0xca,0x56,0xa1,0x89,0x42,0xc9,0x0a,0x5f,0xb0,0x84,0x31, +0xb9,0xb6,0x9b,0xdf,0xc0,0xb8,0x62,0x8c,0xdb,0x07,0xba,0x3e,0x16,0x6f,0xab,0xd1,0xfb,0x43,0x29,0x1d, +0xf4,0x43,0x8f,0x2e,0xf2,0x3a,0x4c,0xe9,0x48,0xc0,0xbb,0x39,0xdc,0xcc,0xbd,0xa9,0x97,0x78,0xa9,0x37, +0xf3,0xc6,0xde,0x99,0xb7,0xf0,0xe6,0x24,0x38,0x8e,0xfc,0xde,0x4e,0x35,0xf0,0x51,0xcc,0x52,0xe3,0x22, +0xc6,0x69,0xd7,0xed,0x1f,0xa3,0xe7,0x17,0x96,0x1c,0x34,0xc7,0xf2,0xc7,0x99,0x1f,0x61,0x0c,0xc0,0xe1, +0x99,0x12,0x1a,0x8b,0xe8,0x50,0xee,0x70,0xdc,0x2f,0x66,0x65,0x21,0x52,0x5e,0xa8,0xd7,0x2a,0x0a,0x95, +0xdd,0x57,0x7a,0x3a,0xa8,0xd3,0xdf,0xf5,0x66,0xfe,0x42,0x2a,0x52,0xa5,0x3b,0xb3,0x41,0xda,0x6a,0xb9, +0xb9,0xbf,0x40,0xaf,0x33,0x01,0x5b,0xf0,0x90,0x94,0x3a,0x93,0x71,0x25,0x83,0x61,0x73,0x4a,0x18,0x78, +0x89,0xbf,0xb5,0x03,0xeb,0xc9,0xc4,0x3f,0xf2,0x7a,0x84,0xb7,0x40,0x76,0xee,0x07,0xc7,0xd3,0x13,0xd8, +0x9b,0x24,0x77,0x12,0x78,0x09,0xfa,0xcd,0x0b,0xaf,0x1a,0xe8,0x89,0x9c,0x7a,0xa2,0xb3,0xb6,0x37,0x82, +0xb2,0x1a,0xb1,0x6b,0x9f,0x61,0xf8,0x69,0x87,0xa2,0x1b,0xf9,0xd7,0x74,0xfe,0x19,0x02,0x7b,0xef,0xda, +0x5c,0x62,0xbd,0xca,0x7a,0x12,0x8d,0x2c,0x59,0x4c,0x02,0xa2,0x7e,0x25,0x09,0x80,0xda,0x65,0x64,0xe4, +0x57,0xd7,0x90,0xc8,0x5e,0xb6,0x80,0x0c,0x90,0x25,0xab,0xc7,0x80,0x93,0x4b,0xa7,0x98,0xf9,0x05,0x41, +0x1d,0x79,0xc0,0xe9,0x2a,0x87,0x11,0x86,0xd4,0x2a,0x14,0x22,0xaa,0x0f,0x6d,0x6b,0xa6,0x17,0x59,0x18, +0xa3,0xb2,0xf9,0x46,0x79,0x09,0xb0,0x84,0x7b,0x60,0x09,0x0e,0xba,0xdb,0x75,0x59,0x9d,0x1b,0x5f,0xe9, +0xa6,0xd1,0x39,0x2c,0x15,0x3f,0x92,0xf3,0xa3,0xe0,0xea,0x02,0xbf,0x12,0x89,0x3d,0x1b,0x66,0xe4,0x98, +0x9a,0x9c,0x52,0x9b,0x0a,0xd4,0xad,0x4c,0x28,0x7a,0xc3,0xfc,0xcb,0x06,0xa4,0x1f,0xc7,0x0a,0xe0,0x32, +0x74,0x9a,0xf8,0x21,0x4d,0x5b,0x64,0x3b,0x83,0x65,0x0a,0x28,0xc2,0x05,0x93,0x50,0x43,0xa1,0x85,0xe0, +0xfc,0xf1,0xcb,0xc7,0xfd,0x0f,0xc0,0x12,0x02,0xbf,0x1a,0x9f,0x09,0xfd,0x73,0x19,0xd0,0x2d,0xfa,0xf6, +0x8d,0x67,0x65,0x24,0xf5,0x53,0x30,0x90,0x6d,0x39,0x54,0x72,0x46,0x31,0xaf,0x33,0x9c,0xb1,0xb8,0x10, +0xf2,0x8e,0x6e,0x04,0x43,0x77,0x04,0xb8,0xa5,0x2c,0x6d,0x14,0x79,0x54,0xad,0x59,0x1f,0xe3,0xc6,0x57, +0x5a,0x71,0x99,0x0a,0xa4,0xa9,0x5e,0xb9,0x01,0x47,0xc7,0x99,0x88,0x23,0x3a,0xf3,0xf1,0xb7,0x20,0x3f, +0x06,0xff,0xc3,0x0e,0x61,0x18,0x81,0x48,0x1e,0xa4,0xa9,0x3f,0xc3,0x23,0x6f,0x69,0x6d,0x0e,0xc5,0xba, +0x0b,0x4e,0x8a,0xe0,0xd6,0xd2,0x13,0x2f,0xd6,0x2f,0x73,0xbf,0xbb,0x95,0x09,0xd7,0xa3,0xd5,0x3c,0xb9, +0x73,0xcd,0x4c,0x35,0x04,0x10,0x32,0xd6,0x0c,0xfa,0x6f,0xe0,0xad,0x67,0x90,0x9d,0xd3,0x80,0xd5,0x61, +0x6b,0xca,0x16,0xed,0xe8,0xa9,0x18,0x8a,0x44,0x86,0xb2,0x77,0xde,0x86,0xf9,0x34,0x19,0x0b,0xe3,0xda, +0x06,0x6a,0x03,0xa0,0x12,0x7c,0xd6,0x72,0x0a,0x97,0xde,0xe1,0x97,0x28,0xcb,0xd1,0xe9,0xa2,0x78,0xc9, +0x2c,0xaf,0x2f,0x87,0xa2,0xde,0xa5,0x37,0x4a,0x87,0xcd,0xb2,0x06,0x07,0xb9,0xc6,0x95,0xa0,0x3b,0xdb, +0x80,0x02,0x49,0x7e,0x71,0x3c,0x64,0x85,0xa2,0xf0,0x0a,0xb7,0xc1,0x59,0x74,0x49,0x1c,0x07,0x3e,0x7e, +0xd3,0xdb,0x3c,0x7e,0x48,0x4f,0xb7,0xf8,0x1b,0x63,0x1d,0xb3,0xcc,0x59,0x7e,0xbd,0xe4,0xaa,0xe0,0x0b, +0xf5,0xb0,0xc2,0xf4,0x32,0xfc,0x88,0x2d,0x53,0xcc,0x11,0x0f,0x38,0x64,0xb1,0x09,0xe1,0xfe,0x87,0x66, +0x79,0x7d,0x67,0x9e,0xcc,0xd1,0x1e,0x1e,0xaf,0xd7,0xcf,0x75,0xe6,0xe8,0x1f,0x0b,0x68,0x27,0xff,0xda, +0xef,0x79,0xd2,0x94,0x9e,0xaa,0x48,0x45,0x00,0xda,0xfe,0x96,0x77,0x91,0x8c,0x22,0xe6,0x88,0xf8,0x17, +0x87,0xbf,0xc4,0x00,0x3e,0x68,0xff,0xf4,0xf7,0xf0,0x2b,0xb9,0x96,0xc5,0x7e,0xa0,0x36,0xee,0x7b,0x26, +0x37,0x82,0x67,0x71,0x30,0x27,0x6f,0xe3,0xf0,0xf7,0x28,0x99,0x85,0x29,0xae,0x94,0x7e,0xa7,0xbb,0x0d, +0xf7,0xd5,0xce,0xb3,0x5f,0x5f,0x9d,0x1e,0xbc,0xf8,0x79,0xef,0xe8,0xcd,0x6f,0x2f,0x4e,0x3f,0xec,0xbd, +0x7a,0x71,0xfa,0xfe,0xe0,0xcd,0xab,0x37,0xef,0x50,0x4f,0x2d,0xef,0x88,0xdf,0x7c,0x2d,0xe9,0x7a,0x14, +0x48,0x17,0x4b,0x91,0xad,0xda,0x51,0x72,0x98,0xa7,0xba,0x50,0x3f,0x97,0xfb,0x9b,0xe3,0x0c,0x58,0xdd, +0x02,0xd5,0xd1,0x30,0x14,0x0d,0x79,0x64,0xe8,0x43,0xc6,0x0f,0xce,0x80,0xbe,0x91,0xf4,0x7d,0xde,0x44, +0xb4,0x78,0x10,0x5b,0xdb,0xdb,0x1b,0x61,0x27,0xc5,0x0d,0x4c,0x85,0x73,0x70,0x07,0x3d,0x5f,0x1d,0xa6, +0x1c,0x4a,0xbd,0xeb,0xb4,0x22,0xd6,0x63,0xcd,0xaa,0xa5,0x27,0xd5,0xd2,0x5a,0x94,0xce,0x8c,0x4a,0x67, +0xae,0x70,0x9b,0x5a,0x29,0x3d,0xaa,0x96,0x0e,0x8a,0xd2,0x01,0x95,0x46,0x6f,0x1c,0x2d,0x3f,0x82,0x49, +0x1a,0x0c,0x46,0x69,0x18,0x7c,0x1e,0xc8,0x2e,0x06,0xe5,0x3e,0x06,0xff,0xc1,0x9d,0x14,0xe7,0x41,0xa5, +0x74,0x50,0x2d,0x9d,0x14,0xa5,0x13,0x2a,0x9d,0x14,0x24,0x6a,0x25,0x3a,0x91,0xd0,0x74,0x13,0x69,0x04, +0x7f,0x9b,0x4e,0xcb,0x42,0x15,0xf2,0xe1,0x60,0xe9,0xaf,0x3d,0x7d,0x04,0xe9,0xae,0x53,0x6a,0xe0,0xa1, +0xd6,0x02,0x3b,0x81,0x48,0x5b,0xce,0x43,0x4f,0x7e,0x4c,0xf4,0x8f,0x11,0x7c,0x54,0x2a,0x08,0x64,0x05, +0xc1,0x5f,0x85,0xa3,0x47,0x26,0xc5,0x16,0x5c,0x83,0x87,0x7a,0x5b,0xb7,0xc1,0x56,0x7d,0x04,0x15,0xd4, +0xd1,0x68,0x15,0x6b,0x83,0xbf,0x50,0x99,0xf0,0x98,0x21,0x1a,0xcf,0xc4,0xdf,0xcb,0x32,0x12,0x00,0xfc, +0x50,0x2b,0xc5,0x75,0x4f,0xf5,0x86,0x32,0xfd,0xe3,0xd2,0xd6,0x6a,0x20,0x2b,0x08,0x96,0xb5,0x6b,0x27, +0x02,0x96,0x7a,0xa8,0x97,0xbf,0x0d,0x06,0x4b,0x89,0x30,0x12,0xb5,0xe1,0xf8,0xdf,0x8a,0x00,0xa3,0x87, +0x5a,0x89,0xfb,0x10,0x60,0x24,0x09,0x40,0xa3,0x78,0xa7,0xce,0x8f,0x54,0xe7,0x47,0xf7,0xec,0xbc,0x54, +0x56,0x49,0x71,0x63,0x06,0xbe,0xf0,0x28,0xa1,0x73,0xaf,0xaa,0x6b,0x8d,0x41,0xa9,0xfd,0xeb,0xa0,0xdf, +0xbb,0x19,0x50,0x64,0x00,0xbc,0x87,0x84,0x1d,0x8a,0xc9,0xd9,0xdc,0xfc,0x3f,0x3f,0x1c,0x77,0xdb,0x7f, +0x0b,0xda,0xe7,0x27,0xd7,0x8f,0x6f,0x1e,0x6c,0x46,0xa4,0x2e,0x55,0xcd,0x7b,0x42,0x79,0xf4,0xa6,0x1c, +0x65,0xef,0x82,0x77,0xcd,0xa8,0x93,0xfa,0xbd,0x1f,0x37,0x34,0x1f,0xc0,0x1c,0x32,0xb0,0xd9,0x83,0xfb, +0x8e,0x07,0xfb,0xc3,0x26,0x06,0x52,0x2a,0x1c,0x46,0x6b,0x05,0x27,0xf6,0x82,0x5b,0x2b,0x0b,0x8e,0xec, +0x05,0x1f,0xd7,0x16,0xdc,0x46,0xe5,0x77,0xb9,0x45,0xc9,0x5a,0x02,0x7b,0x2d,0x4f,0xac,0xb5,0x28,0x1e, +0xa9,0x4c,0xb6,0x0a,0xd5,0x6a,0x01,0x6e,0x4f,0xba,0xee,0x7d,0x49,0xb7,0x9a,0xe6,0x35,0xa4,0xab,0xa7, +0xf9,0x93,0x3b,0x90,0xce,0x3e,0x00,0xb5,0xa4,0x2b,0x66,0xd5,0xd3,0x1a,0xda,0x15,0x10,0x3f,0xd9,0x88, +0x67,0x23,0xc0,0xd6,0x4a,0xca,0xd9,0xf0,0x5e,0x55,0x6a,0x64,0x29,0xb5,0x5d,0x5b,0xea,0x6f,0x76,0x9a, +0x55,0xab,0xf8,0xd1,0x5a,0xc5,0xea,0xb9,0x56,0x47,0xaf,0xbb,0x92,0xab,0x7b,0x2f,0x72,0x6d,0xdd,0x8b, +0x5c,0x4f,0x6a,0x4b,0xfd,0x74,0x5b,0x72,0x3d,0xbd,0x1b,0xb9,0xf0,0x50,0xfd,0x94,0x61,0xc8,0xcf,0x6c, +0xa3,0x89,0xa4,0xf9,0xd4,0x39,0x69,0x3d,0xfc,0x76,0xdc,0xed,0x9d,0x0c,0x3f,0x75,0x86,0x98,0x72,0xb2, +0xe1,0x42,0xa6,0xf7,0xcf,0x07,0xf8,0xe4,0xda,0x47,0x0c,0x70,0xfc,0x17,0xa1,0xc8,0x18,0xd0,0x94,0x48, +0xfd,0x12,0x1a,0x01,0xe4,0x90,0x0f,0xf2,0x22,0xdc,0xab,0xa6,0x2b,0xdb,0x74,0xdc,0x56,0xcf,0xd3,0x12, +0x3c,0x7c,0xe7,0x75,0x1e,0xe2,0xfd,0x53,0x0a,0x1d,0x94,0x79,0x79,0xcf,0x3a,0xeb,0x5e,0xce,0x92,0x00, +0x4d,0x82,0xd0,0xf9,0x85,0x65,0xe4,0x2c,0x73,0x34,0xb7,0x4c,0x94,0x5a,0x0c,0xbd,0x2a,0x86,0x5e,0x39, +0xfb,0xd6,0x28,0x4f,0xee,0x84,0xf2,0x64,0x15,0xca,0xe9,0xb0,0x16,0xe9,0x2a,0x8e,0xd4,0x0b,0x8c,0xc0, +0xf9,0x46,0xa7,0x75,0xdf,0xac,0xa1,0x9c,0x5f,0x2d,0xe4,0xde,0x7e,0x80,0x46,0x77,0xea,0xed,0x68,0x79, +0x6f,0x53,0xd7,0x32,0x4a,0x7f,0x29,0xba,0xc1,0x9d,0xd0,0x2d,0x41,0x97,0x20,0xaf,0xc5,0x9e,0xe1,0x6b, +0xeb,0x11,0x79,0xd0,0x7f,0xaf,0x3d,0x63,0x7d,0xdd,0x82,0xe3,0xbf,0x74,0xcf,0xb0,0x61,0xf0,0xef,0xb6, +0xb3,0x5a,0x71,0xfc,0xd7,0xee,0xac,0xc6,0x42,0xf8,0xd7,0xed,0xb0,0xd3,0x3b,0xad,0x88,0x32,0x34,0x5c, +0xd6,0xfe,0x7f,0xec,0xb1,0xd9,0x9d,0x90,0xce,0xea,0x96,0xf1,0x7f,0xc4,0x0e,0x7b,0x79,0xa7,0xbe,0x5e, +0x2e,0xeb,0xeb,0x7f,0xce,0xfe,0x2a,0x5f,0x9a,0xf1,0x4e,0x0a,0xfb,0xd5,0x51,0x72,0x30,0x19,0x19,0xa2, +0x42,0x2f,0x55,0x8f,0x08,0x3b,0x7e,0x6f,0xf3,0x29,0x5c,0x37,0x77,0xfd,0xed,0xcd,0xa7,0xc3,0x5e,0x3f, +0xdc,0xe9,0x6d,0x3e,0x1e,0xf6,0xda,0x4f,0x37,0x9a,0x61,0x1b,0xb2,0x60,0x74,0x76,0x9f,0x40,0x0e,0x7d, +0x3f,0xc1,0xef,0xae,0x97,0xf9,0x00,0x0e,0x79,0xeb,0xeb,0x50,0xbc,0xb3,0x2d,0x4a,0x21,0x4c,0x08,0xbf, +0x10,0x5a,0x94,0xef,0x6c,0x23,0x78,0x80,0xe0,0x9d,0x6d,0x82,0x16,0x8d,0xec,0x6e,0x71,0x69,0x28,0x4c, +0x80,0x5b,0xdc,0xd0,0x76,0x51,0x74,0x9b,0x9b,0x4a,0xfc,0x66,0xd4,0x6a,0xf6,0xda,0x91,0xbb,0x01,0xff, +0x87,0x5e,0x6e,0xa4,0x5e,0xec,0x37,0xb3,0x16,0xab,0x21,0xaa,0xb4,0x99,0xdf,0x0c,0x30,0x2d,0x28,0xd2, +0xc4,0x13,0xca,0x75,0xda,0x4f,0xbc,0x49,0x3f,0xf6,0x46,0xfd,0xd9,0x0d,0x52,0x04,0xb8,0xbe,0xa3,0xe4, +0x75,0x76,0x69,0xa7,0x08,0xbd,0x8f,0x0a,0xc1,0xdb,0x45,0xf0,0x45,0xe4,0x41,0x9b,0x9c,0x12,0x49,0x68, +0x68,0x31,0x69,0xc7,0x2a,0xc4,0x8e,0xdf,0x5d,0xf3,0x93,0xe1,0x6c,0x33,0x01,0x9c,0x23,0xf4,0x34,0x31, +0x1b,0x76,0xfb,0xa1,0x0f,0x69,0xcd,0xa7,0xad,0x66,0xde,0x4e,0xdd,0xcd,0x99,0xfb,0xf0,0x69,0x3f,0xc7, +0xa4,0xad,0x56,0x33,0x6d,0x87,0x90,0xd2,0x4f,0xf1,0xf3,0x49,0x0b,0xfa,0x9b,0xe3,0x27,0x94,0xdd,0xf4, +0x9f,0x02,0xc1,0x12,0xef,0x7a,0xda,0x07,0x5c,0xfa,0x99,0x77,0xd9,0x0f,0x08,0x6d,0xeb,0x9b,0x56,0x8a, +0xba,0xdc,0xc2,0x14,0x65,0xbe,0x80,0xd5,0x28,0x75,0x9a,0x50,0x06,0x01,0x55,0xc0,0x58,0xc1,0xbf,0xcb, +0x7e,0xcf,0x4b,0xe1,0xdf,0x04,0xfe,0x8d,0xe0,0x1f,0x4a,0x21,0x18,0x30,0xd3,0x23,0x35,0x73,0x1d,0xec, +0x1e,0x43,0x2a,0x41,0xb0,0x20,0x1b,0x26,0xbd,0x78,0xc2,0x5c,0xeb,0x7a,0xd7,0x88,0x8d,0x94,0xe7,0xbb, +0xaa,0x1e,0x21,0xf3,0x46,0x1b,0x25,0x3e,0x0d,0xf4,0x0a,0x84,0xe3,0x0a,0x7c,0x62,0xb2,0x25,0xfb,0xce, +0xd5,0xe5,0xdc,0x69,0x99,0x79,0xfc,0x50,0x60,0x86,0x6e,0xc6,0x70,0xf6,0x81,0xd3,0x77,0x94,0xae,0x23, +0xea,0x40,0xa1,0xba,0xa2,0x41,0x9f,0xce,0x55,0x34,0x9e,0x84,0x39,0xeb,0x59,0x9a,0x39,0x18,0x80,0x6b, +0x16,0x7c,0xb5,0x65,0x61,0x55,0x25,0xcd,0xfd,0xde,0x9a,0x5f,0x85,0x41,0xb3,0xaa,0x37,0xf0,0x17,0x08, +0x5f,0x9b,0x87,0x5e,0x25,0x59,0x1a,0x1f,0x36,0x1f,0x69,0x86,0x07,0xfc,0x62,0xd6,0x16,0x58,0x38,0x8d, +0x8c,0x5e,0xe9,0x9d,0x71,0x94,0xcd,0xe1,0xbb,0xdf,0x40,0x4f,0x0e,0x03,0x47,0xd8,0x20,0x3c,0x72,0x07, +0xa9,0x61,0x98,0xdd,0xe1,0x67,0x54,0xd5,0x8b,0x53,0xca,0x70,0x6b,0xba,0x98,0x62,0x14,0x35,0x0c,0xf6, +0x17,0x36,0x9d,0x51,0x32,0xd6,0xfc,0xc6,0xa7,0x85,0xe3,0x2a,0xbd,0x20,0x80,0xd3,0xfb,0x09,0x1a,0xef, +0xff,0x9a,0xce,0x9a,0x5b,0xb0,0xfd,0x0c,0xd0,0x58,0x2a,0x0c,0xc6,0x45,0xe1,0x47,0x3b,0x84,0x74,0x83, +0xfc,0xc9,0x38,0xe8,0x7d,0x75,0x13,0xdf,0x17,0x76,0x3b,0xdc,0xb5,0x2b,0xaa,0xa2,0x71,0x8d,0xf6,0xc2, +0x13,0x92,0xc4,0xf6,0x1b,0x0b,0xa8,0xec,0x51,0x2b,0x6a,0x39,0x2e,0x74,0xb0,0x9d,0x86,0xf3,0x30,0xc8, +0x1b,0x67,0xf4,0x68,0x2c,0xfe,0x0c,0x6e,0x76,0x36,0xa9,0x5a,0xb4,0x48,0x22,0xff,0x07,0x17,0x33,0x61, +0xe0,0x29,0x34,0xe1,0xaa,0x0f,0x4f,0x1a,0x3d,0x10,0xfc,0x54,0x00,0x96,0xcb,0x4b,0x0d,0xba,0x7b,0x57, +0xa0,0x94,0xf6,0x6e,0x59,0x03,0x82,0x5a,0x91,0xb8,0x47,0x1d,0xec,0x89,0x82,0xeb,0x60,0xbd,0x81,0xa5, +0x15,0x30,0xf8,0x29,0x43,0xba,0xe5,0x6d,0xa3,0xc3,0x2e,0xda,0x3e,0xf2,0xe2,0xa8,0xbc,0x8b,0x87,0xa6, +0x89,0x8c,0x1c,0x4e,0x82,0x7e,0xb4,0x6b,0xcb,0x42,0x4c,0xea,0x73,0xde,0xf3,0x3c,0x94,0x16,0x35,0x64, +0x5b,0x65,0x83,0xdb,0x5f,0xa4,0x59,0x92,0x22,0x18,0x59,0x58,0x09,0xe8,0x6a,0xa5,0xaf,0x17,0x61,0x83, +0x7f,0xf2,0xae,0xd1,0x46,0x4b,0xb7,0x79,0x88,0x25,0xcf,0x82,0xf8,0x32,0xc8,0xaa,0xf0,0x87,0x04,0x68, +0x94,0x7a,0xd4,0x20,0x85,0x3a,0xff,0x51,0xef,0x51,0x83,0xfd,0xb8,0xf9,0x8f,0xb6,0xbb,0x8f,0x1a,0xec, +0x17,0xf4,0x11,0x14,0x42,0x4c,0xb8,0x42,0x2b,0xce,0x00,0xc1,0x18,0xcb,0x6a,0xcf,0x6e,0x89,0xff,0x21, +0x4c,0xfa,0xbb,0xe0,0x0f,0xf0,0x77,0xc7,0x1f,0x0a,0x89,0x87,0xcd,0x15,0xdd,0x00,0xc0,0xfb,0x75,0x03, +0xce,0x8b,0x3b,0x75,0x03,0xe0,0xef,0xde,0x0d,0x3a,0x94,0x56,0xf4,0x00,0x60,0xee,0xd7,0x83,0x83,0x70, +0x7c,0xa7,0x1e,0x00,0xfc,0xdd,0x7b,0x00,0x85,0x56,0xe0,0x0f,0x10,0xf7,0xc3,0xff,0x55,0x8a,0xd1,0x81, +0xef,0xd2,0x03,0x2a,0x71,0xf7,0x3e,0x50,0xb1,0x15,0xbd,0x20,0x98,0xfb,0xf5,0xe3,0xd9,0xec,0x8e,0x2b, +0x1a,0x0b,0xdc,0xbd,0x17,0xcf,0x56,0x4f,0x25,0x04,0xb9,0x5f,0x1f,0xf6,0x66,0xf3,0x69,0x70,0xa7,0x4e, +0x50,0x89,0xbb,0xf7,0x82,0x8a,0xad,0xe8,0x06,0xc1,0xdc,0xaf,0x1f,0x1f,0x58,0x11,0xa3,0x0e,0x69,0x91, +0xfd,0x2c,0xf9,0x62,0x43,0xb3,0x57,0x6c,0x3f,0x64,0x91,0x08,0xeb,0x8b,0x8e,0x1d,0x1d,0x5d,0x6e,0x58, +0xd8,0x56,0x16,0x0e,0x7b,0x85,0x2b,0x6a,0xfd,0x3c,0xf0,0x1a,0x1d,0x1b,0x3d,0x8b,0x64,0xee,0x53,0x09, +0xcc,0x59,0x62,0x16,0xc9,0xd6,0x60,0xed,0x8b,0xe4,0xcf,0x36,0x6a,0x97,0xb4,0xa5,0x3b,0x07,0x76,0x9c, +0x25,0xb3,0xd9,0x66,0x7f,0x09,0xc4,0xea,0xb2,0x74,0xcc,0xb7,0x51,0x5f,0x0d,0xbd,0x40,0x4b,0x18,0x4f, +0x70,0x71,0x18,0x8d,0xfe,0x4b,0x8e,0x9e,0x74,0x2c,0x87,0x78,0xc5,0x56,0x81,0x7c,0x80,0xa4,0x05,0x07, +0x82,0x26,0x09,0x96,0x72,0x26,0xed,0x0c,0x66,0x00,0x13,0x4e,0x55,0x59,0x59,0x59,0x61,0xd5,0xf0,0x57, +0xd4,0x76,0x7b,0xd4,0x78,0x5e,0x56,0xaa,0xe4,0xe4,0xef,0x42,0xf3,0x8e,0x35,0xdf,0x06,0x65,0x19,0xfb, +0xa0,0xa8,0x92,0x53,0xee,0x89,0xe7,0xed,0xab,0xbb,0x15,0x72,0x67,0x95,0x0e,0x73,0x75,0xdf,0x45,0xcb, +0x3b,0xd5,0xca,0x37,0x8b,0x32,0x7f,0x59,0xba,0x40,0x58,0x1c,0x8e,0x85,0x9b,0x5b,0x5e,0xea,0xe7,0x70, +0x45,0x56,0x0e,0xd9,0x99,0x29,0x15,0xd1,0x9e,0x31,0xf8,0x18,0xee,0x17,0xa8,0xbf,0xd8,0xe1,0x8d,0x26, +0xf4,0x22,0xe1,0x70,0xd7,0x0f,0x55,0x90,0x0e,0xd4,0xcc,0xa4,0x50,0x25,0x31,0x87,0x7f,0xd8,0x42,0x6f, +0x7d,0x22,0x3c,0x18,0xc5,0x05,0x2b,0xa2,0x79,0x74,0x07,0x09,0xa4,0x25,0x32,0x2a,0x86,0xb8,0xc8,0x67, +0xff,0x48,0xf3,0x26,0xfd,0x9a,0x27,0x57,0x4d,0xb8,0x94,0xa0,0x33,0x57,0xf5,0x1d,0xd0,0x37,0xc7,0x5b, +0x6e,0xc6,0xbb,0x79,0x6b,0xcb,0x95,0xba,0x7d,0x4d,0x04,0xf6,0xe1,0x1e,0x1a,0xec,0xa4,0xc3,0xbf,0x75, +0xfb,0x5b,0x3f,0x76,0xfb,0x6c,0x73,0x90,0x07,0x71,0x13,0xee,0xf5,0x81,0xbb,0x89,0x20,0xae,0xbb,0x49, +0xc9,0x1f,0xde,0x6c,0xf4,0x7e,0xea,0xba,0xad,0x66,0x02,0xf0,0xf0,0xab,0x0f,0xbf,0x51,0xee,0xf7,0x10, +0x7d,0x94,0x8f,0xfd,0x78,0x33,0xf7,0xa6,0x7e,0x53,0x59,0x2d,0xcc,0x28,0xb3,0x25,0x00,0x76,0x7a,0x5b, +0xdd,0x61,0xaf,0x3f,0xdb,0xdd,0x7a,0xd2,0x1d,0x36,0xe1,0xa3,0x5d,0x00,0xb6,0x49,0x7a,0xb8,0xf9,0xb4, +0xdb,0x9f,0x11,0x18,0x65,0xcf,0x28,0xa1,0xeb,0x2d,0x7c,0x0d,0x10,0x32,0xdc,0x9d,0xa7,0x58,0x51,0x39, +0x51,0x95,0x2b,0x65,0x88,0x5a,0xce,0xf4,0x5a,0x00,0x85,0x6a,0x2d,0x94,0x68,0xab,0x05,0x33,0x44,0x2d, +0xf3,0xb2,0x16,0x53,0x73,0x8a,0x92,0x9a,0x29,0x49,0x6a,0xc6,0x2e,0xdc,0xe4,0xf7,0x2b,0x10,0x0b,0x84, +0x58,0x14,0x10,0xe7,0x15,0x88,0x33,0x84,0x38,0x53,0x10,0x03,0xd2,0x24,0x9f,0xb1,0xd6,0xbb,0x50,0x3f, +0x9a,0x93,0xe2,0xc7,0x3e,0xfd,0xff,0x1c,0x95,0x3f,0x3c,0x06,0x3a,0x40,0xb7,0x3d,0x89,0x17,0x78,0xf8, +0x62,0x7f,0xa3,0xa4,0x65,0x9d,0x3c,0xa1,0x89,0x7b,0xf0,0x73,0x55,0xa0,0x50,0xb6,0x7f,0xcf,0x6c,0x02, +0x04,0xdd,0x32,0x0b,0x85,0x31,0x4b,0x41,0xea,0x45,0x14,0x9a,0xd7,0x13,0x12,0xe3,0x2c,0x05,0x41,0x51, +0x23,0x31,0xcb,0xcb,0xc1,0x0a,0xf9,0x8c,0x26,0x3b,0x12,0xcf,0x91,0x25,0xd9,0x4e,0x6a,0x8a,0x7e,0x3a, +0x9a,0x3e,0xa3,0x5a,0x55,0x11,0x3a,0x99,0xd1,0x84,0x40,0x9a,0xf4,0x68,0x1a,0x64,0x7b,0x70,0x0e,0x47, +0xa3,0x45,0x1e,0x8a,0x98,0x17,0x57,0x67,0xf3,0x36,0xea,0xd3,0xad,0xaf,0x2b,0xcd,0xe0,0xf4,0x38,0x3a, +0x59,0x5f,0x6f,0xe2,0x1f,0x5f,0x2b,0x0c,0x14,0xa9,0x2b,0xec,0x49,0x4f,0x9c,0x58,0x66,0x48,0x05,0xd7, +0xba,0x18,0x3f,0x7b,0x96,0xc9,0x54,0x59,0xe1,0x5a,0xcf,0x2d,0xc2,0xf9,0x10,0xaa,0x24,0xba,0x55,0x4a, +0xb2,0x7a,0xf7,0x00,0x5e,0xba,0xe4,0x8e,0x6a,0x75,0xb8,0x23,0xa5,0xc3,0x3d,0xa8,0xb1,0xda,0x15,0xaa, +0xc4,0xd9,0xc9,0xd0,0xf8,0x22,0x7c,0x84,0x45,0x71,0xd1,0x20,0x21,0x2b,0xa7,0x1d,0x9b,0x17,0xd4,0x4f, +0xa4,0x8a,0x4c,0xaa,0x2a,0x74,0x6a,0xba,0x1e,0x0a,0xaa,0xd8,0xfe,0x4c,0x8a,0xa6,0xb4,0x2f,0x5d,0x18, +0x55,0x10,0x9b,0xf4,0xbc,0x31,0xa0,0xa7,0x33,0x9a,0x25,0x67,0xe8,0x02,0xdc,0x18,0x76,0x56,0x99,0x15, +0x46,0x72,0x62,0xb2,0xd6,0x4b,0x13,0xc8,0xd9,0x54,0xa8,0x03,0x63,0xdc,0x62,0xbb,0x92,0x38,0x29,0x90, +0x7b,0x18,0xf6,0x17,0xf8,0xa9,0x7c,0xef,0x3c,0x17,0xea,0xe5,0x12,0x2d,0x27,0x8a,0x67,0x51,0x4c,0x83, +0xca,0xdc,0x95,0x90,0x8e,0x39,0xee,0x30,0x32,0x13,0x24,0x68,0x9b,0x7b,0xe0,0xf6,0xcb,0xf9,0xe5,0x0a, +0x8a,0x60,0xca,0x46,0x8b,0xa9,0x0c,0x62,0x20,0xa7,0x48,0x5a,0x8e,0xf7,0x89,0x4d,0x97,0x43,0x80,0x56, +0x81,0x10,0x81,0x32,0x94,0x34,0x27,0x13,0x76,0xa7,0x8a,0x5c,0x22,0x71,0x9c,0x06,0x57,0x7c,0x19,0xc8, +0x9a,0xca,0xf4,0x8c,0x1d,0x1e,0xca,0x64,0x44,0x1a,0x4f,0xf1,0x73,0x38,0x31,0xb3,0x15,0xe2,0x1f,0xac, +0xfc,0x94,0x00,0x4f,0x89,0x26,0xb2,0xec,0x68,0xb6,0x48,0x6f,0x53,0x14,0xe1,0x44,0x49,0xb7,0x2f,0xde, +0x67,0x6d,0xa2,0x55,0x94,0xe3,0xda,0x24,0xae,0xab,0x67,0x88,0xa5,0x14,0x22,0x29,0x06,0xa0,0x22,0xaa, +0x8c,0x64,0x0f,0x96,0x31,0x46,0x95,0x6e,0x28,0xe0,0x53,0xd2,0xf8,0x96,0xc2,0x62,0x2b,0x92,0x9c,0x28, +0x38,0xa5,0xd5,0x34,0xa6,0x29,0x23,0x48,0xcc,0x95,0x8b,0xa2,0x2b,0x49,0xcc,0x25,0x89,0xc2,0x12,0x2b, +0x2e,0xf9,0x39,0xfc,0xba,0x42,0xbe,0xc9,0x45,0x09,0x4e,0xdd,0x26,0xc8,0x4a,0xec,0x16,0xa5,0x18,0x10, +0x96,0x55,0xd9,0x12,0xc3,0x58,0xee,0x74,0xd4,0x88,0xd5,0x2e,0xe3,0x9d,0x34,0xab,0x10,0xa5,0xb3,0x81, +0x0f,0x28,0x61,0xe7,0xd5,0xaf,0xd8,0xc5,0x54,0x61,0xa5,0xfc,0xdf,0x7c,0x77,0xb8,0x75,0x13,0x66,0x96, +0xb2,0x14,0x18,0x4a,0x6f,0x01,0x7b,0xb4,0xf8,0x60,0xd6,0x8d,0x93,0x78,0x06,0xb3,0xa8,0x2f,0xd7,0xa8, +0x4a,0xc2,0x18,0x33,0x15,0xb9,0xa8,0xe6,0xe9,0x20,0x84,0x3e,0x24,0x5f,0xcb,0xc2,0xd1,0xbc,0xbc,0xb7, +0x95,0xb6,0xd2,0xa5,0x7b,0x28,0xad,0x95,0x51,0x08,0x07,0x92,0xb9,0xfd,0xe6,0x2a,0x50,0x34,0xc6,0xaa, +0x11,0xd1,0x52,0x52,0x34,0x17,0xae,0x9d,0x87,0x2a,0xdf,0x3e,0xd9,0x54,0x76,0xcd,0x8c,0x52,0xf9,0x75, +0x93,0x07,0x01,0xec,0x9b,0x36,0x6e,0x05,0x2e,0xc6,0x2f,0x0e,0xf3,0x70,0xc5,0x99,0x65,0xec,0x74,0xb7, +0xa0,0xa4,0x4e,0x32,0xd8,0x23,0xd6,0x7a,0x03,0x19,0x26,0x8c,0x64,0x10,0xb0,0xcb,0x1b,0xa2,0x68,0x0c, +0x19,0x21,0xa9,0x9d,0x2a,0x12,0xe7,0x85,0x19,0xb3,0x00,0x17,0x67,0x82,0xa7,0xe5,0xd0,0x71,0x20,0xdc, +0x47,0x08,0x1f,0xcf,0x51,0x1c,0x87,0x29,0x19,0x73,0xec,0x40,0x6d,0x9a,0x6d,0xc7,0xfa,0xba,0x68,0x65, +0x4d,0x6b,0x45,0x42,0x90,0x91,0x00,0xb0,0x4b,0xd5,0x56,0x39,0x9f,0x9e,0xa7,0xee,0x23,0xdb,0x90,0x82, +0x16,0x15,0x57,0x47,0x6b,0x00,0x12,0xc6,0x21,0xba,0x01,0x57,0x97,0x20,0x62,0x6d,0xe4,0x1b,0x1a,0xd3, +0x37,0xe0,0x33,0x5d,0x1a,0x80,0x58,0xde,0xda,0xc8,0xc2,0x8a,0xf4,0x9f,0xaf,0x9c,0x7e,0xe0,0x5b,0xd1, +0x84,0x46,0x74,0x3d,0x69,0x1b,0xdc,0xeb,0x45,0x68,0x42,0x65,0x36,0xa8,0xc3,0x20,0x37,0xa1,0x2e,0x6d, +0x50,0xbf,0x05,0xa5,0x16,0x53,0x1b,0xd4,0x01,0x5a,0xed,0xeb,0x50,0x13,0x1b,0x14,0xc9,0x23,0x4d,0xb8, +0x91,0x0d,0x0e,0x45,0x7e,0x26,0x58,0x60,0x03,0x23,0x91,0x9a,0x09,0x37,0xb7,0xc1,0xa9,0x71,0xbb,0x11, +0xf6,0xa8,0x68,0xcb,0x51,0x72,0x21,0x26,0xa7,0x7c,0x20,0x56,0xbb,0xe1,0xe2,0x44,0x0d,0xaf,0x7b,0xc3, +0xf7,0xd6,0xed,0xee,0x46,0xda,0x11,0x86,0x49,0x83,0x52,0x73,0x25,0x91,0x17,0x5f,0x8f,0x31,0x30,0xb5, +0xf0,0x2f,0xff,0xbd,0x73,0x2f,0x4e,0xe0,0x4e,0xdd,0x51,0x13,0x0e,0xaf,0x09,0x9d,0xc2,0xfc,0x6a,0x7d, +0x7d,0x2d,0x92,0xd7,0xe1,0xee,0x20,0xd6,0xdd,0x14,0x38,0x8e,0xc7,0xc8,0xc0,0xaf,0x1b,0x8c,0x1d,0x67, +0x9a,0x2e,0xea,0x8a,0x07,0xf8,0x0a,0x39,0x6b,0xe9,0x3a,0x0c,0x82,0x7b,0x63,0xaf,0x06,0xc2,0xbf,0x57, +0xe1,0x12,0x7d,0xfe,0x85,0xfc,0xae,0xbb,0xad,0xda,0x12,0xc2,0x5d,0x97,0xa5,0x08,0xb0,0x10,0x70,0xa3, +0xe0,0x75,0xce,0x9e,0x3f,0x74,0xa4,0x67,0x2d,0x84,0xbc,0x11,0xc1,0x45,0x72,0xb3,0x3f,0x37,0x4c,0xd3, +0x71,0xdd,0x42,0x29,0xd1,0x0a,0x2e,0xf3,0xd6,0xb1,0xba,0x1b,0xb5,0xc7,0x8c,0x83,0x20,0x65,0xae,0x02, +0x0e,0x11,0xa2,0x9e,0x70,0x3f,0xa1,0x52,0xa1,0x3f,0x63,0xd3,0x86,0x75,0x01,0x48,0x5c,0x71,0x67,0xdb, +0x63,0xa3,0xef,0x6d,0x8d,0x7a,0xe3,0x5b,0xd1,0xbb,0xbe,0x44,0x2d,0xbd,0x0b,0x3b,0x57,0x0d,0x11,0xec, +0xd7,0xb4,0x64,0x84,0x79,0xa6,0x0f,0xff,0xf4,0xce,0xc3,0x3f,0xbd,0x25,0x3a,0xd3,0x5b,0x90,0xb3,0xb9, +0x68,0x2b,0xf4,0xda,0x3d,0x77,0xe3,0xcc,0xdd,0x94,0x9f,0x72,0x7a,0xdc,0xee,0xca,0x66,0xf0,0xf4,0xc5, +0xf1,0xc7,0xf1,0xc7,0x34,0xcd,0x08,0xb1,0x1b,0x68,0x75,0x9a,0x97,0x34,0x91,0xcf,0xd1,0xcd,0x4a,0x8c, +0x95,0x32,0x13,0x3e,0xee,0x9e,0x90,0xef,0xe0,0x22,0xa1,0x77,0xc2,0x5c,0x12,0x1e,0x5b,0x76,0xcb,0xe0, +0x4a,0x93,0x91,0x79,0x04,0x67,0xd2,0x61,0x0b,0xf4,0xce,0x0b,0xfc,0x9e,0x47,0x7b,0x51,0x24,0xf7,0x22, +0x2f,0x86,0x24,0x58,0xf8,0x1e,0x3a,0xd6,0x9f,0xa2,0x73,0x7d,0x1f,0x25,0x45,0x28,0xe7,0xe9,0x0d,0xa2, +0x0e,0x9a,0x71,0xb2,0x21,0x71,0x27,0x00,0xb0,0x92,0xd8,0x06,0xae,0x3c,0x30,0x59,0xab,0xa9,0x13,0x5c, +0x38,0x95,0xd4,0x91,0x0b,0x95,0xc3,0x35,0x16,0xaa,0x87,0xe3,0x0b,0x1a,0xc8,0x3a,0x97,0xbc,0x1c,0xf7, +0xfd,0xf2,0xd3,0x41,0xf1,0x42,0xc1,0x4b,0x68,0x6d,0x1f,0xaf,0xbf,0xe5,0xbd,0x95,0x66,0xdd,0xb9,0xbf, +0x2f,0x04,0x98,0x15,0x19,0xe2,0xe0,0xbc,0x24,0x46,0x42,0xb3,0xa0,0x19,0x49,0x90,0xc6,0xf4,0x7f,0x36, +0x2e,0x8a,0x49,0x9a,0x74,0xde,0x21,0x4f,0xea,0x24,0x4e,0xea,0x7a,0x5d,0x12,0x27,0x79,0xe7,0x85,0x88, +0x49,0xa6,0xdd,0x28,0xc7,0x28,0x8a,0x31,0x24,0x22,0xad,0x55,0x47,0x7f,0x52,0xe9,0x97,0xf6,0x5c,0x24, +0x3a,0x36,0xb1,0x74,0xec,0xdb,0x37,0x61,0xfe,0x79,0xe9,0x4f,0xea,0xfa,0xe6,0x5d,0xf9,0x97,0xe2,0x4a, +0xf6,0x33,0x5c,0x9a,0x83,0xf4,0x15,0x06,0x90,0x42,0xa1,0x2b,0x22,0xda,0xc5,0xd3,0xe3,0x8a,0xd8,0x0d, +0x1c,0xf8,0xc3,0x3c,0x99,0x43,0x7a,0x1d,0x09,0x7a,0x2e,0xd6,0x67,0x42,0xf7,0x6a,0xa1,0xbb,0x08,0x7d, +0xa9,0x51,0xf6,0x0a,0xbe,0x4c,0xda,0xa1,0x03,0x06,0x01,0x62,0xa4,0xdd,0x70,0xd4,0x9a,0x32,0x55,0xd4, +0x93,0xac,0xa0,0xc9,0x87,0x65,0x34,0xf9,0xe8,0x7f,0xa8,0xa5,0xc9,0xc8,0xff,0xb8,0x82,0x26,0x23,0x1b, +0x4d,0x70,0x82,0xea,0x5d,0xc4,0x0e,0x8e,0x6c,0xe4,0x40,0x78,0x13,0xec,0xa3,0x46,0x87,0x91,0xf8,0xaa, +0x76,0xf9,0x8f,0x4a,0x97,0xb5,0x37,0x5c,0xd1,0xe9,0x3f,0x96,0x75,0xfa,0x17,0xff,0x8f,0xda,0x4e,0x7f, +0xf6,0x7f,0x59,0xd1,0xe9,0xcf,0xd6,0x4e,0xf3,0xc8,0x72,0xd7,0x45,0x6f,0x3e,0x5b,0x3b,0xcd,0x80,0xdd, +0x02,0xec,0x17,0xad,0xd3,0x9f,0xc5,0x57,0xb5,0xd3,0x5f,0x2b,0x9d,0x2e,0x5e,0x7c,0x45,0x9f,0xbf,0x2e, +0xeb,0xf3,0x85,0xff,0xb5,0xb6,0xcf,0x87,0xfe,0xc5,0x8a,0x3e,0x1f,0x2e,0xe9,0x33,0x8f,0x21,0xaa,0xdc, +0x63,0x55,0x4b,0xfa,0xcc,0x80,0x34,0xe1,0x2f,0xb4,0x3e,0x1f,0x8a,0xaf,0x6a,0x9f,0xdf,0x55,0xfa,0xfc, +0xba,0xd4,0xe5,0x77,0xcb,0xba,0xfc,0xc6,0x7f,0x57,0xdb,0xe5,0x2f,0xfe,0x9b,0x15,0x5d,0xfe,0x52,0xe9, +0xf2,0x0f,0xe7,0xdd,0x2e,0x16,0x35,0x33,0x3a,0xbd,0xa7,0xfc,0xdf,0x8f,0x08,0x71,0x6e,0x83,0x78,0x2c, +0xff,0x03,0x88,0xae,0x15,0x62,0x9b,0x72,0xce,0x2d,0x39,0x4f,0xb5,0xda,0xbb,0x5d,0x1b,0xc4,0x4f,0x5a, +0xed,0xe7,0x36,0x88,0x9e,0x42,0xfd,0x8d,0x46,0xf6,0x2f,0xe2,0xab,0x4a,0xf6,0xd7,0x15,0xb2,0x2b,0x75, +0x1b,0x41,0xf6,0xd7,0xcb,0xc8,0x7e,0x6a,0x77,0x74,0xa0,0xb4,0x6d,0x9b,0x0b,0xd8,0xf9,0xe7,0xee,0xe0, +0xb4,0x93,0x96,0x8f,0xb5,0x53,0x3c,0x02,0x4f,0x3b,0x93,0x6a,0xfa,0x04,0xd3,0x47,0xd5,0xf4,0x11,0x9f, +0x79,0x07,0xfe,0xeb,0xda,0xa1,0x7e,0xeb,0x1f,0xac,0x18,0xea,0xb7,0x35,0xb3,0xfb,0x14,0x2d,0x9e,0x3d, +0xfc,0x3b,0x11,0x7f,0x47,0xbc,0x64,0xdf,0xd6,0xcc,0xf2,0x12,0x7a,0x73,0xab,0xe1,0x75,0x7d,0x2a,0x56, +0x7d,0xa0,0x0d,0xd1,0x5b,0xf1,0x55,0x1d,0xa2,0xf7,0x95,0x21,0x52,0xaa,0x44,0x62,0x88,0xde,0x2f,0x1b, +0xa2,0xbd,0xd5,0x43,0x74,0x06,0xe7,0xd8,0x60,0xaf,0x3a,0x44,0x7b,0x38,0x44,0x7b,0xd5,0x21,0xda,0xc3, +0x21,0xda,0xab,0x0e,0xd1,0x9e,0x1c,0xa2,0x57,0xfe,0xfb,0xda,0x21,0x3a,0xf2,0x5f,0xad,0x18,0xa2,0xa3, +0x9a,0x21,0xda,0x13,0x43,0xb4,0x27,0x86,0x68,0x4f,0x0e,0xd1,0x51,0x75,0x0d,0x74,0x69,0x0d,0xbc,0xd2, +0x08,0x7c,0x24,0xbe,0x4c,0x02,0xdf,0x8a,0x95,0x35,0xe4,0xd0,0xa5,0x67,0x88,0xd5,0x9c,0x6c,0x9d,0xa8, +0x87,0x18,0x4b,0x32,0x1e,0x20,0x36,0x35,0xbf,0x03,0x9b,0x5a,0x23,0xab,0xc0,0xb9,0x10,0xd5,0xb2,0x7b, +0x99,0xbd,0x9c,0xd0,0x1a,0xc0,0x67,0x63,0x6b,0xbe,0x50,0x5f,0x04,0x00,0xa1,0x0d,0x7e,0x96,0x64,0xcd, +0xad,0x0d,0xf9,0xa8,0x0b,0x13,0xc9,0x85,0xff,0x65,0x52,0x33,0x3c,0x8b,0x62,0x5b,0xee,0xcc,0x8f,0xe4, +0x05,0x68,0x73,0x0b,0x38,0xdf,0x48,0x5d,0x3e,0x36,0xb7,0x06,0x99,0xe6,0xa6,0xd8,0x9b,0xb5,0x92,0x0d, +0x0d,0x96,0x2e,0x1d,0xae,0x27,0x40,0x60,0x78,0x1d,0x6f,0xdc,0x8e,0x37,0xf4,0xf2,0x02,0xa4,0x57,0x92, +0x3a,0x1a,0xbe,0x50,0xa4,0xe7,0x9f,0x7a,0x88,0xf5,0xf5,0x6e,0x59,0x6c,0x09,0xdc,0xe9,0x50,0x78,0xdb, +0x4e,0xe6,0xc1,0x19,0x30,0xfd,0x78,0xa4,0xf5,0xcb,0x49,0xbd,0x76,0x33,0xed,0x5c,0xee,0x74,0xb6,0x86, +0x9d,0xad,0x3e,0xfc,0x12,0xa2,0x8b,0xca,0x0d,0xb8,0x72,0xa2,0x4d,0x6b,0xc7,0x6a,0x61,0x2b,0x2b,0x47, +0x6a,0xb0,0xd0,0x88,0x01,0x14,0xde,0x98,0x9a,0x37,0x39,0x6e,0xfe,0xcc,0x26,0x84,0x32,0x9a,0x3f,0xab, +0x6d,0x7e,0x6e,0x2b,0xab,0x9a,0x9f,0x6b,0xcd,0x37,0x7b,0x6d,0x18,0x5e,0xb8,0x26,0xda,0x50,0xd8,0xb7, +0x49,0xb8,0x0c,0x14,0x96,0x5d,0x4e,0x2c,0x65,0x15,0x0a,0xe7,0x65,0x14,0x2e,0xdd,0x8d,0x7d,0x1b,0x0a, +0x13,0x9b,0xf8,0x6c,0xe5,0x35,0x42,0xde,0x21,0x2c,0x65,0x15,0x0a,0x97,0x65,0x14,0x52,0x77,0x63,0x62, +0x43,0xe1,0xca,0x2e,0x9b,0x33,0x90,0xb8,0xaa,0x45,0xe2,0x83,0xbd,0xb4,0x42,0xe3,0x43,0x19,0x8d,0x89, +0xbb,0x71,0x65,0x43,0xe3,0xa3,0x55,0xf4,0x67,0x60,0xf1,0xb1,0x16,0x8b,0x91,0xb5,0xb0,0x42,0x62,0x54, +0x46,0x62,0xe4,0x6e,0x7c,0xb4,0x21,0xf1,0x87,0x5d,0xb0,0xb8,0x92,0x9d,0x97,0xbc,0xbc,0xb5,0xb4,0x42, +0xe3,0x97,0x32,0x1a,0x81,0xbb,0xf1,0x47,0x19,0x8d,0xbb,0xec,0xf8,0xb6,0x40,0x06,0x66,0x8c,0x15,0xfd, +0xdd,0xb2,0xe6,0xd5,0xf2,0x36,0x2d,0xbd,0x21,0x45,0x89,0xbb,0x9e,0x2c,0xe5,0xd7,0x97,0xb2,0xf1,0x8a, +0x78,0x0c,0x9f,0xc8,0x47,0x26,0x57,0x3e,0x3b,0x69,0x30,0x65,0x90,0xd2,0xdb,0x92,0x70,0x4c,0x85,0x2a, +0x05,0x4c,0xdc,0xc2,0x9e,0xc1,0x31,0x5c,0x30,0x35,0x35,0x61,0x09,0xbb,0xaf,0x91,0x55,0x51,0x5a,0xe7, +0x72,0xb7,0xb3,0x3d,0x14,0x75,0x50,0x8a,0x83,0xcf,0x17,0x01,0x3d,0x57,0x97,0x92,0xaf,0x30,0x44,0x21, +0x0a,0x80,0x57,0x10,0x6d,0x0f,0x88,0x75,0x49,0x3e,0x39,0xd3,0x64,0x66,0xe8,0x73,0x48,0xd1,0x6a,0xf1, +0x8c,0xaa,0x9f,0xad,0x81,0x5e,0xce,0x61,0xf5,0xa6,0xee,0x9a,0xf2,0x7c,0x24,0x4f,0xcb,0x66,0xe9,0x99, +0x4a,0x23,0x0b,0x1d,0x96,0xc6,0x19,0x2e,0x35,0x3a,0x52,0xf6,0x4b,0x8a,0x4f,0x24,0xa6,0x03,0x53,0x36, +0xbf,0x28,0x7c,0xec,0x11,0x45,0x09,0xd6,0xaf,0x01,0x2d,0x9c,0x9c,0x32,0xe0,0xef,0xab,0x00,0x7f,0xa7, +0x07,0x64,0xb5,0x76,0x4c,0xe6,0x80,0x7b,0x95,0x40,0x2f,0x97,0xf1,0x00,0xb1,0xdf,0xb4,0x02,0x14,0x4c, +0x80,0xc4,0xba,0x1d,0xe1,0x13,0x5a,0x86,0x0f,0xda,0x1d,0x3c,0xb8,0xdb,0xda,0x99,0xed,0x6e,0x36,0xf5, +0x2f,0xb4,0x11,0x6b,0x8b,0x72,0xbf,0xeb,0xe5,0x60,0xa1,0xb6,0xf5,0x83,0x9c,0xca,0x69,0x9f,0xc4,0x1c, +0xd5,0x3a,0x07,0x73,0x59,0xc6,0xaf,0x10,0x6a,0x46,0x1a,0xcf,0xf9,0x4c,0xb8,0x2c,0xdb,0x9f,0x21,0x6f, +0x49,0x3c,0x9f,0xc0,0x53,0xfa,0x13,0xa3,0x2f,0x77,0x09,0xda,0x06,0xd6,0xab,0x2b,0xc7,0xce,0xa8,0xba, +0xe1,0xc3,0x5d,0xde,0x35,0x16,0xa6,0x5b,0x74,0xf0,0x62,0x43,0x03,0x6f,0x46,0xfa,0x77,0x63,0x8a,0x88, +0x30,0xc6,0x88,0x08,0x59,0x07,0x3d,0xa8,0xad,0xaf,0x8f,0x77,0xf8,0x97,0xf2,0xa5,0x46,0x00,0x22,0x96, +0xfb,0x14,0x2d,0xef,0x62,0xe2,0x68,0xd0,0xfe,0xae,0xd0,0xcb,0x9b,0x6d,0xc6,0x80,0x89,0xe2,0xcf,0xdc, +0xc1,0x94,0xc2,0x2e,0x4c,0x5b,0xe8,0xfb,0xdf,0x9b,0xe9,0x1f,0xf2,0x7d,0xfa,0x75,0x76,0xd9,0x9c,0x7a, +0x63,0x2f,0x80,0x33,0xb6,0xb0,0xbd,0xac,0xcc,0x33,0x53,0xbc,0x5f,0x3b,0xdf,0xa4,0x76,0x25,0x52,0xb7, +0x66,0x4a,0xb8,0x9b,0x05,0xa5,0x6e,0x31,0x03,0xb4,0x7a,0xee,0x31,0x48,0x46,0x63,0xcc,0x79,0x01,0x15, +0x80,0x66,0xb3,0xdd,0x1e,0x6a,0x1c,0x0e,0x24,0xbd,0x17,0x65,0x7a,0x0f,0x17,0x7e,0xb7,0xaf,0x72,0x77, +0x7b,0xed,0xea,0x78,0x2c,0xf4,0x01,0x5b,0xc0,0x06,0x58,0x85,0x59,0xec,0x74,0xb6,0x5b,0xb6,0x92,0x38, +0x04,0x89,0x76,0x9e,0x2d,0x34,0x96,0x57,0x30,0xbc,0x96,0xc5,0x4e,0x6f,0x83,0x22,0x62,0x02,0x10,0x14, +0xaf,0x77,0xbd,0xf6,0x02,0xc6,0x6e,0x02,0xff,0x46,0xd6,0x22,0xe2,0xa1,0xd0,0x2c,0x14,0x74,0x52,0x8f, +0x0b,0xda,0x0b,0xf1,0xab,0x61,0xb5,0x0c,0x36,0x84,0x0a,0x8b,0xb6,0x32,0xaf,0xcd,0x22,0x38,0xaf,0xb0, +0x81,0x8c,0x66,0x96,0xad,0x00,0x3d,0x9a,0x9a,0x05,0x82,0xce,0x54,0xe0,0x65,0x2f,0x42,0x2f,0xa8,0xd5, +0x22,0xd8,0x4a,0x1d,0x5a,0xe2,0x65,0xb3,0x28,0x44,0x09,0xa4,0x76,0x79,0xb3,0x44,0x43,0x62,0x22,0x94, +0x43,0xea,0x58,0x03,0x3a,0x19,0x96,0x16,0x67,0x75,0xc5,0xb2,0x6e,0xb0,0xa9,0x07,0x60,0x73,0xa6,0x9b, +0x0a,0x8b,0x50,0x54,0x52,0x90,0x9e,0x19,0x77,0xfd,0xae,0x3c,0xdb,0xf9,0xb8,0x35,0x1c,0xee,0xe9,0xe9, +0x1b,0x6a,0x6b,0xe9,0x75,0xb5,0x0a,0xa4,0x9a,0xae,0x25,0xa3,0xf6,0x98,0x0f,0x97,0x9c,0xd0,0x59,0xb5, +0x7f,0xc5,0x9b,0xa7,0xee,0x21,0x0c,0xfa,0x2d,0x3b,0x19,0x0d,0x4d,0xb5,0x9b,0x08,0x4a,0xf4,0x97,0x73, +0x4f,0x59,0x65,0x14,0xc8,0x01,0x24,0x57,0x58,0x79,0x2e,0x0a,0x55,0x03,0xcc,0xec,0x20,0x6c,0x5f,0xfa, +0x48,0x48,0x87,0xda,0x64,0x0e,0x9a,0x90,0xe0,0xc1,0x30,0xc1,0xbf,0x11,0xfc,0x0b,0x74,0xc8,0xe9,0x50, +0x9b,0x5f,0x08,0x39,0x05,0x88,0x0c,0xfe,0x5d,0x96,0x21,0x83,0xa1,0x39,0xa9,0x44,0xf6,0xca,0x3e,0x21, +0x06,0x06,0xe5,0x3c,0xb2,0xc7,0x06,0xfa,0x49,0xc5,0x51,0x7a,0x57,0x47,0x27,0xed,0xc2,0x41,0xa2,0xc6, +0x8d,0x24,0x1d,0xe0,0x0b,0x61,0xef,0x98,0xf8,0x29,0xfc,0x7f,0xe4,0x47,0xc2,0xdb,0x33,0x1a,0x1d,0x27, +0x30,0x31,0xb2,0xe2,0x75,0x5c,0x9a,0x80,0x73,0x13,0x6a,0x20,0x92,0xce,0xd4,0x8f,0xa1,0x57,0x49,0x27, +0x83,0xbf,0x19,0xfc,0xbd,0x84,0xbf,0x97,0xf5,0xec,0x6d,0x85,0x11,0x0e,0xc4,0x72,0xd2,0xb8,0xdb,0x95, +0x9c,0x30,0xf7,0xbb,0x64,0x8d,0xae,0xc5,0xe8,0x32,0x87,0x87,0x32,0x49,0xd7,0x37,0x5a,0x3e,0x0d,0x71, +0x8c,0xbe,0x83,0x96,0x53,0xa2,0x65,0x46,0xb4,0xbc,0x5c,0x46,0x4b,0x25,0x4f,0x2b,0xd3,0x32,0x05,0xda, +0xa5,0x34,0x1e,0x31,0xcc,0x27,0x1c,0x91,0x18,0xe6,0xd4,0x3f,0x9d,0x96,0x55,0xcb,0x7e,0x0b,0x2d,0x79, +0x02,0xdf,0x9a,0x96,0x34,0x8b,0xad,0x6b,0xad,0xa1,0xa8,0x49,0x7a,0x5e,0x45,0xf8,0x00,0xb9,0x1d,0x85, +0x77,0xe8,0x71,0x7e,0x9f,0x1e,0x4f,0x93,0x2b,0xbb,0x8f,0x6b,0x66,0xe0,0x3d,0x56,0xb5,0x40,0x9f,0x0d, +0x25,0x2d,0x14,0x73,0xc3,0xc5,0x67,0x66,0x76,0x8f,0x4b,0xfe,0x48,0xa5,0xe6,0x5a,0xb6,0x54,0x89,0x17, +0x68,0x89,0x82,0xc7,0xb5,0x2e,0xaa,0xd5,0x09,0x59,0xd5,0x35,0xfa,0xa9,0x4d,0x6f,0xcd,0x89,0x48,0xdd, +0x8c,0xd7,0xc6,0x9b,0x3c,0xb9,0xbc,0xad,0xaf,0xa4,0xca,0xd0,0x8a,0x37,0xfa,0x5b,0xeb,0xda,0x2e,0xb9, +0xb3,0x6a,0x3a,0xf5,0x29,0xdf,0x15,0xd1,0x6e,0x20,0x18,0xc3,0x80,0xa0,0x4a,0x8f,0xe6,0x3b,0x18,0x55, +0x79,0x74,0xf7,0xbf,0xa8,0x16,0xd6,0x41,0xd5,0x3c,0xd2,0x64,0xb5,0x5a,0xe5,0xbb,0x4a,0xd3,0x2f,0xab, +0x1c,0xc9,0x4a,0x77,0x4c,0xf2,0x90,0x85,0x0d,0x79,0x76,0x06,0x57,0xb6,0xd9,0x11,0xd0,0x1a,0xef,0x2a, +0xb7,0xa7,0xee,0x20,0xde,0x49,0x86,0x45,0x35,0x2a,0xae,0x90,0xaa,0x0f,0x03,0x88,0xf5,0xe3,0xf2,0x20, +0xec,0x26,0x2d,0x5d,0x3f,0x8e,0x93,0x81,0x89,0x33,0x80,0xd6,0xd7,0x97,0x57,0x5c,0xae,0xb4,0x5d,0xad, +0xb2,0x55,0xaa,0xf2,0x66,0x99,0x6e,0x26,0x52,0xda,0x66,0xb5,0x6e,0x68,0x4b,0xe0,0x87,0x5f,0xab,0xfa, +0x40,0x97,0x4f,0x1c,0xca,0xf7,0x8b,0xdc,0x54,0x8e,0x33,0x87,0xb5,0x76,0xf0,0x58,0x29,0x8f,0x36,0x0b, +0xa1,0x65,0xeb,0x5f,0xdf,0x2c,0xd3,0x0e,0x36,0x14,0x40,0x74,0x8c,0x6b,0xfd,0xb6,0x4b,0x5d,0x50,0xe1, +0x45,0x43,0x42,0x97,0xf4,0x58,0xb1,0x2b,0x35,0x59,0xbc,0x16,0xc3,0xfc,0x28,0xba,0x08,0x81,0xba,0xba, +0xe3,0x78,0xd8,0x09,0xc4,0xb5,0xbe,0x46,0x61,0xf6,0x46,0xc6,0x53,0xb0,0x04,0xa3,0x96,0xde,0x94,0xf6, +0xe2,0x71,0x8a,0x9b,0x5e,0x67,0xa3,0xf1,0x32,0x82,0xa5,0x96,0x7c,0xd9,0x04,0x6c,0x56,0x36,0x68,0xc3, +0x52,0xcb,0x4e,0xe2,0x90,0xd5,0x5a,0x0d,0x53,0xc8,0x5b,0x20,0x7c,0x63,0x0c,0x47,0x45,0xcb,0xfa,0x3e, +0x03,0xa0,0x4c,0x5f,0xb4,0xb5,0xfd,0xed,0x5b,0x53,0x99,0x09,0xa4,0xc5,0x16,0x21,0x44,0x4d,0xb4,0x4d, +0xe0,0x82,0xe0,0x9a,0x81,0xff,0x9a,0x4c,0x30,0xaa,0x85,0x88,0x20,0x6e,0x43,0x91,0x74,0x74,0xad,0xd2, +0x9b,0x65,0xd8,0xa1,0x76,0x8e,0xce,0x45,0x16,0xad,0xbb,0x03,0x38,0x9c,0xa3,0x82,0x8b,0xcc,0x30,0xaa, +0x5a,0xb5,0xd9,0xb3,0x52,0x20,0xb7,0xbf,0xa0,0xdd,0xa8,0x58,0x45,0xca,0x29,0x3b,0x4c,0x88,0xa1,0x89, +0x4c,0x17,0xad,0x23,0x24,0xa0,0xf4,0xeb,0xbe,0xbe,0xbe,0x06,0x58,0x3b,0x8e,0x49,0x46,0xad,0x76,0xb6, +0xf3,0x93,0x32,0x3a,0xbd,0x3b,0x55,0x63,0x87,0x3b,0x0f,0x35,0x9f,0x8d,0x52,0x8a,0x98,0x1a,0x1b,0xbf, +0x26,0x2a,0x84,0xdb,0x93,0x18,0x50,0xd1,0xb6,0xd0,0xcf,0x76,0x6c,0xf8,0x94,0x4c,0x0f,0xbe,0x13,0x27, +0x12,0x39,0x2c,0xd7,0x08,0x8f,0x96,0x6b,0x84,0x4b,0x93,0x16,0x69,0x8d,0x43,0xa1,0x87,0x00,0x75,0x57, +0x1a,0x3d,0x99,0xc9,0x74,0x7c,0x55,0xe3,0x54,0x08,0x67,0xe6,0xd7,0x98,0xdb,0x3f,0x3e,0xb9,0x29,0x44, +0x7f,0xc0,0x13,0x96,0x8c,0x2d,0xa5,0xac,0x8f,0x6c,0x2e,0x03,0xaa,0xb0,0x33,0x5f,0x64,0xd3,0x66,0x76, +0x9c,0x9c,0xe0,0x6c,0xd1,0x3b,0x9e,0x2d,0x32,0x54,0x91,0x0d,0xc7,0x1c,0xa9,0xce,0x01,0x06,0xd5,0x42, +0xd4,0xc2,0x2c,0xe5,0xaf,0x1b,0xe3,0x35,0x73,0x90,0x71,0xed,0x54,0x96,0xad,0x65,0xe4,0x69,0x8b,0x32, +0x71,0xb4,0x19,0x1b,0xd7,0xe3,0x09,0x08,0xa5,0x9d,0xb3,0x59,0x92,0x85,0x59,0xae,0x49,0x1b,0xa5,0x6e, +0x7b,0x31,0x24,0x95,0x9e,0x00,0x33,0xd9,0x54,0x32,0xef,0xdb,0x88,0x78,0x49,0x80,0xca,0x41,0xbc,0x85, +0x80,0x08,0x39,0x16,0xd5,0x4b,0x16,0x5d,0x21,0xb6,0x96,0x0e,0xfd,0xfb,0x75,0xe5,0x0e,0x3d,0x28,0x5b, +0x57,0xff,0xbb,0x74,0xe1,0x3e,0xa3,0x61,0x37,0xc2,0xfe,0x4f,0xee,0x91,0xee,0x6c,0xe9,0xaf,0x7f,0xbe, +0x88,0x96,0x74,0x1e,0x2e,0xae,0x75,0x9d,0x47,0x6e,0x3b,0xd0,0xb6,0x5d,0xed,0x32,0x64,0x9a,0xf3,0xb8, +0xd7,0x41,0x0d,0x83,0x90,0x14,0x5d,0xe7,0xdd,0x9a,0x1a,0x39,0x7d,0xfe,0xfe,0xdd,0xd1,0xe9,0x11,0xb0, +0xe4,0xaf,0x5e,0x1c,0x9c,0xbe,0xf8,0xed,0xc5,0xbb,0xa3,0x43,0x6c,0xef,0xfe,0xf6,0x66,0xc9,0x3d,0xed, +0xcd,0xc4,0x95,0x3d,0x59,0xba,0x01,0xbb,0xfa,0xd6,0x1e,0xab,0xad,0x7d,0x86,0x97,0x77,0xa8,0x0a,0xb5, +0x7e,0x07,0xe3,0x9d,0x99,0xdc,0xe3,0xc7,0xb0,0xc7,0x17,0xf8,0x38,0xad,0xa6,0x03,0x24,0x9b,0x1d,0x8f, +0x4f,0x3a,0x18,0x4d,0x26,0x9b,0x07,0x67,0xe1,0xd0,0x71,0xfa,0x4e,0xc7,0x69,0x99,0xa9,0xae,0x47,0xdf, +0x1c,0x32,0x2a,0x75,0x6f,0xb4,0x91,0x04,0x06,0xa0,0x30,0xc4,0x14,0xcf,0x4a,0x81,0xed,0xed,0x0b,0xef, +0xb5,0xb7,0x9a,0xa7,0x6c,0xbd,0x94,0x94,0xa7,0x26,0x8c,0xaf,0xcb,0x37,0x0e,0xbb,0x07,0x2e,0xeb,0x22, +0xbb,0xd3,0xe4,0x4c,0xd5,0xe4,0xcc,0x2b,0x61,0xff,0x84,0x52,0xcb,0x5f,0xb1,0x58,0xa5,0x17,0x42,0x2b, +0x91,0x72,0xcb,0xa2,0xa4,0xc8,0xa3,0x14,0x31,0xd0,0x3c,0x6d,0x35,0xf7,0x61,0xd6,0xce,0x53,0xd7,0x1b, +0x26,0x9e,0x1d,0xc3,0xde,0x0a,0x8e,0x58,0x5b,0xb8,0xaa,0x7c,0x15,0xd7,0x9d,0x57,0x6f,0xed,0xe5,0x2b, +0xfb,0x8d,0xb1,0x8b,0x18,0x3e,0xf0,0xca,0x56,0xed,0x7c,0x89,0xb1,0x1a,0x9b,0x16,0xec,0xae,0xd5,0x14, +0xb5,0x9e,0xe4,0x45,0xdc,0x06,0x65,0x6b,0xa5,0x0d,0x41,0xa6,0x73,0x16,0x99,0xc9,0x59,0x04,0x16,0xce, +0x22,0x13,0x97,0x09,0x94,0xb5,0x87,0x5a,0xf8,0x57,0xbc,0xda,0x7c,0x35,0xaf,0x50,0xce,0x31,0x5b,0xc2, +0xe3,0x5a,0x27,0xb1,0xd2,0x9c,0x50,0x3e,0x81,0x29,0x58,0x5e,0xff,0xcd,0x6b,0x33,0xfc,0x10,0x5e,0x8f, +0xf4,0xfb,0x94,0x5c,0x57,0xa1,0x8c,0xb5,0x8a,0xe1,0x39,0x55,0x18,0xd6,0xe4,0xcf,0x68,0x36,0x0b,0x28, +0x1e,0xa6,0x4d,0x73,0x4e,0xc5,0xf0,0x13,0x96,0x00,0x7e,0x67,0xcb,0x5d,0x89,0xbb,0x69,0xce,0xcb,0xbe, +0x10,0xa3,0xb1,0x74,0x84,0xf8,0x01,0x56,0xf7,0x7b,0x7a,0xdb,0x55,0xbe,0x10,0xe7,0x22,0x58,0x67,0xbf, +0x11,0x8c,0xb2,0x64,0xb6,0xc8,0xc3,0x41,0x03,0x45,0x4a,0xc0,0x84,0x36,0x48,0x2c,0x84,0x3f,0x84,0x29, +0x07,0xfe,0x64,0xf3,0x0d,0xf8,0xa5,0xb9,0x4d,0xe4,0xe9,0xaa,0x5c,0x8c,0xc0,0xc0,0x0a,0xff,0x22,0xcf, +0xbe,0xbe,0x19,0x37,0xab,0x6d,0xd7,0x3e,0x9c,0x0d,0x54,0x14,0xa6,0xb4,0xe4,0x8d,0x8f,0x05,0x1d,0x62, +0x8b,0x3f,0x08,0x67,0x01,0x2e,0xb9,0xa2,0xca,0x67,0x8b,0xc9,0xcb,0xe8,0x8b,0x71,0x97,0x45,0xa4,0xc2, +0xbf,0x16,0xa9,0xd0,0xeb,0xb2,0xfb,0xfe,0xf3,0x9c,0x1e,0x3d,0x43,0x94,0xf9,0xc0,0x4d,0x75,0x59,0x64, +0xa9,0x2e,0xdd,0x99,0x6f,0x60,0xe6,0x35,0x39,0xa4,0x97,0x16,0x55,0x6f,0x13,0x67,0xcf,0xee,0xff,0x03, +0xed,0x84,0x12,0x75,0x78,0x8c,0x03,0x00 +}; \ No newline at end of file diff --git a/espurna/static/server.cer.h b/espurna/static/server.cer.h new file mode 100755 index 0000000..a30286a --- /dev/null +++ b/espurna/static/server.cer.h @@ -0,0 +1,33 @@ +#define server_cer_len 587 +const uint8_t server_cer[] PROGMEM = { +0x30,0x82,0x02,0x47,0x30,0x82,0x01,0x2f,0x02,0x09,0x00,0x93,0x02,0xa1,0xb1,0x98,0xba,0x76,0x43,0x30, +0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7,0x0d,0x01,0x01,0x05,0x05,0x00,0x30,0x1c,0x31,0x1a,0x30,0x18, +0x06,0x03,0x55,0x04,0x0a,0x0c,0x11,0x45,0x73,0x70,0x72,0x65,0x73,0x73,0x69,0x66,0x20,0x53,0x79,0x73, +0x74,0x65,0x6d,0x73,0x30,0x1e,0x17,0x0d,0x31,0x36,0x30,0x38,0x32,0x34,0x32,0x31,0x34,0x37,0x33,0x38, +0x5a,0x17,0x0d,0x33,0x30,0x30,0x35,0x30,0x33,0x32,0x31,0x34,0x37,0x33,0x38,0x5a,0x30,0x33,0x31,0x19, +0x30,0x17,0x06,0x03,0x55,0x04,0x0a,0x0c,0x10,0x61,0x78,0x54,0x4c,0x53,0x20,0x6f,0x6e,0x20,0x45,0x53, +0x50,0x38,0x32,0x36,0x36,0x31,0x16,0x30,0x14,0x06,0x03,0x55,0x04,0x03,0x0c,0x0d,0x65,0x73,0x70,0x38, +0x32,0x36,0x36,0x2e,0x6c,0x6f,0x63,0x61,0x6c,0x30,0x81,0x9f,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86, +0xf7,0x0d,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8d,0x00,0x30,0x81,0x89,0x02,0x81,0x81,0x00,0xb4,0x8e, +0x39,0xec,0xc3,0x1e,0x89,0xa6,0xce,0xa6,0x40,0xc8,0x5e,0x67,0x77,0xf6,0x6e,0xbf,0x69,0x4f,0xfc,0x76, +0xab,0xbb,0x76,0xc0,0x9e,0x8b,0x05,0x6f,0x38,0x30,0xe0,0xca,0xd9,0x13,0x14,0xf0,0xe4,0x53,0xd2,0xa5, +0x33,0x53,0xb9,0x69,0xc9,0x71,0x55,0x53,0xb9,0xb4,0x3f,0x09,0x2f,0x8c,0x07,0xdf,0xd3,0x18,0xfd,0xbe, +0x5e,0x4d,0xcf,0xc8,0x42,0x35,0x8f,0x2b,0x89,0x3e,0xec,0x1f,0xb7,0x81,0x09,0xba,0x79,0x60,0x6a,0xb0, +0xc4,0x65,0xdb,0x45,0x60,0xe8,0xfe,0xeb,0x65,0x04,0xeb,0x57,0x14,0x76,0x91,0x65,0x43,0x2f,0x55,0xdb, +0xd4,0x50,0x62,0xbc,0x79,0x1d,0x21,0x89,0xa2,0xc3,0xae,0xc7,0x89,0x7b,0x01,0xc2,0xc5,0x8f,0xf6,0xcc, +0xd7,0xe4,0x68,0x5a,0x37,0x21,0x02,0x03,0x01,0x00,0x01,0x30,0x0d,0x06,0x09,0x2a,0x86,0x48,0x86,0xf7, +0x0d,0x01,0x01,0x05,0x05,0x00,0x03,0x82,0x01,0x01,0x00,0x02,0x7e,0x98,0x36,0x19,0x6a,0xea,0xdf,0xf2, +0xfa,0x3a,0x5f,0x7d,0x82,0x2b,0xb5,0x6c,0x01,0xd9,0x38,0x2b,0x7e,0xb6,0xc8,0x6a,0x9d,0x03,0x9e,0x13, +0x3d,0x3f,0xd4,0x84,0x3f,0x20,0x1b,0xd0,0x3f,0x4f,0xf5,0x33,0x19,0xfe,0x5f,0xfa,0x87,0x67,0x13,0x7e, +0x63,0xca,0xbb,0x18,0x73,0x14,0x4a,0x9b,0x8a,0x17,0x29,0x7e,0x70,0xcd,0x2e,0xd5,0xbc,0xd1,0xc6,0xd9, +0x02,0xa6,0xd0,0x7b,0x97,0x0a,0x04,0xc8,0x07,0x4f,0x8c,0x2e,0x91,0x12,0xfe,0x62,0xab,0xf9,0x45,0x2d, +0xc3,0x7b,0x77,0x84,0x41,0xfa,0x88,0x2d,0xc1,0xd9,0x8b,0xc2,0x9b,0x00,0x14,0xe8,0xa6,0xcc,0x2d,0xc1, +0x06,0xad,0x02,0x11,0x7b,0xcd,0xe0,0x94,0xe9,0x9c,0xa7,0x87,0xf7,0xac,0x60,0x24,0x4e,0xe2,0xe9,0xc8, +0x6e,0x6a,0x4c,0xf9,0xed,0x86,0x2c,0x71,0xd2,0xa3,0xec,0xd7,0xdc,0xdb,0x9b,0x61,0x5d,0x7a,0x88,0x03, +0x73,0xfb,0xf3,0x4f,0xfa,0x07,0xea,0xe4,0x62,0x98,0x90,0x89,0xed,0x7d,0x61,0x45,0xd9,0xd2,0xc8,0x5f, +0x01,0x6c,0xa5,0xf2,0xaa,0xdb,0xaf,0xa5,0xf3,0x04,0x45,0x6d,0xe4,0xdf,0xfa,0x46,0x6f,0xdc,0xf4,0xc6, +0xfe,0x7f,0x3a,0x9b,0x06,0xa9,0xbf,0x30,0xdf,0x04,0xb6,0xfe,0x0b,0x4a,0x14,0x61,0xc1,0x80,0x68,0x06, +0x5b,0x5a,0xe4,0x4f,0x01,0xb1,0x73,0xa1,0x55,0x9b,0xde,0x44,0x4b,0x4d,0xf7,0x0e,0xdd,0x8c,0x88,0x67, +0x71,0x68,0x6f,0x78,0x07,0x13,0x04,0x45,0xe3,0xc3,0x80,0x34,0x30,0x11,0x95,0x74,0x1f,0xfe,0x60,0xc0, +0x28,0x9b,0x9a,0x43,0xbd,0xc5,0xa8 +}; \ No newline at end of file diff --git a/espurna/static/server.key.h b/espurna/static/server.key.h new file mode 100755 index 0000000..0d55551 --- /dev/null +++ b/espurna/static/server.key.h @@ -0,0 +1,34 @@ +#define server_key_len 611 +const uint8_t server_key[] PROGMEM = { +0x30,0x82,0x02,0x5f,0x02,0x01,0x00,0x02,0x81,0x81,0x00,0xb4,0x8e,0x39,0xec,0xc3,0x1e,0x89,0xa6,0xce, +0xa6,0x40,0xc8,0x5e,0x67,0x77,0xf6,0x6e,0xbf,0x69,0x4f,0xfc,0x76,0xab,0xbb,0x76,0xc0,0x9e,0x8b,0x05, +0x6f,0x38,0x30,0xe0,0xca,0xd9,0x13,0x14,0xf0,0xe4,0x53,0xd2,0xa5,0x33,0x53,0xb9,0x69,0xc9,0x71,0x55, +0x53,0xb9,0xb4,0x3f,0x09,0x2f,0x8c,0x07,0xdf,0xd3,0x18,0xfd,0xbe,0x5e,0x4d,0xcf,0xc8,0x42,0x35,0x8f, +0x2b,0x89,0x3e,0xec,0x1f,0xb7,0x81,0x09,0xba,0x79,0x60,0x6a,0xb0,0xc4,0x65,0xdb,0x45,0x60,0xe8,0xfe, +0xeb,0x65,0x04,0xeb,0x57,0x14,0x76,0x91,0x65,0x43,0x2f,0x55,0xdb,0xd4,0x50,0x62,0xbc,0x79,0x1d,0x21, +0x89,0xa2,0xc3,0xae,0xc7,0x89,0x7b,0x01,0xc2,0xc5,0x8f,0xf6,0xcc,0xd7,0xe4,0x68,0x5a,0x37,0x21,0x02, +0x03,0x01,0x00,0x01,0x02,0x81,0x81,0x00,0x8d,0x0c,0x72,0x89,0xd8,0x1f,0xba,0x0e,0xfa,0x6e,0x7c,0x3b, +0x4b,0x2c,0x6f,0x55,0xaf,0x4c,0x8e,0xbb,0xb2,0x91,0x0e,0x35,0x63,0x5e,0xb4,0x3c,0x0c,0x61,0xc7,0x36, +0xbf,0xd5,0x17,0x61,0x45,0xc1,0xad,0xcd,0x21,0xc8,0x76,0x61,0x58,0x7e,0x20,0xa5,0x0d,0xb0,0x5b,0x69, +0x48,0xb9,0x27,0x50,0xb0,0x32,0x15,0x19,0xf7,0xd7,0xc8,0x98,0x96,0x55,0x51,0x85,0xd6,0x6a,0xe6,0xdb, +0x9e,0x44,0xa2,0x55,0x5f,0xeb,0x0a,0xc2,0x99,0xad,0x25,0x46,0xa9,0x38,0xb6,0x7e,0xaa,0xcc,0x5f,0x02, +0x73,0x93,0xc5,0x2d,0xe6,0x62,0x8c,0x12,0x20,0x28,0xf3,0xe3,0x4c,0xbc,0xc7,0x13,0x8c,0xac,0x78,0x70, +0x34,0xeb,0x63,0xea,0x13,0x02,0xb6,0x69,0xdc,0xef,0x0a,0x4d,0xcd,0xde,0x29,0x01,0x02,0x41,0x00,0xe4, +0x03,0xff,0xe9,0xe8,0xd6,0x75,0x3f,0x3c,0x88,0x86,0x12,0xc6,0xe5,0xa6,0x7c,0x41,0xb2,0x64,0xe5,0x0c, +0x6e,0x83,0xf2,0x2a,0x3a,0xfd,0x9a,0x47,0x93,0x69,0xe5,0x11,0xa0,0x37,0x03,0x74,0x9d,0x28,0x65,0x62, +0xb9,0xe2,0x3d,0x6d,0xb2,0x46,0xc3,0x02,0x93,0xcc,0x1a,0x3c,0x20,0xd5,0x93,0xc6,0xb3,0x1d,0x70,0x0e, +0xbc,0x78,0xf1,0x02,0x41,0x00,0xca,0xb7,0x15,0x7b,0x6a,0xf7,0x14,0xcb,0xd5,0x0d,0x31,0x26,0x90,0xcb, +0x38,0x47,0x6b,0x34,0x2b,0xe4,0x1f,0x0d,0x59,0x06,0x49,0x06,0x94,0x79,0x9f,0x65,0xe0,0x83,0x5e,0xd5, +0xe6,0xb8,0x89,0x75,0x93,0x14,0x8b,0x5a,0x57,0x26,0x02,0x04,0x06,0xc4,0xed,0x68,0x6f,0x05,0x66,0xeb, +0x07,0x94,0x20,0x03,0xd9,0xd2,0x75,0xb0,0x21,0x31,0x02,0x41,0x00,0xbb,0xc1,0x23,0x60,0xab,0xee,0xb3, +0xfb,0x0f,0x50,0x67,0xfe,0x5c,0x33,0x4c,0x44,0xf3,0x1f,0xff,0x7e,0xb0,0x1e,0xec,0x9d,0x62,0xf2,0xd2, +0x52,0xd0,0xef,0x6c,0xfa,0x47,0xea,0x42,0x82,0xa0,0xea,0xac,0x11,0xd8,0x1a,0xb2,0x55,0xdc,0xd7,0x38, +0xf7,0x69,0x4e,0xe4,0x79,0x11,0xdc,0x03,0xa2,0x3c,0xb2,0xce,0xe1,0xaf,0xf0,0xb8,0x31,0x02,0x41,0x00, +0xac,0xb8,0x92,0x75,0x36,0x0e,0x90,0x89,0x9c,0x5e,0x41,0x22,0xba,0xfc,0x6c,0x57,0x6d,0xe0,0x66,0x05, +0x58,0xef,0xe6,0x8a,0x94,0x6e,0x26,0xd3,0xfa,0x1c,0xb3,0xcf,0x3b,0x5e,0xc5,0xd7,0x36,0x48,0x17,0xa9, +0xc9,0x92,0x8f,0xee,0xb2,0x88,0xfb,0xbb,0x8f,0x0d,0x0c,0x8b,0x6d,0xc5,0x94,0x0f,0x81,0xb7,0xc6,0x40, +0xac,0x46,0x06,0x01,0x02,0x41,0x00,0xa3,0x7d,0x8a,0x62,0x0e,0x8d,0xe7,0x00,0xea,0x21,0x86,0x61,0xb9, +0x13,0xc3,0xc2,0xcf,0x96,0x88,0x79,0xff,0xaa,0x94,0x6f,0x5a,0xb8,0xe4,0x70,0x84,0xe8,0x3e,0xe0,0x20, +0xd9,0x15,0xca,0xf5,0x0c,0xf3,0xa1,0xd3,0xca,0xf9,0x9f,0xa9,0x9a,0x8c,0x79,0x84,0x55,0xba,0xca,0x4c, +0xe5,0xb2,0x96,0x5a,0x8c,0x82,0xe7,0xf8,0x4b,0x1d,0x50 +}; \ No newline at end of file diff --git a/espurna/system.ino b/espurna/system.ino new file mode 100755 index 0000000..48cf723 --- /dev/null +++ b/espurna/system.ino @@ -0,0 +1,168 @@ +/* + +SYSTEM MODULE + +Copyright (C) 2018 by Xose Pérez + +*/ + +#include + +// ----------------------------------------------------------------------------- + +unsigned long _loop_delay = 0; +bool _system_send_heartbeat = false; + +// Calculated load average 0 to 100; +unsigned short int _load_average = 100; + +// ----------------------------------------------------------------------------- + +#if SYSTEM_CHECK_ENABLED + +// Call this method on boot with start=true to increase the crash counter +// Call it again once the system is stable to decrease the counter +// If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable +// setting _systemOK = false; +// +// An unstable system will only have serial access, WiFi in AP mode and OTA + +bool _systemStable = true; + +void systemCheck(bool stable) { + unsigned char value = EEPROM.read(EEPROM_CRASH_COUNTER); + if (stable) { + value = 0; + DEBUG_MSG_P(PSTR("[MAIN] System OK\n")); + } else { + if (++value > SYSTEM_CHECK_MAX) { + _systemStable = false; + value = 0; + DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n")); + } + } + EEPROM.write(EEPROM_CRASH_COUNTER, value); + EEPROM.commit(); +} + +bool systemCheck() { + return _systemStable; +} + +void systemCheckLoop() { + static bool checked = false; + if (!checked && (millis() > SYSTEM_CHECK_TIME)) { + // Check system as stable + systemCheck(true); + checked = true; + } +} + +#endif + +// ----------------------------------------------------------------------------- + +void systemSendHeartbeat() { + _system_send_heartbeat = true; +} + +unsigned long systemLoopDelay() { + return _loop_delay; +} + + +unsigned long systemLoadAverage() { + return _load_average; +} + +void systemLoop() { + + // ------------------------------------------------------------------------- + // Check system stability + // ------------------------------------------------------------------------- + + #if SYSTEM_CHECK_ENABLED + systemCheckLoop(); + #endif + + // ------------------------------------------------------------------------- + // Heartbeat + // ------------------------------------------------------------------------- + + #if HEARTBEAT_ENABLED + // Heartbeat + static unsigned long last_hbeat = 0; + if (_system_send_heartbeat || (last_hbeat == 0) || (millis() - last_hbeat > HEARTBEAT_INTERVAL)) { + _system_send_heartbeat = false; + last_hbeat = millis(); + heartbeat(); + } + #endif // HEARTBEAT_ENABLED + + // ------------------------------------------------------------------------- + // Load Average calculation + // ------------------------------------------------------------------------- + + static unsigned long last_loadcheck = 0; + static unsigned long load_counter_temp = 0; + load_counter_temp++; + + if (millis() - last_loadcheck > LOADAVG_INTERVAL) { + + static unsigned long load_counter = 0; + static unsigned long load_counter_max = 1; + + load_counter = load_counter_temp; + load_counter_temp = 0; + if (load_counter > load_counter_max) { + load_counter_max = load_counter; + } + _load_average = 100 - (100 * load_counter / load_counter_max); + last_loadcheck = millis(); + + } + + // ------------------------------------------------------------------------- + // Power saving delay + // ------------------------------------------------------------------------- + + delay(_loop_delay); + +} + +void systemSetup() { + + EEPROM.begin(EEPROM_SIZE); + + #if DEBUG_SERIAL_SUPPORT + DEBUG_PORT.begin(SERIAL_BAUDRATE); + #if DEBUG_ESP_WIFI + DEBUG_PORT.setDebugOutput(true); + #endif + #elif defined(SERIAL_BAUDRATE) + Serial.begin(SERIAL_BAUDRATE); + #endif + + #if SPIFFS_SUPPORT + SPIFFS.begin(); + #endif + + // Question system stability + #if SYSTEM_CHECK_ENABLED + systemCheck(false); + #endif + + #if defined(ESPLIVE) + //The ESPLive has an ADC MUX which needs to be configured. + pinMode(16, OUTPUT); + digitalWrite(16, HIGH); //Defualt CT input (pin B, solder jumper B) + #endif + + // Cache loop delay value to speed things (recommended max 250ms) + _loop_delay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str()); + _loop_delay = constrain(_loop_delay, 0, 300); + + // Register Loop + espurnaRegisterLoop(systemLoop); + +} diff --git a/espurna/telnet.ino b/espurna/telnet.ino new file mode 100755 index 0000000..fc3912c --- /dev/null +++ b/espurna/telnet.ino @@ -0,0 +1,187 @@ +/* + +TELNET MODULE + +Copyright (C) 2017-2018 by Xose Pérez +Parts of the code have been borrowed from Thomas Sarlandie's NetServer +(https://github.com/sarfata/kbox-firmware/tree/master/src/esp) + +*/ + +#if TELNET_SUPPORT + +#include + +AsyncServer * _telnetServer; +AsyncClient * _telnetClients[TELNET_MAX_CLIENTS]; +bool _telnetFirst = true; + +// ----------------------------------------------------------------------------- +// Private methods +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _telnetWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "telnet", 6) == 0); +} + +void _telnetWebSocketOnSend(JsonObject& root) { + root["telnetVisible"] = 1; + root["telnetSTA"] = getSetting("telnetSTA", TELNET_STA).toInt() == 1; +} + +#endif + +void _telnetDisconnect(unsigned char clientId) { + _telnetClients[clientId]->free(); + _telnetClients[clientId] = NULL; + delete _telnetClients[clientId]; + wifiReconnectCheck(); + DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId); +} + +bool _telnetWrite(unsigned char clientId, void *data, size_t len) { + if (_telnetClients[clientId] && _telnetClients[clientId]->connected()) { + return (_telnetClients[clientId]->write((const char*) data, len) > 0); + } + return false; +} + +unsigned char _telnetWrite(void *data, size_t len) { + unsigned char count = 0; + for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { + if (_telnetWrite(i, data, len)) ++count; + } + return count; +} + +void _telnetData(unsigned char clientId, void *data, size_t len) { + + // Skip first message since it's always garbage + if (_telnetFirst) { + _telnetFirst = false; + return; + } + + // Capture close connection + char * p = (char *) data; + if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) { + _telnetClients[clientId]->close(); + return; + } + + // Inject into Embedis stream + settingsInject(data, len); + +} + +void _telnetNewClient(AsyncClient *client) { + + if (client->localIP() != WiFi.softAPIP()) { + + // Telnet is always available for the ESPurna Core image + #ifdef ESPURNA_CORE + bool telnetSTA = true; + #else + bool telnetSTA = getSetting("telnetSTA", TELNET_STA).toInt() == 1; + #endif + + if (!telnetSTA) { + DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n")); + client->onDisconnect([](void *s, AsyncClient *c) { + c->free(); + delete c; + }); + client->close(true); + return; + } + + } + + for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { + if (!_telnetClients[i] || !_telnetClients[i]->connected()) { + + _telnetClients[i] = client; + + client->onAck([i](void *s, AsyncClient *c, size_t len, uint32_t time) { + }, 0); + + client->onData([i](void *s, AsyncClient *c, void *data, size_t len) { + _telnetData(i, data, len); + }, 0); + + client->onDisconnect([i](void *s, AsyncClient *c) { + _telnetDisconnect(i); + }, 0); + + client->onError([i](void *s, AsyncClient *c, int8_t error) { + DEBUG_MSG_P(PSTR("[TELNET] Error %s (%d) on client #%u\n"), c->errorToString(error), error, i); + }, 0); + + client->onTimeout([i](void *s, AsyncClient *c, uint32_t time) { + DEBUG_MSG_P(PSTR("[TELNET] Timeout on client #%u at %lu\n"), i, time); + c->close(); + }, 0); + + DEBUG_MSG_P(PSTR("[TELNET] Client #%u connected\n"), i); + + // If there is no terminal support automatically dump info and crash data + #if TERMINAL_SUPPORT == 0 + info(); + wifiStatus(); + debugDumpCrashInfo(); + debugClearCrashInfo(); + #endif + + _telnetFirst = true; + wifiReconnectCheck(); + return; + + } + + } + + DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Too many connections\n")); + client->onDisconnect([](void *s, AsyncClient *c) { + c->free(); + delete c; + }); + client->close(true); + +} + +// ----------------------------------------------------------------------------- +// Public API +// ----------------------------------------------------------------------------- + +bool telnetConnected() { + for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) { + if (_telnetClients[i] && _telnetClients[i]->connected()) return true; + } + return false; +} + +unsigned char telnetWrite(unsigned char ch) { + char data[1] = {ch}; + return _telnetWrite(data, 1); +} + +void telnetSetup() { + + _telnetServer = new AsyncServer(TELNET_PORT); + _telnetServer->onClient([](void *s, AsyncClient* c) { + _telnetNewClient(c); + }, 0); + _telnetServer->begin(); + + #if WEB_SUPPORT + wsOnSendRegister(_telnetWebSocketOnSend); + wsOnReceiveRegister(_telnetWebSocketOnReceive); + #endif + + DEBUG_MSG_P(PSTR("[TELNET] Listening on port %d\n"), TELNET_PORT); + +} + +#endif // TELNET_SUPPORT diff --git a/espurna/thinkspeak.ino b/espurna/thinkspeak.ino new file mode 100755 index 0000000..35ad9a2 --- /dev/null +++ b/espurna/thinkspeak.ino @@ -0,0 +1,281 @@ +/* + +THINGSPEAK MODULE + +Copyright (C) 2018 by Xose Pérez + +*/ + +#if THINGSPEAK_SUPPORT + +#if THINGSPEAK_USE_ASYNC +#include +AsyncClient * _tspk_client; +#else +#include +#endif + +const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM = + "POST %s HTTP/1.1\r\n" + "Host: %s\r\n" + "User-Agent: ESPurna\r\n" + "Connection: close\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %d\r\n\r\n" + "%s\r\n"; + +bool _tspk_enabled = false; +char * _tspk_queue[8] = {NULL}; + +bool _tspk_flush = false; +unsigned long _tspk_last_flush = 0; + +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _tspkWebSocketOnReceive(const char * key, JsonVariant& value) { + return (strncmp(key, "tspk", 4) == 0); +} + +void _tspkWebSocketOnSend(JsonObject& root) { + + unsigned char visible = 0; + + root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1; + root["tspkKey"] = getSetting("tspkKey"); + + JsonArray& relays = root.createNestedArray("tspkRelays"); + for (byte i=0; i 0) visible = 1; + + #if SENSOR_SUPPORT + JsonArray& list = root.createNestedArray("tspkMagnitudes"); + for (byte i=0; i 0) visible = 1; + #endif + + root["tspkVisible"] = visible; + +} + +#endif + +void _tspkConfigure() { + _tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1; + if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) { + _tspk_enabled = false; + setSetting("tspkEnabled", 0); + } +} + +#if THINGSPEAK_USE_ASYNC + +void _tspkPost(String data) { + + if (_tspk_client == NULL) { + _tspk_client = new AsyncClient(); + } + + _tspk_client->onDisconnect([](void *s, AsyncClient *c) { + DEBUG_MSG_P(PSTR("[THINGSPEAK] Disconnected\n")); + _tspk_client->free(); + delete _tspk_client; + _tspk_client = NULL; + }, 0); + + _tspk_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) { + _tspk_client->close(true); + }, 0); + + _tspk_client->onData([](void * arg, AsyncClient * c, void * response, size_t len) { + char * b = (char *) response; + b[len] = 0; + char * p = strstr((char *)response, "\r\n\r\n"); + unsigned int code = (p != NULL) ? atoi(&p[4]) : 0; + DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code); + _tspk_client->close(true); + }, NULL); + + _tspk_client->onConnect([data](void * arg, AsyncClient * client) { + + DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%d\n"), THINGSPEAK_HOST, THINGSPEAK_PORT); + + #if THINGSPEAK_USE_SSL + uint8_t fp[20] = {0}; + sslFingerPrintArray(THINGSPEAK_FINGERPRINT, fp); + SSL * ssl = _tspk_client->getSSL(); + if (ssl_match_fingerprint(ssl, fp) != SSL_OK) { + DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n")); + } + #endif + + DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s\n"), data.c_str()); + + char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()]; + snprintf_P(buffer, sizeof(buffer), + THINGSPEAK_REQUEST_TEMPLATE, + THINGSPEAK_URL, + THINGSPEAK_HOST, + data.length(), + data.c_str() + ); + + client->write(buffer); + + }, NULL); + + #if ASYNC_TCP_SSL_ENABLED + bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT, THINGSPEAK_USE_SSL); + #else + bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT); + #endif + + if (!connected) { + DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n")); + _tspk_client->close(true); + } + +} + +#else // THINGSPEAK_USE_ASYNC + +void _tspkPost(String data) { + + #if THINGSPEAK_USE_SSL + WiFiClientSecure _tspk_client; + #else + WiFiClient _tspk_client; + #endif + + if (_tspk_client.connect(THINGSPEAK_HOST, THINGSPEAK_PORT)) { + + DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%d\n"), THINGSPEAK_HOST, THINGSPEAK_PORT); + + if (!_tspk_client.verify(THINGSPEAK_FINGERPRINT, THINGSPEAK_HOST)) { + DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n")); + } + + DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s\n"), data.c_str()); + char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()]; + snprintf_P(buffer, sizeof(buffer), + THINGSPEAK_REQUEST_TEMPLATE, + THINGSPEAK_URL, + THINGSPEAK_HOST, + data.length(), + data.c_str() + ); + _tspk_client.print(buffer); + + nice_delay(100); + + String response = _tspk_client.readString(); + int pos = response.indexOf("\r\n\r\n"); + unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0; + DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code); + _tspk_client.stop(); + return; + + } + + DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n")); + +} + +#endif // THINGSPEAK_USE_ASYNC + +bool _tspkEnqueue(unsigned char index, char * payload) { + DEBUG_MSG_P(PSTR("[THINGSPEAK] Enqueuing field #%d with value %s\n"), index, payload); + --index; + if (_tspk_queue[index] != NULL) free(_tspk_queue[index]); + _tspk_queue[index] = strdup(payload); +} + +void _tspkFlush() { + + String data; + + // Walk the fields + for (unsigned char id=0; id<8; id++) { + if (_tspk_queue[id] != NULL) { + if (data.length() > 0) data = data + String("&"); + data = data + String("field") + String(id+1) + String("=") + String(_tspk_queue[id]); + free(_tspk_queue[id]); + _tspk_queue[id] = NULL; + } + } + + // POST data if any + if (data.length() > 0) { + data = data + String("&api_key=") + getSetting("tspkKey"); + _tspkPost(data); + _tspk_last_flush = millis(); + } + +} +// ----------------------------------------------------------------------------- + +bool tspkEnqueueRelay(unsigned char index, unsigned char status) { + if (!_tspk_enabled) return true; + unsigned char id = getSetting("tspkRelay", index, 0).toInt(); + if (id > 0) { + char payload[3] = {0}; + itoa(status ? 1 : 0, payload, 10); + _tspkEnqueue(id, payload); + } +} + +bool tspkEnqueueMeasurement(unsigned char index, char * payload) { + if (!_tspk_enabled) return true; + unsigned char id = getSetting("tspkMagnitude", index, 0).toInt(); + if (id > 0) { + _tspkEnqueue(id, payload); + } +} + +void tspkFlush() { + _tspk_flush = true; +} + +bool tspkEnabled() { + return _tspk_enabled; +} + +void tspkSetup() { + + _tspkConfigure(); + + #if WEB_SUPPORT + wsOnSendRegister(_tspkWebSocketOnSend); + wsOnAfterParseRegister(_tspkConfigure); + wsOnReceiveRegister(_tspkWebSocketOnReceive); + #endif + + DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"), + THINGSPEAK_USE_ASYNC ? "ENABLED" : "DISABLED", + THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED" + ); + + // Register loop + espurnaRegisterLoop(tspkLoop); + +} + +void tspkLoop() { + if (!_tspk_enabled) return; + if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return; + if (_tspk_flush && (millis() - _tspk_last_flush > THINGSPEAK_MIN_INTERVAL)) { + _tspkFlush(); + _tspk_flush = false; + } +} + +#endif diff --git a/espurna/uartmqtt.ino b/espurna/uartmqtt.ino new file mode 100755 index 0000000..bc96878 --- /dev/null +++ b/espurna/uartmqtt.ino @@ -0,0 +1,103 @@ +/* + +UART_MQTT MODULE + +Copyright (C) 2018 by Albert Weterings +Adapted by Xose Pérez + +*/ + +#if UART_MQTT_SUPPORT + +char _uartmqttBuffer[UART_MQTT_BUFFER_SIZE]; +bool _uartmqttNewData = false; + +#if UART_MQTT_USE_SOFT + #include + SoftwareSerial _uart_mqtt_serial(UART_MQTT_RX_PIN, UART_MQTT_TX_PIN, false, UART_MQTT_BUFFER_SIZE); + #define UART_MQTT_PORT _uart_mqtt_serial +#else + #define UART_MQTT_PORT UART_MQTT_HW_PORT +#endif + +// ----------------------------------------------------------------------------- +// Private +// ----------------------------------------------------------------------------- + +void _uartmqttReceiveUART() { + + static unsigned char ndx = 0; + + while (UART_MQTT_PORT.available() > 0 && _uartmqttNewData == false) { + + char rc = UART_MQTT_PORT.read(); + + if (rc != '\n') { + + _uartmqttBuffer[ndx] = rc; + if (ndx < UART_MQTT_BUFFER_SIZE - 1) ndx++; + + } else { + _uartmqttBuffer[ndx] = '\0'; + _uartmqttNewData = true; + ndx = 0; + } + + } + +} + +void _uartmqttSendMQTT() { + if (_uartmqttNewData == true && MQTT_SUPPORT) { + DEBUG_MSG_P(PSTR("[UART_MQTT] Send data over MQTT: %s\n"), _uartmqttBuffer); + mqttSend(MQTT_TOPIC_UARTIN, _uartmqttBuffer); + _uartmqttNewData = false; + } +} + +void _uartmqttSendUART(const char * message) { + DEBUG_MSG_P(PSTR("[UART_MQTT] Send data over UART: %s\n"), message); + UART_MQTT_PORT.println(message); +} + +void _uartmqttMQTTCallback(unsigned int type, const char * topic, const char * payload) { + + if (type == MQTT_CONNECT_EVENT) { + mqttSubscribe(MQTT_TOPIC_UARTOUT); + } + + if (type == MQTT_MESSAGE_EVENT) { + + // Match topic + String t = mqttMagnitude((char *) topic); + if (t.equals(MQTT_TOPIC_UARTOUT)) { + _uartmqttSendUART(payload); + } + + } + +} + +// ----------------------------------------------------------------------------- +// SETUP & LOOP +// ----------------------------------------------------------------------------- + +void _uartmqttLoop() { + _uartmqttReceiveUART(); + _uartmqttSendMQTT(); +} + +void uartmqttSetup() { + + // Init port + UART_MQTT_PORT.begin(UART_MQTT_BAUDRATE); + + // Register MQTT callbackj + mqttRegister(_uartmqttMQTTCallback); + + // Register loop + espurnaRegisterLoop(_uartmqttLoop); + +} + +#endif // UART_MQTT_SUPPORT diff --git a/espurna/utils.ino b/espurna/utils.ino new file mode 100755 index 0000000..439a0e7 --- /dev/null +++ b/espurna/utils.ino @@ -0,0 +1,527 @@ +/* + +UTILS MODULE + +Copyright (C) 2017-2018 by Xose Pérez + +*/ + +#include +Ticker _defer_reset; + +String getIdentifier() { + char buffer[20]; + snprintf_P(buffer, sizeof(buffer), PSTR("%s_%06X"), APP_NAME, ESP.getChipId()); + return String(buffer); +} + +void setDefaultHostname() { + if (strlen(HOSTNAME) > 0) { + setSetting("hostname", HOSTNAME); + } else { + setSetting("hostname", getIdentifier()); + } +} + +void setBoardName() { + #ifndef ESPURNA_CORE + setSetting("boardName", DEVICE_NAME); + #endif +} + +String getBoardName() { + return getSetting("boardName", DEVICE_NAME); +} + +String getCoreVersion() { + String version = ESP.getCoreVersion(); + #ifdef ARDUINO_ESP8266_RELEASE + if (version.equals("00000000")) { + version = String(ARDUINO_ESP8266_RELEASE); + } + #endif + version.replace("_", "."); + return version; +} + +String getCoreRevision() { + #ifdef ARDUINO_ESP8266_GIT_VER + return String(ARDUINO_ESP8266_GIT_VER); + #else + return String(""); + #endif +} + +// WTF +// Calling ESP.getFreeHeap() is making the system crash on a specific +// AiLight bulb, but anywhere else... +unsigned int getFreeHeap() { + if (getSetting("wtfHeap", 0).toInt() == 1) return 9999; + return ESP.getFreeHeap(); +} + +String buildTime() { + + const char time_now[] = __TIME__; // hh:mm:ss + unsigned int hour = atoi(&time_now[0]); + unsigned int minute = atoi(&time_now[3]); + unsigned int second = atoi(&time_now[6]); + + const char date_now[] = __DATE__; // Mmm dd yyyy + const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + unsigned int month = 0; + for ( int i = 0; i < 12; i++ ) { + if (strncmp(date_now, months[i], 3) == 0 ) { + month = i + 1; + break; + } + } + unsigned int day = atoi(&date_now[3]); + unsigned int year = atoi(&date_now[7]); + + char buffer[20]; + snprintf_P( + buffer, sizeof(buffer), PSTR("%04d-%02d-%02d %02d:%02d:%02d"), + year, month, day, hour, minute, second + ); + + return String(buffer); + +} + + +unsigned long getUptime() { + + static unsigned long last_uptime = 0; + static unsigned char uptime_overflows = 0; + + if (millis() < last_uptime) ++uptime_overflows; + last_uptime = millis(); + unsigned long uptime_seconds = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000); + + return uptime_seconds; + +} + +#if HEARTBEAT_ENABLED + +void heartbeat() { + + unsigned long uptime_seconds = getUptime(); + unsigned int free_heap = getFreeHeap(); + + #if MQTT_SUPPORT + bool serial = !mqttConnected(); + #else + bool serial = true; + #endif + + // ------------------------------------------------------------------------- + // Serial + // ------------------------------------------------------------------------- + + if (serial) { + DEBUG_MSG_P(PSTR("[MAIN] Uptime: %lu seconds\n"), uptime_seconds); + DEBUG_MSG_P(PSTR("[MAIN] Free heap: %lu bytes\n"), free_heap); + #if ADC_VCC_ENABLED + DEBUG_MSG_P(PSTR("[MAIN] Power: %lu mV\n"), ESP.getVcc()); + #endif + #if NTP_SUPPORT + if (ntpSynced()) DEBUG_MSG_P(PSTR("[MAIN] Time: %s\n"), (char *) ntpDateTime().c_str()); + #endif + } + + // ------------------------------------------------------------------------- + // MQTT + // ------------------------------------------------------------------------- + + #if MQTT_SUPPORT + if (!serial) { + #if (HEARTBEAT_REPORT_INTERVAL) + mqttSend(MQTT_TOPIC_INTERVAL, HEARTBEAT_INTERVAL / 1000); + #endif + #if (HEARTBEAT_REPORT_APP) + mqttSend(MQTT_TOPIC_APP, APP_NAME); + #endif + #if (HEARTBEAT_REPORT_VERSION) + mqttSend(MQTT_TOPIC_VERSION, APP_VERSION); + #endif + #if (HEARTBEAT_REPORT_BOARD) + mqttSend(MQTT_TOPIC_BOARD, getBoardName().c_str()); + #endif + #if (HEARTBEAT_REPORT_HOSTNAME) + mqttSend(MQTT_TOPIC_HOSTNAME, getSetting("hostname").c_str()); + #endif + #if (HEARTBEAT_REPORT_IP) + mqttSend(MQTT_TOPIC_IP, getIP().c_str()); + #endif + #if (HEARTBEAT_REPORT_MAC) + mqttSend(MQTT_TOPIC_MAC, WiFi.macAddress().c_str()); + #endif + #if (HEARTBEAT_REPORT_RSSI) + mqttSend(MQTT_TOPIC_RSSI, String(WiFi.RSSI()).c_str()); + #endif + #if (HEARTBEAT_REPORT_UPTIME) + mqttSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str()); + #endif + #if (HEARTBEAT_REPORT_DATETIME) && (NTP_SUPPORT) + if (ntpSynced()) mqttSend(MQTT_TOPIC_DATETIME, ntpDateTime().c_str()); + #endif + #if (HEARTBEAT_REPORT_FREEHEAP) + mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str()); + #endif + #if (HEARTBEAT_REPORT_RELAY) + relayMQTT(); + #endif + #if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) & (HEARTBEAT_REPORT_LIGHT) + lightMQTT(); + #endif + #if (HEARTBEAT_REPORT_VCC) + #if ADC_VCC_ENABLED + mqttSend(MQTT_TOPIC_VCC, String(ESP.getVcc()).c_str()); + #endif + #endif + #if (HEARTBEAT_REPORT_STATUS) + mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); + #endif + #if (LOADAVG_REPORT) + mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str()); + #endif + } + #endif + + // ------------------------------------------------------------------------- + // InfluxDB + // ------------------------------------------------------------------------- + + #if INFLUXDB_SUPPORT + #if (HEARTBEAT_REPORT_UPTIME) + idbSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str()); + #endif + #if (HEARTBEAT_REPORT_FREEHEAP) + idbSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str()); + #endif + #endif + +} + +#endif /// HEARTBEAT_ENABLED + +unsigned int sectors(size_t size) { + return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE; +} + +void info() { + + DEBUG_MSG_P(PSTR("\n\n")); + DEBUG_MSG_P(PSTR("[INIT] %s %s\n"), (char *) APP_NAME, (char *) APP_VERSION); + DEBUG_MSG_P(PSTR("[INIT] %s\n"), (char *) APP_AUTHOR); + DEBUG_MSG_P(PSTR("[INIT] %s\n\n"), (char *) APP_WEBSITE); + DEBUG_MSG_P(PSTR("[INIT] CPU chip ID: 0x%06X\n"), ESP.getChipId()); + DEBUG_MSG_P(PSTR("[INIT] CPU frequency: %u MHz\n"), ESP.getCpuFreqMHz()); + DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion()); + DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), getCoreVersion().c_str()); + DEBUG_MSG_P(PSTR("[INIT] Core revision: %s\n"), getCoreRevision().c_str()); + DEBUG_MSG_P(PSTR("\n")); + + // ------------------------------------------------------------------------- + + FlashMode_t mode = ESP.getFlashChipMode(); + DEBUG_MSG_P(PSTR("[INIT] Flash chip ID: 0x%06X\n"), ESP.getFlashChipId()); + DEBUG_MSG_P(PSTR("[INIT] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed()); + DEBUG_MSG_P(PSTR("[INIT] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN"); + DEBUG_MSG_P(PSTR("\n")); + DEBUG_MSG_P(PSTR("[INIT] Flash sector size: %8u bytes\n"), SPI_FLASH_SEC_SIZE); + DEBUG_MSG_P(PSTR("[INIT] Flash size (CHIP): %8u bytes\n"), ESP.getFlashChipRealSize()); + DEBUG_MSG_P(PSTR("[INIT] Flash size (SDK): %8u bytes / %4d sectors\n"), ESP.getFlashChipSize(), sectors(ESP.getFlashChipSize())); + DEBUG_MSG_P(PSTR("[INIT] Firmware size: %8u bytes / %4d sectors\n"), ESP.getSketchSize(), sectors(ESP.getSketchSize())); + DEBUG_MSG_P(PSTR("[INIT] OTA size: %8u bytes / %4d sectors\n"), ESP.getFreeSketchSpace(), sectors(ESP.getFreeSketchSpace())); + DEBUG_MSG_P(PSTR("[INIT] EEPROM size: %8u bytes / %4d sectors\n"), settingsMaxSize(), sectors(settingsMaxSize())); + DEBUG_MSG_P(PSTR("[INIT] Empty space: %8u bytes / 4 sectors\n"), 4 * SPI_FLASH_SEC_SIZE); + DEBUG_MSG_P(PSTR("\n")); + + // ------------------------------------------------------------------------- + + #if SPIFFS_SUPPORT + FSInfo fs_info; + bool fs = SPIFFS.info(fs_info); + if (fs) { + DEBUG_MSG_P(PSTR("[INIT] SPIFFS total size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes)); + DEBUG_MSG_P(PSTR("[INIT] used size: %8u bytes\n"), fs_info.usedBytes); + DEBUG_MSG_P(PSTR("[INIT] block size: %8u bytes\n"), fs_info.blockSize); + DEBUG_MSG_P(PSTR("[INIT] page size: %8u bytes\n"), fs_info.pageSize); + DEBUG_MSG_P(PSTR("[INIT] max files: %8u\n"), fs_info.maxOpenFiles); + DEBUG_MSG_P(PSTR("[INIT] max length: %8u\n"), fs_info.maxPathLength); + } else { + DEBUG_MSG_P(PSTR("[INIT] No SPIFFS partition\n")); + } + DEBUG_MSG_P(PSTR("\n")); + #endif + + // ------------------------------------------------------------------------- + + DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str()); + DEBUG_MSG_P(PSTR("[INIT] SUPPORT:")); + + #if ALEXA_SUPPORT + DEBUG_MSG_P(PSTR(" ALEXA")); + #endif + #if BROKER_SUPPORT + DEBUG_MSG_P(PSTR(" BROKER")); + #endif + #if DEBUG_SERIAL_SUPPORT + DEBUG_MSG_P(PSTR(" DEBUG_SERIAL")); + #endif + #if DEBUG_TELNET_SUPPORT + DEBUG_MSG_P(PSTR(" DEBUG_TELNET")); + #endif + #if DEBUG_UDP_SUPPORT + DEBUG_MSG_P(PSTR(" DEBUG_UDP")); + #endif + #if DOMOTICZ_SUPPORT + DEBUG_MSG_P(PSTR(" DOMOTICZ")); + #endif + #if HOMEASSISTANT_SUPPORT + DEBUG_MSG_P(PSTR(" HOMEASSISTANT")); + #endif + #if I2C_SUPPORT + DEBUG_MSG_P(PSTR(" I2C")); + #endif + #if INFLUXDB_SUPPORT + DEBUG_MSG_P(PSTR(" INFLUXDB")); + #endif + #if LLMNR_SUPPORT + DEBUG_MSG_P(PSTR(" LLMNR")); + #endif + #if MDNS_SERVER_SUPPORT + DEBUG_MSG_P(PSTR(" MDNS_SERVER")); + #endif + #if MDNS_CLIENT_SUPPORT + DEBUG_MSG_P(PSTR(" MDNS_CLIENT")); + #endif + #if MQTT_SUPPORT + DEBUG_MSG_P(PSTR(" MQTT")); + #endif + #if NETBIOS_SUPPORT + DEBUG_MSG_P(PSTR(" NETBIOS")); + #endif + #if NOFUSS_SUPPORT + DEBUG_MSG_P(PSTR(" NOFUSS")); + #endif + #if NTP_SUPPORT + DEBUG_MSG_P(PSTR(" NTP")); + #endif + #if RF_SUPPORT + DEBUG_MSG_P(PSTR(" RF")); + #endif + #if SCHEDULER_SUPPORT + DEBUG_MSG_P(PSTR(" SCHEDULER")); + #endif + #if SENSOR_SUPPORT + DEBUG_MSG_P(PSTR(" SENSOR")); + #endif + #if SPIFFS_SUPPORT + DEBUG_MSG_P(PSTR(" SPIFFS")); + #endif + #if SSDP_SUPPORT + DEBUG_MSG_P(PSTR(" SSDP")); + #endif + #if TELNET_SUPPORT + DEBUG_MSG_P(PSTR(" TELNET")); + #endif + #if TERMINAL_SUPPORT + DEBUG_MSG_P(PSTR(" TERMINAL")); + #endif + #if THINGSPEAK_SUPPORT + DEBUG_MSG_P(PSTR(" THINGSPEAK")); + #endif + #if UART_MQTT_SUPPORT + DEBUG_MSG_P(PSTR(" UART_MQTT")); + #endif + #if WEB_SUPPORT + DEBUG_MSG_P(PSTR(" WEB")); + #endif + + #if SENSOR_SUPPORT + + DEBUG_MSG_P(PSTR("\n")); + DEBUG_MSG_P(PSTR("[INIT] SENSORS:")); + + #if AM2320_SUPPORT + DEBUG_MSG_P(PSTR(" AM2320_I2C")); + #endif + #if ANALOG_SUPPORT + DEBUG_MSG_P(PSTR(" ANALOG")); + #endif + #if BMX280_SUPPORT + DEBUG_MSG_P(PSTR(" BMX280")); + #endif + #if DALLAS_SUPPORT + DEBUG_MSG_P(PSTR(" DALLAS")); + #endif + #if DHT_SUPPORT + DEBUG_MSG_P(PSTR(" DHTXX")); + #endif + #if DIGITAL_SUPPORT + DEBUG_MSG_P(PSTR(" DIGITAL")); + #endif + #if ECH1560_SUPPORT + DEBUG_MSG_P(PSTR(" ECH1560")); + #endif + #if EMON_ADC121_SUPPORT + DEBUG_MSG_P(PSTR(" EMON_ADC121")); + #endif + #if EMON_ADS1X15_SUPPORT + DEBUG_MSG_P(PSTR(" EMON_ADX1X15")); + #endif + #if EMON_ANALOG_SUPPORT + DEBUG_MSG_P(PSTR(" EMON_ANALOG")); + #endif + #if EVENTS_SUPPORT + DEBUG_MSG_P(PSTR(" EVENTS")); + #endif + #if GUVAS12SD_SUPPORT + DEBUG_MSG_P(PSTR(" GUVAS12SD")); + #endif + #if HLW8012_SUPPORT + DEBUG_MSG_P(PSTR(" HLW8012")); + #endif + #if MHZ19_SUPPORT + DEBUG_MSG_P(PSTR(" MHZ19")); + #endif + #if PMSX003_SUPPORT + DEBUG_MSG_P(PSTR(" PMSX003")); + #endif + #if PZEM004T_SUPPORT + DEBUG_MSG_P(PSTR(" PZEM004T")); + #endif + #if SHT3X_I2C_SUPPORT + DEBUG_MSG_P(PSTR(" SHT3X_I2C")); + #endif + #if SI7021_SUPPORT + DEBUG_MSG_P(PSTR(" SI7021")); + #endif + #if V9261F_SUPPORT + DEBUG_MSG_P(PSTR(" V9261F")); + #endif + + #endif // SENSOR_SUPPORT + + DEBUG_MSG_P(PSTR("\n\n")); + + // ------------------------------------------------------------------------- + + unsigned char reason = resetReason(); + if (reason > 0) { + char buffer[32]; + strcpy_P(buffer, custom_reset_string[reason-1]); + DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer); + } else { + DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str()); + } + + DEBUG_MSG_P(PSTR("[INIT] Settings size: %u bytes\n"), settingsSize()); + DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), getFreeHeap()); + #if ADC_VCC_ENABLED + DEBUG_MSG_P(PSTR("[INIT] Power: %u mV\n"), ESP.getVcc()); + #endif + + DEBUG_MSG_P(PSTR("[INIT] Power saving delay value: %lu ms\n"), systemLoopDelay()); + + #if SYSTEM_CHECK_ENABLED + if (!systemCheck()) DEBUG_MSG_P(PSTR("\n[INIT] Device is in SAFE MODE\n")); + #endif + + DEBUG_MSG_P(PSTR("\n")); + +} + +// ----------------------------------------------------------------------------- +// SSL +// ----------------------------------------------------------------------------- + +#if ASYNC_TCP_SSL_ENABLED + +bool sslCheckFingerPrint(const char * fingerprint) { + return (strlen(fingerprint) == 59); +} + +bool sslFingerPrintArray(const char * fingerprint, unsigned char * bytearray) { + + // check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59) + if (!sslCheckFingerPrint(fingerprint)) return false; + + // walk the fingerprint + for (unsigned int i=0; i<20; i++) { + bytearray[i] = strtol(fingerprint + 3*i, NULL, 16); + } + + return true; + +} + +bool sslFingerPrintChar(const char * fingerprint, char * destination) { + + // check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59) + if (!sslCheckFingerPrint(fingerprint)) return false; + + // copy it + strncpy(destination, fingerprint, 59); + + // walk the fingerprint replacing ':' for ' ' + for (unsigned char i = 0; i<59; i++) { + if (destination[i] == ':') destination[i] = ' '; + } + + return true; + +} + +#endif + +// ----------------------------------------------------------------------------- +// Reset +// ----------------------------------------------------------------------------- + +unsigned char resetReason() { + static unsigned char status = 255; + if (status == 255) { + status = EEPROM.read(EEPROM_CUSTOM_RESET); + if (status > 0) resetReason(0); + if (status > CUSTOM_RESET_MAX) status = 0; + } + return status; +} + +void resetReason(unsigned char reason) { + EEPROM.write(EEPROM_CUSTOM_RESET, reason); + EEPROM.commit(); +} + +void reset(unsigned char reason) { + resetReason(reason); + ESP.restart(); +} + +void deferredReset(unsigned long delay, unsigned char reason) { + _defer_reset.once_ms(delay, reset, reason); +} + +// ----------------------------------------------------------------------------- + +char * ltrim(char * s) { + char *p = s; + while ((unsigned char) *p == ' ') ++p; + return p; +} + +double roundTo(double num, unsigned char positions) { + double multiplier = 1; + while (positions-- > 0) multiplier *= 10; + return round(num * multiplier) / multiplier; +} + +void nice_delay(unsigned long ms) { + unsigned long start = millis(); + while (millis() - start < ms) delay(1); +} diff --git a/espurna/web.ino b/espurna/web.ino new file mode 100755 index 0000000..8deb4c4 --- /dev/null +++ b/espurna/web.ino @@ -0,0 +1,341 @@ +/* + +WEBSERVER MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if WEB_SUPPORT + +#include +#include +#include +#include +#include +#include + +#if WEB_EMBEDDED +#include "static/index.html.gz.h" +#endif // WEB_EMBEDDED + +#if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED +#include "static/server.cer.h" +#include "static/server.key.h" +#endif // ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED + +// ----------------------------------------------------------------------------- + +AsyncWebServer * _server; +char _last_modified[50]; +std::vector * _webConfigBuffer; +bool _webConfigSuccess = false; + +// ----------------------------------------------------------------------------- +// HOOKS +// ----------------------------------------------------------------------------- + +void _onReset(AsyncWebServerRequest *request) { + deferredReset(100, CUSTOM_RESET_HTTP); + request->send(200); +} + +void _onGetConfig(AsyncWebServerRequest *request) { + + webLog(request); + if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str()); + + AsyncResponseStream *response = request->beginResponseStream("text/json"); + + DynamicJsonBuffer jsonBuffer; + JsonObject &root = jsonBuffer.createObject(); + root["app"] = APP_NAME; + root["version"] = APP_VERSION; + settingsGetJson(root); + root.prettyPrintTo(*response); + + char buffer[100]; + snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str()); + response->addHeader("Content-Disposition", buffer); + + request->send(response); + +} + +void _onPostConfig(AsyncWebServerRequest *request) { + webLog(request); + if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str()); + request->send(_webConfigSuccess ? 200 : 400); +} + +void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + + // No buffer + if (final && (index == 0)) { + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject((char *) data); + if (root.success()) _webConfigSuccess = settingsRestoreJson(root); + return; + } + + // Buffer start => reset + if (index == 0) if (_webConfigBuffer) delete _webConfigBuffer; + + // init buffer if it doesn't exist + if (!_webConfigBuffer) { + _webConfigBuffer = new std::vector(); + _webConfigSuccess = false; + } + + // Copy + if (len > 0) { + _webConfigBuffer->reserve(_webConfigBuffer->size() + len); + _webConfigBuffer->insert(_webConfigBuffer->end(), data, data + len); + } + + // Ending + if (final) { + + _webConfigBuffer->push_back(0); + + // Parse JSON + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject((char *) _webConfigBuffer->data()); + if (root.success()) _webConfigSuccess = settingsRestoreJson(root); + delete _webConfigBuffer; + + } + +} + +#if WEB_EMBEDDED +void _onHome(AsyncWebServerRequest *request) { + + webLog(request); + if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str()); + + if (request->header("If-Modified-Since").equals(_last_modified)) { + + request->send(304); + + } else { + + #if ASYNC_TCP_SSL_ENABLED + + // Chunked response, we calculate the chunks based on free heap (in multiples of 32) + // This is necessary when a TLS connection is open since it sucks too much memory + DEBUG_MSG_P(PSTR("[MAIN] Free heap: %d bytes\n"), getFreeHeap()); + size_t max = (getFreeHeap() / 3) & 0xFFE0; + + AsyncWebServerResponse *response = request->beginChunkedResponse("text/html", [max](uint8_t *buffer, size_t maxLen, size_t index) -> size_t { + + // Get the chunk based on the index and maxLen + size_t len = index_html_gz_len - index; + if (len > maxLen) len = maxLen; + if (len > max) len = max; + if (len > 0) memcpy_P(buffer, index_html_gz + index, len); + + DEBUG_MSG_P(PSTR("[WEB] Sending %d%%%% (max chunk size: %4d)\r"), int(100 * index / index_html_gz_len), max); + if (len == 0) DEBUG_MSG_P(PSTR("\n")); + + // Return the actual length of the chunk (0 for end of file) + return len; + + }); + + #else + + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", index_html_gz, index_html_gz_len); + + #endif + + response->addHeader("Content-Encoding", "gzip"); + response->addHeader("Last-Modified", _last_modified); + request->send(response); + + } + +} +#endif + +#if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED + +int _onCertificate(void * arg, const char *filename, uint8_t **buf) { + +#if WEB_EMBEDDED + + if (strcmp(filename, "server.cer") == 0) { + uint8_t * nbuf = (uint8_t*) malloc(server_cer_len); + memcpy_P(nbuf, server_cer, server_cer_len); + *buf = nbuf; + DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename); + return server_cer_len; + } + + if (strcmp(filename, "server.key") == 0) { + uint8_t * nbuf = (uint8_t*) malloc(server_key_len); + memcpy_P(nbuf, server_key, server_key_len); + *buf = nbuf; + DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename); + return server_key_len; + } + + DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - ERROR\n"), filename); + *buf = 0; + return 0; + +#else + + File file = SPIFFS.open(filename, "r"); + if (file) { + size_t size = file.size(); + uint8_t * nbuf = (uint8_t*) malloc(size); + if (nbuf) { + size = file.read(nbuf, size); + file.close(); + *buf = nbuf; + DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - OK\n"), filename); + return size; + } + file.close(); + } + DEBUG_MSG_P(PSTR("[WEB] SSL File: %s - ERROR\n"), filename); + *buf = 0; + return 0; + +#endif + +} + +#endif + +void _onUpgrade(AsyncWebServerRequest *request) { + + webLog(request); + if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str()); + + char buffer[10]; + if (!Update.hasError()) { + sprintf_P(buffer, PSTR("OK")); + } else { + sprintf_P(buffer, PSTR("ERROR %d"), Update.getError()); + } + + AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", buffer); + response->addHeader("Connection", "close"); + if (!Update.hasError()) { + deferredReset(100, CUSTOM_RESET_UPGRADE); + } + request->send(response); + +} + +void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if (!index) { + DEBUG_MSG_P(PSTR("[UPGRADE] Start: %s\n"), filename.c_str()); + Update.runAsync(true); + if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + } + } + if (!Update.hasError()) { + if (Update.write(data, len) != len) { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + } + } + if (final) { + if (Update.end(true)){ + DEBUG_MSG_P(PSTR("[UPGRADE] Success: %u bytes\n"), index + len); + } else { + #ifdef DEBUG_PORT + Update.printError(DEBUG_PORT); + #endif + } + } else { + DEBUG_MSG_P(PSTR("[UPGRADE] Progress: %u bytes\r"), index + len); + } +} + +// ----------------------------------------------------------------------------- + +bool _authenticate(AsyncWebServerRequest *request) { + #if USE_PASSWORD + String password = getSetting("adminPass", ADMIN_PASS); + char httpPassword[password.length() + 1]; + password.toCharArray(httpPassword, password.length() + 1); + return request->authenticate(WEB_USERNAME, httpPassword); + #else + return true; + #endif +} + +// ----------------------------------------------------------------------------- + +AsyncWebServer * webServer() { + return _server; +} + +unsigned int webPort() { + #if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED + return 443; + #else + return getSetting("webPort", WEB_PORT).toInt(); + #endif +} + +void webLog(AsyncWebServerRequest *request) { + DEBUG_MSG_P(PSTR("[WEBSERVER] Request: %s %s\n"), request->methodToString(), request->url().c_str()); +} + +void webSetup() { + + // Cache the Last-Modifier header value + snprintf_P(_last_modified, sizeof(_last_modified), PSTR("%s %s GMT"), __DATE__, __TIME__); + + // Create server + unsigned int port = webPort(); + _server = new AsyncWebServer(port); + + // Rewrites + _server->rewrite("/", "/index.html"); + + // Serve home (basic authentication protection) + #if WEB_EMBEDDED + _server->on("/index.html", HTTP_GET, _onHome); + #endif + _server->on("/reset", HTTP_GET, _onReset); + _server->on("/config", HTTP_GET, _onGetConfig); + _server->on("/config", HTTP_POST | HTTP_PUT, _onPostConfig, _onPostConfigData); + _server->on("/upgrade", HTTP_POST, _onUpgrade, _onUpgradeData); + + // Serve static files + #if SPIFFS_SUPPORT + _server->serveStatic("/", SPIFFS, "/") + .setLastModified(_last_modified) + .setFilter([](AsyncWebServerRequest *request) -> bool { + webLog(request); + return true; + }); + #endif + + // 404 + _server->onNotFound([](AsyncWebServerRequest *request){ + request->send(404); + }); + + // Run server + #if ASYNC_TCP_SSL_ENABLED & WEB_SSL_ENABLED + _server->onSslFileRequest(_onCertificate, NULL); + _server->beginSecure("server.cer", "server.key", NULL); + #else + _server->begin(); + #endif + DEBUG_MSG_P(PSTR("[WEBSERVER] Webserver running on port %u\n"), port); + +} + +#endif // WEB_SUPPORT diff --git a/espurna/wifi.ino b/espurna/wifi.ino new file mode 100755 index 0000000..3b781f7 --- /dev/null +++ b/espurna/wifi.ino @@ -0,0 +1,454 @@ +/* + +WIFI MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#include "JustWifi.h" +#include + +uint32_t _wifi_scan_client_id = 0; + +// ----------------------------------------------------------------------------- +// PRIVATE +// ----------------------------------------------------------------------------- + +void _wifiConfigure() { + + jw.setHostname(getSetting("hostname").c_str()); + #if USE_PASSWORD + jw.setSoftAP(getSetting("hostname").c_str(), getSetting("adminPass", ADMIN_PASS).c_str()); + #else + jw.setSoftAP(getSetting("hostname").c_str()); + #endif + jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT); + wifiReconnectCheck(); + jw.setAPMode(WIFI_AP_MODE); + jw.cleanNetworks(); + + // If system is flagged unstable we do not init wifi networks + #if SYSTEM_CHECK_ENABLED + if (!systemCheck()) return; + #endif + + // Clean settings + _wifiClean(WIFI_MAX_NETWORKS); + + int i; + for (i = 0; i< WIFI_MAX_NETWORKS; i++) { + if (getSetting("ssid" + String(i)).length() == 0) break; + if (getSetting("ip" + String(i)).length() == 0) { + jw.addNetwork( + getSetting("ssid" + String(i)).c_str(), + getSetting("pass" + String(i)).c_str() + ); + } else { + jw.addNetwork( + getSetting("ssid" + String(i)).c_str(), + getSetting("pass" + String(i)).c_str(), + getSetting("ip" + String(i)).c_str(), + getSetting("gw" + String(i)).c_str(), + getSetting("mask" + String(i)).c_str(), + getSetting("dns" + String(i)).c_str() + ); + } + } + + jw.scanNetworks(getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1); + +} + +void _wifiScan(uint32_t client_id = 0) { + + DEBUG_MSG_P(PSTR("[WIFI] Start scanning\n")); + + #if WEB_SUPPORT + String output; + #endif + + unsigned char result = WiFi.scanNetworks(); + + if (result == WIFI_SCAN_FAILED) { + DEBUG_MSG_P(PSTR("[WIFI] Scan failed\n")); + #if WEB_SUPPORT + output = String("Failed scan"); + #endif + } else if (result == 0) { + DEBUG_MSG_P(PSTR("[WIFI] No networks found\n")); + #if WEB_SUPPORT + output = String("No networks found"); + #endif + } else { + + DEBUG_MSG_P(PSTR("[WIFI] %d networks found:\n"), result); + + // Populate defined networks with scan data + for (int8_t i = 0; i < result; ++i) { + + String ssid_scan; + int32_t rssi_scan; + uint8_t sec_scan; + uint8_t* BSSID_scan; + int32_t chan_scan; + bool hidden_scan; + char buffer[128]; + + WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan, hidden_scan); + + snprintf_P(buffer, sizeof(buffer), + PSTR("BSSID: %02X:%02X:%02X:%02X:%02X:%02X SEC: %s RSSI: %3d CH: %2d SSID: %s"), + BSSID_scan[1], BSSID_scan[2], BSSID_scan[3], BSSID_scan[4], BSSID_scan[5], BSSID_scan[6], + (sec_scan != ENC_TYPE_NONE ? "YES" : "NO "), + rssi_scan, + chan_scan, + (char *) ssid_scan.c_str() + ); + + DEBUG_MSG_P(PSTR("[WIFI] > %s\n"), buffer); + + #if WEB_SUPPORT + if (client_id > 0) output = output + String(buffer) + String("
"); + #endif + + } + + } + + #if WEB_SUPPORT + if (client_id > 0) { + output = String("{\"scanResult\": \"") + output + String("\"}"); + wsSend(client_id, output.c_str()); + } + #endif + + WiFi.scanDelete(); + +} + +bool _wifiClean(unsigned char num) { + + bool changed = false; + int i = 0; + + // Clean defined settings + while (i < num) { + + // Skip on first non-defined setting + if (!hasSetting("ssid", i)) { + delSetting("ssid", i); + break; + } + + // Delete empty values + if (!hasSetting("pass", i)) delSetting("pass", i); + if (!hasSetting("ip", i)) delSetting("ip", i); + if (!hasSetting("gw", i)) delSetting("gw", i); + if (!hasSetting("mask", i)) delSetting("mask", i); + if (!hasSetting("dns", i)) delSetting("dns", i); + + ++i; + + } + + // Delete all other settings + while (i < WIFI_MAX_NETWORKS) { + changed = hasSetting("ssid", i); + delSetting("ssid", i); + delSetting("pass", i); + delSetting("ip", i); + delSetting("gw", i); + delSetting("mask", i); + delSetting("dns", i); + ++i; + } + + return changed; + +} + +// Inject hardcoded networks +void _wifiInject() { + + if (strlen(WIFI1_SSID)) { + + if (!hasSetting("ssid", 0)) { + setSetting("ssid", 0, WIFI1_SSID); + setSetting("pass", 0, WIFI1_PASS); + setSetting("ip", 0, WIFI1_IP); + setSetting("gw", 0, WIFI1_GW); + setSetting("mask", 0, WIFI1_MASK); + setSetting("dns", 0, WIFI1_DNS); + } + + if (strlen(WIFI2_SSID)) { + if (!hasSetting("ssid", 1)) { + setSetting("ssid", 1, WIFI2_SSID); + setSetting("pass", 1, WIFI2_PASS); + setSetting("ip", 1, WIFI2_IP); + setSetting("gw", 1, WIFI2_GW); + setSetting("mask", 1, WIFI2_MASK); + setSetting("dns", 1, WIFI2_DNS); + } + } + + } +} + +#if DEBUG_SUPPORT + +void _wifiDebug(justwifi_messages_t code, char * parameter) { + + if (code == MESSAGE_SCANNING) { + DEBUG_MSG_P(PSTR("[WIFI] Scanning\n")); + } + + if (code == MESSAGE_SCAN_FAILED) { + DEBUG_MSG_P(PSTR("[WIFI] Scan failed\n")); + } + + if (code == MESSAGE_NO_NETWORKS) { + DEBUG_MSG_P(PSTR("[WIFI] No networks found\n")); + } + + if (code == MESSAGE_NO_KNOWN_NETWORKS) { + DEBUG_MSG_P(PSTR("[WIFI] No known networks found\n")); + } + + if (code == MESSAGE_FOUND_NETWORK) { + DEBUG_MSG_P(PSTR("[WIFI] %s\n"), parameter); + } + + if (code == MESSAGE_CONNECTING) { + DEBUG_MSG_P(PSTR("[WIFI] Connecting to %s\n"), parameter); + } + + if (code == MESSAGE_CONNECT_WAITING) { + // too much noise + } + + if (code == MESSAGE_CONNECT_FAILED) { + DEBUG_MSG_P(PSTR("[WIFI] Could not connect to %s\n"), parameter); + } + + if (code == MESSAGE_CONNECTED) { + wifiStatus(); + } + + if (code == MESSAGE_ACCESSPOINT_CREATED) { + wifiStatus(); + } + + if (code == MESSAGE_DISCONNECTED) { + DEBUG_MSG_P(PSTR("[WIFI] Disconnected\n")); + } + + if (code == MESSAGE_ACCESSPOINT_CREATING) { + DEBUG_MSG_P(PSTR("[WIFI] Creating access point\n")); + } + + if (code == MESSAGE_ACCESSPOINT_FAILED) { + DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n")); + } + +} + +#endif // DEBUG_SUPPORT + +// ----------------------------------------------------------------------------- +// SETTINGS +// ----------------------------------------------------------------------------- + +#if TERMINAL_SUPPORT + +void _wifiInitCommands() { + + settingsRegisterCommand(F("WIFI.RESET"), [](Embedis* e) { + _wifiConfigure(); + wifiDisconnect(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("WIFI.AP"), [](Embedis* e) { + createAP(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + + settingsRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) { + _wifiScan(); + DEBUG_MSG_P(PSTR("+OK\n")); + }); + +} + +#endif + +// ----------------------------------------------------------------------------- +// WEB +// ----------------------------------------------------------------------------- + +#if WEB_SUPPORT + +bool _wifiWebSocketOnReceive(const char * key, JsonVariant& value) { + if (strncmp(key, "wifi", 4) == 0) return true; + if (strncmp(key, "ssid", 4) == 0) return true; + if (strncmp(key, "pass", 4) == 0) return true; + if (strncmp(key, "ip", 2) == 0) return true; + if (strncmp(key, "gw", 2) == 0) return true; + if (strncmp(key, "mask", 4) == 0) return true; + if (strncmp(key, "dns", 3) == 0) return true; + return false; +} + +void _wifiWebSocketOnSend(JsonObject& root) { + root["maxNetworks"] = WIFI_MAX_NETWORKS; + root["wifiScan"] = getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1; + JsonArray& wifi = root.createNestedArray("wifi"); + for (byte i=0; i 0) { + _wifiScan(_wifi_scan_client_id); + _wifi_scan_client_id = 0; + } + +} diff --git a/espurna/ws.ino b/espurna/ws.ino new file mode 100755 index 0000000..2ecd397 --- /dev/null +++ b/espurna/ws.ino @@ -0,0 +1,448 @@ +/* + +WEBSOCKET MODULE + +Copyright (C) 2016-2018 by Xose Pérez + +*/ + +#if WEB_SUPPORT + +#include +#include +#include +#include +#include +#include "libs/WebSocketIncommingBuffer.h" + +AsyncWebSocket _ws("/ws"); +Ticker _web_defer; + +std::vector _ws_on_send_callbacks; +std::vector _ws_on_action_callbacks; +std::vector _ws_on_after_parse_callbacks; +std::vector _ws_on_receive_callbacks; + +// ----------------------------------------------------------------------------- +// Private methods +// ----------------------------------------------------------------------------- + +#if MQTT_SUPPORT +void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) { + if (type == MQTT_CONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": true}")); + if (type == MQTT_DISCONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": false}")); +} +#endif + +bool _wsStore(String key, String value) { + + // HTTP port + if (key == "webPort") { + if ((value.toInt() == 0) || (value.toInt() == 80)) { + return delSetting(key); + } + } + + if (value != getSetting(key)) { + return setSetting(key, value); + } + + return false; + +} + +bool _wsStore(String key, JsonArray& value) { + + bool changed = false; + + unsigned char index = 0; + for (auto element : value) { + if (_wsStore(key + index, element.as())) changed = true; + index++; + } + + // Delete further values + for (unsigned char i=index; iid(); + + // Parse JSON input + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject((char *) payload); + if (!root.success()) { + DEBUG_MSG_P(PSTR("[WEBSOCKET] Error parsing data\n")); + wsSend_P(client_id, PSTR("{\"message\": 3}")); + return; + } + + // Check actions ----------------------------------------------------------- + + const char* action = root["action"]; + if (action) { + + DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action); + + if (strcmp(action, "reboot") == 0) { + deferredReset(100, CUSTOM_RESET_WEB); + return; + } + + if (strcmp(action, "reconnect") == 0) { + _web_defer.once_ms(100, wifiDisconnect); + return; + } + + if (strcmp(action, "factory_reset") == 0) { + DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n")); + resetSettings(); + deferredReset(100, CUSTOM_RESET_FACTORY); + return; + } + + JsonObject& data = root["data"]; + if (data.success()) { + + // Callbacks + for (unsigned char i = 0; i < _ws_on_action_callbacks.size(); i++) { + (_ws_on_action_callbacks[i])(client_id, action, data); + } + + // Restore configuration via websockets + if (strcmp(action, "restore") == 0) { + if (settingsRestoreJson(data)) { + wsSend_P(client_id, PSTR("{\"message\": 5}")); + } else { + wsSend_P(client_id, PSTR("{\"message\": 4}")); + } + } + + return; + + } + + }; + + // Check configuration ----------------------------------------------------- + + JsonObject& config = root["config"]; + if (config.success()) { + + DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing configuration data\n")); + + String adminPass; + bool save = false; + #if MQTT_SUPPORT + bool changedMQTT = false; + #endif + + for (auto kv: config) { + + bool changed = false; + String key = kv.key; + JsonVariant& value = kv.value; + + // Check password + if (key == "adminPass") { + if (!value.is()) continue; + JsonArray& values = value.as(); + if (values.size() != 2) continue; + if (values[0].as().equals(values[1].as())) { + String password = values[0].as(); + if (password.length() > 0) { + setSetting(key, password); + save = true; + wsSend_P(client_id, PSTR("{\"action\": \"reload\"}")); + } + } else { + wsSend_P(client_id, PSTR("{\"message\": 7}")); + } + continue; + } + + // Check if key has to be processed + bool found = false; + for (unsigned char i = 0; i < _ws_on_receive_callbacks.size(); i++) { + found |= (_ws_on_receive_callbacks[i])(key.c_str(), value); + // TODO: remove this to call all OnReceiveCallbacks with the + // current key/value + if (found) break; + } + if (!found) { + delSetting(key); + continue; + } + + // Store values + if (value.is()) { + if (_wsStore(key, value.as())) changed = true; + } else { + if (_wsStore(key, value.as())) changed = true; + } + + // Update flags if value has changed + if (changed) { + save = true; + #if MQTT_SUPPORT + if (key.startsWith("mqtt")) changedMQTT = true; + #endif + } + + } + + // Save settings + if (save) { + + // Callbacks + for (unsigned char i = 0; i < _ws_on_after_parse_callbacks.size(); i++) { + (_ws_on_after_parse_callbacks[i])(); + } + + // This should got to callback as well + // but first change management has to be in place + #if MQTT_SUPPORT + if (changedMQTT) mqttReset(); + #endif + + // Persist settings + saveSettings(); + + wsSend_P(client_id, PSTR("{\"message\": 8}")); + + } else { + + wsSend_P(client_id, PSTR("{\"message\": 9}")); + + } + + } + +} + +void _wsUpdate(JsonObject& root) { + root["heap"] = getFreeHeap(); + root["uptime"] = getUptime(); + root["rssi"] = WiFi.RSSI(); + root["loadaverage"] = systemLoadAverage(); + root["vcc"] = ESP.getVcc(); + #if NTP_SUPPORT + if (ntpSynced()) root["now"] = now(); + #endif +} + +bool _wsOnReceive(const char * key, JsonVariant& value) { + if (strncmp(key, "ws", 2) == 0) return true; + if (strncmp(key, "admin", 5) == 0) return true; + if (strncmp(key, "hostname", 8) == 0) return true; + if (strncmp(key, "webPort", 7) == 0) return true; + return false; +} + +void _wsOnStart(JsonObject& root) { + + #if USE_PASSWORD && WEB_FORCE_PASS_CHANGE + String adminPass = getSetting("adminPass", ADMIN_PASS); + bool changePassword = adminPass.equals(ADMIN_PASS); + #else + bool changePassword = false; + #endif + + if (changePassword) { + + root["webMode"] = WEB_MODE_PASSWORD; + + } else { + + char chipid[7]; + snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId()); + uint8_t * bssid = WiFi.BSSID(); + char bssid_str[20]; + snprintf_P(bssid_str, sizeof(bssid_str), + PSTR("%02X:%02X:%02X:%02X:%02X:%02X"), + bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5] + ); + + root["webMode"] = WEB_MODE_NORMAL; + + root["app_name"] = APP_NAME; + root["app_version"] = APP_VERSION; + root["app_build"] = buildTime(); + root["app_revision"] = APP_REVISION; + root["manufacturer"] = MANUFACTURER; + root["chipid"] = String(chipid); + root["mac"] = WiFi.macAddress(); + root["bssid"] = String(bssid_str); + root["channel"] = WiFi.channel(); + root["device"] = DEVICE; + root["hostname"] = getSetting("hostname"); + root["network"] = getNetwork(); + root["deviceip"] = getIP(); + root["sketch_size"] = ESP.getSketchSize(); + root["free_size"] = ESP.getFreeSketchSpace(); + root["sdk"] = ESP.getSdkVersion(); + root["core"] = getCoreVersion(); + + _wsUpdate(root); + + root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt(); + root["webPort"] = getSetting("webPort", WEB_PORT).toInt(); + root["wsAuth"] = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1; + #if TERMINAL_SUPPORT + root["cmdVisible"] = 1; + #endif + + } + +} + +void _wsStart(uint32_t client_id) { + for (unsigned char i = 0; i < _ws_on_send_callbacks.size(); i++) { + wsSend(client_id, _ws_on_send_callbacks[i]); + } +} + +void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + + if (type == WS_EVT_CONNECT) { + IPAddress ip = client->remoteIP(); + DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url()); + _wsStart(client->id()); + client->_tempObject = new WebSocketIncommingBuffer(&_wsParse, true); + wifiReconnectCheck(); + + } else if(type == WS_EVT_DISCONNECT) { + DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u disconnected\n"), client->id()); + if (client->_tempObject) { + delete (WebSocketIncommingBuffer *) client->_tempObject; + } + wifiReconnectCheck(); + + } else if(type == WS_EVT_ERROR) { + DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u error(%u): %s\n"), client->id(), *((uint16_t*)arg), (char*)data); + + } else if(type == WS_EVT_PONG) { + DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u pong(%u): %s\n"), client->id(), len, len ? (char*) data : ""); + + } else if(type == WS_EVT_DATA) { + //DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u data(%u): %s\n"), client->id(), len, len ? (char*) data : ""); + WebSocketIncommingBuffer *buffer = (WebSocketIncommingBuffer *)client->_tempObject; + AwsFrameInfo * info = (AwsFrameInfo*)arg; + buffer->data_event(client, info, data, len); + + } + +} + +void _wsLoop() { + static unsigned long last = 0; + if (!wsConnected()) return; + if (millis() - last > WS_UPDATE_INTERVAL) { + last = millis(); + wsSend(_wsUpdate); + } +} + +// ----------------------------------------------------------------------------- +// Public API +// ----------------------------------------------------------------------------- + +bool wsConnected() { + return (_ws.count() > 0); +} + +void wsOnSendRegister(ws_on_send_callback_f callback) { + _ws_on_send_callbacks.push_back(callback); +} + +void wsOnReceiveRegister(ws_on_receive_callback_f callback) { + _ws_on_receive_callbacks.push_back(callback); +} + +void wsOnActionRegister(ws_on_action_callback_f callback) { + _ws_on_action_callbacks.push_back(callback); +} + +void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback) { + _ws_on_after_parse_callbacks.push_back(callback); +} + +void wsSend(ws_on_send_callback_f callback) { + if (_ws.count() > 0) { + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + callback(root); + String output; + root.printTo(output); + _ws.textAll((char *) output.c_str()); + } +} + +void wsSend(const char * payload) { + if (_ws.count() > 0) { + _ws.textAll(payload); + } +} + +void wsSend_P(PGM_P payload) { + if (_ws.count() > 0) { + char buffer[strlen_P(payload)]; + strcpy_P(buffer, payload); + _ws.textAll(buffer); + } +} + +void wsSend(uint32_t client_id, ws_on_send_callback_f callback) { + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.createObject(); + callback(root); + String output; + root.printTo(output); + _ws.text(client_id, (char *) output.c_str()); +} + +void wsSend(uint32_t client_id, const char * payload) { + _ws.text(client_id, payload); +} + +void wsSend_P(uint32_t client_id, PGM_P payload) { + char buffer[strlen_P(payload)]; + strcpy_P(buffer, payload); + _ws.text(client_id, buffer); +} + +void wsConfigure() { + #if USE_PASSWORD + bool auth = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1; + if (auth) { + _ws.setAuthentication(WEB_USERNAME, (const char *) getSetting("adminPass", ADMIN_PASS).c_str()); + } else { + _ws.setAuthentication("", ""); + } + #endif +} + +void wsSetup() { + _ws.onEvent(_wsEvent); + wsConfigure(); + webServer()->addHandler(&_ws); + #if MQTT_SUPPORT + mqttRegister(_wsMQTTCallback); + #endif + wsOnSendRegister(_wsOnStart); + wsOnReceiveRegister(_wsOnReceive); + wsOnAfterParseRegister(wsConfigure); + espurnaRegisterLoop(_wsLoop); +} + +#endif // WEB_SUPPORT diff --git a/extra_scripts.py b/extra_scripts.py new file mode 100755 index 0000000..1cb1ed4 --- /dev/null +++ b/extra_scripts.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +from subprocess import call +import os +import time + +Import("env") + +# ------------------------------------------------------------------------------ +# Utils +# ------------------------------------------------------------------------------ + +class Color(object): + BLACK = '\x1b[1;30m' + RED = '\x1b[1;31m' + GREEN = '\x1b[1;32m' + YELLOW = '\x1b[1;33m' + BLUE = '\x1b[1;34m' + MAGENTA = '\x1b[1;35m' + CYAN = '\x1b[1;36m' + WHITE = '\x1b[1;37m' + LIGHT_GREY = '\x1b[0;30m' + LIGHT_RED = '\x1b[0;31m' + LIGHT_GREEN = '\x1b[0;32m' + LIGHT_YELLOW = '\x1b[0;33m' + LIGHT_BLUE = '\x1b[0;34m' + LIGHT_MAGENTA = '\x1b[0;35m' + LIGHT_CYAN = '\x1b[0;36m' + LIGHT_WHITE = '\x1b[0;37m' + +def clr(color, text): + return color + str(text) + '\x1b[0m' + +# ------------------------------------------------------------------------------ +# Callbacks +# ------------------------------------------------------------------------------ + +def remove_float_support(): + + flags = " ".join(env['LINKFLAGS']) + flags = flags.replace("-u _printf_float", "") + flags = flags.replace("-u _scanf_float", "") + newflags = flags.split() + + env.Replace( + LINKFLAGS = newflags + ) + +def cpp_check(source, target, env): + print("Started cppcheck...\n") + call(["cppcheck", os.getcwd()+"/espurna", "--force", "--enable=all"]) + print("Finished cppcheck...\n") + +def check_size(source, target, env): + time.sleep(2) + size = target[0].get_size() + print clr(Color.LIGHT_BLUE, "Binary size: %s bytes" % size) + #if size > 512000: + # print clr(Color.LIGHT_RED, "File too large for OTA!") + # Exit(1) + +# ------------------------------------------------------------------------------ +# Hooks +# ------------------------------------------------------------------------------ + +remove_float_support() + +#env.AddPreAction("buildprog", cpp_check) +env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", check_size) diff --git a/gulpfile.js b/gulpfile.js new file mode 100755 index 0000000..947bad4 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,134 @@ +/* + +ESP8266 file system builder + +Copyright (C) 2016-2018 by Xose Pérez + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +*/ + +/*eslint quotes: ["error", "single"]*/ +/*eslint-env es6*/ + +// ----------------------------------------------------------------------------- +// File system builder +// ----------------------------------------------------------------------------- + +const fs = require('fs'); +const gulp = require('gulp'); +const htmlmin = require('gulp-htmlmin'); +const uglify = require('gulp-uglify'); +const gzip = require('gulp-gzip'); +const inline = require('gulp-inline'); +const inlineImages = require('gulp-css-base64'); +const favicon = require('gulp-base64-favicon'); +const htmllint = require('gulp-htmllint'); +const log = require('fancy-log'); +const csslint = require('gulp-csslint'); +const crass = require('gulp-crass'); + +const dataFolder = 'espurna/data/'; +const staticFolder = 'espurna/static/'; + +var toHeader = function(filename) { + + var source = dataFolder + filename; + var destination = staticFolder + filename + '.h'; + var safename = filename.split('.').join('_'); + + var wstream = fs.createWriteStream(destination); + wstream.on('error', function (err) { + log.error(err); + }); + + var data = fs.readFileSync(source); + + wstream.write('#define ' + safename + '_len ' + data.length + '\n'); + wstream.write('const uint8_t ' + safename + '[] PROGMEM = {'); + + for (var i=0; i 0) { + issues.forEach(function (issue) { + log.info( + '[gulp-htmllint] ' + + filepath + ' [' + + issue.line + ',' + + issue.column + ']: ' + + '(' + issue.code + ') ' + + issue.msg + ); + }); + process.exitCode = 1; + } +}; + +gulp.task('build_certs', function() { + toHeader('server.cer'); + toHeader('server.key'); +}); + +gulp.task('csslint', function() { + gulp.src('html/*.css'). + pipe(csslint({ids: false})). + pipe(csslint.formatter()); +}); + +gulp.task('buildfs_embeded', ['buildfs_inline'], function() { + toHeader('index.html.gz'); +}); + +gulp.task('buildfs_inline', function() { + return gulp.src('html/*.html'). + pipe(htmllint({ + 'failOnError': true, + 'rules': { + 'id-class-style': false, + 'label-req-for': false, + } + }, htmllintReporter)). + pipe(favicon()). + pipe(inline({ + base: 'html/', + js: [uglify], + css: [crass, inlineImages], + disabledTypes: ['svg', 'img'] + })). + pipe(htmlmin({ + collapseWhitespace: true, + removeComments: true, + minifyCSS: true, + minifyJS: true + })). + pipe(gzip()). + pipe(gulp.dest(dataFolder)); +}); + + +gulp.task('default', ['buildfs_embeded']); diff --git a/html/custom.css b/html/custom.css new file mode 100755 index 0000000..9002e6f --- /dev/null +++ b/html/custom.css @@ -0,0 +1,320 @@ +/* ----------------------------------------------------------------------------- + General + -------------------------------------------------------------------------- */ + +#menu .pure-menu-heading { + font-size: 100%; + padding: .5em .5em; + white-space: normal; +} + +.pure-g { + margin-bottom: 0; +} + +.pure-form legend { + font-weight: bold; + letter-spacing: 0; + margin: 10px 0 1em 0; +} + +.pure-form .pure-g > label { + margin: .4em 0 .2em; +} + +.pure-form input { + margin-bottom: 10px; +} + +.pure-form input[type=text][disabled] { + color: #777777; +} + +@media screen and (max-width: 32em) { + .header > h1 { + line-height: 100%; + font-size: 2em; + } +} + +h2 { + font-size: 1em; +} + +.panel { + display: none; +} + +.block { + display: block; +} + +.content { + margin: 0; +} + +.page { + margin-top: 10px; +} + +.hint { + color: #ccc; + font-size: 80%; + margin: -10px 0 10px 0; +} + +.hint a { + color:inherit; +} + +legend.module, +.module { + display: none; +} + +.template { + display: none; +} + +input[name=upgrade] { + display: none; +} + +select { + margin-bottom: 10px; + width: 100%; +} + +input.center { + margin-bottom: 0; +} + +div.center { + margin: .5em 0 1em; +} + +.webmode { + display: none; +} + +#credentials { + font-size: 200%; + height: 100px; + left: 50%; + margin-left: -200px; + margin-top: -50px; + position: fixed; + text-align: center; + top: 50%; + width: 400px; +} + +div.state { + border-top: 1px solid #eee; + margin-top: 20px; + padding-top: 30px; +} + +.state div { + font-size: 80%; +} + +.state span { + font-size: 80%; + font-weight: bold; +} + +.right { + text-align: right; +} + +.pure-g span.terminal, +.pure-g textarea.terminal { + font-family: 'Courier New', monospace; + font-size: 80%; + line-height: 100%; + background-color: #000; + color: #0F0; +} + +/* ----------------------------------------------------------------------------- + Buttons + -------------------------------------------------------------------------- */ + +.pure-button { + border-radius: 4px; + color: white; + letter-spacing: 0; + margin-bottom: 10px; + padding: 8px 8px; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); +} + +.main-buttons { + margin: 20px auto; + text-align: center; +} +.main-buttons button { + width: 100px; +} + +.button-reboot, +.button-reconnect, +.button-ha-del, +.button-rfb-forget, +.button-del-network, +.button-del-schedule, +.button-dbg-clear, +.button-upgrade, +.button-settings-factory { + background: rgb(192, 0, 0); /* redish */ +} + +.button-update, +.button-update-password, +.button-add-network, +.button-rfb-learn, +.button-upgrade-browse, +.button-ha-add, +.button-ha-config, +.button-settings-backup, +.button-settings-restore, +.button-dbgcmd, +.button-apikey { + background: rgb(0, 192, 0); /* green */ +} + +.button-add-switch-schedule, +.button-add-light-schedule { + background: rgb(0, 192, 0); /* green */ + display: none; +} + +.button-more-network, +.button-more-schedule, +.button-wifi-scan, +.button-rfb-send { + background: rgb(255, 128, 0); /* orange */ +} + +.button-upgrade-browse, +.button-dbgcmd, +.button-ha-add, +.button-apikey, +.button-upgrade { + margin-left: 5px; +} + +/* ----------------------------------------------------------------------------- + Sliders + -------------------------------------------------------------------------- */ + +input.slider { + margin-top: 10px; +} + +span.slider { + font-size: 70%; + letter-spacing: 0; + margin-left: 10px; + margin-top: 7px; +} + +/* ----------------------------------------------------------------------------- + Loading + -------------------------------------------------------------------------- */ + +.loading { + background-image: url('images/loading.gif'); + display: none; + height: 20px; + margin: 8px 0 0 10px; + width: 20px; +} + +/* ----------------------------------------------------------------------------- + Menu + -------------------------------------------------------------------------- */ + +#menu .small { + font-size: 60%; + padding-left: 9px; +} + +#menu div.footer { + color: #999; + font-size: 80%; + padding: 10px; +} +#menu div.footer a { + padding: 0; + text-decoration: none; +} + +/* ----------------------------------------------------------------------------- + RF Bridge panel + -------------------------------------------------------------------------- */ + +#panel-rfb fieldset { + margin: 10px 2px; + padding: 20px; +} + +#panel-rfb input { + margin-right: 5px; +} + +#panel-rfb label { + padding-top: 5px; +} + +#panel-rfb input { + text-align: center; +} + +/* ----------------------------------------------------------------------------- + Admin panel + -------------------------------------------------------------------------- */ + +#upgrade-progress { + display: none; + height: 20px; + margin-top: 10px; + width: 100%; +} + +#uploader, +#downloader { + display: none; +} + +/* ----------------------------------------------------------------------------- + Wifi panel + -------------------------------------------------------------------------- */ + +#networks .pure-g, +#schedules .pure-g { + border-bottom: 1px solid #eee; + margin-bottom: 10px; + padding: 10px 0 10px 0; +} + +#networks .more { + display: none; +} + +#haConfig, +#scanResult { + margin-top: 10px; + display: none; + padding: 10px; +} + +/* ----------------------------------------------------------------------------- + Logs + -------------------------------------------------------------------------- */ + +#weblog { + height: 400px; + margin-bottom: 10px; +} diff --git a/html/custom.js b/html/custom.js new file mode 100755 index 0000000..26bdb4b --- /dev/null +++ b/html/custom.js @@ -0,0 +1,1363 @@ +var websock; +var password = false; +var maxNetworks; +var maxSchedules; +var messages = []; +var free_size = 0; +var webhost; + +var numChanged = 0; +var numReboot = 0; +var numReconnect = 0; +var numReload = 0; + +var useWhite = false; + +var now = 0; +var ago = 0; + +// ----------------------------------------------------------------------------- +// Messages +// ----------------------------------------------------------------------------- + +function initMessages() { + messages[1] = "Remote update started"; + messages[2] = "OTA update started"; + messages[3] = "Error parsing data!"; + messages[4] = "The file does not look like a valid configuration backup or is corrupted"; + messages[5] = "Changes saved. You should reboot your board now"; + messages[7] = "Passwords do not match!"; + messages[8] = "Changes saved"; + messages[9] = "No changes detected"; + messages[10] = "Session expired, please reload page..."; +} + +function sensorName(id) { + var names = [ + "DHT", "Dallas", "Emon Analog", "Emon ADC121", "Emon ADS1X15", + "HLW8012", "V9261F", "ECH1560", "Analog", "Digital", + "Events", "PMSX003", "BMX280", "MHZ19", "SI7021", + "SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD" + ]; + if (1 <= id && id <= names.length) { + return names[id - 1]; + } + return null; +} + +function magnitudeType(type) { + var types = [ + "Temperature", "Humidity", "Pressure", + "Current", "Voltage", "Active Power", "Apparent Power", + "Reactive Power", "Power Factor", "Energy", "Energy (delta)", + "Analog", "Digital", "Events", + "PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UV" + ]; + if (1 <= type && type <= types.length) { + return types[type - 1]; + } + return null; +} + +function magnitudeError(error) { + var errors = [ + "OK", "Out of Range", "Warming Up", "Timeout", "Wrong ID", + "Data Error", "I2C Error", "GPIO Error", "Calibration error" + ]; + if (0 <= error && error < errors.length) { + return errors[error]; + } + return "Error " + error; +} + +// ----------------------------------------------------------------------------- +// Utils +// ----------------------------------------------------------------------------- + +$.fn.enterKey = function (fnc) { + return this.each(function () { + $(this).keypress(function (ev) { + var keycode = parseInt(ev.keyCode ? ev.keyCode : ev.which, 10); + if (13 === keycode) { + return fnc.call(this, ev); + } + }); + }); +}; + +function keepTime() { + if (0 === now) { return; } + var date = new Date(now * 1000); + var text = date.toISOString().substring(0, 19).replace("T", " "); + $("input[name='now']").val(text); + $("span[name='now']").html(text); + $("span[name='ago']").html(ago); + now++; + ago++; +} + +// http://www.the-art-of-web.com/javascript/validate-password/ +function checkPassword(str) { + // at least one lowercase and one uppercase letter or number + // at least five characters (letters, numbers or special characters) + var re = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/; + return re.test(str); +} + +function zeroPad(number, positions) { + var zeros = ""; + for (var i = 0; i < positions; i++) { + zeros += "0"; + } + return (zeros + number).slice(-positions); +} + +function loadTimeZones() { + + var time_zones = [ + -720, -660, -600, -570, -540, + -480, -420, -360, -300, -240, + -210, -180, -120, -60, 0, + 60, 120, 180, 210, 240, + 270, 300, 330, 345, 360, + 390, 420, 480, 510, 525, + 540, 570, 600, 630, 660, + 720, 765, 780, 840 + ]; + + for (var i in time_zones) { + var value = time_zones[i]; + var offset = value >= 0 ? value : -value; + var text = "GMT" + (value >= 0 ? "+" : "-") + + zeroPad(parseInt(offset / 60, 10), 2) + ":" + + zeroPad(offset % 60, 2); + $("select[name='ntpOffset']").append( + $(""). + attr("value",value). + text(text)); + } + +} + +function validateForm(form) { + + // password + var adminPass1 = $("input[name='adminPass']", form).first().val(); + if (adminPass1.length > 0 && !checkPassword(adminPass1)) { + alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!"); + return false; + } + + var adminPass2 = $("input[name='adminPass']", form).last().val(); + if (adminPass1 !== adminPass2) { + alert("Passwords are different!"); + return false; + } + + return true; + +} + +function getValue(element) { + + if ($(element).attr("type") === "checkbox") { + return $(element).prop("checked") ? 1 : 0; + } else if ($(element).attr("type") === "radio") { + if (!$(element).prop("checked")) { + return null; + } + } + + return $(element).val(); + +} + +function addValue(data, name, value) { + + // These fields will always be a list of values + var is_group = [ + "ssid", "pass", "gw", "mask", "ip", "dns", + "schEnabled", "schSwitch","schAction","schType","schHour","schMinute","schWDs", + "relayBoot", "relayPulse", "relayTime", + "mqttGroup", "mqttGroupInv", "relayOnDisc", + "dczRelayIdx", "dczMagnitude", + "tspkRelay", "tspkMagnitude", + "ledMode", + "adminPass" + ]; + + if (name in data) { + if (!Array.isArray(data[name])) { + data[name] = [data[name]]; + } + data[name].push(value); + } else if (is_group.indexOf(name) >= 0) { + data[name] = [value]; + } else { + data[name] = value; + } + +} + +function getData(form) { + + var data = {}; + + // Populate data + $("input,select", form).each(function() { + var name = $(this).attr("name"); + var value = getValue(this); + if (null !== value) { + addValue(data, name, value); + } + }); + + // Post process + addValue(data, "schSwitch", 0xFF); + delete data["filename"]; + delete data["rfbcode"]; + + return data; + +} + +function randomString(length, chars) { + var mask = ""; + if (chars.indexOf("a") > -1) { mask += "abcdefghijklmnopqrstuvwxyz"; } + if (chars.indexOf("A") > -1) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; } + if (chars.indexOf("#") > -1) { mask += "0123456789"; } + if (chars.indexOf("@") > -1) { mask += "ABCDEF"; } + if (chars.indexOf("!") > -1) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; } + var result = ""; + for (var i = length; i > 0; --i) { + result += mask[Math.round(Math.random() * (mask.length - 1))]; + } + return result; +} + +function generateAPIKey() { + var apikey = randomString(16, "@#"); + $("input[name='apiKey']").val(apikey); + return false; +} + +function getJson(str) { + try { + return JSON.parse(str); + } catch (e) { + return false; + } +} + +// ----------------------------------------------------------------------------- +// Actions +// ----------------------------------------------------------------------------- + +function sendAction(action, data) { + websock.send(JSON.stringify({action: action, data: data})); +} + +function sendConfig(data) { + websock.send(JSON.stringify({config: data})); +} + +function resetOriginals() { + $("input,select").each(function() { + $(this).attr("original", $(this).val()); + }); + numReboot = numReconnect = numReload = 0; +} + +function doReload(milliseconds) { + setTimeout(function() { + window.location.reload(); + }, parseInt(milliseconds, 10)); +} + +/** + * Check a file object to see if it is a valid firmware image + * The file first byte should be 0xE9 + * @param {file} file File object + * @param {Function} callback Function to call back with the result + */ +function checkFirmware(file, callback) { + + var reader = new FileReader(); + + reader.onloadend = function(evt) { + if (FileReader.DONE === evt.target.readyState) { + callback(0xE9 === evt.target.result.charCodeAt(0)); + } + }; + + var blob = file.slice(0, 1); + reader.readAsBinaryString(blob); + +} + +function doUpgrade() { + + var file = $("input[name='upgrade']")[0].files[0]; + + if (typeof file === "undefined") { + alert("First you have to select a file from your computer."); + return false; + } + + if (file.size > free_size) { + alert("Image it too large to fit in the available space for OTA. Consider doing a two-step update."); + return false; + } + + checkFirmware(file, function(ok) { + + if (!ok) { + alert("The file does not seem to be a valid firmware image."); + return; + } + + var data = new FormData(); + data.append("upgrade", file, file.name); + + $.ajax({ + + // Your server script to process the upload + url: webhost + "upgrade", + type: "POST", + + // Form data + data: data, + + // Tell jQuery not to process data or worry about content-type + // You *must* include these options! + cache: false, + contentType: false, + processData: false, + + success: function(data, text) { + $("#upgrade-progress").hide(); + if ("OK" === data) { + alert("Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds."); + doReload(5000); + } else { + alert("There was an error trying to upload the new image, please try again (" + data + ")."); + } + }, + + // Custom XMLHttpRequest + xhr: function() { + $("#upgrade-progress").show(); + var myXhr = $.ajaxSettings.xhr(); + if (myXhr.upload) { + // For handling the progress of the upload + myXhr.upload.addEventListener("progress", function(e) { + if (e.lengthComputable) { + $("progress").attr({ value: e.loaded, max: e.total }); + } + } , false); + } + return myXhr; + } + + }); + + }); + + return false; + +} + +function doUpdatePassword() { + var form = $("#formPassword"); + if (validateForm(form)) { + sendConfig(getData(form)); + } + return false; +} + +function checkChanges() { + + if (numChanged > 0) { + var response = window.confirm("Some changes have not been saved yet, do you want to save them first?"); + if (response) { + doUpdate(); + } + } + +} + +function doAction(question, action) { + + checkChanges(); + + if (question) { + var response = window.confirm(question); + if (false === response) { + return false; + } + } + + sendAction(action, {}); + doReload(5000); + return false; + +} + +function doReboot(ask) { + + var question = (typeof ask === "undefined" || false === ask) ? + null : + "Are you sure you want to reboot the device?"; + return doAction(question, "reboot"); + +} + +function doReconnect(ask) { + + var question = (typeof ask === "undefined" || false === ask) ? + null : + "Are you sure you want to disconnect from the current WIFI network?"; + return doAction(question, "reconnect"); + +} + +function doUpdate() { + + var form = $("#formSave"); + if (validateForm(form)) { + + // Get data + sendConfig(getData(form)); + + // Empty special fields + $(".pwrExpected").val(0); + $("input[name='pwrResetCalibration']"). + prop("checked", false). + iphoneStyle("refresh"); + $("input[name='pwrResetE']"). + prop("checked", false). + iphoneStyle("refresh"); + + // Change handling + numChanged = 0; + setTimeout(function() { + + var response; + + if (numReboot > 0) { + response = window.confirm("You have to reboot the board for the changes to take effect, do you want to do it now?"); + if (response) { doReboot(false); } + } else if (numReconnect > 0) { + response = window.confirm("You have to reconnect to the WiFi for the changes to take effect, do you want to do it now?"); + if (response) { doReconnect(false); } + } else if (numReload > 0) { + response = window.confirm("You have to reload the page to see the latest changes, do you want to do it now?"); + if (response) { doReload(0); } + } + + resetOriginals(); + + }, 1000); + + } + + return false; + +} + +function doBackup() { + document.getElementById("downloader").src = webhost + "config"; + return false; +} + +function onFileUpload(event) { + + var inputFiles = this.files; + if (typeof inputFiles === "undefined" || inputFiles.length === 0) { + return false; + } + var inputFile = inputFiles[0]; + this.value = ""; + + var response = window.confirm("Previous settings will be overwritten. Are you sure you want to restore this settings?"); + if (!response) { + return false; + } + + var reader = new FileReader(); + reader.onload = function(e) { + var data = getJson(e.target.result); + if (data) { + sendAction("restore", data); + } else { + alert(messages[4]); + } + }; + reader.readAsText(inputFile); + + return false; + +} + +function doRestore() { + if (typeof window.FileReader !== "function") { + alert("The file API isn't supported on this browser yet."); + } else { + $("#uploader").click(); + } + return false; +} + +function doFactoryReset() { + var response = window.confirm("Are you sure you want to restore to factory settings?"); + if (response === false) { + return false; + } + websock.send(JSON.stringify({"action": "factory_reset"})); + doReload(5000); + return false; +} + +function doToggle(element, value) { + var id = parseInt(element.attr("data"), 10); + sendAction("relay", {id: id, status: value ? 1 : 0 }); + return false; +} + +function doScan() { + $("#scanResult").html(""); + $("div.scan.loading").show(); + sendAction("scan", {}); + return false; +} + +function doHAConfig() { + $("#haConfig").html(""); + sendAction("haconfig", {}); + return false; +} + +function doDebugCommand() { + var el = $("input[name='dbgcmd']"); + var command = el.val(); + el.val(""); + sendAction("dbgcmd", {command: command}); + return false; +} + +function doDebugClear() { + $("#weblog").text(""); + return false; +} + +// ----------------------------------------------------------------------------- +// Visualization +// ----------------------------------------------------------------------------- + +function toggleMenu() { + $("#layout").toggleClass("active"); + $("#menu").toggleClass("active"); + $("#menuLink").toggleClass("active"); +} + +function showPanel() { + $(".panel").hide(); + if ($("#layout").hasClass("active")) { toggleMenu(); } + $("#" + $(this).attr("data")).show(). + find("input[type='checkbox']"). + iphoneStyle("calculateDimensions"). + iphoneStyle("refresh"); +} + +// ----------------------------------------------------------------------------- +// Relays & magnitudes mapping +// ----------------------------------------------------------------------------- + +function createRelayList(data, container, template_name) { + + var current = $("#" + container + " > div").length; + if (current > 0) { return; } + + var template = $("#" + template_name + " .pure-g")[0]; + for (var i in data) { + var line = $(template).clone(); + $("label", line).html("Switch #" + i); + $("input", line).attr("tabindex", 40 + i).val(data[i]); + line.appendTo("#" + container); + } + +} + +function createMagnitudeList(data, container, template_name) { + + var current = $("#" + container + " > div").length; + if (current > 0) { return; } + + var template = $("#" + template_name + " .pure-g")[0]; + for (var i in data) { + var magnitude = data[i]; + var line = $(template).clone(); + $("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10)); + $("div.hint", line).html(magnitude.name); + $("input", line).attr("tabindex", 40 + i).val(magnitude.idx); + line.appendTo("#" + container); + } + +} + +// ----------------------------------------------------------------------------- +// Wifi +// ----------------------------------------------------------------------------- + +function delNetwork() { + var parent = $(this).parents(".pure-g"); + $(parent).remove(); +} + +function moreNetwork() { + var parent = $(this).parents(".pure-g"); + $(".more", parent).toggle(); +} + +function addNetwork() { + + var numNetworks = $("#networks > div").length; + if (numNetworks >= maxNetworks) { + alert("Max number of networks reached"); + return null; + } + + var tabindex = 200 + numNetworks * 10; + var template = $("#networkTemplate").children(); + var line = $(template).clone(); + $(line).find("input").each(function() { + $(this).attr("tabindex", tabindex); + tabindex++; + }); + $(line).find(".button-del-network").on("click", delNetwork); + $(line).find(".button-more-network").on("click", moreNetwork); + line.appendTo("#networks"); + + return line; + +} + +// ----------------------------------------------------------------------------- +// Relays scheduler +// ----------------------------------------------------------------------------- +function delSchedule() { + var parent = $(this).parents(".pure-g"); + $(parent).remove(); +} + +function moreSchedule() { + var parent = $(this).parents(".pure-g"); + $("div.more", parent).toggle(); +} + +function addSchedule(event) { + var numSchedules = $("#schedules > div").length; + if (numSchedules >= maxSchedules) { + alert("Max number of schedules reached"); + return null; + } + var tabindex = 200 + numSchedules * 10; + var template = $("#scheduleTemplate").children(); + var line = $(template).clone(); + + var type = (1 === event.data.schType) ? "switch" : "light"; + + template = $("#" + type + "ActionTemplate").children(); + var actionLine = template.clone(); + $(line).find("#schActionDiv").append(actionLine); + + $(line).find("input").each(function() { + $(this).attr("tabindex", tabindex); + tabindex++; + }); + $(line).find(".button-del-schedule").on("click", delSchedule); + $(line).find(".button-more-schedule").on("click", moreSchedule); + line.appendTo("#schedules"); + + $(line).find("input[type='checkbox']"). + prop("checked", false). + iphoneStyle("calculateDimensions"). + iphoneStyle("refresh"); + + return line; +} + +// ----------------------------------------------------------------------------- +// Relays +// ----------------------------------------------------------------------------- + +function initRelays(data) { + + var current = $("#relays > div").length; + if (current > 0) { return; } + + var template = $("#relayTemplate .pure-g")[0]; + for (var i=0; i").attr("value",i).text("Switch #" + i)); + + } + + +} + +function initRelayConfig(data) { + + var current = $("#relayConfig > div").length; + if (current > 0) { return; } + + var template = $("#relayConfigTemplate").children(); + for (var i in data) { + var relay = data[i]; + var line = $(template).clone(); + $("span.gpio", line).html(relay.gpio); + $("span.id", line).html(i); + $("select[name='relayBoot']", line).val(relay.boot); + $("select[name='relayPulse']", line).val(relay.pulse); + $("input[name='relayTime']", line).val(relay.pulse_ms); + $("input[name='mqttGroup']", line).val(relay.group); + $("select[name='mqttGroupInv']", line).val(relay.group_inv); + $("select[name='relayOnDisc']", line).val(relay.on_disc); + line.appendTo("#relayConfig"); + } + +} + +// ----------------------------------------------------------------------------- +// Sensors & Magnitudes +// ----------------------------------------------------------------------------- + +function initMagnitudes(data) { + + // check if already initialized + var done = $("#magnitudes > div").length; + if (done > 0) { return; } + + // add templates + var template = $("#magnitudeTemplate").children(); + for (var i in data) { + var magnitude = data[i]; + var line = $(template).clone(); + $("label", line).html(magnitudeType(magnitude.type) + " #" + parseInt(magnitude.index, 10)); + $("div.hint", line).html(magnitude.description); + $("input", line).attr("data", i); + line.appendTo("#magnitudes"); + } + +} + +// ----------------------------------------------------------------------------- +// Lights +// ----------------------------------------------------------------------------- + +function initColorRGB() { + + // check if already initialized + var done = $("#colors > div").length; + if (done > 0) { return; } + + // add template + var template = $("#colorRGBTemplate").children(); + var line = $(template).clone(); + line.appendTo("#colors"); + + // init color wheel + $("input[name='color']").wheelColorPicker({ + sliders: "wrgbp" + }).on("sliderup", function() { + var value = $(this).wheelColorPicker("getValue", "css"); + sendAction("color", {rgb: value}); + }); + + // init bright slider + $("#brightness").on("change", function() { + var value = $(this).val(); + var parent = $(this).parents(".pure-g"); + $("span", parent).html(value); + sendAction("color", {brightness: value}); + }); + +} + +function initColorHSV() { + + // check if already initialized + var done = $("#colors > div").length; + if (done > 0) { return; } + + // add template + var template = $("#colorHSVTemplate").children(); + var line = $(template).clone(); + line.appendTo("#colors"); + + // init color wheel + $("input[name='color']").wheelColorPicker({ + sliders: "whsvp" + }).on("sliderup", function() { + var color = $(this).wheelColorPicker("getColor"); + var value = parseInt(color.h * 360, 10) + "," + parseInt(color.s * 100, 10) + "," + parseInt(color.v * 100, 10); + sendAction("color", {hsv: value}); + }); + +} + +function initChannels(num) { + + // check if already initialized + var done = $("#channels > div").length > 0; + if (done) { return; } + + // does it have color channels? + var colors = $("#colors > div").length > 0; + + // calculate channels to create + var max = num; + if (colors) { + max = num % 3; + if ((max > 0) & useWhite) { + max--; + } + } + var start = num - max; + + var onChannelSliderChange = function() { + var id = $(this).attr("data"); + var value = $(this).val(); + var parent = $(this).parents(".pure-g"); + $("span", parent).html(value); + sendAction("channel", {id: id, value: value}); + }; + + // add templates + var template = $("#channelTemplate").children(); + for (var i=0; i").attr("value",i).text("Channel #" + i)); + + } + +} + +// ----------------------------------------------------------------------------- +// RFBridge +// ----------------------------------------------------------------------------- + +function rfbLearn() { + var parent = $(this).parents(".pure-g"); + var input = $("input", parent); + sendAction("rfblearn", {id: input.attr("data-id"), status: input.attr("data-status")}); +} + +function rfbForget() { + var parent = $(this).parents(".pure-g"); + var input = $("input", parent); + sendAction("rfbforget", {id: input.attr("data-id"), status: input.attr("data-status")}); +} + +function rfbSend() { + var parent = $(this).parents(".pure-g"); + var input = $("input", parent); + sendAction("rfbsend", {id: input.attr("data-id"), status: input.attr("data-status"), data: input.val()}); +} + +function addRfbNode() { + + var numNodes = $("#rfbNodes > legend").length; + + var template = $("#rfbNodeTemplate").children(); + var line = $(template).clone(); + var status = true; + $("span", line).html(numNodes); + $(line).find("input").each(function() { + $(this).attr("data-id", numNodes); + $(this).attr("data-status", status ? 1 : 0); + status = !status; + }); + $(line).find(".button-rfb-learn").on("click", rfbLearn); + $(line).find(".button-rfb-forget").on("click", rfbForget); + $(line).find(".button-rfb-send").on("click", rfbSend); + line.appendTo("#rfbNodes"); + + return line; +} + +// ----------------------------------------------------------------------------- +// Processing +// ----------------------------------------------------------------------------- + +function processData(data) { + + // title + if ("app_name" in data) { + var title = data.app_name; + if ("app_version" in data) { + title = title + " " + data.app_version; + } + $("span[name=title]").html(title); + if ("hostname" in data) { + title = data.hostname + " - " + title; + } + document.title = title; + } + + Object.keys(data).forEach(function(key) { + + var i; + var value = data[key]; + + // --------------------------------------------------------------------- + // Web mode + // --------------------------------------------------------------------- + + if ("webMode" === key) { + password = (1 === value); + $("#layout").toggle(!password); + $("#password").toggle(password); + } + + // --------------------------------------------------------------------- + // Actions + // --------------------------------------------------------------------- + + if ("action" === key) { + if ("reload" === data.action) { doReload(1000); } + return; + } + + // --------------------------------------------------------------------- + // RFBridge + // --------------------------------------------------------------------- + + if ("rfbCount" === key) { + for (i=0; i 0 && position === key.length - 7) { + var module = key.slice(0,-7); + $(".module-" + module).show(); + return; + } + + if ("now" === key) { + now = value; + ago = 0; + return; + } + + if ("free_size" === key) { + free_size = parseInt(value, 10); + } + + // Pre-process + if ("network" === key) { + value = value.toUpperCase(); + } + if ("mqttStatus" === key) { + value = value ? "CONNECTED" : "NOT CONNECTED"; + } + if ("ntpStatus" === key) { + value = value ? "SYNC'D" : "NOT SYNC'D"; + } + if ("uptime" === key) { + var uptime = parseInt(value, 10); + var seconds = uptime % 60; uptime = parseInt(uptime / 60, 10); + var minutes = uptime % 60; uptime = parseInt(uptime / 60, 10); + var hours = uptime % 24; uptime = parseInt(uptime / 24, 10); + var days = uptime; + value = days + "d " + zeroPad(hours, 2) + "h " + zeroPad(minutes, 2) + "m " + zeroPad(seconds, 2) + "s"; + } + + // --------------------------------------------------------------------- + // Matching + // --------------------------------------------------------------------- + + var pre; + var post; + + // Look for INPUTs + var input = $("input[name='" + key + "']"); + if (input.length > 0) { + if (input.attr("type") === "checkbox") { + input. + prop("checked", value). + iphoneStyle("refresh"); + } else if (input.attr("type") === "radio") { + input.val([value]); + } else { + pre = input.attr("pre") || ""; + post = input.attr("post") || ""; + input.val(pre + value + post); + } + } + + // Look for SPANs + var span = $("span[name='" + key + "']"); + if (span.length > 0) { + pre = span.attr("pre") || ""; + post = span.attr("post") || ""; + span.html(pre + value + post); + } + + // Look for SELECTs + var select = $("select[name='" + key + "']"); + if (select.length > 0) { + select.val(value); + } + + }); + + // Auto generate an APIKey if none defined yet + if ($("input[name='apiKey']").val() === "") { + generateAPIKey(); + } + + resetOriginals(); + +} + +function hasChanged() { + + var newValue, originalValue; + if ($(this).attr("type") === "checkbox") { + newValue = $(this).prop("checked"); + originalValue = ($(this).attr("original") === "true"); + } else { + newValue = $(this).val(); + originalValue = $(this).attr("original"); + } + var hasChanged = $(this).attr("hasChanged") || 0; + var action = $(this).attr("action"); + + if (typeof originalValue === "undefined") { return; } + if ("none" === action) { return; } + + if (newValue !== originalValue) { + if (0 === hasChanged) { + ++numChanged; + if ("reconnect" === action) { ++numReconnect; } + if ("reboot" === action) { ++numReboot; } + if ("reload" === action) { ++numReload; } + $(this).attr("hasChanged", 1); + } + } else { + if (1 === hasChanged) { + --numChanged; + if ("reconnect" === action) { --numReconnect; } + if ("reboot" === action) { --numReboot; } + if ("reload" === action) { --numReload; } + $(this).attr("hasChanged", 0); + } + } + +} + +// ----------------------------------------------------------------------------- +// Init & connect +// ----------------------------------------------------------------------------- + +function connect(host) { + + if (typeof host === "undefined") { + host = window.location.href.replace("#", ""); + } else { + if (host.indexOf("http") !== 0) { + host = "http://" + host + "/"; + } + } + if (host.indexOf("http") !== 0) { return; } + + webhost = host; + var wshost = host.replace("http", "ws") + "ws"; + + if (websock) { websock.close(); } + websock = new WebSocket(wshost); + websock.onmessage = function(evt) { + var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")); + if (data) { + processData(data); + } + }; +} + +$(function() { + + initMessages(); + loadTimeZones(); + setInterval(function() { keepTime(); }, 1000); + + $("#menuLink").on("click", toggleMenu); + $(".pure-menu-link").on("click", showPanel); + $("progress").attr({ value: 0, max: 100 }); + + $(".button-update").on("click", doUpdate); + $(".button-update-password").on("click", doUpdatePassword); + $(".button-reboot").on("click", doReboot); + $(".button-reconnect").on("click", doReconnect); + $(".button-wifi-scan").on("click", doScan); + $(".button-ha-config").on("click", doHAConfig); + $(".button-dbgcmd").on("click", doDebugCommand); + $("input[name='dbgcmd']").enterKey(doDebugCommand); + $(".button-dbg-clear").on("click", doDebugClear); + $(".button-settings-backup").on("click", doBackup); + $(".button-settings-restore").on("click", doRestore); + $(".button-settings-factory").on("click", doFactoryReset); + $("#uploader").on("change", onFileUpload); + $(".button-upgrade").on("click", doUpgrade); + + $(".button-apikey").on("click", generateAPIKey); + $(".button-upgrade-browse").on("click", function() { + $("input[name='upgrade']")[0].click(); + return false; + }); + $("input[name='upgrade']").change(function (){ + var file = this.files[0]; + $("input[name='filename']").val(file.name); + }); + $(".button-add-network").on("click", function() { + $(".more", addNetwork()).toggle(); + }); + $(".button-add-switch-schedule").on("click", { schType: 1 }, addSchedule); + $(".button-add-light-schedule").on("click", { schType: 2 }, addSchedule); + + $(document).on("change", "input", hasChanged); + $(document).on("change", "select", hasChanged); + + connect(); + +}); diff --git a/html/favicon.ico b/html/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..eeafbe449063dd5d361db30a3a6858896eaa4475 GIT binary patch literal 1150 zcmaizOK4M35QZmh(u5{8FDq)WEhL7z_fbIvUFgD%A_zX{MwE(+f>~H`5kYX{i=wD& zk%|-qsS6D*x+qeOS(pl14K@Z_5f{?trcG{=`}?1yHzYw4!gpupKl4w{$;lP5@Z<3a zp93;t71<;rgM<>5s?H$7HNUe;rEc>GLF;JK(sG&pDCuw6w7$qbS3_S6yMu-X>ZA2K z>l5|0nNuGp4@gL9){n--*5x&9hZe8&BoPU$tf_fn)6)>gVeQMU7TAyAaxP(w8+rF) z5xsv3=$J85UC#}Kp}lYLG#Bp>=~*bkPmi1fBnoVPsKIg?*qAJ+?>^aVq2?gF$VeF3 z8_razY@0W5bZj1xp|?17U=DH~a6Kxa@7t<6&s@mRE|_!ZJ47Z(UhUnJ(P5i1aPZ;+ zUIm`uMgLRmJhoJsA!h*Jq^@SDy=2L8bB>C=^SS%Bmt}NL0{iY{&@)`X=-%0i*KxND zUq=7z{iToXKz3WbPvx$d7I4OL@Gk1ukS@ZWC_{#U?%PFJE-_v!VG!A~&AwWFZ^g%b zP4PE|_ot4o*JW5A8|eOCg8ge5?xc=jUMmKn*=nA;gl8Edm9y~W|j*Z=?jU-3V;pKD04vtxj(k)8oFBLf42;!hS< zE(T@>9UvR1L4<*U$!1Rf%G13{cbWob39$7tnDk`EJl9Pqo3OM~N%pf@q1Pc(TlH1@ z^mg7h{?o8??)q;=a_qYwUVPaQwJgM$5n&(;0}}%e0~^plu{pAZ-7C2d6nJ$AK3)@+ zQdu(nSOl_C4xmylAY@(@GWJg@Zp(fc$2$xj>%98hy5=GZe-tePX!W6+ZMRxs&_b-^6=wnrvT ztO$cyfQG`{YqFA|Y==aJMvLNY$!m|66~uZ}B_er^32v_V921ax{5ey9008fN{CIA2c literal 0 HcmV?d00001 diff --git a/html/index.html b/html/index.html new file mode 100755 index 0000000..c7ad105 --- /dev/null +++ b/html/index.html @@ -0,0 +1,1425 @@ + + + + + ESPurna 0.0.0 + + + + + + + + + + + + + + + + + +

+ +
+ + + + + + + +
+ +
+ +
+

STATUS

+

Current configuration

+
+ +
+ +
+
+ +
+ +
+ +
+ +
+ +
+ +
Manufacturer
+
+ +
Device
+
+ +
Chip ID
+
+ +
Wifi MAC
+
+ +
SDK version
+
+ +
Core version
+
+ +
Firmware name
+
+ +
Firmware version
+
+ +
Firmware revision
+
+ +
Firmware build date
+
+ +
Firmware size
+
+ +
Free space
+
+ +
+ +
+ +
Network
+
+ +
BSSID
+
+ +
Channel
+
+ +
RSSI
+
+ +
IP
+
+ +
Free heap
+
+ +
Load average
+
%
+ +
VCC
+
mV
+ +
MQTT Status
+
NOT AVAILABLE
+ +
NTP Status
+
NOT AVAILABLE
+ +
Current time
+
+ +
Uptime
+
+ +
Last update
+
? seconds ago
+ +
+ +
+ +
+
+
+ +
+ +
+ +
+

GENERAL

+

General configuration values

+
+ +
+ +
+ +
+ + +
+
+
+ This name will identify this device in your network (http://<hostname>.local). For this setting to take effect you should restart the wifi interface by clicking the "Reconnect" button. +
+
+ +
+ + +
+
+
+ Delay in milliseconds to detect a double click (from 0 to 1000ms).
+ The lower this number the faster the device will respond to button clicks but the harder it will be to get a double click. + Increase this number if you are having trouble to double click the button. + Set this value to 0 to disable double click. You won't be able to set the device in AP mode manually but your device will respond immediately to button clicks.
+ You will have to reboot the device after updating for this setting to apply. +
+
+ +
+ + +
+
+
+ This setting defines the behaviour of the main LED in the board.
+ When in "WiFi status" it will blink at 1Hz when trying to connect. If successfully connected it will briefly blink every 5 seconds if in STA mode or every second if in AP mode.
+ When in "Relay status" mode the LED will be ON whenever any relay is ON, and OFF otherwise. This is global status notification.
+ When in "MQTT managed" mode you will be able to set the LED state sending a message to "<base_topic>/led/0/set" with a payload of 0, 1 or 2 (to toggle it).
+ When in "Find me" mode the LED will be ON when all relays are OFF. This is meant to locate switches at night.
+ When in "Relay & WiFi" mode it will follow the WiFi status but will stay mostly off when relays are OFF, and mostly ON when any of them is ON.
+ When in "Find me & WiFi" mode is the opposite of the "Relay & WiFi", it will follow the WiFi status but will stay mostly on when relays are OFF, and mostly OFF when any of them is ON.
+ "Always ON" and "Always OFF" modes are self-explanatory. +
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+

SWITCHES

+

Switch / relay configuration

+
+ +
+ +
+ + General + +
+ + +
+
Define how the different switches should be synchronized.
+
+ +
+ +
+ +
+
+ +
+ +
+

LIGHTS

+

Lights configuration

+
+ +
+ +
+ +
+ +
+
+
+
Use the first three channels as RGB channels. This will also enable the color picker in the web UI. Will only work if the device has at least 3 dimmable channels.
Reload the page to update the web interface.
+
+ +
+ +
+
+
+
Use RGB color picker if enabled (plus brightness), otherwise use HSV (hue-saturation-value) style
+
+ +
+ +
+
+
+
Use forth dimmable channel as white when first 3 have the same RGB value.
Will only work if the device has at least 4 dimmable channels.
Enabling this will render useless the "Channel 4" slider in the status page.
Reload the page to update the web interface.
+
+ +
+ +
+
+
+
Use gamma correction for RGB channels.
Will only work if "use colorpicker" above is also ON.
+
+ +
+ +
+
+
+
Use CSS style to report colors to MQTT and REST API.
Red will be reported as "#FF0000" if ON, otherwise "255,0,0"
+
+ +
+ +
+
+
+
If enabled color changes will be smoothed.
+
+ +
+ +
+
+
+
Time in millisecons to transition from one color to another.
+
+ +
+
+
+
+
Sync color between different lights.
+
+ +
+
+
+ +
+ +
+

ADMINISTRATION

+

Device administration and security settings

+
+ +
+ +
+ +
+ +
+
+
+
+ +
+ + +
+
+ The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).
+ It must have at least five characters (numbers and letters and any of these special characters: _,.;:~!?@#$%^&*<>\|(){}[]) and at least one lowercase and one uppercase or one number.
+
+ +
+ + +
+ +
+ + +
+
+
+ This is the port for the web interface and API requests. + If different than 80 (standard HTTP port) you will have to add it explicitly to your requests: http://myip:myport/ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+
+
+ By default, some magnitudes are being preprocessed and filtered to avoid spurious values. + If you want to get real-time values (not preprocessed) in the API turn on this setting. +
+
+ +
+ + +
+
+
+ This is the key you will have to pass with every HTTP request to the API, either to get or write values. + All API calls must contain the apikey parameter with the value above. + To know what APIs are enabled do a call to /apis. +
+
+ +
+ +
+
+
+
Turn ON to be able to telnet to your device while connected to your home router.
TELNET is always enabled in AP mode.
+
+ + +
+ +
+
+ +
+ + +
+
This name address of the NoFUSS server for automatic remote updates (see https://bitbucket.org/xoseperez/nofuss).
+
+ +
+ + +
+
+
+
The device has bytes available for OTA updates. If your image is larger than this consider doing a two-step update.
+
+
+ +
+ +
+
+
+ +
+ +
+

WIFI

+

You can configure up to 5 different WiFi networks. The device will try to connect in order of signal strength.

+
+ +
+ +
+ + General + +
+ +
+
+
+
+ ESPurna will scan for visible WiFi SSIDs and try to connect to networks defined below in order of signal strength, even if multiple AP share the same SSID. + When disabled, ESPurna will try to connect to the networks in the same order they are listed below. + Disable this option if you are connecting to a single access point (or router) or to a hidden SSID. + +
+
+ +
+
+ +
+ +
+ + Networks + +
+ + + +
+
+
+ +
+ +
+

SCHEDULE

+

Turn switches ON and OFF based on the current time.

+
+ +
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ +
+

MQTT

+

Configure an MQTT broker in your network and you will be able to change the switch status via an MQTT message.

+
+ +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ If left empty the firmware will generate a client ID based on the serial number of the chip. +
+
+ +
+ + +
+ +
+ +
+
+ +
+ + +
+ +
+ +
+
+ +
+ + +
+
+ This is the fingerprint for the SSL certificate of the server.
+ You can get it using https://www.grc.com/fingerprints.htm
+ or using openssl from a linux box by typing:
+
$ openssl s_client -connect <host>:<port> < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin
+
+
+ +
+ + +
+
+ This is the root topic for this device. The {hostname} and {mac} placeholders will be replaced by the device hostname and MAC address.
+ - <root>/relay/#/set Send a 0 or a 1 as a payload to this topic to switch it on or off. You can also send a 2 to toggle its current state. Replace # with the switch ID (starting from 0). If the board has only one switch it will be 0.
+ - <root>/rgb/set Set the color using this topic, your can either send an "#RRGGBB" value or "RRR,GGG,BBB" (0-255 each).
+ - <root>/hsv/set Set the color using hue (0-360), saturation (0-100) and value (0-100) values, comma separated.
+ - <root>/brightness/set Set the brighness (0-255).
+ - <root>/channel/#/set Set the value for a single color channel (0-255). Replace # with the channel ID (starting from 0 and up to 4 for RGBWC lights).
+ - <root>/mired/set Set the temperature color in mired.
+ - <root>/status The device will report a 1 to this topic every few minutes. Upon MQTT disconnecting this will be set to 0.
+ - Other values reported (depending on the build) are: firmware and version, hostname, IP, MAC, signal strenth (RSSI), uptime (in seconds), free heap and power supply. +
+
+ +
+ +
+
+
+ All messages (except the device status) will be included in a JSON payload along with the timestamp and hostname + and sent under the <root>/data topic.
+ Messages will be queued and sent after 100ms, so different messages could be merged into a single payload.
+ Subscriptions will still be done to single topics. +
+
+ +
+
+ +
+ +
+ +
+

NTP

+

Configure your NTP (Network Time Protocol) servers and local configuration to keep your device time up to the second for your location.

+
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+ +
+ + +
+ +
+
+ +
+ +
+ +
+

DOMOTICZ

+

+ Configure the connection to your Domoticz server. +

+
+ +
+ +
+ + General + +
+ +
+
+ +
+ + +
+ +
+ + +
+ + Sensors & actuators + +
+
Set IDX to 0 to disable notifications from that component.
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+

HOME ASSISTANT

+

+ Add this device to your Home Assistant. +

+
+ +
+
+ + Discover + +
+ +
+
+
+
+ Home Assistant auto-discovery feature. Enable and save to add the device to your HA console. + When using a colour light you might want to disable CSS style so Home Assistant can parse the color. +
+
+ +
+ + +
+ + Configuration + +
+ +
+
+
+ These are the settings you should copy to your Home Assistant "configuration.yaml" file. + If any of the sections below (switch, light, sensor) already exists, do not duplicate it, + simply copy the contents of the section below the ones already present. +
+
+
+ +
+ + +
+
+ +
+ + + +
+ +
+

THINGSPEAK

+

+ Send your sensors data to Thingspeak. +

+
+ +
+ +
+ + General + +
+ +
+
+ +
+ + +
+ + Sensors & actuators + +
+
Enter the field number to send each data to, 0 disable notifications from that component.
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+

INFLUXDB

+

+ Configure the connection to your InfluxDB server. Leave the host field empty to disable InfluxDB connection. +

+
+ +
+ +
+ +
+ +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+

DEBUG LOG

+

+ Shows debug messages from the device +

+
+ +
+ +
+ +
+
+ Write a command and click send to execute it on the device. The output will be shown in the debug text area below. +
+ +
+
+ +
+ +
+
+ +
+ +
+ +
+ +
+ +
+

SENSOR CONFIGURATION

+

+ Configure and calibrate your device sensors. +

+
+ +
+ +
+ + General + +
+ + +
+
+
+ Select the interval between readings. These will be filtered and averaged for the report. The default and recommended value is 6 seconds. +
+
+ +
+ +
+
+
+
+ Select the number of readings to average and report +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ Temperature correction value is added to the measured value which may be inaccurate due to many factors. The value can be negative. +
+
+ +
+ + +
+
+
+ Humidity correction value is added to the measured value which may be inaccurate due to many factors. The value can be negative. +
+
+ + Energy monitor + +
+ + +
+
Mains voltage in your system (in V).
+
+ +
+ + +
+
In Amperes (A). If you are using a pure resistive load like a bulb, this will be the ratio between the two previous values, i.e. power / voltage. You can also use a current clamp around one of the power wires to get this value.
+
+ +
+ + +
+
In Volts (V). Enter your the nominal AC voltage for your household or facility, or use multimeter to get this value.
+
+ +
+ + +
+
In Watts (W). Calibrate your sensor connecting a pure resistive load (like a bulb) and enter here the its nominal power or use a multimeter.
+
+ +
+ +
+
+
+
Move this switch to ON and press "Save" to revert to factory calibration values.
+
+ +
+ +
+
+
+
Move this switch to ON and press "Save" to set energy count to 0.
+
+ +
+
+ +
+ +
+ +
+

RADIO FREQUENCY

+

+ Sonoff 433 RF Bridge & RF Link Configuration

+ This page allows you to configure the RF codes for the Sonoff RFBridge 433 and also for a basic RF receiver.

+ To learn a new code click LEARN (the Sonoff RFBridge will beep) then press a button on the remote, the new code should show up (and the RFBridge will double beep). If the device double beeps but the code does not update it has not been properly learnt. Keep trying.

+ Modify or create new codes manually and then click SAVE to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.

+ Delete any code clicking the FORGET button. +

You can also specify 116-chars long RAW codes. Raw codes require a specific firmware for for the EFM8BB1.
+

+
+ +
+
+
+
+
+
+ +
+ +
+ +
+ + + +
+ + Switch # + +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+ +
+ + +
+
+ + + + + + +
+
Leave empty for DNS negotiation
+ + + +
+
Set when using a static IP
+ + + +
+
Usually 255.255.255.0 for /24 networks
+ + + +
+
Set the Domain Name Server IP to use when using a static IP
+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
 h
+
+
+ +
 m
+
+
+ + +
+ +
+
 1 for Monday, 2 for Tuesday...
+ +
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+
+
+ +
+ Switch # (GPIO) +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + +
+ +
+ + + +
+
+ +
+
+ + +
+
+ +
+
+ + + +
+
+ +
+
+ +
+ +
+
+
+
+ + + + + + + + + + + + + + diff --git a/html/vendor/checkboxes.css b/html/vendor/checkboxes.css new file mode 100755 index 0000000..cf39d14 --- /dev/null +++ b/html/vendor/checkboxes.css @@ -0,0 +1,120 @@ +.iPhoneCheckContainer { + -webkit-transform:translate3d(0,0,0); + position: relative; + height: 30px; + cursor: pointer; + overflow: hidden; + margin: 5px 0 10px 0; +} + +.iPhoneCheckContainer input { + position: absolute; + top: 5px; + left: 30px; + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); + opacity: 0; +} + +.iPhoneCheckContainer label { + white-space: nowrap; + font-size: 17px; + line-height: 17px; + font-weight: bold; + font-family: "Helvetica Neue", Arial, Helvetica, sans-serif; + cursor: pointer; + display: block; + height: 27px; + position: absolute; + width: auto; + top: 0; + padding-top: 5px; + overflow: hidden; +} + +.iPhoneCheckContainer, .iPhoneCheckContainer label { + user-select: none; + -moz-user-select: none; + -khtml-user-select: none; +} + +.iPhoneCheckDisabled { + filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50); + opacity: 0.5; +} + +div.iPhoneCheckBorderOn { + position: absolute; + left: 0; + width: 4px; + height: 100%; + background-image: url('images/border-on.png'); + background-repeat: no-repeat; +} + +label.iPhoneCheckLabelOn { + color: white; + background-image: url('images/label-on.png'); + background-repeat: repeat-x; + text-shadow: 0px 0px 2px rgba(0, 0, 0, 0.6); + left: 0; + padding-top: 5px; + margin-left: 4px; + margin-top: 0px; +} + +label.iPhoneCheckLabelOn span { + padding-left: 4px; +} + +label.iPhoneCheckLabelOff { + color: #8b8b8b; + background-image: url('images/label-off.png'); + background-repeat: repeat-x; + text-shadow: 0px 0px 2px rgba(255, 255, 255, 0.6); + text-align: right; + margin-right: 4px; + margin-top: 0px; + right: 0; +} + +label.iPhoneCheckLabelOff span { + padding-right: 4px; +} + +div.iPhoneCheckBorderOff { + position: absolute; + width: 4px; + height: 100%; + background-image: url('images/border-off.png'); + background-repeat: no-repeat; +} + +.iPhoneCheckHandle { + display: block; + height: 27px; + cursor: pointer; + position: absolute; + top: 0; + left: 0; + width: 0; + background-image: url('images/handle-left.png'); + background-repeat: no-repeat; + padding-left: 4px; +} + +.iPhoneCheckHandleCenter { + position: absolute; + width: 100%; + height: 100%; + background-image: url('images/handle-center.png'); + background-repeat: repeat-x; +} + +.iPhoneCheckHandleRight { + position: absolute; + height: 100%; + width: 4px; + right: 0px; + background-image: url('images/handle-right.png'); + background-repeat: no-repeat; +} diff --git a/html/vendor/checkboxes.js b/html/vendor/checkboxes.js new file mode 100755 index 0000000..a77b770 --- /dev/null +++ b/html/vendor/checkboxes.js @@ -0,0 +1,366 @@ +// Generated by CoffeeScript 1.6.2 +/*eslint quotes: ["error", "double"]*/ +/*eslint-env es6*/ + +(function() { + var iOSCheckbox, matched, userAgent, + __slice = [].slice; + + if ($.browser == null) { + userAgent = navigator.userAgent || ""; + jQuery.uaMatch = function(ua) { + var match; + + ua = ua.toLowerCase(); + match = /(chrome)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version)?[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+))?/.exec(ua) || []; + return { + browser: match[1] || "", + version: match[2] || "0" + }; + }; + matched = jQuery.uaMatch(userAgent); + jQuery.browser = {}; + if (matched.browser) { + jQuery.browser[matched.browser] = true; + jQuery.browser.version = matched.version; + } + if (jQuery.browser.webkit) { + jQuery.browser.safari = true; + } + } + + iOSCheckbox = (function() { + function iOSCheckbox(elem, options) { + var key, opts, value; + + this.elem = $(elem); + opts = $.extend({}, iOSCheckbox.defaults, options); + for (key in opts) { + if ({}.hasOwnProperty.call(opts, key)) { + value = opts[key]; + this[key] = value; + } + } + this.elem.data(this.dataName, this); + this.wrapCheckboxWithDivs(); + this.attachEvents(); + this.disableTextSelection(); + this.calculateDimensions(); + } + + iOSCheckbox.prototype.calculateDimensions = function() { + if (this.resizeHandle) { + this.optionallyResize("handle"); + } + if (this.resizeContainer) { + this.optionallyResize("container"); + } + return this.initialPosition(); + }; + + iOSCheckbox.prototype.isDisabled = function() { + return this.elem.is(":disabled"); + }; + + iOSCheckbox.prototype.wrapCheckboxWithDivs = function() { + this.elem.wrap("
"); + this.container = this.elem.parent(); + this.offLabel = $("").appendTo(this.container); + this.offSpan = this.offLabel.children("span"); + this.onLabel = $("").appendTo(this.container); + this.onBorder = $("
").appendTo(this.container); + this.offBorder = $("
").appendTo(this.container); + this.onSpan = this.onLabel.children("span"); + this.handle = $("
").appendTo(this.container); + this.handleCenter = $("
").appendTo(this.handle); + this.handleRight = $("
").appendTo(this.handle); + return true; + }; + + iOSCheckbox.prototype.disableTextSelection = function() { + if ($.browser.msie) { + return $([this.handle, this.offLabel, this.onLabel, this.container]).attr("unselectable", "on"); + } + }; + + iOSCheckbox.prototype._getDimension = function(elem, dimension) { + if ($.fn.actual != null) { + return elem.actual(dimension); + } else { + return elem[dimension](); + } + }; + + iOSCheckbox.prototype.optionallyResize = function(mode) { + var newWidth, offLabelWidth, offSpan, onLabelWidth, onSpan; + + onSpan = this.onLabel.find("span"); + onLabelWidth = this._getDimension(onSpan, "width"); + onLabelWidth += parseInt(onSpan.css("padding-left"), 10); + offSpan = this.offLabel.find("span"); + offLabelWidth = this._getDimension(offSpan, "width"); + offLabelWidth += parseInt(offSpan.css("padding-right"), 10); + if (mode === "container") { + newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth; + newWidth += this._getDimension(this.handle, "width") + this.handleMargin; + return this.container.css({ + width: newWidth + }); + } else { + newWidth = onLabelWidth > offLabelWidth ? onLabelWidth : offLabelWidth; + this.handleCenter.css({ + width: newWidth + 4 + }); + return this.handle.css({ + width: newWidth + 7 + }); + } + }; + + iOSCheckbox.prototype.onMouseDown = function(event) { + var x; + + event.preventDefault(); + if (this.isDisabled()) { + return; + } + x = event.pageX || event.originalEvent.changedTouches[0].pageX; + iOSCheckbox.currentlyClicking = this.handle; + iOSCheckbox.dragStartPosition = x; + return iOSCheckbox.handleLeftOffset = parseInt(this.handle.css("left"), 10) || 0; + }; + + iOSCheckbox.prototype.onDragMove = function(event, x) { + var newWidth, p; + + if (iOSCheckbox.currentlyClicking !== this.handle) { + return; + } + p = (x + iOSCheckbox.handleLeftOffset - iOSCheckbox.dragStartPosition) / this.rightSide; + if (p < 0) { + p = 0; + } + if (p > 1) { + p = 1; + } + newWidth = p * this.rightSide; + this.handle.css({ + left: newWidth + }); + this.onLabel.css({ + width: newWidth + this.handleRadius + }); + this.offSpan.css({ + marginRight: -newWidth + }); + return this.onSpan.css({ + marginLeft: -(1 - p) * this.rightSide + }); + }; + + iOSCheckbox.prototype.onDragEnd = function(event, x) { + var p; + + if (iOSCheckbox.currentlyClicking !== this.handle) { + return; + } + if (this.isDisabled()) { + return; + } + if (iOSCheckbox.dragging) { + p = (x - iOSCheckbox.dragStartPosition) / this.rightSide; + this.elem.prop("checked", p >= 0.5).change(); + } else { + this.elem.prop("checked", !this.elem.prop("checked")).change(); + } + iOSCheckbox.currentlyClicking = null; + iOSCheckbox.dragging = null; + if (typeof this.onChange === "function") { + this.onChange(this.elem, this.elem.prop("checked")); + } + return this.didChange(); + }; + + iOSCheckbox.prototype.refresh = function() { + return this.didChange(); + }; + + iOSCheckbox.prototype.didChange = function() { + var newLeft; + + if (this.isDisabled()) { + this.container.addClass(this.disabledClass); + return false; + } else { + this.container.removeClass(this.disabledClass); + } + newLeft = this.elem.prop("checked") ? this.rightSide + 2 : 0; + this.handle.animate({ + left: newLeft + }, this.duration); + this.onLabel.animate({ + width: newLeft + this.handleRadius + }, this.duration); + this.offSpan.animate({ + marginRight: - newLeft + }, this.duration); + return this.onSpan.animate({ + marginLeft: newLeft - this.rightSide + }, this.duration); + }; + + iOSCheckbox.prototype.attachEvents = function() { + var localMouseMove, localMouseUp, self; + + self = this; + localMouseMove = function(event) { + return self.onGlobalMove.apply(self, arguments); + }; + localMouseUp = function(event) { + self.onGlobalUp.apply(self, arguments); + $(document).unbind("mousemove touchmove", localMouseMove); + return $(document).unbind("mouseup touchend", localMouseUp); + }; + this.elem.change(function() { + return self.refresh(); + }); + return this.container.bind("mousedown touchstart", function(event) { + self.onMouseDown.apply(self, arguments); + $(document).bind("mousemove touchmove", localMouseMove); + return $(document).bind("mouseup touchend", localMouseUp); + }); + }; + + iOSCheckbox.prototype.initialPosition = function() { + var containerWidth, offset; + + containerWidth = this._getDimension(this.container, "width"); + this.offLabel.css({ + width: containerWidth - this.containerRadius - 4 + }); + this.offBorder.css({ + left: containerWidth - 4 + }); + offset = this.containerRadius + 1; + if ($.browser.msie && $.browser.version < 7) { + offset -= 3; + } + this.rightSide = containerWidth - this._getDimension(this.handle, "width") - offset; + if (this.elem.is(":checked")) { + this.handle.css({ + left: this.rightSide + }); + this.onLabel.css({ + width: this.rightSide + this.handleRadius + }); + this.offSpan.css({ + marginRight: -this.rightSide, + }); + } else { + this.onLabel.css({ + width: 0 + }); + this.onSpan.css({ + marginLeft: -this.rightSide + }); + } + if (this.isDisabled()) { + return this.container.addClass(this.disabledClass); + } + }; + + iOSCheckbox.prototype.onGlobalMove = function(event) { + var x; + + if (!(!this.isDisabled() && iOSCheckbox.currentlyClicking)) { + return; + } + event.preventDefault(); + x = event.pageX || event.originalEvent.changedTouches[0].pageX; + if (!iOSCheckbox.dragging && (Math.abs(iOSCheckbox.dragStartPosition - x) > this.dragThreshold)) { + iOSCheckbox.dragging = true; + } + return this.onDragMove(event, x); + }; + + iOSCheckbox.prototype.onGlobalUp = function(event) { + var x; + + if (!iOSCheckbox.currentlyClicking) { + return; + } + event.preventDefault(); + x = event.pageX || event.originalEvent.changedTouches[0].pageX; + this.onDragEnd(event, x); + return false; + }; + + iOSCheckbox.defaults = { + duration: 200, + checkedLabel: "ON", + uncheckedLabel: "OFF", + resizeHandle: true, + resizeContainer: true, + disabledClass: "iPhoneCheckDisabled", + containerClass: "iPhoneCheckContainer", + labelOnClass: "iPhoneCheckLabelOn", + labelOffClass: "iPhoneCheckLabelOff", + handleClass: "iPhoneCheckHandle", + handleCenterClass: "iPhoneCheckHandleCenter", + handleRightClass: "iPhoneCheckHandleRight", + dragThreshold: 5, + handleMargin: 15, + handleRadius: 4, + containerRadius: 5, + dataName: "iphoneStyle", + onChange: function() {} + }; + + return iOSCheckbox; + + })(); + + $.iphoneStyle = this.iOSCheckbox = iOSCheckbox; + + $.fn.iphoneStyle = function() { + var args, checkbox, dataName, existingControl, method, params, _i, _len, _ref, _ref1, _ref2, _ref3; + + args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; + dataName = (_ref = (_ref1 = args[0]) != null ? _ref1.dataName : void 0) != null ? _ref : iOSCheckbox.defaults.dataName; + _ref2 = this.filter(":checkbox"); + for (_i = 0, _len = _ref2.length; _i < _len; _i++) { + checkbox = _ref2[_i]; + existingControl = $(checkbox).data(dataName); + if (existingControl != null) { + method = args[0], params = 2 <= args.length ? __slice.call(args, 1) : []; + if ((_ref3 = existingControl[method]) != null) { + _ref3.apply(existingControl, params); + } + } else { + new iOSCheckbox(checkbox, args[0]); + } + } + return this; + }; + + $.fn.iOSCheckbox = function(options) { + var opts; + + if (options == null) { + options = {}; + } + opts = $.extend({}, options, { + resizeHandle: false, + disabledClass: "iOSCheckDisabled", + containerClass: "iOSCheckContainer", + labelOnClass: "iOSCheckLabelOn", + labelOffClass: "iOSCheckLabelOff", + handleClass: "iOSCheckHandle", + handleCenterClass: "iOSCheckHandleCenter", + handleRightClass: "iOSCheckHandleRight", + dataName: "iOSCheckbox" + }); + return this.iphoneStyle(opts); + }; + +}).call(this); diff --git a/html/vendor/images/border-off.png b/html/vendor/images/border-off.png new file mode 100755 index 0000000000000000000000000000000000000000..2163fb0d522c6fae5fb3ad7459b1cc60c685d02c GIT binary patch literal 308 zcmV-40n7f0P)+An#Wo7>l3=Dh=#99oKCQbU^+uQrUsHo_FNJt2Psqrvu00x2J z^N+ebg2&0aSuj=LDp8O2$-Os1uh%OieQ=#*u8GTK2iU&mM3KW_0l+Z^`e^9A!`;DJ zD{5_Mt=&K=1)-57cm)1{azWn_=-wfRM2L4lW|A;=v6c$24wd`Ts5lQenjJb+xsbOG zGs4UeKRc)cbW+|sWCj|+2e8-K7sxStj0B+GMsnWbQi6zY?+=jlzb{Px8G!KLKZZAN zUxNg8Gdz0q=>PHKC;qQpyY?**YcXutu;DonuLfdp%mD!Vb2*W0&XQ*U0000Nkl2ce?Ku{2am;K-ZYC9;b(>PGu1#1VFK8+bvw-IUvB&6!wikOB$ zXaOmd5P~uYu0wcn1Q%nKVN)*PRSZkEz`=h4X&5n=V@|1@y&g?y7;On?0IiMgplaY1 z!zj!NEIDNsW>6sCK^IIbU&^T`%oZ0KaAT-s&;~^O+k!9t4T6t>Uj+V9#uSsA->)|e zSiFXD_51yP_wn|C-_LhLf2-xrI0cHi380t*0F#v;|8JT0%>V!Z07*qoM6N<$g7xuw A;{X5v literal 0 HcmV?d00001 diff --git a/html/vendor/images/handle-center.png b/html/vendor/images/handle-center.png new file mode 100755 index 0000000000000000000000000000000000000000..49ceb533e1a3af9469633bcf13293fb3d2789710 GIT binary patch literal 190 zcmeAS@N?(olHy`uVBq!ia0vp^EI=&H!2~3)2=7b>QmvjYjv*C{z7r0vJ!Zhu^i$@& zy~}#pwd;*Ph&*|b?{_Rg;NdR4xyL`-Y*OL=^}aS;w{+q`p{x}*vzZgtu`MtNE$uPy z;7~cv;8dA1EiiW`|J59poORtoA0K7gT5e-JD)l5q)kNIo*!DwSzMOo<3saP8J@^XW pOR@F!thkfC_I~)??`uE*W8_?^79$nTyc_5s22WQ%mvv4FO#p3;N1^}# literal 0 HcmV?d00001 diff --git a/html/vendor/images/handle-left.png b/html/vendor/images/handle-left.png new file mode 100755 index 0000000000000000000000000000000000000000..9d9257d71c2583a65d1e2796d51a6ada0ada3b0e GIT binary patch literal 264 zcmV+j0r&oiP)mneRS+eXOJCeA$DgE$liK#=`;%NYSHXq)3VE-ft>_+ z{`>zU&h1BiY}=Mn3K|0Rt2uY>+`F4MZ!-M(^BWS_%a<<)#oPo?%mDyMyDh&#bD}l? O0000Zt~6hy~k%2a7U+8b=cDRQ%%REFSCT=2 zWzhTv6fuyXTt10sa5p7*2T2?X-r*EDR$)i8+~cI+>^V&xZj52KBf% zI8)c_t=A1w=gS#=uA&OI9B?6(z*;4R%o)9t;XcN~>3jxk6NQK%OCTXB;XYL$6LiDM z2*cfuiVV1cLa>WzyO7vYh#lR4KtFwqE)-?_`41iI=cj(Rq0{t*E&rO>HFbGF21Eb= N002ovPDHLkV1m)qRkQ#A literal 0 HcmV?d00001 diff --git a/html/vendor/images/label-on.png b/html/vendor/images/label-on.png new file mode 100755 index 0000000000000000000000000000000000000000..47e4c2eee09d732ec59adcf67f393574149f4571 GIT binary patch literal 214 zcmV;{04e{8P)bY)!)j1asIuih$%m%WWnX!xLOsvS#kxek!UOj;5{W=Rm@3#YlcCss0 z(n-uRY|ltyk@f_Z=$V8Kt381w+YW1k^h_2jQTv=^BfItd7743g|Na8_4@!C~R+hqE Q;s5{u07*qoM6N<$f<-%2uK)l5 literal 0 HcmV?d00001 diff --git a/html/vendor/jquery-3.2.1.min.js b/html/vendor/jquery-3.2.1.min.js new file mode 100755 index 0000000..644d35e --- /dev/null +++ b/html/vendor/jquery-3.2.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), +a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), +null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("