Compare commits

..

2 Commits
main ... R4

Author SHA1 Message Date
Siwat Sirichai 2f7b04ddd4 R4A 2023-11-12 23:56:43 +07:00
Siwat Sirichai 3a335a9a08 r4 functions 2023-11-09 20:58:16 +07:00
122 changed files with 339 additions and 13428 deletions

3
.gitignore vendored
View File

@ -1,3 +0,0 @@
.DS_Store
/ESPMegaPRO-OS-SDK

View File

@ -1,18 +0,0 @@
{
"configurations": [
{
"name": "windows-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "gcc",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "windows-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

24
.vscode/launch.json vendored
View File

@ -1,24 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": true,
"cwd": "d:/Git/ESPMegaPRO-v3-SDK/ESPMegaPRO-OS-SDK/src",
"program": "d:/Git/ESPMegaPRO-v3-SDK/ESPMegaPRO-OS-SDK/src/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

59
.vscode/settings.json vendored
View File

@ -1,59 +0,0 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false
}

View File

@ -1,288 +0,0 @@
{
"C_Cpp_Runner.cCompilerPath": "C:/Users/siwat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin/xtensa-esp32-elf-gcc.exe",
"C_Cpp_Runner.cppCompilerPath": "C:/Users/siwat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin/xtensa-esp32-elf-g++.exe",
"C_Cpp_Runner.debuggerPath": "C:/Users/siwat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/bin/xtensa-esp32-elf-gdb.exe",
"C_Cpp_Runner.cStandard": "gnu99",
"C_Cpp_Runner.cppStandard": "gnu++11",
"C_Cpp_Runner.msvcBatchPath": "",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [
"D:/Git/ESPMegaPRO-v3-SDK/ESPMegaPRO-DevKit-BTNLT/include",
"D:/Git/ESPMegaPRO-v3-SDK/ESPMegaPRO-DevKit-BTNLT/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/newlib/platform_include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions/freertos",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/port/xtensa/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freertos/include/esp_additions",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/include/soc/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hw_support/port/esp32/private_include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/heap/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/log/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/include/apps/sntp",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/lwip/src/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/lwip/port/esp32/include/arch",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/soc/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/hal/platform_port/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/include/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rom/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_common/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/soc",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_system/port/public_compat",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/xtensa/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/driver/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_pm/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ringbuf/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/efuse/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/vfs/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_wifi/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_event/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_netif/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_eth/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcpip_adapter/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_phy/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_ipc/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_trace/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_timer/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/port/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/mbedtls/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mbedtls/esp_crt_bundle/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/app_update/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spi_flash/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bootloader_support/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nvs_flash/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/pthread/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/xtensa",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_gdbstub/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espcoredump/include/port/xtensa",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/port/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wpa_supplicant/esp_supplicant/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ieee802154/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/console",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/asio/asio/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/asio/port/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/osi/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/include/esp32/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/api/include/api",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/blufi/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/common/btc/profile/esp/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/host/bluedroid/api/include/api",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_common/tinycrypt/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_core/storage",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/btc/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/common/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/client/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/mesh_models/server/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/core/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api/models/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/bt/esp_ble_mesh/api",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cbor/port/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/unity/unity/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/cmock/CMock/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/port/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/coap/libcoap/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/port/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/nghttp/nghttp2/lib/includes",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-tls/esp-tls-crypto",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_adc_cal/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_hid/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/tcp_transport/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_client/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_http_server/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_ota/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_https_server/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_lcd/interface",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protobuf-c/protobuf-c",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/common",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/security",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/protocomm/include/transports",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mdns/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_local_ctrl/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/sdmmc/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_serial_slave_link/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_websocket_client/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/expat/expat/lib",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/expat/port/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wear_levelling/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/diskio",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/vfs",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fatfs/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/freemodbus/freemodbus/common/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/idf_test/include/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/jsmn/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json/cJSON",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/libsodium/src/libsodium/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/libsodium/port_include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/mqtt/esp-mqtt/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/openssl/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/perfmon/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/spiffs/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ulp/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/wifi_provisioning/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rmaker_common/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_diagnostics/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/rtc_store/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_insights/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_parser/upstream",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/json_generator/upstream",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_schedule/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp_secure_cert_mgr/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_rainmaker/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/gpio_button/button/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/qrcode/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/ws2812_led",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp_littlefs/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/tool",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/typedef",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/image",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/math",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/nn",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/layer",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/detect",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-dl/include/model_zoo",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-sr/src/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-sr/esp-tts/esp_tts_chinese/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp-sr/include/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/driver/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/esp32-camera/conversions/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dotprod/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/support/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/hann/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_harris/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/blackman_nuttall/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/nuttall/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/windows/flat_top/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/iir/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fir/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/add/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sub/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mul/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/addc/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/mulc/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/math/sqrt/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/matrix/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/fft/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/dct/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/conv/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/common/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/espressif__esp-dsp/modules/kalman/ekf_imu13states/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/include/fb_gfx/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/tools/sdk/esp32/qio_qspi/include",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/cores/esp32",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/variants/wt32-eth01",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/ArduinoOTA/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/AsyncUDP/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/BLE/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/BluetoothSerial/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/DNSServer/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/EEPROM/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/ESP32/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/ESPmDNS/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/Ethernet/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/FFat/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/FS/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/HTTPClient/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdate/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/HTTPUpdateServer/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/I2S/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/Insights/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/LittleFS/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/NetBIOS/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/Preferences/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/RainMaker/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/SD/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/SD_MMC/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/SPI/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/SPIFFS/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/SimpleBLE/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/Ticker/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/USB/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/Update/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/WebServer/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/WiFi/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/WiFiClientSecure/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/WiFiProv/src",
"C:/Users/siwat/.platformio/packages/framework-arduinoespressif32/libraries/Wire/src",
""
],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false
}

View File

@ -1,23 +0,0 @@
#include <ESPMegaProOS.hpp>
// Function Prototypes
void setup();
void loop();
void inputCallback(uint8_t pin, uint8_t state);
ESPMegaPRO espmega = ESPMegaPRO();
void setup() {
espmega.begin();
espmega.inputs.registerCallback(inputCallback);
}
void loop() {
espmega.loop();
}
void inputCallback(uint8_t pin, uint8_t state) {
// Match PWM Channel with the same number as the input pin
espmega.outputs.digitalWrite(pin, state);
Serial.printf("Input %d changed to %d\n", pin, state);
}

View File

@ -1,63 +0,0 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false,
"files.associations": {
"istream": "cpp",
"map": "cpp"
}
}

View File

@ -1,16 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:wt32-eth01]
platform = espressif32
board = wt32-eth01
framework = arduino
lib_deps = siwats/ESPMegaPROR3@^2.0.2
monitor_speed = 115200

View File

@ -1,218 +0,0 @@
#include <ESPMegaProOS.hpp>
#include <IRReceiver.hpp>
/**
* @brief This program helps construct an ir timing table for a air conditioner
*
* Each run of this program will generate a new table, for a specific mode and fan speed
* It will iterate through all the temperature settings, and record the timing for each temperature
* It will then print the timing table as a C++ 2D array, which can be copied into your main program
* The first dimension is the temperature, the second dimension is the timing
*/
#define min_temp 16
#define max_temp 32
#define MAX_TIMINGS 1000 // 1000 timings should be enough for any remote
#define CAPTURE_TIMEOUT 5 // seconds
uint32_t timings[max_temp - min_temp + 1][MAX_TIMINGS] = {0};
uint16_t timings_count[max_temp - min_temp + 1] = {0};
ESPMegaPRO espmega = ESPMegaPRO();
void beginRoutine()
{
Serial.println("Beginning IR capture routine");
for (int i = min_temp; i <= max_temp; i++)
{
Serial.printf("Please press the button on your remote for %d degrees\n", i);
IRReceiver::start_long_receive();
for (int i = 0; i < CAPTURE_TIMEOUT; i++)
{
Serial.printf("Waiting for IR signal... (%d seconds left)\n", CAPTURE_TIMEOUT - i);
// During this period, if the user press any key, the routine will be restarted
// Unless it is a space bar, which will be used to confirm the timings
if (Serial.available())
{
char c = Serial.read();
if (c != ' ')
{
Serial.println("User interrupted, restarting routine...");
return;
}
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
ir_data_t data = IRReceiver::end_long_receive();
// Retry if no data was received
if (data.size == 0)
{
Serial.println("No data received, retrying...");
i--;
continue;
}
// Remove last timing
data.size--;
Serial.printf("Received timing of size %d\n", data.size);
// Print out the timing array
for (int i = 0; i < data.size; i++)
{
Serial.printf("%u%s", data.data[i], i == data.size - 1 ? "\n" : ", ");
}
// If any timings exceed 20000, print a warning
for (int i = 0; i < data.size; i++)
{
if (data.data[i] > 50000U)
{
Serial.println("WARNING: Timing exceeds 50000, Possible data corruption!");
break;
}
}
// Ask the user if the timings are correct
Serial.println("Are the timings correct? (y/n)");
// Flush the serial buffer
while (Serial.available())
{
Serial.read();
}
// Wait for user input
while (!Serial.available())
{
vTaskDelay(100 / portTICK_PERIOD_MS);
}
char c = Serial.read();
if (c != 'y')
{
Serial.println("Retrying...");
i--;
continue;
}
// Store the timings count
timings_count[i - min_temp] = data.size;
// Copy the timings into the timings array
memcpy(timings[i - min_temp], data.data, sizeof(uint32_t) * data.size);
free(data.data);
}
Serial.println("Generating C++ code for the timings, please wait...");
// Find the maximum number of timings
int max_timings = 0;
for (int i = 0; i < max_temp - min_temp + 1; i++)
{
if (timings_count[i] > max_timings)
{
max_timings = timings_count[i];
}
}
// Print the timings
Serial.println("Done!, please copy the following into your main program");
Serial.printf("uint16_t timings[%d][%d] = {\n", max_temp - min_temp + 1, max_timings);
for (int i = 0; i < max_temp - min_temp + 1; i++)
{
Serial.printf(" {");
for (int j = 0; j < timings_count[i]; j++)
{
Serial.printf("%u%s", timings[i][j], j == timings_count[i] - 1 ? "" : ", ");
}
Serial.println(i == max_temp - min_temp ? "}" : "},");
}
Serial.println("};");
Serial.println("Stopping IR capture routine");
Serial.printf("IR Capture routine finished\n");
}
void capture_single()
{
Serial.println("Please press the button on your remote");
IRReceiver::start_long_receive();
for (int i = 0; i < CAPTURE_TIMEOUT; i++)
{
Serial.printf("Waiting for IR signal... (%d seconds left)\n", CAPTURE_TIMEOUT - i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
ir_data_t data = IRReceiver::end_long_receive();
// Remove last timing
data.size--;
Serial.printf("Received timing of size %d\n", data.size);
// If any timings exceed 20000, print a warning
for (int i = 0; i < data.size; i++)
{
if (data.data[i] > 50000U)
{
Serial.println("WARNING: Timing exceeds 50000, Possible data corruption!");
break;
}
}
if (data.size == 0)
{
Serial.println("No data received, retrying...");
capture_single();
return;
}
// Print the timings
Serial.println("Done!, please copy the following into your main program");
Serial.printf("uint32_t timings[%d] = {", data.size);
for (int i = 0; i < data.size; i++)
{
Serial.printf("%u%s", data.data[i], i == data.size - 1 ? "" : ", ");
}
Serial.println("};");
free(data.data);
Serial.println("Do you want to capture another signal? (y/n)");
// Flush the serial buffer
while (Serial.available())
{
Serial.read();
}
// Wait for user input
while (!Serial.available())
{
vTaskDelay(100 / portTICK_PERIOD_MS);
}
char c = Serial.read();
if (c == 'y')
{
capture_single();
}
}
void menu_init()
{
// Print the menu
// The menu will have 2 options, one to start the routine, and one capture a single IR signal
Serial.println("ESPMegaPRO IR Development Kit - IR Capture");
Serial.println("1. Begin IR Capture Routine");
Serial.println("2. Capture Single IR Signal");
Serial.println("Please select an option:");
// Wait for user input
while (!Serial.available())
{
vTaskDelay(100 / portTICK_PERIOD_MS);
}
char c = Serial.read();
// Start the routine
if (c == '1')
{
beginRoutine();
}
// Capture a single IR signal
else if (c == '2')
{
capture_single();
}
}
void setup()
{
IRReceiver::begin(36);
espmega.begin();
}
void loop()
{
menu_init();
Serial.println("Press any key to return to the main menu");
while (!Serial.available())
{
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}

View File

@ -1,6 +0,0 @@
.pio/
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
temp/

View File

@ -1,62 +0,0 @@
{
"files.associations": {
"*.cps": "javascript",
"array": "cpp",
"deque": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp",
"adafruit_ads1x15.h": "c",
"*.tcc": "cpp",
"memory": "cpp",
"random": "cpp",
"functional": "cpp",
"atomic": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"istream": "cpp",
"limits": "cpp",
"new": "cpp",
"ostream": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"cinttypes": "cpp",
"typeinfo": "cpp",
"bitset": "cpp",
"regex": "cpp"
},
"cmake.configureOnOpen": true,
"cmake.sourceDirectory": "D:/Git/ESPMegaPRO-v3-SDK/Template Project/.pio/libdeps/wt32-eth01/Adafruit BusIO",
"editor.tokenColorCustomizations": {
"comments": "",
"textMateRules": []
}
}

View File

@ -1,80 +0,0 @@
#!/usr/bin/env python
import os
import re
html_files = []
directory = "lib/ESPMegaPRO/html"
for filename in os.listdir(directory):
if filename.endswith(".html"):
html_files.append(os.path.join(directory, filename))
# Iterate over the list of html files
for html_file in html_files:
# Open the html file
f = open(html_file, "r")
# Read the content of the html file
html_content = f.read()
# Close the html file
f.close()
# Section Disabled, Using client side JS to replace variables
# First, replace % with %%
#html_content = html_content.replace("%", "%%")
# The html file contains placeholders for the variables
# These placeholders are in the form of $(variable_name)$
# Replace these with %variable_name%
#html_content = re.sub(r"\$\((.*?)\)\$", r"%\1%", html_content)
# Create the temp folder if it doesn't exist
if not os.path.exists("temp"):
os.makedirs("temp")
# Save the new content to a new file in the temp directory
# The new file will have the same name as the original file
nf = open("temp/" + os.path.basename(html_file), "w")
nf.write(html_content)
nf.close()
# Use xxd to convert the html file to a C++ file
# The C++ file will have the same name as the original file but with a .h extension
# Save the C++ file in lib/ESPMegaPRO/html
os.system("xxd -i temp/" + os.path.basename(html_file) + " > lib/ESPMegaPRO/" + os.path.basename(html_file).replace(".html", "_html.h"))
# Now delete the temp file
os.remove("temp/" + os.path.basename(html_file))
# Next we open the C++ file
f = open("lib/ESPMegaPRO/" + os.path.basename(html_file).replace(".html", "_html.h"), "r")
# Read the content of the C++ file
cpp_content = f.read()
# Close the C++ file
f.close()
# By default, xxd will create a unsigned char array
# We need to change this to a const char array
# We also need to add a null terminator at the end of the array
# Additionally we want to place the array in PROGMEM
# Ex. From unsigned char index_html[] = { to const char index_html[] PROGMEM = {
# We will use regex to do this
cpp_content = re.sub(r"unsigned char (.*?)\[\] = \{", r"const char \1[] PROGMEM = {", cpp_content)
# Add null terminator
cpp_content = cpp_content.replace("};", ", 0x00};")
# Lastly, we do not need the length of the array so we remove it
# Ex. From unsigned int index_html_len = 1000; to
cpp_content = re.sub(r"unsigned int (.*?)_len = (.*?);", r"", cpp_content)
# Change the name of the variable to <filename>_html where <filename> is the name of the html file
filename = os.path.basename(html_file).replace(".html", "")
cpp_content = re.sub(r"const char (.*?)\[\] PROGMEM = {", r"const char " + filename + r"_html[] PROGMEM = {", cpp_content)
# Reopen the C++ file
f = open("lib/ESPMegaPRO/" + os.path.basename(html_file).replace(".html", "_html.h"), "w")
# Write the new content to the C++ file
f.write(cpp_content)
# Close the C++ file
f.close()
# Lastly, we need to create a header file that includes all the html files
# Open the header file
f = open("lib/ESPMegaPRO/all_html.h", "w")
# Write the pragma once directive
f.write("#pragma once\n")
# Write the includes for all the html files
for html_file in html_files:
f.write("#include \"" + os.path.basename(html_file).replace(".html", "_html.h") + "\"\n")
# Close the header file

View File

@ -1,205 +0,0 @@
/**
* @file AnalogCard.cpp
* @brief Implementation of the AnalogCard class.
*/
#include <AnalogCard.hpp>
#include "esp_log.h"
/**
* @brief Default constructor for the AnalogCard class.
*/
AnalogCard::AnalogCard() : dac0(DAC0_ADDRESS),
dac1(DAC1_ADDRESS),
dac2(DAC2_ADDRESS),
dac3(DAC3_ADDRESS),
analogInputBankA(),
analogInputBankB(),
dac_change_callbacks()
{
this->handler_count = 0;
}
/**
* @brief Writes a value to the specified DAC pin.
* @param pin The DAC pin to write to.
* @param value The value to write.
*/
void AnalogCard::dacWrite(uint8_t pin, uint16_t value)
{
ESP_LOGV("AnalogCard", "DAC Write: %d, %d", pin, value);
this->setDACState(pin, value > 0);
this->setDACValue(pin, value);
}
/**
* @brief Sets the state of the specified DAC pin.
* @param pin The DAC pin to set the state of.
* @param state The state to set (true = on, false = off).
*/
void AnalogCard::setDACState(uint8_t pin, bool state)
{
ESP_LOGD("AnalogCard", "Setting DAC state: %d, %d", pin, state);
this->dac_state[pin] = state;
this->sendDataToDAC(pin, this->dac_value[pin] * state);
for (const auto& callback : this->dac_change_callbacks)
{
callback.second(pin, state, this->dac_value[pin]);
}
}
/**
* @brief Sets the value of the specified DAC pin.
* @param pin The DAC pin to set the value of.
* @param value The value to set.
*/
void AnalogCard::setDACValue(uint8_t pin, uint16_t value)
{
ESP_LOGD("AnalogCard", "Setting DAC value: %d, %d", pin, value);
this->dac_value[pin] = value;
this->sendDataToDAC(pin, value * this->dac_state[pin]);
for (const auto& callback : this->dac_change_callbacks)
{
callback.second(pin, this->dac_state[pin], value);
}
}
/**
* @brief Gets the value of the specified DAC pin.
* @param pin The DAC pin to get the value of.
* @return The value of the DAC pin.
*/
uint16_t AnalogCard::getDACValue(uint8_t pin)
{
return this->dac_value[pin];
}
/**
* @brief Gets the state of the specified DAC pin.
* @param pin The DAC pin to get the state of.
* @return The state of the DAC pin (true = on, false = off).
*/
bool AnalogCard::getDACState(uint8_t pin)
{
return this->dac_state[pin];
}
/**
* @brief Sends data to the specified DAC pin.
* @param pin The DAC pin to send data to.
* @param value The data to send.
* @note This function does not call the DAC change callbacks.
*/
void AnalogCard::sendDataToDAC(uint8_t pin, uint16_t value)
{
switch (pin)
{
case 0:
this->dac0.writeDAC(value);
break;
case 1:
this->dac1.writeDAC(value);
break;
case 2:
this->dac2.writeDAC(value);
break;
case 3:
this->dac3.writeDAC(value);
break;
}
}
/**
* @brief Reads the value from the specified analog pin.
* @param pin The analog pin to read from.
* @return The value read from the analog pin.
*/
uint16_t AnalogCard::analogRead(uint8_t pin)
{
if (pin >= 0 && pin <= 3)
{
return this->analogInputBankA.readADC_SingleEnded(pin);
}
else if (pin >= 4 && pin <= 7)
{
return this->analogInputBankB.readADC_SingleEnded(pin - 4);
}
return 65535;
}
/**
* @brief Initializes the AnalogCard.
* @return True if initialization is successful, false otherwise.
*/
bool AnalogCard::begin()
{
bool success = true;
if (!this->dac0.begin())
{
ESP_LOGE("AnalogCard", "Card Analog ERROR: Failed to install DAC0");
// success = false;
}
if (!this->dac1.begin())
{
ESP_LOGE("AnalogCard", "Card Analog ERROR: Failed to install DAC1");
// success = false;
}
if (!this->dac2.begin())
{
ESP_LOGE("AnalogCard", "Card Analog ERROR: Failed to install DAC2");
// success = false;
}
if (!this->dac3.begin())
{
ESP_LOGE("AnalogCard", "Card Analog ERROR: Failed to install DAC3");
// success = false;
}
if (!this->analogInputBankA.begin(ANALOG_INPUT_BANK_A_ADDRESS))
{
ESP_LOGE("AnalogCard", "Card Analog ERROR: Failed to install analog input bank A");
success = false;
}
if (!this->analogInputBankB.begin(ANALOG_INPUT_BANK_B_ADDRESS))
{
ESP_LOGE("AnalogCard", "Card Analog ERROR: Failed to install analog input bank B");
success = false;
}
return success;
}
/**
* @brief The main loop of the AnalogCard.
* @note This function does nothing.
*/
void AnalogCard::loop()
{
}
/**
* @brief Gets the type of the AnalogCard.
* @return The type of the AnalogCard.
*/
uint8_t AnalogCard::getType()
{
return CARD_TYPE_ANALOG;
}
/**
* @brief Registers a callback function to be called when the state or value of a DAC pin changes.
* @param callback The callback function to register.
* @return The handler ID of the registered callback.
*/
uint8_t AnalogCard::registerDACChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback)
{
this->dac_change_callbacks[this->handler_count] = callback;
return this->handler_count++;
}
/**
* @brief Unregisters a previously registered DAC change callback.
* @param handler The handler ID of the callback to unregister.
*/
void AnalogCard::unregisterDACChangeCallback(uint8_t handler)
{
this->dac_change_callbacks.erase(handler);
}

View File

@ -1,55 +0,0 @@
#pragma once
#include <ExpansionCard.hpp>
#include <Adafruit_ADS1X15.h>
#include <MCP4725.h>
#include <vector>
#include <map>
// Analog Card
#define CARD_TYPE_ANALOG 0x02
// Analog Card FRAM Address
#define ANALOG_INPUT_BANK_A_ADDRESS 0x48
#define ANALOG_INPUT_BANK_B_ADDRESS 0x49
#define DAC0_ADDRESS 0x60
#define DAC1_ADDRESS 0x61
#define DAC2_ADDRESS 0x62
#define DAC3_ADDRESS 0x63
/**
* @brief This class represents the Analog Card.
*
* The analog card has 8 analog inputs accross two banks, and 4 DAC outputs.
*
* @note You do not need to specify the ESPMega I/O Address when creating an instance of this class as there can only be one Analog Card installed in the ESPMegaPRO board.
* @warning There can only be one Analog Card installed in the ESPMegaPRO board.
*
*/
class AnalogCard : public ExpansionCard {
public:
AnalogCard();
void dacWrite(uint8_t pin, uint16_t value);
void sendDataToDAC(uint8_t pin, uint16_t value);
uint16_t analogRead(uint8_t pin);
bool begin();
void loop();
bool getDACState(uint8_t pin);
uint16_t getDACValue(uint8_t pin);
void setDACState(uint8_t pin, bool state);
void setDACValue(uint8_t pin, uint16_t value);
uint8_t registerDACChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
void unregisterDACChangeCallback(uint8_t handler);
uint8_t getType();
private:
uint8_t handler_count;
// Map of handler IDs to callback functions
std::map<uint8_t, std::function<void(uint8_t, bool, uint16_t)>> dac_change_callbacks;
bool dac_state[4];
uint16_t dac_value[4];
MCP4725 dac0;
MCP4725 dac1;
MCP4725 dac2;
MCP4725 dac3;
Adafruit_ADS1115 analogInputBankA;
Adafruit_ADS1115 analogInputBankB;
};

View File

@ -1,424 +0,0 @@
/**
* @file AnalogIoT.cpp
* @brief Implementation of the AnalogIoT class.
*
* This file contains the implementation of the AnalogIoT class, which provides functionality for handling analog input and output operations in an IoT system.
* The class allows for setting the state and value of digital-to-analog converters (DACs), as well as reading the value of analog-to-digital converters (ADCs).
* It also supports publishing the state and value of DACs and ADCs over MQTT.
*/
#include <AnalogIoT.hpp>
/**
* @brief Default constructor for the AnalogIoT class.
*
* This constructor initializes the AnalogIoT object and sets up the ADC conversion callbacks.
*/
AnalogIoT::AnalogIoT() : adc_conversion_callbacks() {
for (uint8_t i = 0; i < 8; i++) {
adc_publish_enabled[i] = false;
adc_conversion_interval[i] = 1000;
}
this->adc_conversion_callback_index = 0;
}
/**
* @brief Default destructor for the AnalogIoT class.
*/
AnalogIoT::~AnalogIoT() {
this->adc_conversion_callbacks.clear();
}
/**
* @brief Initializes the AnalogIoT object.
* @param card_id The ID of the card.
* @param card A pointer to the card object.
* @param mqtt A pointer to the MQTT client object.
* @param base_topic The base MQTT topic.
* @return True if the initialization was successful, false otherwise.
* @note This function can be called from the main program but it is recommended to use ESPMegaIoT to initialize the IoT Components.
* This function initializes the AnalogIoT object and registers the callbacks for handling DAC changes.
*/
bool AnalogIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->mqtt = mqtt;
this->base_topic = base_topic;
this->card = (AnalogCard*)card;
this-> card_id = card_id;
this->dac_set_state_length = strlen(DAC_SET_STATE_TOPIC);
this->dac_set_value_length = strlen(DAC_SET_VALUE_TOPIC);
this->dac_state_length = strlen(DAC_STATE_TOPIC);
this->dac_value_length = strlen(DAC_VALUE_TOPIC);
this->request_state_length = strlen(REQUEST_STATE_TOPIC);
this->dac_publish_enable_length = strlen(DAC_PUBLISH_ENABLE_TOPIC);
// Register callbacks
auto bindedCallback = std::bind(&AnalogIoT::handleDACChange, this, std::placeholders::_1, std::placeholders::_2);
this->card->registerDACChangeCallback(bindedCallback);
return true;
}
/**
* @brief Publishes the state of all DACs.
* @note This function is called when a request state message is received.
*/
void AnalogIoT::handleMqttMessage(char *topic, char *payload){
uint8_t topic_length = strlen(topic);
if(this-> processDACSetStateMessage(topic, payload, topic_length)) return;
if(this-> processDACSetValueMessage(topic, payload, topic_length)) return;
if(this-> processRequestStateMessage(topic, payload, topic_length)) return;
if(this-> processADCSetConversionIntervalMessage(topic, payload, topic_length)) return;
if(this-> processADCSetConversionEnabledMessage(topic, payload, topic_length)) return;
}
/**
* @brief Publishes the state of all DACs.
*/
void AnalogIoT::publishADCs() {
for (uint8_t i = 0; i < 8; i++) {
this->publishADC(i);
}
}
/**
* @brief Publishes the state of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::publishADC(uint8_t pin) {
if (this->adc_publish_enabled[pin]) {
uint16_t value = this->card->analogRead(pin);
char *topic = new char[15];
sprintf(topic, "adc/%02d/value", pin);
char *payload = new char[10];
sprintf(payload, "%d", value);
this->publishRelative(topic, payload);
delete[] topic;
delete[] payload;
// Call all callbacks
for (auto& callback : this->adc_conversion_callbacks) {
callback.second(pin, value);
}
}
}
/**
* @brief Sets the interval at which the state of all DACs is published.
* @param interval The interval in milliseconds.
*/
void AnalogIoT::setADCsPublishInterval(uint32_t interval) {
for (uint8_t i = 0; i < 8; i++) {
adc_conversion_interval[i] = interval;
}
}
/**
* @brief Sets whether the state of all DACs is published.
* @param enabled True if the state of all DACs should be published, false otherwise.
*/
void AnalogIoT::setADCsPublishEnabled(bool enabled) {
for (uint8_t i = 0; i < 8; i++) {
adc_publish_enabled[i] = enabled;
}
}
/**
* @brief Registers a callback for handling ADC conversions.
* @param callback The callback function.
* @return The handler of the callback.
*/
uint8_t AnalogIoT::registerADCConversionCallback(std::function<void(uint8_t, uint16_t)> callback) {
this->adc_conversion_callbacks[this->adc_conversion_callback_index] = callback;
return this->adc_conversion_callback_index++;
}
/**
* @brief Unregisters a callback for handling ADC conversions.
* @param handler The handler of the callback.
*/
void AnalogIoT::unregisterADCConversionCallback(uint8_t handler) {
this->adc_conversion_callbacks.erase(handler);
}
/**
* @brief Sets the interval at which the value of an ADC channel is read.
* @param pin The pin of the ADC channel.
* @param interval The interval in milliseconds.
*/
void AnalogIoT::setADCConversionInterval(uint8_t pin, uint16_t interval) {
adc_conversion_interval[pin] = interval;
}
/**
* @brief Enables or disables the periodic reading of the value of an ADC channel.
* @param pin The pin of the ADC channel.
* @param enabled True if the value of the ADC channel should be read, false otherwise.
*/
void AnalogIoT::setADCConversionEnabled(uint8_t pin, bool enabled) {
adc_publish_enabled[pin] = enabled;
}
/**
* @brief Processes a message received on the MQTT topic for setting the state of a DAC.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processADCSetConversionIntervalMessage(char *topic, char *payload, uint8_t topic_length) {
// TODO: Process payload matching the criteria
// Topic: adc/<%02d>/set/conversion_interval
// The first 4 characters are "adc/"
// The length of the topic must be 30 characters
// The last 24 characters must be "/set/conversion_interval"
// After all these conditions are met, the topic is valid
// Extract the pin number from the topic
if (topic_length != 30) {
return false;
}
if (strncmp(topic, "adc/", 4)) {
return false;
}
if (strncmp(topic + 26, "/set/conversion_interval", 24)) {
return false;
}
uint8_t pin = (topic[4] - '0') * 10 + (topic[5] - '0');
// Extract the payload
uint16_t interval = atoi(payload);
// Set the interval
this->setADCConversionInterval(pin, interval);
return true;
}
/**
* @brief Processes a message received on the MQTT topic for setting the value of a DAC.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processADCSetConversionEnabledMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: adc/<%02d>/set/conversion_enabled
// The first 4 characters are "adc/"
// The length of the topic must be 29 characters
// The last 23 characters must be ""/set/conversion_enabled
// After all these conditions are met, the topic is valid
// Extract the pin number from the topic
if (topic_length != 29) {
return false;
}
if (strncmp(topic, "adc/", 4)) {
return false;
}
if (strncmp(topic + 25, "/set/conversion_enabled", 23)) {
return false;
}
uint8_t pin = (topic[4] - '0') * 10 + (topic[5] - '0');
// Extract the payload
bool enabled = atoi(payload);
// Set conversion enabled
this->setADCConversionEnabled(pin, enabled);
return true;
}
/**
* @brief Processes a message received on the MQTT topic for setting the state of a DAC.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processDACSetStateMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: dac/<%02d>/set/state
// The first 4 characters are "dac/"
// The length of the topic must be 16 characters
// The last 10 characters must be "/set/state"
// After all these conditions are met, the topic is valid
// Extract the pin number from the topic
if (topic_length != 16) {
return false;
}
if (strncmp(topic, "dac/", 4)) {
return false;
}
if (strncmp(topic + 12, "/set/state", 10)) {
return false;
}
uint8_t pin = (topic[4] - '0') * 10 + (topic[5] - '0');
// Extract the payload
bool state = atoi(payload);
// Set the state
this->card->setDACState(pin, state);
return true;
}
/**
* @brief Processes a message received on the MQTT topic for setting the value of a DAC.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processDACSetValueMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: dac/<%02d>/set/value
// The first 4 characters are "dac/"
// The length of the topic must be 16 characters
// The last 10 characters must be "/set/value"
// After all these conditions are met, the topic is valid
// Extract the pin number from the topic
if (topic_length != 16) {
return false;
}
if (strncmp(topic, "dac/", 4)) {
return false;
}
if (strncmp(topic + 12, "/set/value", 10)) {
return false;
}
uint8_t pin = (topic[4] - '0') * 10 + (topic[5] - '0');
// Extract the payload
uint16_t value = atoi(payload);
// Set the value
this->card->setDACValue(pin, value);
return true;
}
/**
* @brief Processes a message received on the MQTT topic for requesting the state of all DACs.
* @param topic The topic of the message.
* @param payload The payload of the message.
* @param topic_length The length of the topic.
* @note This function is not meant to be called from user code.
* @return True if the message was processed, false otherwise.
*/
bool AnalogIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length) {
// Topic: requeststate
// The length of the topic must be 12 characters
// After all these conditions are met, the topic is valid
if (topic_length != 12) {
return false;
}
if (strncmp(topic, REQUEST_STATE_TOPIC, 12)) {
return false;
}
// Publish the state of all DACs
this->publishDACs();
// Publish the state of all ADCs
this->publishADCs();
return false;
}
/**
* @brief Subscribes to all MQTT topics used by the AnalogIoT object.
* @note This function is called when the MQTT client connects.
*/
void AnalogIoT::subscribe() {
// There are 4 DACs and 8 ADCs
// DACs: dac/<%02d>/set/state, dac/<%02d>/set/value, dac/publish_enable
// ADCs: adc/<%02d>/set/conversion_interval, adc/<%02d>/set/conversion_enabled
// Subscribe to all set state topics
char topic[20];
for (uint8_t i = 0; i < 4; i++) {
sprintf(topic, "dac/%02d/set/state", i);
this->subscribeRelative(topic);
}
// Subscribe to all set value topics
for (uint8_t i = 0; i < 4; i++) {
sprintf(topic, "dac/%02d/set/value", i);
this->subscribeRelative(topic);
}
// Subscribe to all set conversion interval topics
for (uint8_t i = 0; i < 8; i++) {
sprintf(topic, "adc/%02d/set/conversion_interval", i);
this->subscribeRelative(topic);
}
// Subscribe to all set conversion enabled topics
for (uint8_t i = 0; i < 8; i++) {
sprintf(topic, "adc/%02d/set/conversion_enabled", i);
this->subscribeRelative(topic);
}
// Subscribe to publish enable topic
this->subscribeRelative("dac/publish_enable");
}
void AnalogIoT::loop() {
// Iterate over all ADCs and publish if enabled and interval has passed
uint32_t now = millis();
for (uint8_t i = 0; i < 8; i++) {
if (this->adc_publish_enabled[i] && now - this->last_adc_publish > this->adc_conversion_interval[i]) {
this->publishADC(i);
this->last_adc_publish = now;
}
}
}
/**
* @brief Publishes the state of all DACs.
*/
void AnalogIoT::publishReport() {
publishADCs();
publishDACs();
}
/**
* @brief Gets the type of the card.
* @return The type of the card.
*/
uint8_t AnalogIoT::getType() {
return CARD_TYPE_ANALOG;
}
/**
* @brief Publishes the state of all DACs.
*/
void AnalogIoT::publishDACs() {
for (uint8_t i = 0; i < 4; i++) {
this->publishDAC(i);
}
}
/**
* @brief Publishes the state of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::publishDAC(uint8_t pin) {
this->publishDACState(pin);
this->publishDACValue(pin);
}
/**
* @brief Publishes the state of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::publishDACState(uint8_t pin) {
char *topic = new char[15];
sprintf(topic, "dac/%02d/state", pin);
char *payload = new char[2];
sprintf(payload, "%d", this->card->getDACState(pin));
this->publishRelative(topic, payload);
delete[] topic;
delete[] payload;
}
/**
* @brief Publishes the value of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::publishDACValue(uint8_t pin) {
char *topic = new char[15];
sprintf(topic, "dac/%02d/value", pin);
char *payload = new char[5];
sprintf(payload, "%d", this->card->getDACValue(pin));
this->publishRelative(topic, payload);
delete[] topic;
delete[] payload;
}
/**
* @brief Publishes the state of a DAC.
* @param pin The pin of the DAC.
*/
void AnalogIoT::handleDACChange(uint8_t pin, uint16_t value) {
this->publishDAC(pin);
}

View File

@ -1,65 +0,0 @@
#pragma once
#include <IoTComponent.hpp>
#include <AnalogCard.hpp>
#include <map>
// MQTT Topics
#define DAC_SET_STATE_TOPIC "/set/state"
#define DAC_SET_VALUE_TOPIC "/set/value"
#define DAC_STATE_TOPIC "/dac/00/state"
#define DAC_VALUE_TOPIC "/dac/00/value"
#define DAC_PUBLISH_ENABLE_TOPIC "/publish_enable"
#define REQUEST_STATE_TOPIC "requeststate"
/**
* @brief The AnalogIoT class is a class for connecting the Analog Card to the IoT module.
*
* This function allows you to control the Analog Card using MQTT.
*
* @warning You should not instantiate this class directly, instead use ESPMegaIoT's registerCard function.
*/
class AnalogIoT : public IoTComponent {
public:
AnalogIoT();
~AnalogIoT();
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void handleDACChange(uint8_t pin, uint16_t value);
void publishADCs();
void publishADC(uint8_t pin);
void publishDACs();
void publishDAC(uint8_t pin);
void publishDACState(uint8_t pin);
void publishDACValue(uint8_t pin);
void setADCsPublishInterval(uint32_t interval);
void setADCsPublishEnabled(bool enabled);
uint8_t registerADCConversionCallback(std::function<void(uint8_t, uint16_t)> callback);
void unregisterADCConversionCallback(uint8_t handler);
void setADCConversionInterval(uint8_t pin, uint16_t interval);
void setADCConversionEnabled(uint8_t pin, bool enabled);
bool processADCSetConversionIntervalMessage(char *topic, char *payload, uint8_t topic_length);
bool processADCSetConversionEnabledMessage(char *topic, char *payload, uint8_t topic_length);
bool processDACSetStateMessage(char *topic, char *payload, uint8_t topic_length);
bool processDACSetValueMessage(char *topic, char *payload, uint8_t topic_length);
bool processRequestStateMessage(char *topic, char *payload, uint8_t topic_length);
void publishReport();
void subscribe();
void loop();
uint8_t getType();
private:
// The index of the next callback to be registered
uint8_t adc_conversion_callback_index = 0;
// We keep track of the length of the topics so we don't have to calculate it every time
uint8_t dac_set_state_length;
uint8_t dac_set_value_length;
uint8_t dac_state_length;
uint8_t dac_value_length;
uint8_t request_state_length;
uint8_t dac_publish_enable_length;
uint32_t last_adc_publish = 0;
AnalogCard *card;
bool adc_publish_enabled[8];
uint16_t adc_conversion_interval[8];
uint32_t last_adc_conversion[8];
std::map<uint8_t, std::function<void(uint8_t, uint16_t)>> adc_conversion_callbacks;
};

View File

@ -1,468 +0,0 @@
/**
* @file ClimateCard.cpp
* @brief Implementation file for the ClimateCard class.
*
* This file contains the implementation of the ClimateCard class, which represents a climate control card.
* The ClimateCard class provides methods for controlling an air conditioner, reading temperature and humidity
* from sensors, and saving and loading the state to/from FRAM memory.
*/
#include <ClimateCard.hpp>
/**
* @brief Construct a new ClimateCard object.
*
* @note RMT channel must be universally unique, this means that you can't use the same channel for multiple cards.
*
* @param ir_pin The GPIO pin number of the IR transmitter.
* @param ac The AirConditioner object that represents the air conditioner.
* @param sensor_type The type of the sensor connected to the card.
* @param sensor_pin The GPIO pin number of the sensor.
* @param channel The RMT channel to use for IR transmission.
*/
ClimateCard::ClimateCard(uint8_t ir_pin, AirConditioner ac, uint8_t sensor_type, uint8_t sensor_pin, rmt_channel_t channel) : ir_blaster(ir_pin, channel)
{
this->ir_pin = ir_pin;
this->ac = ac;
this->sensor_type = sensor_type;
this->sensor_pin = sensor_pin;
// Initialize Pointers
this->dht = nullptr;
this->ds18b20 = nullptr;
this->fram = nullptr;
// Initialize Variables
this->fram_address = 0;
this->fram_auto_save = false;
this->state.ac_temperature = 0;
this->state.ac_mode = 0;
this->state.ac_fan_speed = 0;
this->humidity = 0;
this->room_temperature = 0;
// Initialize state
this->state.ac_temperature = 25;
this->state.ac_mode = 0;
this->state.ac_fan_speed = 0;
}
/**
* @brief Construct a new ClimateCard object.
*
* @param ir_pin The GPIO pin number of the IR transmitter.
* @param ac The AirConditioner object that represents the air conditioner.
* @param channel The RMT channel to use for IR transmission.
*
* @note RMT channel must be universally unique, this means that you can't use the same channel for multiple cards.
*
* @note This constructor can be used when no sensor is connected to the card.
*/
ClimateCard::ClimateCard(uint8_t ir_pin, AirConditioner ac, rmt_channel_t channel) : ClimateCard(ir_pin, ac, AC_SENSOR_TYPE_NONE, 0, channel)
{
}
/**
* @brief The destructor of the ClimateCard class.
*/
ClimateCard::~ClimateCard()
{
delete dht;
delete ds18b20;
}
/**
* @brief Initialize the ClimateCard object.
*
* @return true if initialization was successful.
* @return false if initialization failed.
*/
bool ClimateCard::begin()
{
switch (sensor_type)
{
case AC_SENSOR_TYPE_DHT22:
dht = new DHTNEW(sensor_pin);
break;
case AC_SENSOR_TYPE_DS18B20:
OneWire oneWire(sensor_pin);
ds18b20 = new DS18B20(&oneWire);
break;
}
updateAirConditioner();
return true;
}
/**
* @brief Loop function of the ClimateCard class.
*
* @note When this card is installed in an ESPMega, this function is called automatically by the ESPMega class.
*/
void ClimateCard::loop()
{
static uint32_t last_sensor_update = 0;
if (millis() - last_sensor_update >= AC_SENSOR_READ_INTERVAL)
{
last_sensor_update = millis();
updateSensor();
}
}
/**
* @brief bind FRAM memory to the ClimateCard object at the specified address.
*
* @note This function must be called before calling loadStateFromFRAM() or saveStateToFRAM().
* @note This card takes up 3 bytes of FRAM memory.
*
* @param fram The FRAM object.
* @param fram_address The starting address of the card in FRAM memory.
*/
void ClimateCard::bindFRAM(FRAM *fram, uint16_t fram_address)
{
this->fram = fram;
this->fram_address = fram_address;
}
/**
* @brief Set whether the state should be automatically saved to FRAM memory.
*
* @note This function has no effect if bindFRAM() has not been called.
* @param autoSave Whether the state should be automatically saved to FRAM memory.
*/
void ClimateCard::setFRAMAutoSave(bool autoSave)
{
this->fram_auto_save = autoSave;
}
/**
* @brief Save the state to FRAM memory.
* @note This function has no effect if bindFRAM() has not been called.
*/
void ClimateCard::saveStateToFRAM()
{
if (fram == nullptr)
return;
fram->write8(fram_address, state.ac_temperature);
fram->write8(fram_address + 1, state.ac_mode);
fram->write8(fram_address + 2, state.ac_fan_speed);
}
/**
* @brief Load the state from FRAM memory.
*
* @note This function has no effect if bindFRAM() has not been called.
*/
void ClimateCard::loadStateFromFRAM()
{
if (fram == nullptr)
return;
// Retrieve state from FRAM
state.ac_temperature = fram->read8(fram_address);
state.ac_mode = fram->read8(fram_address + 1);
state.ac_fan_speed = fram->read8(fram_address + 2);
if (state.ac_temperature > ac.max_temperature)
state.ac_temperature = ac.max_temperature;
else if (state.ac_temperature < ac.min_temperature)
state.ac_temperature = ac.min_temperature;
// If mode is out of range, set to 0
if (state.ac_mode > ac.modes)
state.ac_mode = 0;
// If fan speed is out of range, set to 0
if (state.ac_fan_speed > ac.fan_speeds)
state.ac_fan_speed = 0;
updateAirConditioner();
for (const auto &callback : callbacks)
{
callback.second(this->state.ac_mode, this->state.ac_fan_speed, this->state.ac_temperature);
}
}
/**
* @brief Set the temperature of the air conditioner.
*
* @param temperature The temperature to set.
* @note If the temperature is out of range, it will be set to its respective maximum or minimum.
*/
void ClimateCard::setTemperature(uint8_t temperature)
{
// If temperature is out of range, set to its respective maximum or minimum
if (temperature > ac.max_temperature)
temperature = ac.max_temperature;
else if (temperature < ac.min_temperature)
temperature = ac.min_temperature;
this->state.ac_temperature = temperature;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}
/**
* @brief Set the mode of the air conditioner.
*
* @note If the mode is out of range, it will be set to 0.
* @param mode The mode to set.
*/
void ClimateCard::setMode(uint8_t mode)
{
if (mode > ac.modes)
mode = 0;
this->state.ac_mode = mode;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}
/**
* @brief Get the name of the current mode.
* @return The name of the current mode.
*/
char *ClimateCard::getModeName()
{
return (char *)ac.mode_names[state.ac_mode];
}
/**
* @brief Get the name of the current fan speed.
*
* @return The name of the current fan speed.
*/
char *ClimateCard::getFanSpeedName()
{
return (char *)ac.fan_speed_names[state.ac_fan_speed];
}
/**
* @brief Set the fan speed of the air conditioner.
*
* @note If the fan speed is out of range, it will be set to 0.
* @param fan_speed The fan speed to set.
*/
void ClimateCard::setFanSpeed(uint8_t fan_speed)
{
if (fan_speed > ac.fan_speeds)
fan_speed = 0;
this->state.ac_fan_speed = fan_speed;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}
/**
* @brief Set fan speed by name.
*
* @param fan_speed_name The name of the fan speed to set.
* @note If the fan speed is not found, the function will not do anything.
*/
void ClimateCard::setFanSpeedByName(const char *fan_speed_name)
{
for (uint8_t i = 0; i < ac.fan_speeds; i++)
{
if (strcmp(fan_speed_name, ac.fan_speed_names[i]) == 0)
{
setFanSpeed(i);
return;
}
}
}
/**
* @brief Set mode by name.
*
* @param mode_name The name of the mode to set.
* @note If the mode is not found, the function will not do anything.
*/
void ClimateCard::setModeByName(const char *mode_name)
{
for (uint8_t i = 0; i < ac.modes; i++)
{
if (strcmp(mode_name, ac.mode_names[i]) == 0)
{
setMode(i);
return;
}
}
}
/**
* @brief Register a callback function that will be called when the state of the air conditioner changes.
*
* @param callback The callback function to register.
*
* @return uint8_t The handler of the callback function.
*/
uint8_t ClimateCard::registerChangeCallback(std::function<void(uint8_t, uint8_t, uint8_t)> callback)
{
callbacks[callbacks_handler_count] = callback;
return callbacks_handler_count++;
}
/**
* @brief Get the type of the card.
*
* @return The handler of the callback function.
*/
uint8_t ClimateCard::getType()
{
return CARD_TYPE_CLIMATE;
}
/**
* @brief update environmental sensor data.
*
* @note This function is called automatically by the loop() function.
* @note This function has no effect if no sensor is connected to the card.
* @note This function also calls the sensor callbacks.
*/
void ClimateCard::updateSensor()
{
if (sensor_type == AC_SENSOR_TYPE_NONE)
return;
// Read sensor data and update variables
switch (sensor_type)
{
case AC_SENSOR_TYPE_DHT22:
if (millis() - dht->lastRead() < AC_SENSOR_READ_INTERVAL)
return;
dht->read();
room_temperature = dht->getTemperature();
humidity = dht->getHumidity();
break;
case AC_SENSOR_TYPE_DS18B20:
ds18b20->requestTemperatures();
uint32_t start = millis();
while (!ds18b20->isConversionComplete())
{
if (millis() - start >= AC_SENSOR_READ_TIMEOUT)
{
return;
}
}
room_temperature = ds18b20->getTempC();
break;
}
for (const auto &callback : sensor_callbacks)
{
callback.second(room_temperature, humidity);
}
}
/**
* @brief Update the air conditioner state to match the state of the card.
*
* @warning This function is not working yet.
*/
void ClimateCard::updateAirConditioner()
{
const uint16_t* ir_code_ptr = nullptr;
size_t itemCount = (*(this->ac.getInfraredCode))(this->state.ac_mode, this->state.ac_fan_speed, this->state.ac_temperature-this->ac.min_temperature, &ir_code_ptr);
if (ir_code_ptr == nullptr)
return;
ir_blaster.send(ir_code_ptr, itemCount);
// Publish state
for (const auto &callback : callbacks)
{
callback.second(this->state.ac_mode, this->state.ac_fan_speed, this->state.ac_temperature);
}
}
/**
* @brief Get the type of the sensor connected to the card.
*
* @return The type of the sensor connected to the card.
*/
uint8_t ClimateCard::getSensorType()
{
return sensor_type;
}
/**
* @brief Get the room temperature in degrees Celsius.
*
* @return The room temperature.
*/
float ClimateCard::getRoomTemperature()
{
return room_temperature;
}
/**
* @brief Get the humidity in percent.
*
* @return The humidity.
*/
float ClimateCard::getHumidity()
{
return humidity;
}
/**
* @brief Get the temperature of the air conditioner.
*
* @return The temperature of the air conditioner.
*/
uint8_t ClimateCard::getTemperature()
{
return state.ac_temperature;
}
/**
* @brief Get the mode of the air conditioner.
*
* @return The mode of the air conditioner.
*/
uint8_t ClimateCard::getMode()
{
return state.ac_mode;
}
/**
* @brief Get the fan speed of the air conditioner.
*
* @return The fan speed of the air conditioner.
*/
uint8_t ClimateCard::getFanSpeed()
{
return state.ac_fan_speed;
}
/**
* @brief Register a callback function that will be called when the sensor data changes.
*
* @param callback The callback function to register.
*
* @return The handler of the callback function
*/
uint8_t ClimateCard::registerSensorCallback(std::function<void(float, float)> callback)
{
sensor_callbacks[sensor_callbacks_handler_count] = callback;
return sensor_callbacks_handler_count++;
}
/**
* @brief Unregister a callback function.
*
* @param handler The handler of the callback function to unregister.
*/
void ClimateCard::unregisterChangeCallback(uint8_t handler)
{
callbacks.erase(handler);
}
/**
* @brief Unregister a sensor callback function.
*
* @param handler The handler of the callback function to unregister.
*/
void ClimateCard::unregisterSensorCallback(uint8_t handler)
{
sensor_callbacks.erase(handler);
}
void ClimateCard::setState(uint8_t mode, uint8_t fan_speed, uint8_t temperature)
{
this->state.ac_mode = mode;
this->state.ac_fan_speed = fan_speed;
this->state.ac_temperature = temperature;
updateAirConditioner();
if (fram_auto_save)
saveStateToFRAM();
}

View File

@ -1,123 +0,0 @@
#pragma once
#include <ExpansionCard.hpp>
#include <IRBlaster.hpp>
#include <FRAM.h>
#include <OneWire.h>
#include <DS18B20.h>
#include <dhtnew.h>
#include <map>
#define CARD_TYPE_CLIMATE 0x03
#define AC_SENSOR_TYPE_NONE 0x00
#define AC_SENSOR_TYPE_DHT22 0x01
#define AC_SENSOR_TYPE_DS18B20 0x02
#define AC_SENSOR_READ_INTERVAL 5000
#define AC_SENSOR_READ_TIMEOUT 250
/**
* @brief The struct is used to store the state of the air conditioner
*
* @note This struct is stored in FRAM if it is used
* @note This struct is 3 bytes long
*/
struct ClimateCardData {
uint8_t ac_temperature; ///< Temperature of the air conditioner
uint8_t ac_mode; ///< Mode of the air conditioner
uint8_t ac_fan_speed;///< Fan speed of the air conditioner
};
/**
* @brief This struct is used to store information about an air conditioner
*/
struct AirConditioner {
uint8_t max_temperature; ///< Maximum temperature
uint8_t min_temperature; ///< Minimum temperature
uint8_t modes; ///< Number of modes
const char **mode_names; ///< Names of modes in the form of an array of strings
uint8_t fan_speeds; ///< Number of fan speeds
const char **fan_speed_names; ///< Names of fan speeds in the form of an array of strings
/**
* @brief Function to get IR code
*
* @param mode Mode of the air conditioner
* @param fan_speed Fan speed of the air conditioner
* @param temperature Temperature of the air conditioner
* @param code Pointer to the IR code array
*
* @return Size of the IR code array
*/
size_t (*getInfraredCode)(uint8_t, uint8_t, uint8_t, const uint16_t**);
};
// This requires 3 bytes of FRAM
/**
* @brief The ClimateCard class is a class for controlling an air conditioner
*
* This class allows you to control an air conditioner using an IR LED.
* It is meant to be used with the ESPMega Climate Card.
*
* @note You can also use a DHT22 or DS18B20 temperature sensor to get the room temperature (and humidity if using a DHT22). Although, this is optional.
*
*/
class ClimateCard : public ExpansionCard {
public:
ClimateCard(uint8_t ir_pin, AirConditioner ac, uint8_t sensor_type, uint8_t sensor_pin, rmt_channel_t channel);
ClimateCard(uint8_t ir_pin, AirConditioner ac, rmt_channel_t channel);
~ClimateCard();
bool begin();
void loop();
void bindFRAM(FRAM *fram, uint16_t fram_address);
void setFRAMAutoSave(bool autoSave);
void saveStateToFRAM();
void loadStateFromFRAM();
void setTemperature(uint8_t temperature);
uint8_t getTemperature();
void setMode(uint8_t mode);
void setModeByName(const char* mode_name);
uint8_t getMode();
char* getModeName();
void setFanSpeed(uint8_t fan_speed);
void setFanSpeedByName(const char* fan_speed_name);
uint8_t getFanSpeed();
void setState(uint8_t mode, uint8_t fan_speed, uint8_t temperature);
char* getFanSpeedName();
float getRoomTemperature();
float getHumidity();
uint8_t getSensorType();
uint8_t registerChangeCallback(std::function<void(uint8_t, uint8_t, uint8_t)> callback);
uint8_t registerSensorCallback(std::function<void(float, float)> callback);
void unregisterChangeCallback(uint8_t handler);
void unregisterSensorCallback(uint8_t handler);
uint8_t getType();
private:
// Sensor objects
// We use pointers here because we don't know which sensor will be used
DHTNEW *dht;
DS18B20 *ds18b20;
// Callbacks
uint8_t callbacks_handler_count = 0;
uint8_t sensor_callbacks_handler_count = 0;
std::map<uint8_t,std::function<void(uint8_t, uint8_t, uint8_t)>> callbacks;
std::map<uint8_t,std::function<void(float, float)>> sensor_callbacks;
// Update functions
void updateSensor();
void updateAirConditioner();
// IR variables
uint8_t ir_pin;
IRBlaster ir_blaster;
// Air conditioner variables
AirConditioner ac;
ClimateCardData state;
// Sensor variables
uint8_t sensor_type;
uint8_t sensor_pin;
float humidity;
float room_temperature;
// FRAM variables
FRAM *fram;
uint16_t fram_address;
bool fram_auto_save;
uint16_t* getIrIndex(uint8_t mode, uint8_t fan_speed, uint8_t temperature);
};

View File

@ -1,258 +0,0 @@
#include <ClimateIoT.hpp>
ClimateIoT::ClimateIoT() {
}
/**
* @brief Destructor for the ClimateIoT class.
*/
ClimateIoT::~ClimateIoT() {
// Destructor implementation
}
/**
* @brief Initializes the ClimateIoT component.
*
* This function sets the MQTT client, base topic, card ID, and card pointer.
* It also registers the sensor and air conditioner update callbacks.
*
* @param card_id The ID of the expansion card.
* @param card A pointer to the ExpansionCard object.
* @param mqtt A pointer to the PubSubClient object.
* @param base_topic The base topic for MQTT communication.
* @return True if the initialization is successful, false otherwise.
*/
bool ClimateIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->mqtt = mqtt;
this->base_topic = base_topic;
this->card_id = card_id;
this->card = (ClimateCard *)card;
// Reister Callbacks
auto bindedSensorCallback = std::bind(&ClimateIoT::handleSensorUpdate, this, std::placeholders::_1, std::placeholders::_2);
this->card->registerSensorCallback(bindedSensorCallback);
auto bindedAirConditionerCallback = std::bind(&ClimateIoT::handleAirConditionerUpdate, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->card->registerChangeCallback(bindedAirConditionerCallback);
ESP_LOGD("ClimateIoT", "Climate IoT Component initialized");
return true;
}
/**
* @brief Handles MQTT messages for the ClimateIoT component.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
*/
void ClimateIoT::handleMqttMessage(char *topic, char *payload) {
uint8_t topic_length = strlen(topic);
if (this->processSetTemperatureMessage(topic, payload, topic_length))
return;
if (this->processSetModeMessage(topic, payload, topic_length))
return;
if (this->processSetFanSpeedMessage(topic, payload, topic_length))
return;
if (this->processRequestStateMessage(topic, payload, topic_length))
return;
}
/**
* @brief Publishes the temperature of the air conditioner to the MQTT broker.
*/
void ClimateIoT::publishClimateTemperature() {
char payload[5];
itoa(this->card->getTemperature(), payload, 10);
this->publishRelative(AC_TEMPERATURE_REPORT_TOPIC, payload);
}
/**
* @brief Publishes the mode of the air conditioner to the MQTT broker.
*/
void ClimateIoT::publishClimateMode() {
this->publishRelative(AC_MODE_REPORT_TOPIC, this->card->getModeName());
}
/**
* @brief Publishes the fan speed of the air conditioner to the MQTT broker.
*/
void ClimateIoT::publishClimateFanSpeed() {
this->publishRelative(AC_FAN_SPEED_REPORT_TOPIC, this->card->getFanSpeedName());
}
/**
* @brief Publishes the temperature and humidity of the room to the MQTT broker.
*/
void ClimateIoT::publishSensor() {
this->publishRoomTemperature();
this->publishHumidity();
}
/**
* @brief Publishes the climate data (temperature, mode, fan speed) to the MQTT broker.
*/
void ClimateIoT::publishClimate() {
this->publishClimateTemperature();
this->publishClimateMode();
this->publishClimateFanSpeed();
}
/**
* @brief Publishes the room temperature to the MQTT broker.
*/
void ClimateIoT::publishRoomTemperature() {
if (this->card->getSensorType() == AC_SENSOR_TYPE_NONE ) {
return;
}
char payload[5];
itoa(this->card->getRoomTemperature(), payload, 10);
this->publishRelative(AC_ROOM_TEMPERATURE_REPORT_TOPIC, payload);
}
/**
* @brief Publishes the humidity of the room to the MQTT broker.
*/
void ClimateIoT::publishHumidity() {
if (this->card->getSensorType() == AC_SENSOR_TYPE_DHT22) {
char payload[5];
itoa(this->card->getHumidity(), payload, 10);
this->publishRelative(AC_HUMIDITY_REPORT_TOPIC, payload);
}
}
/**
* @brief Handle Air Conditioner state change.
*
* @note This function is called by the underlying ClimateCard object and is not meant to be called manually.
*
* @param temperature Temperature of the air conditioner
* @param mode Mode of the air conditioner
* @param fan_speed Fan speed of the air conditioner
*/
void ClimateIoT::handleStateChange(uint8_t temperature, uint8_t mode, uint8_t fan_speed) {
this->publishClimate();
}
/**
* @brief Publishes the climate and sensor data to the MQTT broker.
*/
void ClimateIoT::publishReport() {
this->publishClimate();
this->publishSensor();
}
/**
* @brief Subscribes to MQTT topics.
*/
void ClimateIoT::subscribe() {
ESP_LOGD("ClimateIoT", " topics");
this->subscribeRelative(AC_TEMPERATURE_SET_TOPIC);
this->subscribeRelative(AC_MODE_SET_TOPIC);
this->subscribeRelative(AC_FAN_SPEED_SET_TOPIC);
this->subscribeRelative(AC_REQUEST_STATE_TOPIC);
ESP_LOGD("ClimateIoT", "Subscribed to topics");
}
/**
* @brief The loop function for the ClimateIoT component.
*
* @note This function does nothing.
*/
void ClimateIoT::loop() {
}
/**
* @brief Returns the type of the expansion card.
*
* @return The type of the expansion card.
*/
uint8_t ClimateIoT::getType() {
return CARD_TYPE_CLIMATE;
}
/**
* @brief Processes the set temperature MQTT message.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
* @param topic_length The length of the topic.
* @return True if the message is processed, false otherwise.
*/
bool ClimateIoT::processSetTemperatureMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_TEMPERATURE_SET_TOPIC)) {
uint8_t temperature = atoi(payload);
this->card->setTemperature(temperature);
return true;
}
return false;
}
/**
* @brief Processes the set mode MQTT message.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
* @param topic_length The length of the topic.
* @return True if the message is processed, false otherwise.
*/
bool ClimateIoT::processSetModeMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_MODE_SET_TOPIC)) {
this->card->setModeByName(payload);
return true;
}
return false;
}
/**
* @brief Processes the set fan speed MQTT message.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
* @param topic_length The length of the topic.
* @return True if the message is processed, false otherwise.
*/
bool ClimateIoT::processSetFanSpeedMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_FAN_SPEED_SET_TOPIC)) {
this->card->setFanSpeedByName(payload);
}
return false;
}
/**
* @brief Processes the request state MQTT message.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
* @param topic_length The length of the topic.
* @return True if the message is processed, false otherwise.
*/
bool ClimateIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length) {
if (!strcmp(topic, AC_REQUEST_STATE_TOPIC)) {
this->publishReport();
return true;
}
return false;
}
/**
* @brief This function is a callback function registered with the Climate card to be called when the sensor data is updated.
*
* @param temperature The room temperature.
* @param humidity The room humidity.
*
* @note The temperature and humidity are not used in this function but are required by the ClimateCard class to match the signature of the callback function.
*/
void ClimateIoT::handleSensorUpdate(float temperature, float humidity) {
this->publishSensor();
}
/**
* @brief This function is a callback function registered with the Climate card to be called when the air conditioner state is updated.
*
* @param mode The mode of the air conditioner.
* @param fan_speed The fan speed of the air conditioner.
* @param temperature The temperature of the air conditioner.
*/
void ClimateIoT::handleAirConditionerUpdate(uint8_t mode, uint8_t fan_speed, uint8_t temperature) {
this->publishClimate();
}

View File

@ -1,50 +0,0 @@
#pragma once
#include <IoTComponent.hpp>
#include <ExpansionCard.hpp>
#include <ClimateCard.hpp>
// MQTT Topics
#define AC_MODE_REPORT_TOPIC "mode"
#define AC_MODE_SET_TOPIC "set/mode"
#define AC_TEMPERATURE_REPORT_TOPIC "temperature"
#define AC_TEMPERATURE_SET_TOPIC "set/temperature"
#define AC_FAN_SPEED_REPORT_TOPIC "fan_speed"
#define AC_FAN_SPEED_SET_TOPIC "set/fan_speed"
#define AC_ROOM_TEMPERATURE_REPORT_TOPIC "room_temperature"
#define AC_HUMIDITY_REPORT_TOPIC "humidity"
#define AC_REQUEST_STATE_TOPIC "requeststate"
/**
* @brief The ClimateIoT class is a class for connecting the Climate Card to the IoT module.
*
* This function allows you to control the Climate Card using MQTT.
*
* @warning You should not instantiate this class directly, instead use ESPMegaIoT's registerCard function.
*/
class ClimateIoT : public IoTComponent {
public:
ClimateIoT();
~ClimateIoT();
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void publishClimate();
void publishClimateTemperature();
void publishClimateMode();
void publishClimateFanSpeed();
void publishSensor();
void publishRoomTemperature();
void publishHumidity();
void handleStateChange(uint8_t temperature, uint8_t mode, uint8_t fan_speed);
void handleSensorUpdate(float temperature, float humidity);
void handleAirConditionerUpdate(uint8_t mode, uint8_t fan_speed, uint8_t temperature);
void publishReport();
void subscribe();
void loop();
uint8_t getType();
private:
ClimateCard *card;
bool processSetTemperatureMessage(char *topic, char *payload, uint8_t topic_length);
bool processSetModeMessage(char *topic, char *payload, uint8_t topic_length);
bool processSetFanSpeedMessage(char *topic, char *payload, uint8_t topic_length);
bool processRequestStateMessage(char *topic, char *payload, uint8_t topic_length);
};

View File

@ -1,106 +0,0 @@
#include <CurrentTransformerCard.hpp>
CurrentTransformerCard::CurrentTransformerCard(AnalogCard* analogCard, uint8_t pin, float *voltage, std::function<float(uint16_t)> adcToCurrent, uint32_t conversionInterval)
{
this->analogCard = analogCard;
this->pin = pin;
this->voltage = voltage;
this->adcToCurrent = adcToCurrent;
this->conversionInterval = conversionInterval;
}
void CurrentTransformerCard::bindFRAM(FRAM *fram, uint32_t framAddress)
{
this->fram = fram;
this->framAddress = framAddress;
}
bool CurrentTransformerCard::begin()
{
// Is analogCard a nullptr?
if (this->analogCard == nullptr) {
return false;
}
this->beginConversion();
return true;
}
void CurrentTransformerCard::loop()
{
if (this->lastConversionTime == 0) {
this->lastConversionTime = millis();
}
if (millis() - lastConversionLoopTime > this->conversionInterval) {
this->beginConversion();
lastConversionLoopTime = millis();
}
}
void CurrentTransformerCard::beginConversion()
{
uint16_t adcValue = this->analogCard->analogRead(this->pin);
this->current = this->adcToCurrent(adcValue);
this->setEnergy(this->energy + this->current * *this->voltage * (millis() - this->lastConversionTime) / 3600000); // in Wh
this->lastConversionTime = millis();
}
void CurrentTransformerCard::setEnergy(float energy)
{
this->energy = energy;
if (this->autoSave) {
this->saveEnergy();
}
for (auto const& callback : this->callbacks) {
callback.second(this->current, this->energy);
}
}
void CurrentTransformerCard::resetEnergy()
{
this->setEnergy(0);
}
float CurrentTransformerCard::getCurrent()
{
return this->current;
}
double CurrentTransformerCard::getEnergy()
{
return this->energy;
}
uint8_t CurrentTransformerCard::registerCallback(std::function<void(float, double)> callback) {
this->callbacks[this->handler_count] = callback;
return this->handler_count++;
}
void CurrentTransformerCard::unregisterCallback(uint8_t handler) {
this->callbacks.erase(handler);
}
void CurrentTransformerCard::saveEnergy(){
this->fram->write(this->framAddress, (uint8_t*)&this->energy, sizeof(this->energy));
}
void CurrentTransformerCard::loadEnergy(){
this->fram->read(this->framAddress, (uint8_t*)&this->energy, sizeof(this->energy));
if (this->energy < 0 || isnan(this->energy)) {
this->energy = 0;
}
}
void CurrentTransformerCard::setEnergyAutoSave(bool autoSave){
this->autoSave = autoSave;
}
float CurrentTransformerCard::getVoltage(){
return *this->voltage;
}
float CurrentTransformerCard::getPower(){
return this->current * *this->voltage;
}
uint8_t CurrentTransformerCard::getType() {
return CARD_TYPE_CT;
}

View File

@ -1,51 +0,0 @@
#pragma once
#include <AnalogCard.hpp>
#include <ExpansionCard.hpp>
#include <FRAM.h>
#include <map>
#define CARD_TYPE_CT 4
/**
* @brief The CurrentTransformer class is a class for reading the current and energy from a current transformer that's connected to the AnalogCard.
* Also supports storing energy to FRAM.
*/
class CurrentTransformerCard : public ExpansionCard
{
public:
CurrentTransformerCard(AnalogCard* analogCard, uint8_t pin, float *voltage, std::function<float(uint16_t)> adcToCurrent, uint32_t conversionInterval);
void bindFRAM(FRAM *fram, uint32_t framAddress); // Takes 16 bytes of FRAM (long double energy)
bool begin();
void loop();
void beginConversion();
void setEnergy(float energy);
void resetEnergy();
float getCurrent();
double getEnergy();
float getPower();
void saveEnergy();
void loadEnergy();
void setEnergyAutoSave(bool autoSave);
float getVoltage();
uint8_t registerCallback(std::function<void(float, double)> callback);
void unregisterCallback(uint8_t handler);
uint8_t getType();
private:
AnalogCard* analogCard;
uint8_t pin;
uint32_t framAddress;
FRAM *fram;
uint32_t conversionInterval;
bool autoSave;
float calibration;
double energy;
float current;
float *voltage;
uint32_t lastConversionTime;
std::function<float(uint16_t)> adcToCurrent; // std::function that convert adc value to current in amps
uint8_t handler_count = 0;
std::map<uint8_t,std::function<void(float, double)>> callbacks;
uint32_t lastConversionLoopTime;
};

View File

@ -1,66 +0,0 @@
#include <CurrentTransformerIoT.hpp>
CurrentTransformerIoT::CurrentTransformerIoT() {
}
CurrentTransformerIoT::~CurrentTransformerIoT() {
}
bool CurrentTransformerIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
ESP_LOGD("CurrentTransformerIoT", "Beginning CurrentTransformerIoT");
this->card_id = card_id;
this->currentTransformerCard = (CurrentTransformerCard*) card;
this->mqtt = mqtt;
this->base_topic = base_topic;
auto bindedCTCallback = std::bind(&CurrentTransformerIoT::handleCTCallback, this, std::placeholders::_1, std::placeholders::_2);
this->currentTransformerCard->registerCallback(bindedCTCallback);
return true;
}
void CurrentTransformerIoT::handleMqttMessage(char *topic, char *payload) {
uint8_t payload_length = strlen(payload);
if(this->processSetEnergyMessage(topic, payload, payload_length)) return;
if (!strcmp(topic, CT_RESET_ENERGY_TOPIC)) {
this->currentTransformerCard->resetEnergy();
return;
} else if (!strcmp(topic, CT_REQUESTSTATE_TOPIC)) {
this->publishReport();
return;
}
}
void CurrentTransformerIoT::subscribe() {
this->subscribeRelative(CT_SET_ENERGY_TOPIC);
this->subscribeRelative(CT_RESET_ENERGY_TOPIC);
this->subscribeRelative(CT_REQUESTSTATE_TOPIC);
}
void CurrentTransformerIoT::loop() {
// Not used, still need this to meet polymorphism requirements
}
void CurrentTransformerIoT::publishReport() {
char outputBuffer[256];
snprintf(outputBuffer, sizeof(outputBuffer), "%.2f", this->currentTransformerCard->getPower());
this->publishRelative(CT_POWER_TOPIC, outputBuffer);
snprintf(outputBuffer, sizeof(outputBuffer), "%.2f", this->currentTransformerCard->getEnergy());
this->publishRelative(CT_ENERGY_TOPIC, outputBuffer);
snprintf(outputBuffer, sizeof(outputBuffer), "%.2f", this->currentTransformerCard->getCurrent());
this->publishRelative(CT_CURRENT_TOPIC, outputBuffer);
}
uint8_t CurrentTransformerIoT::getType() {
return this->currentTransformerCard->getType();
}
bool CurrentTransformerIoT::processSetEnergyMessage(char* topic, char* payload, uint8_t topic_length) {
if(strcmp(topic, CT_SET_ENERGY_TOPIC)) return false;
this->currentTransformerCard->setEnergy(atof(payload));
return true;
}
void CurrentTransformerIoT::handleCTCallback(float current, double energy) {
this->publishReport();
}

View File

@ -1,27 +0,0 @@
#include <CurrentTransformerCard.hpp>
#include <IoTComponent.hpp>
#include <ExpansionCard.hpp>
#define CT_REQUESTSTATE_TOPIC "requeststate"
#define CT_SET_ENERGY_TOPIC "energy/set"
#define CT_RESET_ENERGY_TOPIC "energy/reset"
#define CT_ENERGY_TOPIC "energy"
#define CT_POWER_TOPIC "power"
#define CT_CURRENT_TOPIC "current"
class CurrentTransformerIoT : public IoTComponent
{
public:
CurrentTransformerIoT();
~CurrentTransformerIoT();
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void subscribe();
void loop();
void publishReport();
uint8_t getType();
private:
CurrentTransformerCard *currentTransformerCard;
bool processSetEnergyMessage(char* topic, char* payload, uint8_t topic_length);
void handleCTCallback(float current, double energy);
};

View File

@ -1,401 +0,0 @@
#include <DigitalInputCard.hpp>
/**
* @brief Create a new Digital Input Card object with the specified address
* @note If you are using the ESPMegaI/O board, you should use the dip switch constructor
*
* @param address_a The ESPMegaI/O address of bank A
* @param address_b The ESPMegaI/O address of bank B
*/
DigitalInputCard::DigitalInputCard(uint8_t address_a, uint8_t address_b) : callbacks()
{
this->address_a = address_a;
this->address_b = address_b;
this->callbacks_handler_index = 0;
}
/**
* @brief Create a new Digital Input Card object with the specified position on the dip switch
*
* @note The bit 0 are at the left of the dip switch
*
* @warning There are 6 switches on the dip switch, 3 for bank A and 3 for bank B, They should be unique for each bank accross all the cards
*
* @param bit0 The position of the first switch on the dip switch
* @param bit1 The position of the second switch on the dip switch
* @param bit2 The position of the third switch on the dip switch
* @param bit3 The position of the fourth switch on the dip switch
* @param bit4 The position of the fifth switch on the dip switch
* @param bit5 The position of the sixth switch on the dip switch
*/
DigitalInputCard::DigitalInputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4, bool bit5)
{
this->address_a = 0x20;
this->address_b = 0x20;
this->inputBufferA = 0;
this->inputBufferB = 0;
if (bit0)
this->address_a += 1;
if (bit1)
this->address_a += 2;
if (bit2)
this->address_a += 4;
if (bit3)
this->address_b += 1;
if (bit4)
this->address_b += 2;
if (bit5)
this->address_b += 4;
}
/**
* @brief Initialize the Digital Input Card
*
* @return True if the initialization is successful, false otherwise
*/
bool DigitalInputCard::begin()
{
this->inputBankA = PCF8574(this->address_a);
this->inputBankB = PCF8574(this->address_b);
if (!this->inputBankA.begin())
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Failed to install input bank A");
this->initOk = false;
return false;
}
if (!this->inputBankB.begin())
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Failed to install input bank B");
this->initOk = false;
return false;
}
// Set the debounce time for all pins to 50ms
for (int i = 0; i < 16; i++)
{
this->debounceTime[i] = 50;
this->lastDebounceTime[i] = 0;
}
// Initialize the pin map to the default values
for (int i = 0; i < 16; i++)
{
this->pinMap[i] = i;
this->virtualPinMap[i] = i;
}
this->initOk = true;
return true;
}
/**
* @brief Read the input from the specified pin, always refresh the input buffers
*
* @param pin The pin to read from
* @return True if the pin is HIGH, false if the pin is LOW
*/
bool DigitalInputCard::digitalRead(uint8_t pin)
{
return this->digitalRead(pin, true);
}
/**
* @brief Read the input from the specified pin, also refresh the input buffers if refresh is true
*
* @param pin The pin to read from
* @param refresh If true, the input buffers will be refreshed before reading the pin
* @return True if the pin is HIGH, false if the pin is LOW
*/
bool DigitalInputCard::digitalRead(uint8_t pin, bool refresh)
{
if(!this->initOk)
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Card not initialized");
return false;
}
pin = pinMap[pin];
// First check if the pin is in bank A or B
if (pin >= 0 && pin <= 7)
{
// Refresh the input buffers if refresh is true
if (refresh)
refreshInputBankA();
// Extract the bit from the buffer
return ((inputBufferA >> (7 - pin)) & 1);
}
else if (pin >= 8 && pin <= 15)
{
// Refresh the input buffers if refresh is true
if (refresh)
refreshInputBankB();
// Extract the bit from the buffer
return ((inputBufferB >> (15 - pin)) & 1);
}
return false;
}
/**
* @brief Check if the specified pin changed since the last call to this function
*
* @note This function compares the current input buffer with the previous input buffer to detect changes
*
* @param pin The pin to check
* @param currentBuffer The current input buffer
* @param previousBuffer The previous input buffer
*/
void DigitalInputCard::handlePinChange(int pin, uint8_t &currentBuffer, uint8_t &previousBuffer)
{
// Get the index of the pin in the pin map
uint8_t virtualPin = virtualPinMap[pin];
if (pin < 8)
{
// Handle Bank A
if (((previousBuffer >> (7 - pin)) & 1) != ((currentBuffer >> (7 - pin)) & 1))
{
// When the pin first change, store the time
if (!pinChanged[virtualPin])
{
ESP_LOGD("DigitalInputCard", "Pin %d changed", virtualPin);
pinChanged[virtualPin] = true;
lastDebounceTime[virtualPin] = millis();
}
else
{
ESP_LOGD("DigitalInputCard", "Pin %d (%d>%d) debounce time: %d", virtualPin, ((previousBuffer >> (7 - pin)) & 1), ((currentBuffer >> (7 - pin)) & 1), millis() - lastDebounceTime[virtualPin]);
// If the pin was already changed, check if the debounce time has passed
if ((millis() - lastDebounceTime[virtualPin]) > debounceTime[virtualPin])
{
ESP_LOGD("DigitalInputCard", "Pin %d triggered", virtualPin);
// Call the callback function
for (auto const &callback : callbacks)
{
callback.second(virtualPin, ((currentBuffer >> (7 - pin)) & 1));
}
// Store the previous buffer at the specified pin (bitwise operation)
// new value : (currentBuffer >> (7 - pin)) & 1)
previousBuffer = (previousBuffer & ~(1 << (7 - pin))) | (((currentBuffer >> (7 - pin)) & 1) << (7 - pin));
// Reset the pin changed flag
pinChanged[virtualPin] = false;
}
}
}
else
{
// Pin bounce back to previous state, reset the debounce time
lastDebounceTime[virtualPin] = millis();
pinChanged[virtualPin] = false;
}
}
else
{
// Handle Bank B
if (((previousBuffer >> (15 - pin)) & 1) != ((currentBuffer >> (15 - pin)) & 1))
{
// When the pin first change, store the time
if (!pinChanged[virtualPin])
{
ESP_LOGD("DigitalInputCard", "Pin %d changed", virtualPin);
pinChanged[virtualPin] = true;
lastDebounceTime[virtualPin] = millis();
}
else
{
ESP_LOGD("DigitalInputCard", "Pin %d (%d>%d) debounce time: %d", virtualPin, ((previousBuffer >> (15 - pin)) & 1), ((currentBuffer >> (15 - pin)) & 1), millis() - lastDebounceTime[virtualPin]);
// If the pin was already changed, check if the debounce time has passed
if ((millis() - lastDebounceTime[virtualPin]) > debounceTime[virtualPin])
{
ESP_LOGD("DigitalInputCard", "Pin %d triggered", virtualPin);
// Call the callback function
for (auto const &callback : callbacks)
{
callback.second(virtualPin, ((currentBuffer >> (15 - pin)) & 1));
}
// Store the previous buffer at the specified pin (bitwise operation)
// new value : (currentBuffer >> (15 - pin)) & 1)
previousBuffer = (previousBuffer & ~(1 << (15 - pin))) | (((currentBuffer >> (15 - pin)) & 1) << (15 - pin));
// Reset the pin changed flag
pinChanged[virtualPin] = false;
}
}
}
else
{
// Pin bounce back to previous state, reset the debounce time
lastDebounceTime[virtualPin] = millis();
pinChanged[virtualPin] = false;
}
}
}
/**
* @brief A loop to refresh the input buffers and check for pin changes
*
* @note Although this function can be called in the main loop, it is recommended install the card in ESPMega to automatically manage the loop
*/
// Preform a loop to refresh the input buffers
void DigitalInputCard::loop()
{
// Refresh the input buffers
refreshInputBankA();
refreshInputBankB();
// Iterate over all pins and check if they changed
for (int i = 0; i < 16; i++)
{
// Check which bank the pin is in
if (i < 8)
{
handlePinChange(i, inputBufferA, previousInputBufferA);
}
else if (i >= 8 && i <= 15)
{
handlePinChange(i, inputBufferB, previousInputBufferB);
}
}
}
/**
* @brief Get the input buffer for bank A (the first 8 pins)
*
* @return The input buffer for bank A where the first bit is the first pin and the last bit is the last pin
*/
uint8_t DigitalInputCard::getInputBufferA()
{
// Rearrange the bits to match the pin map
uint8_t inputBufferA_rearranged = 0;
for (int i = 0; i < 8; i++)
{
inputBufferA_rearranged |= ((inputBufferA >> i) & 1) << (7 - i);
}
return inputBufferA_rearranged;
}
/**
* @brief Get the input buffer for bank B (the last 8 pins)
*
* @return The input buffer for bank B where the first bit is the first pin and the last bit is the last pin
*/
uint8_t DigitalInputCard::getInputBufferB()
{
// Rearrange the bits to match the pin map
uint8_t inputBufferB_rearranged = 0;
for (int i = 0; i < 8; i++)
{
inputBufferB_rearranged |= ((inputBufferB >> i) & 1) << (7 - i);
}
return inputBufferB_rearranged;
}
/**
* @brief Register a callback function to be called when a pin changes
*
* @param callback The callback function to be called
* @return The handler of the callback function
*/
uint8_t DigitalInputCard::registerCallback(std::function<void(uint8_t, bool)> callback)
{
callbacks[this->callbacks_handler_index] = callback;
return this->callbacks_handler_index++;
}
/**
* @brief Read the input state from the input ic and store it in the input buffer for bank A
*/
void DigitalInputCard::refreshInputBankA()
{
if(!this->initOk)
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Card not initialized");
return;
}
inputBufferA = inputBankA.read8();
}
/**
* @brief Read the input state from the input ic and store it in the input buffer for bank B
*/
void DigitalInputCard::refreshInputBankB()
{
if(!this->initOk)
{
ESP_LOGE("DigitalInputCard", "Input Card ERROR: Card not initialized");
return;
}
inputBufferB = inputBankB.read8();
}
/**
* @brief Set the debounce time for the specified pin
*
* Debounce is the time in milliseconds that the pin should be stable before the callback function is called
* This is useful to prevent false triggers when the input is noisy
* An example of this is when the input is connected to a mechanical switch
*
* @param pin The pin to set the debounce time for
* @param debounceTime The debounce time in milliseconds
*/
void DigitalInputCard::setDebounceTime(uint8_t pin, uint32_t debounceTime)
{
pin = pinMap[pin];
this->debounceTime[pin] = debounceTime;
}
/**
* @brief Unregister a callback function
*
* @param handler The handler of the callback function to unregister
*/
void DigitalInputCard::unregisterCallback(uint8_t handler)
{
callbacks.erase(handler);
}
/**
* @brief Load the pin map for the card
*
* A pin map is an array of 16 elements that maps the physical pins to virtual pins
* The virtual pins are the pins that are used in the callback functions and are used for all the functions in this class
* The physical pins are the pins on the Input IC, This can be found on the schematic of the ESPMegaI/O board
* This function is useful if you want to change the number identification of the pins to match your project needs
*
* @param pinMap The pin map to load
*/
void DigitalInputCard::loadPinMap(uint8_t pinMap[16])
{
for (int i = 0; i < 16; i++)
{
// Load the pin map (physical pin to virtual pin)
this->pinMap[i] = pinMap[i];
// Load the virtual pin map (virtual pin to physical pin)
this->virtualPinMap[pinMap[i]] = i;
}
}
/**
* @brief Get the type of the card
*
* @return The type of the card
*/
uint8_t DigitalInputCard::getType()
{
return CARD_TYPE_DIGITAL_INPUT;
}
/**
* @brief Preload the previous input buffer and the input buffer
*
* @note This function is useful if you want to preload the input buffers with a run-time value
*/
void DigitalInputCard::preloadInputBuffer()
{
refreshInputBankA();
refreshInputBankB();
previousInputBufferA = inputBufferA;
previousInputBufferB = inputBufferB;
}
/**
* @brief Get the status of the card
*
* @return True if the card is initialized, false otherwise
*/
bool DigitalInputCard::getStatus()
{
return this->initOk;
}

View File

@ -1,72 +0,0 @@
#pragma once
#include <ExpansionCard.hpp>
#include <PCF8574.h>
#include <map>
// Card Type
#define CARD_TYPE_DIGITAL_INPUT 0x01
/**
* @brief ESPMegaPRO Digital Input Card
*
* This class represents the ESPMegaPRO Digital Input Card.
* It allows you to read the state of the digital inputs from the ESPMegaPRO Digital Input Card.
* It also allows you to register callback functions to be called when a pin changes.
* The callback function also support debouncing.
*
*/
class DigitalInputCard : public ExpansionCard {
public:
// Instantiate the card with the specified address
DigitalInputCard(uint8_t address_a, uint8_t address_b);
// Instantiate the card with the specified position on the dip switch
DigitalInputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4, bool bit5);
// Initialize the card
bool begin();
// Refresh and Read the input from the specified pin, always refresh the input buffers
bool digitalRead(uint8_t pin);
// Read the input from the specified pin, also refresh the input buffers if refresh is true
bool digitalRead(uint8_t pin, bool refresh);
// Preform a loop to refresh the input buffers
void loop();
// Get the input buffer for bank A
uint8_t getInputBufferA();
// Get the input buffer for bank B
uint8_t getInputBufferB();
// Set the debounce time for the specified pin
void setDebounceTime(uint8_t pin, uint32_t debounceTime);
// Register a callback function to be called when a pin changes
uint8_t registerCallback(std::function<void(uint8_t, bool)> callback);
// Unregister the callback function
void unregisterCallback(uint8_t handler);
// Load a new pin map
void loadPinMap(uint8_t pinMap[16]);
// Preload previousInputBuffer and inputBuffer
void preloadInputBuffer();
// Status of card
bool getStatus();
// Get type of card
uint8_t getType();
private:
bool initOk = false;
PCF8574 inputBankA;
PCF8574 inputBankB;
uint8_t address_a;
uint8_t address_b;
uint8_t inputBufferA;
uint8_t inputBufferB;
uint8_t previousInputBufferA;
uint8_t previousInputBufferB;
uint32_t debounceTime[16];
uint32_t lastDebounceTime[16];
bool pinChanged[16];
// A map of the physical pin to the virtual pin
uint8_t pinMap[16];
// A map of the virtual pin to the physical pin
uint8_t virtualPinMap[16];
uint8_t callbacks_handler_index = 0;
std::map<uint8_t, std::function<void(uint8_t, bool)>> callbacks;
void refreshInputBankA();
void refreshInputBankB();
void handlePinChange(int pin, uint8_t& currentBuffer, uint8_t& previousBuffer);
};

View File

@ -1,128 +0,0 @@
#include <DigitalInputIoT.hpp>
/**
* @brief Initializes the DigitalInputIoT object.
*
* This function sets the necessary parameters for the DigitalInputIoT object, such as the card ID, expansion card, MQTT client, and base topic.
* It also enables the publishing of digital input values and registers a callback function for handling value changes.
*
* @note Although this function can be called in the main program, it is recommended to use ESPMegaIoT::registerCard() to automatically manage the instantiation and initialization of this component.
*
* @param card_id The ID of the card.
* @param card Pointer to the DigitalInputCard object.
* @param mqtt Pointer to the PubSubClient object.
* @param base_topic The base topic for MQTT communication.
* @return True if the initialization is successful, false otherwise.
*/
bool DigitalInputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic) {
this->card = (DigitalInputCard *)card;
if(!this->card->getStatus()) {
return false;
}
this->card_id = card_id;
this->mqtt = mqtt;
this->base_topic = base_topic;
this->setDigitalInputsPublishEnabled(true);
this->card->registerCallback(std::bind(&DigitalInputIoT::handleValueChange, this, std::placeholders::_1, std::placeholders::_2));
return true;
}
/**
* @brief Subscribes to the MQTT topics for the DigitalInputIoT component.
*/
void DigitalInputIoT::subscribe() {
this->subscribeRelative(PUBLISH_ENABLE_TOPIC);
this->subscribeRelative(INPUT_REQUEST_STATE_TOPIC);
}
/**
* @brief Handles MQTT messages for the DigitalInputIoT component.
*
* @param topic The trimmed topic of the MQTT message.
* @param payload The null-terminated payload of the MQTT message.
*/
void DigitalInputIoT::handleMqttMessage(char *topic, char *payload) {
if (!strcmp(topic, INPUT_REQUEST_STATE_TOPIC)) {
this->publishDigitalInputs();
}
// payload is char '0' or '1'
else if (!strcmp(topic, PUBLISH_ENABLE_TOPIC)) {
if (payload[0] == '1') {
this->setDigitalInputsPublishEnabled(true);
} else {
this->setDigitalInputsPublishEnabled(false);
}
}
}
/**
* @brief Publish all digital inputs to the MQTT broker.
*/
void DigitalInputIoT::publishDigitalInputs() {
if (!this->digital_inputs_publish_enabled) {
return;
}
for (int i = 0; i < 16; i++) {
this->publishDigitalInput(i);
}
}
/**
* @brief Set if the digital inputs should be published to the MQTT broker.
*
* @param enabled True if the digital inputs should be published, false otherwise.
*/
void DigitalInputIoT::setDigitalInputsPublishEnabled(bool enabled) {
this->digital_inputs_publish_enabled = enabled;
if (enabled) {
this->publishDigitalInputs();
}
}
/**
* @brief Handles a value change for a digital input.
*
* @note This function is registered as a callback function for the DigitalInputCard object.
*
* @param pin The pin that changed.
* @param value The new value of the pin.
*/
void DigitalInputIoT::handleValueChange(uint8_t pin, uint8_t value) {
if (this->digital_inputs_publish_enabled) {
this->publishDigitalInput(pin);
}
}
/**
* @brief Publish all inputs to the MQTT Broker
*
* @note This function is overriden from the IoTComponent class and is called by ESPMegaIoT.
*
* @param pin The pin to publish.
*/
void DigitalInputIoT::publishReport() {
this->publishDigitalInputs();
}
uint8_t DigitalInputIoT::getType() {
return CARD_TYPE_DIGITAL_INPUT;
}
/**
* @brief Publish a digital input to the MQTT broker.
*
* @param pin The pin to publish.
*/
void DigitalInputIoT::publishDigitalInput(uint8_t pin) {
char topic[20] = {0};
char payload[20] = {0};
topic[0] = pin/10 + '0';
topic[1] = pin%10 + '0';
topic[2] = '\0';
payload[0] = this->card->digitalRead(pin, false) + '0';
payload[1] = '\0';
this->publishRelative(topic, payload);
}

View File

@ -1,32 +0,0 @@
#pragma once
#include <IoTComponent.hpp>
#include <DigitalInputCard.hpp>
#include <FRAM.h>
// MQTT Topics
#define PUBLISH_ENABLE_TOPIC "publish_enable"
#define INPUT_REQUEST_STATE_TOPIC "requeststate"
/**
* @brief The DigitalInputIoT class is a class for connecting the Digital Input Card to the IoT module.
*
* This function allows you to control the Digital Input Card using MQTT.
*
* @warning You should not instantiate this class directly, instead use ESPMegaIoT's registerCard function.
*/
class DigitalInputIoT : public IoTComponent {
public:
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void publishDigitalInputs();
void publishDigitalInput(uint8_t pin);
void setDigitalInputsPublishEnabled(bool enabled);
void handleValueChange(uint8_t pin, uint8_t value);
void publishReport();
void subscribe();
uint8_t getType();
private:
bool digital_inputs_publish_enabled = false;
DigitalInputCard *card;
};

View File

@ -1,83 +0,0 @@
#include <DigitalInputRTU.hpp>
DigitalInputRTU::DigitalInputRTU()
{
this->iot = nullptr;
this->size = 0;
this->callbackCount = 0;
for (int i = 0; i < 16; i++)
this->inputBuffer[i] = false;
}
DigitalInputRTU::~DigitalInputRTU()
{
}
void DigitalInputRTU::begin(char *remoteBaseTopic, uint8_t remote_card_slot, ESPMegaIoT *iot)
{
this->remoteBaseTopic = remoteBaseTopic;
this->remoteBaseTopicLength = strlen(remoteBaseTopic);
this->cardSlot = remote_card_slot;
this->iot = iot;
}
void DigitalInputRTU::subscribe()
{
// Subscribe to all pins
for(uint8_t i = 0; i < 16; i++)
{
char* topic = (char*)calloc(this->remoteBaseTopicLength + 6, sizeof(char));
sprintf(topic, "%s/%02d/%02d", this->remoteBaseTopic, this->cardSlot, i);
this->iot->subscribe(topic);
free(topic);
}
}
bool DigitalInputRTU::digitalRead(uint8_t pin)
{
return this->inputBuffer[pin];
}
uint8_t DigitalInputRTU::registerCallback(std::function<void(uint8_t, bool)> callback)
{
this->callbacks[callbackCount] = callback;
return callbackCount++;
}
void DigitalInputRTU::unregisterCallback(uint8_t handler)
{
this->callbacks.erase(handler);
}
void DigitalInputRTU::mqttCallback(char *topic, char *payload)
{
// First trim base topic from topic
char* topicWithoutBase = topic + this->remoteBaseTopicLength;
// Then check if topic is in format /cardSlot/pin
// Example trimmed topic: /01/00
// Card slot range from 00 to 99
// Pin range from 00 to 15
// Note that both card and pin are padded with 0 if they are less than 10
if(strlen(topicWithoutBase) != 6)
return;
if(topicWithoutBase[0] != '/')
return;
if(topicWithoutBase[3] != '/')
return;
// Extract card slot and pin
uint8_t cardSlot = (topicWithoutBase[1] - '0') * 10 + (topicWithoutBase[2] - '0');
uint8_t pin = (topicWithoutBase[4] - '0') * 10 + (topicWithoutBase[5] - '0');
// Check if card slot is correct
if(cardSlot != this->cardSlot)
return;
// Extract value
bool value = strcmp(payload, "1") == 0;
// Update input buffer
this->inputBuffer[pin] = value;
// Call callbacks
for(auto const& callback : this->callbacks)
{
callback.second(pin, value);
}
}

View File

@ -1,20 +0,0 @@
#pragma once
#include <ESPMegaRTU.hpp>
class DigitalInputRTU : public ESPMegaRTU {
public:
DigitalInputRTU();
~DigitalInputRTU();
void begin(char* remoteBaseTopic, uint8_t remote_card_slot, ESPMegaIoT* iot);
void subscribe();
bool digitalRead(uint8_t pin);
uint8_t registerCallback(std::function<void(uint8_t, bool)> callback);
void unregisterCallback(uint8_t handler);
private:
void mqttCallback(char* topic, char* payload);
ESPMegaIoT* iot;
size_t size;
uint16_t callbackCount;
std::map<uint8_t, std::function<void(uint8_t, bool)>> callbacks;
bool inputBuffer[16];
};

View File

@ -1,318 +0,0 @@
#include <DigitalOutputCard.hpp>
/**
* @brief Create a new Digital Output Card object with the specified address
*
* @note If you are using the ESPMegaI/O board, you should use the dip switch constructor
*
* @param address The ESPMegaI/O address of the card
*/
DigitalOutputCard::DigitalOutputCard(uint8_t address) : change_callbacks(){
this->address = address;
// load default pin map
for (int i = 0; i < 16; i++) {
this->pinMap[i] = i;
this->virtualPinMap[i] = i;
}
this->framBinded = false;
this->callbacks_handler_index = 0;
}
/**
* @brief Create a new Digital Output Card object with the specified position on the dip switch
*
* @note The bit 0 are at the left of the dip switch
*
* @warning There are 5 switches on the dip switch, they should be unique accross all the cards
*
* @param bit0 The position of the first switch on the dip switch
* @param bit1 The position of the second switch on the dip switch
* @param bit2 The position of the third switch on the dip switch
* @param bit3 The position of the fourth switch on the dip switch
* @param bit4 The position of the fifth switch on the dip switch
*/
DigitalOutputCard::DigitalOutputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4) :
DigitalOutputCard(0x40+bit0+bit1*2+bit2*4+bit3*8+bit4*16)
{
}
/**
* @brief Initialize the Digital Output Card
*
* @note Although this function can be called inside the main program, it is recommended to use ESPMegaPRO::installCard() instead
*
* @return True if the initialization is successful, false otherwise
*/
bool DigitalOutputCard::begin() {
this->pwm = Adafruit_PWMServoDriver(this->address);
this->pwm.begin();
pwm.setOutputMode(true);
// Output card don't send ack, we can't check if it's connected
// so we just return true
return true;
}
/**
* @brief Write a digtal LOW or HIGH to the specified pin
*
* @note This function set both the state and the pwm value of the pin
*
* @param pin The pin to set the state
* @param state The logic level to set the pin to
*/
void DigitalOutputCard::digitalWrite(uint8_t pin, bool state) {
this->pwm.setPin(virtualPinMap[pin], state ? 4095 : 0);
this->state_buffer[pin] = state;
this->value_buffer[pin] = state ? 4095 : 0;
if (this->framAutoSave) {
this->saveStateToFRAM();
this->savePinValueToFRAM(pin);
}
for (const auto& callback : change_callbacks)
{
callback.second(pin, state, state ? 4095 : 0);
}
}
/**
* @brief Write a pwm value to the specified pin
*
* @note This function set both the state and the pwm value of the pin
*
* @param pin The pin to set the pwm value
* @param value The pwm value to set
*/
void DigitalOutputCard::analogWrite(uint8_t pin, uint16_t value) {
// If value is greater than 4095, set it to 4095
if (value > 4095) value = 4095;
// Set the pwm value
this->pwm.setPin(virtualPinMap[pin], value);
if (this->framAutoSave) {
this->saveStateToFRAM();
this->savePinValueToFRAM(pin);
}
this->state_buffer[pin] = value > 0;
this->value_buffer[pin] = value;
for (const auto& callback : change_callbacks)
{
callback.second(pin, value > 0, value);
}
}
/**
* @brief The main loop for the Digital Output Card object.
*
* @note This function is not used, it is only here to implement the ExpansionCard interface
*/
void DigitalOutputCard::loop() {
}
/**
* @brief Get the state of the specified pin
*
* @param pin The pin to get the state
* @return The state of the pin
*/
bool DigitalOutputCard::getState(uint8_t pin) {
return this->state_buffer[pin];
}
/**
* @brief Get the pwm value of the specified pin
*
* @param pin The pin to get the pwm value
* @return The pwm value of the pin
*/
uint16_t DigitalOutputCard::getValue(uint8_t pin) {
return this->value_buffer[pin];
}
/**
* @brief Get the type of the card
*
* @return The type of the card
*/
uint8_t DigitalOutputCard::getType() {
return CARD_TYPE_DIGITAL_OUTPUT;
}
/**
* @brief Set the state of the specified pin
*
* @param pin The pin to set the state
* @param state The state of the pin
*/
void DigitalOutputCard::setState(uint8_t pin, bool state) {
this-> state_buffer[pin] = state;
this->pwm.setPin(virtualPinMap[pin], state*value_buffer[pin]);
if(this->framAutoSave) {
this->saveStateToFRAM();
}
for(const auto& callback : change_callbacks) {
callback.second(pin, state, value_buffer[pin]);
}
}
/**
* @brief Set the pwm value of the specified pin
*
* @param pin The pin to set the pwm value
* @param value The pwm value to set
*/
void DigitalOutputCard::setValue(uint8_t pin, uint16_t value) {
// If value is greater than 4095, set it to 4095
if (value > 4095) value = 4095;
this-> value_buffer[pin] = value;
this->pwm.setPin(virtualPinMap[pin], state_buffer[pin]*value);
if (this->framAutoSave) {
this->savePinValueToFRAM(pin);
}
for (const auto& callback : change_callbacks)
{
callback.second(pin, state_buffer[pin], value);
}
}
/**
* @brief Register a callback function for the specified pin
*
* @param callback The callback function to be called, the first parameter is the pin, the second parameter is the state, the third parameter is the pwm value
* @return The handler of the callback function
*/
uint8_t DigitalOutputCard::registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback) {
this->change_callbacks[this->callbacks_handler_index] = callback;
return this->callbacks_handler_index++;
}
/**
* @brief Unregister a callback function
*
* @param handler The handler of the callback function to be unregistered
*/
void DigitalOutputCard::unregisterChangeCallback(uint8_t handler) {
this->change_callbacks.erase(handler);
}
/**
* @brief Load a pin map
*
* A pin map is an array of 16 elements that maps the physical pins to virtual pins
* The virtual pins are the pins that are used in the callback functions and are used for all the functions in this class
* The physical pins are the pins on the Output IC, This can be found on the schematic of the ESPMegaI/O board
* This function is useful if you want to change the number identification of the pins to match your project needs
*
* @param pinMap The pin map to load
*/
void DigitalOutputCard::loadPinMap(uint8_t pinMap[16]) {
for(int i = 0; i < 16; i++) {
this->pinMap[i] = pinMap[i];
this->virtualPinMap[pinMap[i]] = i;
}
}
/**
* @brief Bind a FRAM to the card
*
* @note The Output Card use 34 bytes of FRAM
*
* @warning If the fram range overlap with another card, undefined behavior will occur
*
* @param fram The FRAM to bind
* @param address The address of the card in the FRAM
*/
void DigitalOutputCard::bindFRAM(FRAM *fram, uint16_t address) {
this->fram = fram;
this->framBinded = true;
this->framAddress = address;
}
/**
* @brief Pack the states of all the pins into a 16 bit integer
*
* @return The packed states
*/
uint16_t DigitalOutputCard::packStates() {
uint16_t packed = 0;
for(int i = 0; i < 16; i++) {
packed |= (state_buffer[i] << i);
}
return packed;
}
/**
* @brief Unpack the states of all the pins from a 16 bit integer
*
* @param states The packed states
*/
void DigitalOutputCard::unpackStates(uint16_t states) {
for(int i = 0; i < 16; i++) {
this->setState(i, (states >> i) & 1);
}
}
/**
* @brief Save the states and values of all the pins to the FRAM
*/
void DigitalOutputCard::saveToFRAM() {
if(!framBinded) return;
// Save the state
uint16_t packed = packStates();
this->fram->write16(framAddress, packed);
// Save the value
this->fram->write(framAddress+2, (uint8_t*)value_buffer, 32);
}
/**
* @brief Load the states and values of all the pins from the FRAM
*/
void DigitalOutputCard::loadFromFRAM() {
if(!framBinded) return;
// Load the state
uint16_t packed = this->fram->read16(framAddress);
unpackStates(packed);
// Load the value
uint16_t value[16];
this->fram->read(framAddress+2, (uint8_t*)value, 32);
for(int i = 0; i < 16; i++) {
this->setValue(i, value[i]);
}
}
/**
* @brief Set the auto save to FRAM
*
* @param autoSave True to enable auto save, false to disable auto save
*/
void DigitalOutputCard::setAutoSaveToFRAM(bool autoSave) {
this->framAutoSave = autoSave;
}
/**
* @brief Save a single pin value to FRAM
*
* @param pin The pin to save
*/
void DigitalOutputCard::savePinValueToFRAM(uint8_t pin) {
if(!framBinded) return;
this->fram->write(framAddress+2+pin*2, (uint8_t*)&value_buffer[pin], 2);
}
/**
* @brief Save the states of all the pins to FRAM
*/
void DigitalOutputCard::saveStateToFRAM() {
if(!framBinded) return;
uint16_t packed = packStates();
this->fram->write16(framAddress, packed);
}
/**
* @brief Toggle the state of the specified pin
*
* @param pin The pin to toggle
*/
void DigitalOutputCard::toggleState(uint8_t pin) {
this->setState(pin, !this->state_buffer[pin]);
}

View File

@ -1,118 +0,0 @@
#pragma once
#include <ExpansionCard.hpp>
#include <Adafruit_PWMServoDriver.h>
#include <FRAM.h>
#include <map>
// Protocol for digital output card
// Note that pin is always 2 characters long and padded with 0 if necessary
// Set pin state topic: <pin>/set/state payload: 0/1
// Set pin pwm topic: <pin>/set/value payload: 0-4095
// Publish pin state topic: <pin>/state payload: 0/1
// Publish pin pwm topic: <pin>/value payload: 0-4095
// Publish all topic: requeststate payload: N/A
// Enable/disable publish topic: publish_enable payload: 0/1
// MQTT Topics
#define SET_STATE_TOPIC "/set/state"
#define SET_VALUE_TOPIC "/set/value"
#define STATE_TOPIC "/state"
#define VALUE_TOPIC "/value"
#define REQUEST_STATE_TOPIC "requeststate"
#define PUBLISH_ENABLE_TOPIC "publish_enable"
// Card type
#define CARD_TYPE_DIGITAL_OUTPUT 0x00
/**
* @brief The DigitalOutputCard class is a class for controlling the Digital Output Card.
*
* This Digital Output Card has 16 digital outputs.
* All outputs are PWM capable.
* ALl outputs are 12V Push-Pull outputs.
* Outputs is grouped in 4 groups of 4 outputs.(0-3, 4-7, 8-11, 12-15)
* Each pin is capable of 0.6A, however each group's total current is limited to 1.2A.
*
* @warning You should not instantiate this class directly, instead use ESPMegaIO's registerCard function.
*/
class DigitalOutputCard : public ExpansionCard
{
public:
// Instantiate the card with the specified address
DigitalOutputCard(uint8_t address);
// Instantiate the card with the specified position on the dip switch
DigitalOutputCard(bool bit0, bool bit1, bool bit2, bool bit3, bool bit4);
// Initialize the card
bool begin();
// Dummy loop function
void loop();
// Set the output to the specified state
// This function set both the state and the pwm value
void digitalWrite(uint8_t pin, bool state);
// Set the output to the specified pwm value
// This function set both the state and the pwm value
void analogWrite(uint8_t pin, uint16_t value);
// Set the state of the specified pin
void setState(uint8_t pin, bool state);
// Set the pwm value of the specified pin
void setValue(uint8_t pin, uint16_t value);
// Get the state of the specified pin
bool getState(uint8_t pin);
// Toggle the state of the specified pin
void toggleState(uint8_t pin);
// Get the pwm value of the specified pin
uint16_t getValue(uint8_t pin);
// Register a callback function that will be called when the state of a pin changes
uint8_t registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
// Unregister the callback function
void unregisterChangeCallback(uint8_t handler);
// Load a new pin map
void loadPinMap(uint8_t pinMap[16]);
// Bind the fram object to the card
// The Output card use the fram in this layout:
// [2 bytes] 0-1 : state
// [32 bytes] 2-33 : value
void bindFRAM(FRAM *fram, uint16_t address);
// Save the state and value to the fram
void saveToFRAM();
// Load the state and value from the fram
void loadFromFRAM();
// Set the auto save to fram flag
void setAutoSaveToFRAM(bool autoSave);
// Save a single pin value to fram
void savePinValueToFRAM(uint8_t pin);
// Save state to fram
void saveStateToFRAM();
// Save value to fram
void saveValueToFRAM();
// Get type of card
uint8_t getType();
private:
// FRAM address
uint16_t framAddress;
// FRAM is binded
bool framBinded = false;
// Auto save to fram
bool framAutoSave = false;
// The fram object pointer
FRAM *fram;
// The pwm driver
Adafruit_PWMServoDriver pwm;
// The address of the card
uint8_t address;
// The state of the card
bool state_buffer[16];
// The pwm value of the card
uint16_t value_buffer[16];
// The callback function
uint8_t callbacks_handler_index = 0;
std::map<uint8_t, std::function<void(uint8_t, bool, uint16_t)>> change_callbacks;
// Physical pin to virtual pin map
uint8_t pinMap[16];
// Return 16 bit value representing all 16 channels
uint16_t packStates();
// Unpack the 16 bit value to the state buffer
void unpackStates(uint16_t states);
// Virtual pin to physical pin map
uint8_t virtualPinMap[16];
};

View File

@ -1,325 +0,0 @@
#include <DigitalOutputIoT.hpp>
/**
* @brief Create a new DigitalOutputIoT object
*/
DigitalOutputIoT::DigitalOutputIoT()
{
this->state_report_topic = new char[10];
this->value_report_topic = new char[10];
this->digital_outputs_publish_enabled = true;
}
/**
* @brief Destroy the DigitalOutputIoT object
*/
DigitalOutputIoT::~DigitalOutputIoT()
{
delete[] this->state_report_topic;
delete[] this->value_report_topic;
}
/**
* @brief Initialize the DigitalOutputIoT object
*
* @note ALthough this function can be called inside the main program, it is recommended to use ESPMegaPRO::installCard() instead
*
* @param card_id The id of the card
* @param card The card object
* @param mqtt The PubSubClient object
* @param base_topic The base topic of the card
* @return True if the initialization is successful, false otherwise
*/
bool DigitalOutputIoT::begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic)
{
ESP_LOGD("DigitalOutputIoT", "Beginning DigitalOutputIoT");
this->mqtt = mqtt;
this->base_topic = base_topic;
this->card = (DigitalOutputCard *)card;
this->card_id = card_id;
this->set_state_length = strlen(SET_STATE_TOPIC);
this->set_value_length = strlen(SET_VALUE_TOPIC);
this->state_length = strlen(STATE_TOPIC);
this->value_length = strlen(VALUE_TOPIC);
this->request_state_length = strlen(REQUEST_STATE_TOPIC);
this->publish_enable_length = strlen(PUBLISH_ENABLE_TOPIC);
strcpy(this->state_report_topic, "00/state");
strcpy(this->value_report_topic, "00/value");
ESP_LOGV("DigitalOutputIoT", "Registering callbacks inside DigitalOutputIoT::begin");
// Register callbacks
auto bindedCallback = std::bind(&DigitalOutputIoT::handleValueChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->card->registerChangeCallback(bindedCallback);
ESP_LOGV("DigitalOutputIoT", "DigitalOutputIoT::begin complete");
return true;
}
/**
* @brief Handle the MQTT messages for the DigitalOutputIoT card
*
* @param topic The topic of the message
* @param payload The payload of the message
*/
void DigitalOutputIoT::handleMqttMessage(char *topic, char *payload)
{
// Protocol for digital output card
// Note that pin is always 2 characters long and padded with 0 if necessary
// Set pin state topic: <pin>/set/state payload: 0/1
// Set pin pwm topic: <pin>/set/value payload: 0-4095
// Publish pin state topic: <pin>/state payload: 0/1
// Publish pin pwm topic: <pin>/value payload: 0-4095
// Publish all topic: requeststate payload: N/A
// Enable/disable publish topic: publish_enable payload: 0/1
uint8_t topic_length = strlen(topic);
if (this->processSetStateMessage(topic, payload, topic_length))
return;
if (this->processSetValueMessage(topic, payload, topic_length))
return;
if (this->processRequestStateMessage(topic, payload, topic_length))
return;
}
/**
* @brief Process a set state message
*
* @param topic The null terminated topic
* @param payload The null terminated payload
* @param topic_length The length of the topic
* @return True if the message is a set state message, false otherwise
*/
bool DigitalOutputIoT::processSetStateMessage(char *topic, char *payload, uint8_t topic_length)
{
// Check if the topic is a set state topic
// The correct format is <pin>/set/state
// This mean that the topic must end with /set/state
// Check if the 3rd character is /
if (topic[2] != '/')
{
return false;
}
// The topic must be set_state_length + 2 characters long
if (topic_length != set_state_length + 2)
{
return false;
}
// Check if the topic ends with /set/state
if (!strncmp(topic + 2, SET_STATE_TOPIC, set_state_length))
{
// Get the pin number
uint8_t pin = (topic[0] - '0') * 10 + (topic[1] - '0');
// Get the state
bool state = false;
char state_char = payload[0];
if (state_char == '0')
{
state = false;
}
else if (state_char == '1')
{
state = true;
}
else
{
return false;
}
// Set the state
card->setState(pin, state);
return true;
}
return false;
}
/**
* @brief Process a set value message
*
* @param topic The null terminated topic
* @param payload The null terminated payload
* @param topic_length The length of the topic
* @return True if the message is a set value message, false otherwise
*/
bool DigitalOutputIoT::processSetValueMessage(char *topic, char *payload, uint8_t topic_length)
{
// Check if the topic is a set value topic
// The correct format is <pin>/set/value
// This mean that the topic must end with /set/value
// Check if the 3rd character is /
if (topic[2] != '/')
{
return false;
}
// The topic must be set_value_length + 2 characters long
if (topic_length != set_value_length + 2)
{
return false;
}
// Check if the topic ends with /set/value
if (!strncmp(topic + 2, SET_VALUE_TOPIC, set_value_length))
{
// Get the pin number
uint8_t pin = (topic[0] - '0') * 10 + (topic[1] - '0');
// Get the value
uint16_t value = atoi(payload);
// Set the value
card->setValue(pin, value);
return true;
}
return false;
}
/**
* @brief Process a request state message
*
* @param topic The null terminated topic
* @param payload The null terminated payload
* @param topic_length The length of the topic
* @return True if the message is a request state message, false otherwise
*/
bool DigitalOutputIoT::processRequestStateMessage(char *topic, char *payload, uint8_t topic_length)
{
// Check if the topic is a request state topic
// The correct format is requeststate
// This mean that the topic must be request_state_length characters long
// The topic must be request_state_length characters long
if (topic_length != request_state_length)
{
return false;
}
// Check if the topic is requeststate
if (!strncmp(topic, REQUEST_STATE_TOPIC, request_state_length))
{
// Publish the state of all pins
publishDigitalOutputs();
return true;
}
return false;
}
/**
* @brief Publish the state of all digital outputs
*/
void DigitalOutputIoT::publishDigitalOutputs()
{
if (!digital_outputs_publish_enabled)
return;
for (int i = 0; i < 16; i++)
{
publishDigitalOutput(i);
}
}
/**
* @brief Publish the state and value of the specified digital output
*
* @param pin The pin to publish
*/
void DigitalOutputIoT::publishDigitalOutput(uint8_t pin)
{
publishDigitalOutputState(pin);
publishDigitalOutputValue(pin);
}
/**
* @brief Publish the state of the specified digital output
*
* @param pin The pin to publish
*/
void DigitalOutputIoT::publishDigitalOutputState(uint8_t pin)
{
if (!digital_outputs_publish_enabled)
return;
state_report_topic[0] = pin / 10 + '0';
state_report_topic[1] = pin % 10 + '0';
publishRelative(state_report_topic, card->getState(pin) ? "1" : "0");
}
/**
* @brief Publish the value of the specified digital output
*
* @param pin The pin to publish
*/
void DigitalOutputIoT::publishDigitalOutputValue(uint8_t pin)
{
if (!digital_outputs_publish_enabled)
return;
value_report_topic[0] = pin / 10 + '0';
value_report_topic[1] = pin % 10 + '0';
char payload[5];
sprintf(payload, "%d", card->getValue(pin));
publishRelative(value_report_topic, payload);
}
/**
* @brief Enable/disable publishing of digital outputs
*
* @param enabled True to enable publishing, false to disable publishing
*/
void DigitalOutputIoT::setDigitalOutputsPublishEnabled(bool enabled)
{
digital_outputs_publish_enabled = enabled;
}
/**
* @brief Handle the value change of a pin
*
* @note This function is registered as a callback function with the DigitalOutputCard object
*
* @param pin The pin that changed
* @param state The new state of the pin
* @param value The new value of the pin
*/
void DigitalOutputIoT::handleValueChange(uint8_t pin, bool state, uint16_t value)
{
publishDigitalOutput(pin);
}
/**
* @brief Publish all digital outputs
*
* @note This function is called by the ESPMegaIoT object
*/
void DigitalOutputIoT::publishReport()
{
publishDigitalOutputs();
}
/**
* @brief Get the type of the IoT component
*
* @return The type of the IoT component
*/
uint8_t DigitalOutputIoT::getType()
{
return CARD_TYPE_DIGITAL_OUTPUT;
}
/**
* @brief Subscribe to the MQTT topics used by the DigitalOutputIoT object
*/
void DigitalOutputIoT::subscribe()
{
char topic[20];
// Subscribe to all set state topics
for (int i = 0; i < 16; i++)
{
sprintf(topic, "%02d/set/state", i);
subscribeRelative(topic);
}
// Subscribe to all set value topics
for (int i = 0; i < 16; i++)
{
sprintf(topic, "%02d/set/value", i);
subscribeRelative(topic);
}
// Subscribe to request state topic
subscribeRelative(REQUEST_STATE_TOPIC);
// Subscribe to publish enable topic
subscribeRelative(PUBLISH_ENABLE_TOPIC);
}
/**
* @brief The main loop for the DigitalOutputIoT object
*
* @note This function is not used, it is only here to implement the IoTComponent interface
*/
void DigitalOutputIoT::loop()
{
}

View File

@ -1,41 +0,0 @@
#pragma once
#include <IoTComponent.hpp>
#include <ExpansionCard.hpp>
#include <DigitalOutputCard.hpp>
/**
* @brief The DigitalOutputIoT class is a class interfacing with the Digital Output Card through MQTT.
*
* @warning You should not instantiate this class directly, instead use ESPMegaIO's registerCard function.
*/
class DigitalOutputIoT : public IoTComponent {
public:
DigitalOutputIoT();
~DigitalOutputIoT();
bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
void handleMqttMessage(char *topic, char *payload);
void publishDigitalOutputs();
void publishDigitalOutput(uint8_t pin);
void publishDigitalOutputState(uint8_t pin);
void publishDigitalOutputValue(uint8_t pin);
void setDigitalOutputsPublishEnabled(bool enabled);
void handleValueChange(uint8_t pin, bool state, uint16_t value);
void publishReport();
void subscribe();
void loop();
uint8_t getType();
private:
bool digital_outputs_publish_enabled = false;
bool processSetStateMessage(char *topic, char *payload, uint8_t topic_length);
bool processSetValueMessage(char *topic, char *payload, uint8_t topic_length);
bool processRequestStateMessage(char *topic, char *payload, uint8_t topic_length);
DigitalOutputCard *card;
char *state_report_topic;
char *value_report_topic;
uint8_t set_state_length;
uint8_t set_value_length;
uint8_t state_length;
uint8_t value_length;
uint8_t request_state_length;
uint8_t publish_enable_length;
};

View File

@ -1,145 +0,0 @@
#include <DigitalOutputRTU.hpp>
DigitalOutputRTU::DigitalOutputRTU()
{
// constructor implementation
}
DigitalOutputRTU::~DigitalOutputRTU()
{
// destructor implementation
}
void DigitalOutputRTU::begin(char *remoteBaseTopic, uint8_t remote_card_slot, ESPMegaIoT *iot)
{
// begin implementation
this->remoteBaseTopic = remoteBaseTopic;
this->remoteBaseTopicLength = strlen(remoteBaseTopic);
this->cardSlot = remote_card_slot;
this->iot = iot;
}
void DigitalOutputRTU::subscribe()
{
// Subscribe to all pins
// State Topic format: <base_topic>/<card_slot>/<pin>/state
// Value Topic format: <base_topic>/<card_slot>/<pin>/value
char buffer[100];
for (uint8_t i = 0; i < 16; i++)
{
sprintf(buffer, "%s/%02d/%02d/state", this->remoteBaseTopic, this->cardSlot, i);
this->iot->subscribe(buffer);
sprintf(buffer, "%s/%02d/%02d/value", this->remoteBaseTopic, this->cardSlot, i);
this->iot->subscribe(buffer);
}
}
void DigitalOutputRTU::digitalWrite(uint8_t pin, bool state)
{
// Publish state to topic <base_topic>/<card_slot>/<pin>/set/state
// Publish 4095 to topic <base_topic>/<card_slot>/<pin>/set/value
char buffer[100];
sprintf(buffer, "%s/%02d/%02d/set/state", this->remoteBaseTopic, this->cardSlot, pin);
this->iot->publish(buffer, state ? "1" : "0");
sprintf(buffer, "%s/%02d/%02d/set/value", this->remoteBaseTopic, this->cardSlot, pin);
this->iot->publish(buffer, "4095");
}
void DigitalOutputRTU::analogWrite(uint8_t pin, uint16_t value)
{
// Publish 1 to topic <base_topic>/<card_slot>/<pin>/set/state
// Publish value to topic <base_topic>/<card_slot>/<pin>/set/value
char buffer[100];
sprintf(buffer, "%s/%02d/%02d/set/state", this->remoteBaseTopic, this->cardSlot, pin);
this->iot->publish(buffer, "1");
sprintf(buffer, "%s/%02d/%02d/set/value", this->remoteBaseTopic, this->cardSlot, pin);
char valueBuffer[10];
itoa(value, valueBuffer, 10);
this->iot->publish(buffer, valueBuffer);
}
void DigitalOutputRTU::setState(uint8_t pin, bool state)
{
// Publish state to topic <base_topic>/<card_slot>/<pin>/set/state
char buffer[100];
sprintf(buffer, "%s/%02d/%02d/set/state", this->remoteBaseTopic, this->cardSlot, pin);
this->iot->publish(buffer, state ? "1" : "0");
}
void DigitalOutputRTU::setValue(uint8_t pin, uint16_t value)
{
// Publish value to topic <base_topic>/<card_slot>/<pin>/set/value
char buffer[100];
sprintf(buffer, "%s/%02d/%02d/set/value", this->remoteBaseTopic, this->cardSlot, pin);
char valueBuffer[10];
itoa(value, valueBuffer, 10);
this->iot->publish(buffer, valueBuffer);
}
bool DigitalOutputRTU::getState(uint8_t pin)
{
return this->outputStateBuffer[pin];
}
uint16_t DigitalOutputRTU::getValue(uint8_t pin)
{
return this->outputValueBuffer[pin];
}
uint8_t DigitalOutputRTU::registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback)
{
this->change_callbacks[callbackCount] = callback;
return callbackCount++;
}
void DigitalOutputRTU::unregisterChangeCallback(uint8_t handler)
{
this->change_callbacks.erase(handler);
}
void DigitalOutputRTU::mqttCallback(char *topic, char *payload) {
// First trim base topic from topic
char* topicWithoutBase = topic + this->remoteBaseTopicLength;
// Then check if topic is in format /<cardSlot>/<pin>/state or /<cardSlot>/<pin>/value
// Example trimmed topic: /01/00/state, /01/00/value
// Card slot range from 00 to 99
// Pin range from 00 to 15
// Note that both card and pin are padded with 0 if they are less than 10
if(strlen(topicWithoutBase) != 12)
return;
if(topicWithoutBase[0] != '/')
return;
if(topicWithoutBase[3] != '/')
return;
if(topicWithoutBase[6] != '/')
return;
// Extract card slot and pin
uint8_t cardSlot = (topicWithoutBase[1] - '0') * 10 + (topicWithoutBase[2] - '0');
// Check if card slot is correct
if(cardSlot != this->cardSlot)
return;
uint8_t pin = (topicWithoutBase[4] - '0') * 10 + (topicWithoutBase[5] - '0');
// Extract value
uint16_t value = atoi(payload);
// Is it state or value?
if(!strcmp(topicWithoutBase + 7, "state"))
{
// Update state buffer
this->outputStateBuffer[pin] = strcmp(payload, "1") == 0;
// Call callbacks
for(auto const& callback : this->change_callbacks)
{
callback.second(pin, this->outputStateBuffer[pin], this->outputValueBuffer[pin]);
}
}
else if(!strcmp(topicWithoutBase + 7, "value"))
{
// Update value buffer
this->outputValueBuffer[pin] = value;
// Call callbacks
for(auto const& callback : this->change_callbacks)
{
callback.second(pin, this->outputStateBuffer[pin], this->outputValueBuffer[pin]);
}
}
}

View File

@ -1,29 +0,0 @@
#pragma once
#include <ESPMegaRTU.hpp>
#include <DigitalOutputCard.hpp>
class DigitalOutputRTU : public ESPMegaRTU
{
public:
DigitalOutputRTU();
~DigitalOutputRTU();
void begin(char *remoteBaseTopic, uint8_t remote_card_slot, ESPMegaIoT *iot);
void subscribe();
void digitalWrite(uint8_t pin, bool state);
void analogWrite(uint8_t pin, uint16_t value);
void setState(uint8_t pin, bool state);
void setValue(uint8_t pin, uint16_t value);
bool getState(uint8_t pin);
uint16_t getValue(uint8_t pin);
uint8_t registerChangeCallback(std::function<void(uint8_t, bool, uint16_t)> callback);
void unregisterChangeCallback(uint8_t handler);
private:
void mqttCallback(char *topic, char *payload);
ESPMegaIoT *iot;
size_t size;
uint16_t callbackCount;
std::map<uint8_t, std::function<void(uint8_t, bool, uint16_t)>> change_callbacks;
bool outputStateBuffer[16];
uint16_t outputValueBuffer[16];
};

View File

@ -1,3 +0,0 @@
#pragma once
#define SDK_VESRION "2.10.0"

View File

@ -1,778 +0,0 @@
#include <ESPMegaDisplay.hpp>
/**
* @brief Receives and processes serial commands from the display adapter.
* @param process Flag indicating whether to process the received commands.
* @return True if data is received, false otherwise.
*/
bool ESPMegaDisplay::recieveSerialCommand(bool process)
{
bool dataRecieved = false;
while (displayAdapter->available())
{
// Read the serial buffer if available
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return false;
}
rx_buffer[rx_buffer_index] = displayAdapter->read();
xSemaphoreGive(this->serialMutex);
rx_buffer_index++;
// Check for overflow
if (rx_buffer_index >= 256)
{
rx_buffer_index = 0;
}
if (process)
this->processSerialCommand();
dataRecieved = true;
}
return dataRecieved;
}
/**
* @brief Receives and processes serial commands from the display adapter.
* @return True if data is received, false otherwise.
*/
bool ESPMegaDisplay::recieveSerialCommand()
{
return recieveSerialCommand(true);
}
/**
* @brief Processes the received serial command.
* @note This function interacts directly with the rx_buffer.
*/
void ESPMegaDisplay::processSerialCommand()
{
// Check if the rx buffer ended with stop bytes (0xFF 0xFF 0xFF)
if (!payloadIsValid())
return;
// The rx buffer ended with stop bytes
// Check if the rx buffer is a push payload
// The payload type is the first byte of the payload
// 0x00 is invalid instruction
// 0x01 is success
// 0x65 is a touch event
// 0x66 is a page report event
if (rx_buffer[0] == 0x65)
{
processTouchPayload();
}
else if (rx_buffer[0] == 0x66)
{
processPageReportPayload();
}
else
{
// The payload does not match any of the expected payload types
// Pass the payload to the payload callbacks
// type, payload, length
for (auto const &callback : payload_callbacks)
{
callback.second(rx_buffer[0], reinterpret_cast<unsigned char *>(&rx_buffer[1]), rx_buffer_index - 4);
;
}
}
this->rx_buffer_index = 0;
}
/**
* @brief Processes the touch event payload.
* @note This function interacts directly with the rx_buffer.
*/
void ESPMegaDisplay::processTouchPayload()
{
if (rx_buffer_index != 7)
return;
// The second byte of the payload is the page number
uint8_t page = rx_buffer[1];
// The third byte of the payload is the component id
uint8_t component = rx_buffer[2];
// The fourth byte of the payload is the event
uint8_t event = rx_buffer[3];
// 0x01 is press, 0x00 is release
ESP_LOGV("ESPMegaDisplay", "Touch event: page=%d, component=%d, event=%d", page, component, event);
for (auto const &callback : touch_callbacks)
{
callback.second(page, component, event);
}
}
/**
* @brief Processes the page report event payload.
* @note This function interacts directly with the rx_buffer.
*/
void ESPMegaDisplay::processPageReportPayload()
{
if (rx_buffer_index != 5)
return;
// The second byte of the payload is the page number
// Check if the page number is different from the current page
// If it is different, we call the page change callbacks
if (rx_buffer[1] == this->currentPage)
return;
this->currentPage = rx_buffer[1];
ESP_LOGV("ESPMegaDisplay", "Page change event: page=%d", this->currentPage);
for (auto const &callback : page_change_callbacks)
{
callback.second(this->currentPage);
}
}
/**
* @brief Sends stop bytes to the display adapter.
*
* @note This function does not take the serial mutex, it is assumed that the caller has already taken the mutex.
*/
void ESPMegaDisplay::sendStopBytes()
{
displayAdapter->write(0xFF);
displayAdapter->write(0xFF);
displayAdapter->write(0xFF);
}
/**
* @brief Sends a command to the display adapter.
* @param command The command to send.
*/
void ESPMegaDisplay::sendCommand(char *command)
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return;
}
displayAdapter->print(command);
sendStopBytes();
xSemaphoreGive(this->serialMutex);
}
/**
* @brief Jumps to the specified page on the display.
* @param page The page number to jump to.
*/
void ESPMegaDisplay::jumpToPage(int page)
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return;
}
this->displayAdapter->print("page ");
this->displayAdapter->print(page);
sendStopBytes();
xSemaphoreGive(this->serialMutex);
}
/**
* @brief Sets the value of a number component on the display.
* @param component The component name.
* @param value The value to set.
*/
void ESPMegaDisplay::setNumber(const char *component, int value)
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return;
}
this->displayAdapter->print(component);
this->displayAdapter->print("=");
this->displayAdapter->print(value);
sendStopBytes();
xSemaphoreGive(this->serialMutex);
}
/**
* @brief Sets the value of a string component on the display.
* @param component The component name.
* @param value The value to set.
*/
void ESPMegaDisplay::setString(const char *component, const char *value)
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return;
}
this->displayAdapter->print(component);
this->displayAdapter->print("=\"");
this->displayAdapter->print(value);
this->displayAdapter->print("\"");
sendStopBytes();
xSemaphoreGive(this->serialMutex);
}
/**
* @brief Gets the value of a number component from the display.
* @warning This function is blocking.
* @warning If the display does not respond or is not connected, this function will block for up to DISPLAY_FETCH_RETRY_COUNT * DISPLAY_FETCH_TIMEOUT milliseconds.
* @param component The component name.
* @return The value of the number component.
*/
uint32_t ESPMegaDisplay::getNumber(const char *component)
{
// We might be in the middle of a serial command
// We reset the rx buffer index to 0 to ensure that we don't read the wrong data
this->rx_buffer_index = 0;
uint32_t start = millis();
// Send the get command
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return 0;
}
this->displayAdapter->print("get ");
this->displayAdapter->print(component);
sendStopBytes();
xSemaphoreGive(this->serialMutex);
// Try to get a valid payload DISPLAY_FETCH_RETRY_COUNT times
// Wait for the response
bool validPayload = false;
for (int i = 0; i < DISPLAY_FETCH_RETRY_COUNT; i++)
{
if (!waitForValidPayload(DISPLAY_FETCH_TIMEOUT))
{
ESP_LOGE("ESPMegaDisplay", "Timeout while waiting for display response");
return 0;
}
if (rx_buffer[0] != 0x71)
{
ESP_LOGI("ESPMegaDisplay", "Invalid payload type: %d, retrying.", rx_buffer[0]);
// The rx buffer is invalid, reset the rx buffer index
rx_buffer_index = 0;
continue;
}
else
{
validPayload = true;
break;
}
}
if (!validPayload)
{
ESP_LOGE("ESPMegaDisplay", "Invalid payload type: %d, max retry excceded.", rx_buffer[0]);
return 0;
}
// The rx buffer is valid
// The expected payload is type 0x71
// The 2nd to 5th byte of the payload is the value
// It's a 4 byte 32-bit value in little endian order.
// Convert the 4 bytes to a 32-bit value
uint32_t value = 0;
value |= rx_buffer[1];
value |= rx_buffer[2] << 8;
value |= rx_buffer[3] << 16;
value |= rx_buffer[4] << 24;
return value;
}
/**
* @brief Gets the value of a string component from the display.
* @warning This function is blocking.
* @warning If the display does not respond or is not connected, this function will block for up to DISPLAY_FETCH_RETRY_COUNT * DISPLAY_FETCH_TIMEOUT milliseconds.
* @param component The component name.
* @return The value of the string component.
* @note The returned char array must be freed after use.
*/
const char *ESPMegaDisplay::getString(const char *component)
{
// We might be in the middle of a serial command
// We reset the rx buffer index to 0 to ensure that we don't read the wrong data
this->rx_buffer_index = 0;
uint32_t start = millis();
// Send the get command
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return nullptr;
}
this->displayAdapter->print("get ");
this->displayAdapter->print(component);
sendStopBytes();
xSemaphoreGive(this->serialMutex);
// Wait for the response
// Try to get a valid payload DISPLAY_FETCH_RETRY_COUNT times
// Wait for the response
bool validPayload = false;
for (int i = 0; i < DISPLAY_FETCH_RETRY_COUNT; i++)
{
if (!waitForValidPayload(DISPLAY_FETCH_TIMEOUT))
{
ESP_LOGE("ESPMegaDisplay", "Timeout while waiting for display response");
return nullptr;
}
if (rx_buffer[0] != 0x70)
{
ESP_LOGI("ESPMegaDisplay", "Invalid payload type: %d, retrying.", rx_buffer[0]);
// The rx buffer is invalid, reset the rx buffer index
rx_buffer_index = 0;
continue;
}
else
{
validPayload = true;
break;
}
}
if (!validPayload)
{
ESP_LOGE("ESPMegaDisplay", "Invalid payload type: %d, max retry excceded.", rx_buffer[0]);
return nullptr;
}
// The 2nd bytes onwards before the stop bytes is the string
// The length of the string is the length of the payload minus 4
uint8_t length = rx_buffer_index - 4;
// First we malloc a char array with the length of the string
char *value = (char *)malloc(length + 1);
// Copy the string from the rx buffer to the char array
memcpy(value, rx_buffer + 1, length);
// Add the null terminator
value[length] = '\0';
// Return the char array
return value;
}
/**
* @brief Gets the value of a number component from the display and stores it in a buffer.
* @warning This function is blocking.
* @warning If the display does not respond or is not connected, this function will block for up to DISPLAY_FETCH_RETRY_COUNT * DISPLAY_FETCH_TIMEOUT milliseconds.
* @param component The component name.
* @param buffer The buffer to store the value.
* @param buffer_size The size of the buffer.
* @return True if the value is successfully stored in the buffer, false otherwise.
*/
bool ESPMegaDisplay::getStringToBuffer(const char *component, char *buffer, uint8_t buffer_size)
{
// We might be in the middle of a serial command
// We reset the rx buffer index to 0 to ensure that we don't read the wrong data
this->rx_buffer_index = 0;
uint32_t start = millis();
// Send the get command
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return false;
}
this->displayAdapter->print("get ");
this->displayAdapter->print(component);
sendStopBytes();
xSemaphoreGive(this->serialMutex);
// Wait for the response
// Try to get a valid payload DISPLAY_FETCH_RETRY_COUNT times
// Wait for the response
bool validPayload = false;
for (int i = 0; i < DISPLAY_FETCH_RETRY_COUNT; i++)
{
if (!waitForValidPayload(DISPLAY_FETCH_TIMEOUT))
{
ESP_LOGE("ESPMegaDisplay", "Timeout while waiting for display response");
this->sendStopBytes();
return 0;
}
if (rx_buffer[0] != 0x70)
{
ESP_LOGI("ESPMegaDisplay", "Invalid payload type: %d, retrying.", rx_buffer[0]);
this->sendStopBytes();
// The rx buffer is invalid, reset the rx buffer index
rx_buffer_index = 0;
continue;
}
else
{
validPayload = true;
break;
}
}
if (!validPayload)
{
ESP_LOGE("ESPMegaDisplay", "Invalid payload type: %d, max retry excceded.", rx_buffer[0]);
this->sendStopBytes();
return 0;
}
// The 2nd bytes onwards before the stop bytes is the string
// The length of the string is the length of the payload minus 4
uint8_t length = rx_buffer_index - 4;
// Check if the buffer is large enough to hold the string
if (length > buffer_size)
{
ESP_LOGE("ESPMegaDisplay", "Buffer size too small");
this->sendStopBytes();
return false;
}
// Copy the string from the rx buffer to the char array
memcpy(buffer, rx_buffer + 1, length);
// Add the null terminator
buffer[length] = '\0';
return 1;
}
/**
* @brief Waits for a valid payload from the display adapter.
* @param timeout The timeout value in milliseconds.
* @return True if a valid payload is received, false otherwise.
*/
bool ESPMegaDisplay::waitForValidPayload(uint32_t timeout)
{
uint32_t start = millis();
// If the payload is not valid, keep reading the serial buffer until timeout
while (!this->payloadIsValid())
{
if (millis() - start > timeout)
{
return false;
}
recieveSerialCommand(false);
}
return true;
}
/**
* @brief Checks if the received payload is valid.
* @return True if the payload is valid, false otherwise.
*/
bool ESPMegaDisplay::payloadIsValid()
{
// Check if the rx buffer ended with stop bytes (0xFF 0xFF 0xFF)
if (rx_buffer_index < 3)
return false;
if (rx_buffer[rx_buffer_index - 1] != 0xFF)
return false;
if (rx_buffer[rx_buffer_index - 2] != 0xFF)
return false;
if (rx_buffer[rx_buffer_index - 3] != 0xFF)
return false;
return true;
}
/**
* @brief Sets the brightness of the display.
* @param value The brightness value.
*/
void ESPMegaDisplay::setBrightness(int value)
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return;
}
this->displayAdapter->print("dim=");
this->displayAdapter->print(value);
sendStopBytes();
xSemaphoreGive(this->serialMutex);
}
/**
* @brief Sets the volume of the display.
* @param value The volume value.
*/
void ESPMegaDisplay::setVolume(int value)
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return;
}
this->displayAdapter->print("vol=");
this->displayAdapter->print(value);
sendStopBytes();
xSemaphoreGive(this->serialMutex);
}
/**
* @brief Restarts the display.
*/
void ESPMegaDisplay::reset()
{
// First we send a stop bytes to clear the serial buffer
// This ensures that the display is ready to receive the reset command
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return;
}
sendStopBytes();
this->displayAdapter->print("rest");
sendStopBytes();
xSemaphoreGive(this->serialMutex);
}
/**
* @brief Constructor for the ESPMegaDisplay class.
* @param displayAdapter The serial adapter connected to the display.
*/
ESPMegaDisplay::ESPMegaDisplay(HardwareSerial *displayAdapter, uint32_t baudRate, uint32_t uploadBaudRate, uint8_t txPin, uint8_t rxPin)
{
this->baudRate = baudRate;
this->uploadBaudRate = uploadBaudRate;
this->txPin = txPin;
this->rxPin = rxPin;
this->serialMutex = xSemaphoreCreateMutex();
this->otaBytesWritten = 0;
this->displayAdapter = displayAdapter;
this->currentPage = 0;
this->rx_buffer_index = 0;
}
/**
* @brief Initializes the display.
*/
void ESPMegaDisplay::begin()
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGE("ESPMegaDisplay", "Failed to take serial mutex");
return;
}
this->displayAdapter->begin(this->baudRate, SERIAL_8N1, this->rxPin, this->txPin);
this->displayAdapter->setTimeout(100);
this->displayAdapter->flush();
xSemaphoreGive(this->serialMutex);
this->reset();
}
/**
* @brief The main loop function of the display.
*/
void ESPMegaDisplay::loop()
{
// Check if there is data in the serial buffer
// If there is data, process the data
recieveSerialCommand();
}
/**
* @brief Registers a callback function for touch events.
* @param callback The callback function.
* @return The handle of the callback function.
*/
uint16_t ESPMegaDisplay::registerTouchCallback(std::function<void(uint8_t, uint8_t, uint8_t)> callback)
{
uint16_t handle = touch_callbacks.size();
touch_callbacks[handle] = callback;
return handle;
}
/**
* @brief Unregisters a callback function for touch events.
* @param handle The handle of the callback function.
*/
void ESPMegaDisplay::unregisterTouchCallback(uint16_t handle)
{
touch_callbacks.erase(handle);
}
/**
* @brief Registers a callback function for page change events.
* @param callback The callback function.
* @return The handle of the callback function.
*/
uint16_t ESPMegaDisplay::registerPageChangeCallback(std::function<void(uint8_t)> callback)
{
uint16_t handle = page_change_callbacks.size();
page_change_callbacks[handle] = callback;
return handle;
}
/**
* @brief Unregisters a callback function for page change events.
* @param handle The handle of the callback function.
*/
void ESPMegaDisplay::unregisterPageChangeCallback(uint16_t handle)
{
page_change_callbacks.erase(handle);
}
/**
* @brief Registers a callback function for payloads.
* @param callback The callback function.
* @return The handle of the callback function.
*/
uint16_t ESPMegaDisplay::registerPayloadCallback(std::function<void(uint8_t, uint8_t *, uint8_t)> callback)
{
uint16_t handle = payload_callbacks.size();
payload_callbacks[handle] = callback;
return handle;
}
/**
* @brief Unregisters a callback function for payloads.
* @param handle The handle of the callback function.
*/
void ESPMegaDisplay::unregisterPayloadCallback(uint16_t handle)
{
payload_callbacks.erase(handle);
}
/**
* @brief Takes the serial mutex.
*
* @note only neccessary if you are using the display adapter directly, otherwise the mutex is taken by the helper functions.
*/
bool ESPMegaDisplay::takeSerialMutex()
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return false;
}
return true;
}
/**
* @brief Gives the serial mutex.
*/
void ESPMegaDisplay::giveSerialMutex()
{
ESP_LOGD("ESPMegaDisplay", "Giving serial mutex");
xSemaphoreGive(this->serialMutex);
}
bool ESPMegaDisplay::beginUpdate(size_t size)
{
// The display's baudrate might be stuck at 9600 if the display is not initialized
// We try to initiate the display at the user specified baud rate first, if it fails, we try again at 9600
if (!beginUpdate(size, baudRate))
{
ESP_LOGW("ESPMegaDisplay", "Failed to initiate LCD update at %d baud, retrying at 9600 baud.", uploadBaudRate);
if (!beginUpdate(size, 9600))
{
ESP_LOGE("ESPMegaDisplay", "Failed to initiate LCD update at 9600 baud.");
return false;
}
}
return true;
}
/**
* @brief Starts an OTA update.
* @param size The size of the update.
* @param baudRate The baud rate use to connect to the display to initiate the update.
* @note The baud rate that is used to transfer the data is defined by the uploadBaudRate parameter in the constructor.
* @return True if the OTA update is started, false otherwise.
*/
bool ESPMegaDisplay::beginUpdate(size_t size, uint32_t baudRate)
{
if (xSemaphoreTake(this->serialMutex, DISPLAY_MUTEX_TAKE_TIMEOUT) == pdFALSE)
{
ESP_LOGI("ESPMegaDisplay", "Failed to take serial mutex");
return false;
}
ESP_LOGD("ESPMegaDisplay", "LCD OTA Subroutine has taken the serial mutex, all other tasks will be blocked until the OTA update is complete.");
// We have taken the serial mutex, all helper functions will be blocked until the OTA update is complete
// Thus, we have to interact directly with the display adapter
this->displayAdapter->begin(baudRate, SERIAL_8N1, this->rxPin, this->txPin);
this->sendStopBytes();
this->displayAdapter->print("rest");
this->sendStopBytes();
delay(500);
this->displayAdapter->print("connect");
this->sendStopBytes();
delay(1000);
// Flush the serial recieve buffer
while (this->displayAdapter->available())
this->displayAdapter->read();
this->displayAdapter->print("whmi-wri ");
this->displayAdapter->print(size);
this->displayAdapter->print(",");
this->displayAdapter->print(uploadBaudRate);
this->displayAdapter->print(",res0");
this->sendStopBytes();
this->displayAdapter->begin(uploadBaudRate, SERIAL_8N1, this->rxPin, this->txPin);
delay(1000);
// If the display is ready, it will send a 0x05 byte
// If it does, return true, otherwise return false
unsigned long startTime = millis();
while (millis() - startTime < OTA_WAIT_TIMEOUT)
{
if (this->displayAdapter->available())
{
if (this->displayAdapter->read() == 0x05)
{
ESP_LOGV("ESPMegaDisplay", "LCD Update Subroutine is ready to receive data.");
return true;
}
}
}
// FLush the serial recieve buffer
while (this->displayAdapter->available())
this->displayAdapter->read();
ESP_LOGE("ESPMegaDisplay", "LCD Update Subroutine failed to initialize.");
xSemaphoreGive(this->serialMutex);
return false;
}
/**
* @brief Writes data to the display during an update.
* @param data The data to write.
* @param size The size of the data.
* @return True if the data is written, false otherwise.
*/
bool ESPMegaDisplay::writeUpdate(uint8_t *data, size_t size)
{
// Check if the data size is too large
if (size > 4096)
{
ESP_LOGE("ESPMegaDisplay", "LCD Update Subroutine failed to write data, data size is too large.");
return false;
}
// Flush the serial recieve buffer
while (this->displayAdapter->available())
this->displayAdapter->read();
// Write the data
for (int i = 0; i < size; i++)
{
this->displayAdapter->write(data[i]);
// After every 4096 bytes, we have to wait for the display to send a 0x05 byte
// If it doesn't, return false
otaBytesWritten ++;
if (otaBytesWritten % 4096 == 0)
{
unsigned long startTime = millis();
bool ready = false;
while (millis() - startTime < OTA_WAIT_TIMEOUT)
{
if (this->displayAdapter->available())
{
if (this->displayAdapter->read() == 0x05)
{
ready = true;
break;
}
}
}
if (!ready)
{
ESP_LOGE("ESPMegaDisplay", "LCD Update Subroutine failed to write data, display is not ready.");
return false;
}
}
}
return true;
}
/**
* @brief Ends an LCD update.
*/
void ESPMegaDisplay::endUpdate()
{
xSemaphoreGive(this->serialMutex);
this->reset();
delay(500);
esp_restart();
}
/**
* @brief Gets the number of bytes written during an OTA update.
* @return The number of bytes written.
*/
size_t ESPMegaDisplay::getUpdateBytesWritten()
{
return this->otaBytesWritten;
}

View File

@ -1,69 +0,0 @@
#pragma once
#include <Arduino.h>
#include <map>
#define DISPLAY_MUTEX_TAKE_TIMEOUT 1000 // ms
#define OTA_WAIT_TIMEOUT 1000 // ms
#define DISPLAY_FETCH_TIMEOUT 100 // ms
#define DISPLAY_FETCH_RETRY_COUNT 5
/**
* @brief The ESPMegaDisplay class is a class for controlling the ESPMegaDisplay.
*
* @note The ESPMegaDisplay is a UART controlled display.
* @note Connect the Display's TX pin to the ESPMega's RX pin and the Display's RX pin to the ESPMega's TX pin.
*/
class ESPMegaDisplay
{
public:
ESPMegaDisplay(HardwareSerial *displayAdapter, uint32_t baudRate, uint32_t uploadBaudRate, uint8_t txPin, uint8_t rxPin);
void begin();
void loop();
void reset();
void setBrightness(int value);
void setVolume(int value);
void jumpToPage(int page);
void setString(const char* component, const char* value);
void setNumber(const char* component, int value);
const char* getString(const char* component);
bool getStringToBuffer(const char* component, char* buffer, uint8_t buffer_size);
uint32_t getNumber(const char* component);
uint16_t registerTouchCallback(std::function<void(uint8_t, uint8_t, uint8_t)> callback);
void unregisterTouchCallback(uint16_t handle);
uint16_t registerPageChangeCallback(std::function<void(uint8_t)> callback);
void unregisterPageChangeCallback(uint16_t handle);
uint16_t registerPayloadCallback(std::function<void(uint8_t, uint8_t*, uint8_t)> callback);
void unregisterPayloadCallback(uint16_t handle);
bool takeSerialMutex();
void giveSerialMutex();
SemaphoreHandle_t serialMutex;
bool beginUpdate(size_t size);
bool beginUpdate(size_t size, uint32_t baudRate);
bool writeUpdate(uint8_t* data, size_t size);
void endUpdate();
size_t getUpdateBytesWritten();
protected:
uint32_t baudRate;
uint32_t uploadBaudRate;
uint8_t txPin;
uint8_t rxPin;
size_t otaBytesWritten;
uint8_t currentPage;
uint8_t rx_buffer_index;
char rx_buffer[256];
char tx_buffer[256];
bool recieveSerialCommand();
bool recieveSerialCommand(bool process);
void processSerialCommand();
void processTouchPayload();
void processPageReportPayload();
void sendStopBytes();
void sendCommand(char* command);
bool payloadIsValid();
bool waitForValidPayload(uint32_t timeout);
HardwareSerial *displayAdapter;
std::map<uint16_t, std::function<void(uint8_t, uint8_t, uint8_t)>> touch_callbacks;
std::map<uint16_t, std::function<void(uint8_t)>> page_change_callbacks;
std::map<uint16_t, std::function<void(uint8_t, uint8_t*, uint8_t)>> payload_callbacks;
};

View File

@ -1,101 +0,0 @@
#include <ESPMegaDisplayOTA.hpp>
ESPMegaDisplayOTA::ESPMegaDisplayOTA() {
}
void ESPMegaDisplayOTA::begin(const char* base_path, ESPMegaDisplay *display, ESPMegaWebServer *webServer) {
this->display = display;
this->webServer = webServer;
this->server = webServer->getServer();
this->base_path = base_path;
char ota_begin_path[100];
char ota_write_path[100];
char ota_end_path[100];
char ota_page_path[100];
sprintf(ota_begin_path, "%s/ota/begin", base_path);
sprintf(ota_write_path, "%s/ota/write", base_path);
sprintf(ota_end_path, "%s/ota/end", base_path);
sprintf(ota_page_path, "%s/index.html", base_path);
this->otaUpdateBeginWebHandler = new AsyncCallbackJsonWebHandler(ota_begin_path, std::bind(&ESPMegaDisplayOTA::otaUpdateBeginHandler, this, std::placeholders::_1, std::placeholders::_2));
this->otaUpdateWriteWebHandler = new AsyncCallbackJsonWebHandler(ota_write_path, std::bind(&ESPMegaDisplayOTA::otaUpdateWriteHandler, this, std::placeholders::_1, std::placeholders::_2), 8192U);
this->otaUpdateEndWebHandler = new AsyncCallbackJsonWebHandler(ota_end_path, std::bind(&ESPMegaDisplayOTA::otaUpdateEndHandler, this, std::placeholders::_1, std::placeholders::_2));
this->server->addHandler(this->otaUpdateBeginWebHandler);
this->server->addHandler(this->otaUpdateWriteWebHandler);
this->server->addHandler(this->otaUpdateEndWebHandler);
this->server->on(ota_page_path, HTTP_GET, std::bind(&ESPMegaDisplayOTA::displayWebPageHandler, this, std::placeholders::_1));
}
void ESPMegaDisplayOTA::otaUpdateBeginHandler(AsyncWebServerRequest *request, JsonVariant &json) {
this->webServer->checkAuthentication(request);
// The content type of the request is application/json
// The body of the request is a JSON object with the following field:
// - size: the size of the update
// Parse the JSON object
JsonObject content = json.as<JsonObject>();
// Check if the size field is present
if(!content.containsKey("size"))
return;
// Get the size field
this->updateSize = content["size"].as<size_t>();
// Begin the update
if(!this->display->beginUpdate(this->updateSize)) {
// If the update cannot be started, return an error
request->send(500, "application/json", "{\"status\": \"error\"}");
} else {
// If the update can be started, return a success
request->send(200, "application/json", "{\"status\": \"success\"}");
}
}
void ESPMegaDisplayOTA::otaUpdateWriteHandler(AsyncWebServerRequest *request, JsonVariant &json) {
this->webServer->checkAuthentication(request);
// The content type of the request is application/json
// The body of the request is a JSON object with the following field:
// - size: the size of the update in bytes
// - data: the data to write
//Parse the JSON object
JsonObject content = json.as<JsonObject>();
// // Check if the size and data fields are present
// Serial.println("Checking if size and data fields are present");
// if(!content.containsKey("size") || !content.containsKey("data"))
// Serial.println("Size or data field is missing");
// request->send(500, "application/json", "{\"status\": \"error\", \"message\": \"The size or data field is missing\"}");
// return;
// Serial.println("Size and data fields are present, getting size");
// Get the size field
size_t size = content["size"].as<size_t>();
if(size>4096) {
// If the size is greater than 4096 bytes, return an error
request->send(500, "application/json", "{\"status\": \"error\", \"message\": \"The size of the update is too big\"}");
return;
}
// Get the data field
JsonArray data = content["data"].as<JsonArray>();
if(this->updateProgress+size>this->updateSize) {
// If the update is too big, return an error
request->send(500, "application/json", "{\"status\": \"error\", \"message\": \"The update chunk is too big\"}");
return;
}
// Convert JsonArray to uint8_t*
uint8_t data_array[4096];
for(size_t i=0; i<size; i++) {
data_array[i] = data[i].as<uint8_t>();
}
// Write the data to the display
display->writeUpdate(data_array, size);
request->send(200, "application/json", "{\"status\": \"success\",\"bytes_written\": "+String(this->display->getUpdateBytesWritten())+"}");
}
void ESPMegaDisplayOTA::otaUpdateEndHandler(AsyncWebServerRequest *request, JsonVariant &json) {
this->webServer->checkAuthentication(request);
display->endUpdate();
request->send(200, "application/json", "{\"status\": \"success\"}");
esp_restart();
}
void ESPMegaDisplayOTA::displayWebPageHandler(AsyncWebServerRequest *request) {
this->webServer->checkAuthentication(request);
request->send_P(200, "text/html", display_html);
}

View File

@ -1,23 +0,0 @@
#pragma once
#include <ESPMegaDisplay.hpp>
#include <ESPMegaWebServer.hpp>
class ESPMegaDisplayOTA {
public:
ESPMegaDisplayOTA();
void begin(const char* base_path, ESPMegaDisplay *display, ESPMegaWebServer *webServer);
private:
AsyncCallbackJsonWebHandler *otaUpdateBeginWebHandler;
AsyncCallbackJsonWebHandler *otaUpdateWriteWebHandler;
AsyncCallbackJsonWebHandler *otaUpdateEndWebHandler;
void otaUpdateBeginHandler(AsyncWebServerRequest *request, JsonVariant &json);
void otaUpdateWriteHandler(AsyncWebServerRequest *request, JsonVariant &json);
void otaUpdateEndHandler(AsyncWebServerRequest *request, JsonVariant &json);
void displayWebPageHandler(AsyncWebServerRequest *request);
const char *base_path;
AsyncWebServer *server;
ESPMegaDisplay *display;
ESPMegaWebServer *webServer;
size_t updateSize;
size_t updateProgress;
};

View File

@ -1,931 +0,0 @@
#include <ESPMegaIoT.hpp>
#include <ETH.h>
/**
* @brief Create a new ESPMegaIoT object
*
* @note You shold not create this object directly, Instead, you should use the ESPMegaPRO::iot object
*/
ESPMegaIoT::ESPMegaIoT() : mqtt(tcpClient)
{
tcpClient.setTimeout(TCP_TIMEOUT_SEC);
// Initialize the components array
for (int i = 0; i < 255; i++)
{
components[i] = NULL;
}
active = false;
mqtt_connected = false;
}
/**
* @brief Destroy the ESPMegaIoT object
*/
ESPMegaIoT::~ESPMegaIoT()
{
}
/**
* @brief The mqtt callback function, This function is called when a message is received on a subscribed topic
*
* This function is called when a message is received on a subscribed topic
* The payload is copied to a buffer and a null terminator is added
* The payload is then passed to the respective card's mqtt callback
*
* @param topic The topic of the message
* @param payload The payload of the message in byte form
* @param length The length of the payload
*/
void ESPMegaIoT::mqttCallback(char *topic, byte *payload, unsigned int length)
{
// Create a null terminated string from the payload
memcpy(payload_buffer, payload, length);
payload_buffer[length] = '\0';
// If the topic is not appended with the base topic, call only the absolute callbacks
for (const auto &callback : mqtt_callbacks)
{
callback.second(topic, payload_buffer);
}
if (strncmp(topic, this->mqtt_config.base_topic, base_topic_length) != 0)
{
return;
}
// Remove the base topic from the topic
char *topic_without_base = topic + strlen(this->mqtt_config.base_topic) + 1;
for (const auto &callback : mqtt_relative_callbacks)
{
callback.second(topic_without_base, payload_buffer);
}
// Check for global state request
if (!strcmp(topic_without_base,"requeststate")) {
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
components[i]->publishReport();
}
}
return;
}
// Check for global summary request
if (!strcmp(topic_without_base,"requestinfo")) {
this->publishSystemSummary();
return;
}
// Call the respective card's mqtt callback
// Note that after the base topic, there should be the card id
// /base_topic/card_id/...
// First, get the card id in integer form
char *card_id_str = strtok(topic_without_base, "/");
uint8_t card_id = atoi(card_id_str);
// Check if the card is registered
if (components[card_id] == NULL)
{
return;
}
components[card_id]->handleMqttMessage(topic_without_base + 3, payload_buffer);
}
/**
* @brief Set the base topic for the IoT
*
* @param base_topic The base topic
*/
void ESPMegaIoT::setBaseTopic(char *base_topic)
{
strcpy(this->mqtt_config.base_topic, base_topic);
base_topic_length = strlen(this->mqtt_config.base_topic);
}
/**
* @brief Begin the ESPMegaIoT object
*
* @param cards The array of ExpansionCard objects
*/
void ESPMegaIoT::intr_begin(ExpansionCard *cards[])
{
this->cards = cards;
active = true;
}
/**
* @brief The main loop for the ESPMegaIoT object
*
* @note Normally you should not call this function, Instead, you should call ESPMegaPRO::loop()
*/
void ESPMegaIoT::loop()
{
if (!active)
return;
// Call each component's loop function
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
components[i]->loop();
}
}
mqtt.loop();
sessionKeepAlive();
}
/**
* @brief Register an existing card for use with IoT
*
* This function registers an existing card for use with IoT
* The card should be installed using ESPMegaPRO::installCard() before calling this function
*
* @param card_id The id of the card
*/
void ESPMegaIoT::registerCard(uint8_t card_id)
{
// Check if the card is already registered
if (components[card_id] != NULL)
{
return;
}
// Check if the physical card is installed
if (cards[card_id] == NULL)
{
ESP_LOGE("ESPMegaIoT", "Registering card %d failed: Card not installed", card_id);
return;
}
// Get the card type
uint8_t card_type = cards[card_id]->getType();
// Create the respective IoT component
switch (card_type)
{
case CARD_TYPE_ANALOG:
components[card_id] = new AnalogIoT();
components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic);
if (mqtt_connected)
{
components[card_id]->subscribe();
components[card_id]->publishReport();
}
break;
case CARD_TYPE_DIGITAL_INPUT:
components[card_id] = new DigitalInputIoT();
components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic);
if (mqtt_connected)
{
components[card_id]->subscribe();
components[card_id]->publishReport();
}
break;
case CARD_TYPE_DIGITAL_OUTPUT:
components[card_id] = new DigitalOutputIoT();
components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic);
if (mqtt_connected)
{
components[card_id]->subscribe();
components[card_id]->publishReport();
}
break;
case CARD_TYPE_CLIMATE:
components[card_id] = new ClimateIoT();
components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic);
if (mqtt_connected)
{
components[card_id]->subscribe();
components[card_id]->publishReport();
}
break;
case CARD_TYPE_CT:
components[card_id] = new CurrentTransformerIoT();
components[card_id]->begin(card_id, cards[card_id], &mqtt, this->mqtt_config.base_topic);
if (mqtt_connected)
{
components[card_id]->subscribe();
components[card_id]->publishReport();
}
default:
ESP_LOGE("ESPMegaIoT", "Registering card %d failed: Unknown card", card_id);
return;
}
}
/**
* @brief Unregister a card
*
* @param card_id The id of the card
*/
void ESPMegaIoT::unregisterCard(uint8_t card_id)
{
// Check if the card is registered
if (components[card_id] == NULL)
{
return;
}
// Delete the IoT component
delete components[card_id];
components[card_id] = NULL;
}
/**
* @brief Publish all cards's reports
*/
void ESPMegaIoT::publishCard(uint8_t card_id)
{
// Check if the card is registered
if (components[card_id] == NULL)
{
return;
}
// Publish the card
components[card_id]->publishReport();
}
/**
* @brief Subscribe to a topic
*
* @param topic The topic to subscribe to
*/
void ESPMegaIoT::subscribe(const char *topic)
{
mqtt.subscribe(topic);
}
/**
* @brief Unsubscribe from a topic
*
* @param topic The topic to unsubscribe from
*/
void ESPMegaIoT::unsubscribeFromTopic(const char *topic)
{
mqtt.unsubscribe(topic);
}
/**
* @brief Connect to a wifi network
*
* @param ssid The SSID of the wifi network
* @param password The password of the wifi network
*/
void ESPMegaIoT::connectToWifi(const char *ssid, const char *password)
{
WiFi.begin(ssid, password);
}
/**
* @brief Connect to a unsecured wifi network
*
* @param ssid The SSID of the wifi network
*/
void ESPMegaIoT::connectToWifi(const char *ssid)
{
WiFi.begin(ssid);
}
/**
* @brief Disconnect from the wifi network
*/
void ESPMegaIoT::disconnectFromWifi()
{
WiFi.disconnect();
}
/**
* @brief Check if the wifi is connected
*
* @return True if the wifi is connected, false otherwise
*/
bool ESPMegaIoT::wifiConnected()
{
return WiFi.status() == WL_CONNECTED;
}
/**
* @brief Connect to a MQTT broker with authentication
*
* @param client_id The client id to use
* @param mqtt_server The MQTT server to connect to
* @param mqtt_port The MQTT port to connect to
* @param mqtt_user The MQTT username to use
* @param mqtt_password The MQTT password to use
* @return True if the connection is successful, false otherwise
*/
bool ESPMegaIoT::connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port, char *mqtt_user, char *mqtt_password)
{
mqtt.setServer(mqtt_server, mqtt_port);
auto boundCallback = std::bind(&ESPMegaIoT::mqttCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
mqtt.setCallback(boundCallback);
if (mqtt_user == nullptr || mqtt_password == nullptr || strlen(mqtt_user) == 0 || strlen(mqtt_password) == 0)
{
mqtt_connected = false;
ESP_LOGE("ESPMegaIoT", "MQTT Connection failed: Username or password not set but MQTT use_auth is true");
return false;
}
// Create availability topic
char availability_topic[base_topic_length + 15];
sprintf(availability_topic, "%s/availability", this->mqtt_config.base_topic);
if (mqtt.connect(client_id, mqtt_user, mqtt_password, availability_topic, 0, true, "offline"))
{
sessionKeepAlive();
mqttSubscribe();
// Publish all cards
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
components[i]->publishReport();
}
}
mqtt_connected = true;
mqtt.publish(availability_topic, "online", true);
return true;
}
mqtt_connected = false;
return false;
}
/**
* @brief Connect to a MQTT broker without authentication
*
* @param client_id The client id to use
* @param mqtt_server The MQTT server to connect to
* @param mqtt_port The MQTT port to connect to
* @return True if the connection is successful, false otherwise
*/
bool ESPMegaIoT::connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port)
{
ESP_LOGD("ESPMegaIoT", "Setting MQTT server to %s:%d", mqtt_server, mqtt_port);
mqtt.setServer(mqtt_server, mqtt_port);
auto boundCallback = std::bind(&ESPMegaIoT::mqttCallback, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
ESP_LOGD("ESPMegaIoT", "Binding MQTT callback");
mqtt.setCallback(boundCallback);
// Create availability topic
char availability_topic[base_topic_length + 15];
sprintf(availability_topic, "%s/availability", this->mqtt_config.base_topic);
if (mqtt.connect(client_id, availability_topic, 0, true, "offline"))
{
ESP_LOGD("ESPMegaIoT", "MQTT Connected, Calling session keep alive");
sessionKeepAlive();
ESP_LOGD("ESPMegaIoT", "Subscribing to topics");
mqttSubscribe();
ESP_LOGD("ESPMegaIoT", "Publishing reports");
// Publish all cards
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
components[i]->publishReport();
}
}
ESP_LOGI("ESPMegaIoT", "MQTT Connected OK.");
mqtt_connected = true;
mqtt.publish(availability_topic, "online", true);
return true;
}
ESP_LOGW("ESPMegaIoT", "MQTT Connection failed: %d", mqtt.state());
mqtt_connected = false;
return false;
}
/**
* @brief Disconnect from the MQTT broker
*/
void ESPMegaIoT::disconnectFromMqtt()
{
mqtt.disconnect();
}
/**
* @brief Publish a message to a topic
*
* @param topic The topic to publish to
* @param payload The payload to publish
*/
void ESPMegaIoT::publish(const char *topic, const char *payload)
{
mqtt.publish(topic, payload);
}
/**
* @brief Publish a message to a topic
*
* @param topic The topic to publish to
* @param payload The payload to publish
* @param length The length of the payload
*/
void ESPMegaIoT::publish(const char *topic, const char *payload, unsigned int length)
{
mqtt.publish(topic, (const uint8_t *)payload, length);
}
/**
* @brief Register a callback for MQTT messages
*
* @param callback The callback function
* @return The handler for the callback
*/
uint16_t ESPMegaIoT::registerMqttCallback(std::function<void(char *, char *)> callback)
{
mqtt_callbacks[mqtt_callbacks_handler_index] = callback;
return mqtt_callbacks_handler_index++;
}
/**
* @brief Unregister a callback
*
* @param handler The handler of the callback
*/
void ESPMegaIoT::unregisterMqttCallback(uint16_t handler)
{
mqtt_callbacks.erase(handler);
}
/**
* @brief Subscribe to all components's topics and all other topics
*/
void ESPMegaIoT::mqttSubscribe()
{
ESP_LOGD("ESPMegaIoT", "Begin MQTT Subscription");
for (const auto &callback : subscribe_callbacks)
{
callback.second();
mqtt.loop();
}
// Subscribe to all topics
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
ESP_LOGD("ESPMegaIoT", "Subscribing component %d", i);
components[i]->subscribe();
mqtt.loop();
}
}
// Global topics
this->subscribeRelative("requeststate");
this->subscribeRelative("requestinfo");
}
/**
* @brief Publish relative to the base topic
*/
void ESPMegaIoT::publishRelative(uint8_t card_id, char *topic, char *payload)
{
char absolute_topic[100];
sprintf(absolute_topic, "%s/%d/%s", this->mqtt_config.base_topic, card_id, topic);
mqtt.publish(absolute_topic, payload);
}
/**
* @brief Subscribe relative to the base topic
*/
bool ESPMegaIoT::mqttReconnect()
{
if (this->mqtt_config.mqtt_useauth)
{
return this->connectToMqtt(this->network_config.hostname, this->mqtt_config.mqtt_server, this->mqtt_config.mqtt_port, this->mqtt_config.mqtt_user, this->mqtt_config.mqtt_password);
}
else
{
return this->connectToMqtt(this->network_config.hostname, this->mqtt_config.mqtt_server, this->mqtt_config.mqtt_port);
}
}
/**
* @brief Keep the MQTT session alive
* @note This function is called automatically by the ESPMegaIoT object, You should not call this function directly
*/
void ESPMegaIoT::sessionKeepAlive()
{
// This reconnect the MQTT if it disconnect.
// If a disconnect happens, this will reconnect the MQTT within 1 second.
// A connection attempt will be made at most once every MQTT_RECONNECT_INTERVAL
// This have the effect of reconnecting to the server immediately if the connection is lost
// and the connection was previously stable for at least MQTT_RECONNECT_INTERVAL
// But will not reconnect if the connection was unstable and the connection was lost
static unsigned long lastSessionKeepAlive = 0;
static unsigned long lastConnectionAttempt = 0;
if (millis() - lastSessionKeepAlive > 1000)
{
lastSessionKeepAlive = millis();
// Check if mqtt is connected
if (!mqtt.connected())
{
// Try to reconnect if lastConnectionAttempt exceed MQTT_RECONNECT_INTERVAL
if (millis() - lastConnectionAttempt > MQTT_RECONNECT_INTERVAL)
{
lastConnectionAttempt = millis();
mqtt_connected = mqttReconnect();
}
}
}
}
/**
* @brief Register a callback for MQTT messages relative to the base topic
*
* The message's base topic will be removed before calling the callback
*
* @param callback The callback function
* @return The handler for the callback
*/
uint16_t ESPMegaIoT::registerRelativeMqttCallback(std::function<void(char *, char *)> callback)
{
mqtt_relative_callbacks[mqtt_relative_callbacks_handler_index] = callback;
return mqtt_relative_callbacks_handler_index++;
}
/**
* @brief Unregister a relative MQTT callback
*
* @param handler The handler of the callback
*/
void ESPMegaIoT::unregisterRelativeMqttCallback(uint16_t handler)
{
mqtt_relative_callbacks.erase(handler);
}
/**
* @brief Publish a message relative to the base topic
*
* @param topic The topic to publish to
* @param payload The payload to publish
*/
void ESPMegaIoT::publishRelative(const char *topic, const char *payload)
{
char absolute_topic[100];
sprintf(absolute_topic, "%s/%s", this->mqtt_config.base_topic, topic);
mqtt.publish(absolute_topic, payload);
mqtt.loop();
}
/**
* @brief Publish a message relative to the base topic
*
* @param topic The topic to publish to
* @param payload The payload to publish
* @param length The length of the payload
*/
void ESPMegaIoT::publishRelative(const char *topic, const char *payload, unsigned int length)
{
char absolute_topic[100];
sprintf(absolute_topic, "%s/%s", this->mqtt_config.base_topic, topic);
mqtt.publish(absolute_topic, (const uint8_t *)payload, length);
mqtt.loop();
}
/**
* @brief Subscribe to a topic relative to the base topic
*
* @param topic The topic to subscribe to
*/
void ESPMegaIoT::subscribeRelative(const char *topic)
{
char absolute_topic[100];
sprintf(absolute_topic, "%s/%s", this->mqtt_config.base_topic, topic);
mqtt.subscribe(absolute_topic);
mqtt.loop();
}
/**
* @brief Register a function to be called when the ESPMegaIoT object is subscribing to topics
*
* @param callback The callback function
* @return The handler for the callback
*/
uint16_t ESPMegaIoT::registerSubscribeCallback(std::function<void(void)> callback)
{
subscribe_callbacks[subscribe_callbacks_handler_index] = callback;
return subscribe_callbacks_handler_index++;
}
/**
* @brief Unregister a subscribe callback
*
* @param handler The handler of the callback
*/
void ESPMegaIoT::unregisterSubscribeCallback(uint16_t handler)
{
subscribe_callbacks.erase(handler);
}
/**
* @brief Set the network config
*
* @param network_config The network config struct
*/
void ESPMegaIoT::setNetworkConfig(NetworkConfig network_config)
{
this->network_config = network_config;
}
/**
* @brief Load the network config from FRAM
*/
void ESPMegaIoT::loadNetworkConfig()
{
// Load the network config from FRAM
network_config.ip = fram->read32(IOT_FRAM_ADDRESS);
network_config.gateway = fram->read32(IOT_FRAM_ADDRESS + 4);
network_config.subnet = fram->read32(IOT_FRAM_ADDRESS + 8);
network_config.dns1 = fram->read32(IOT_FRAM_ADDRESS + 12);
network_config.dns2 = fram->read32(IOT_FRAM_ADDRESS + 16);
fram->read(IOT_FRAM_ADDRESS + 20, (uint8_t *)network_config.hostname, 32);
network_config.useStaticIp = fram->read8(IOT_FRAM_ADDRESS + 52);
network_config.useWifi = fram->read8(IOT_FRAM_ADDRESS + 53);
network_config.wifiUseAuth = fram->read8(IOT_FRAM_ADDRESS + 54);
fram->read(IOT_FRAM_ADDRESS + 55, (uint8_t *)network_config.ssid, 32);
fram->read(IOT_FRAM_ADDRESS + 87, (uint8_t *)network_config.password, 32);
// If ip,gateway,subnet,dns1,dns2 is 0, the device is not configured
// set to default values
// ip: 192.168.0.99
// gateway: 192.168.0.1
// subnet: 255.255.255.0
// dns1: 1.1.1.1
// dns2: 9.9.9.9
if ((uint32_t)network_config.ip == 0 && (uint32_t)network_config.gateway == 0 && (uint32_t)network_config.subnet == 0 && (uint32_t)network_config.dns1 == 0 && (uint32_t)network_config.dns2 == 0)
{
network_config.ip = IPAddress(192, 168, 0, 99);
network_config.gateway = IPAddress(192, 168, 0, 1);
network_config.subnet = IPAddress(255, 255, 255, 0);
network_config.dns1 = IPAddress(1, 1, 1, 1);
network_config.useStaticIp = true;
}
}
/**
* @brief Save the network config to FRAM
*/
void ESPMegaIoT::saveNetworkConfig()
{
// Save the network config to FRAM
fram->write32(IOT_FRAM_ADDRESS, network_config.ip);
fram->write32(IOT_FRAM_ADDRESS + 4, network_config.gateway);
fram->write32(IOT_FRAM_ADDRESS + 8, network_config.subnet);
fram->write32(IOT_FRAM_ADDRESS + 12, network_config.dns1);
fram->write32(IOT_FRAM_ADDRESS + 16, network_config.dns2);
fram->write(IOT_FRAM_ADDRESS + 20, (uint8_t *)network_config.hostname, 32);
fram->write8(IOT_FRAM_ADDRESS + 52, network_config.useStaticIp);
fram->write8(IOT_FRAM_ADDRESS + 53, network_config.useWifi);
fram->write8(IOT_FRAM_ADDRESS + 54, network_config.wifiUseAuth);
fram->write(IOT_FRAM_ADDRESS + 55, (uint8_t *)network_config.ssid, 32);
fram->write(IOT_FRAM_ADDRESS + 87, (uint8_t *)network_config.password, 32);
}
/**
* @brief Begin the ethernet interface
*/
void ESPMegaIoT::ethernetBegin()
{
ethernetIface->setHostname(network_config.hostname);
}
/**
* @brief Load the MQTT config from FRAM
*/
void ESPMegaIoT::loadMqttConfig()
{
// Load the mqtt config from FRAM
// We skip bytes 119-127 because they are reserved for the network config
mqtt_config.mqtt_port = fram->read16(IOT_FRAM_ADDRESS + 128);
fram->read(IOT_FRAM_ADDRESS + 130, (uint8_t *)mqtt_config.mqtt_server, 32);
fram->read(IOT_FRAM_ADDRESS + 162, (uint8_t *)mqtt_config.mqtt_user, 32);
fram->read(IOT_FRAM_ADDRESS + 194, (uint8_t *)mqtt_config.mqtt_password, 32);
mqtt_config.mqtt_useauth = fram->read8(IOT_FRAM_ADDRESS + 226);
fram->read(IOT_FRAM_ADDRESS + 227, (uint8_t *)mqtt_config.base_topic, 32);
this->base_topic_length = strlen(mqtt_config.base_topic);
}
/**
* @brief Save the MQTT config to FRAM
*/
void ESPMegaIoT::saveMqttConfig()
{
fram->write16(IOT_FRAM_ADDRESS + 128, mqtt_config.mqtt_port);
fram->write(IOT_FRAM_ADDRESS + 130, (uint8_t *)mqtt_config.mqtt_server, 32);
fram->write(IOT_FRAM_ADDRESS + 162, (uint8_t *)mqtt_config.mqtt_user, 32);
fram->write(IOT_FRAM_ADDRESS + 194, (uint8_t *)mqtt_config.mqtt_password, 32);
fram->write8(IOT_FRAM_ADDRESS + 226, mqtt_config.mqtt_useauth);
fram->write(IOT_FRAM_ADDRESS + 227, (uint8_t *)mqtt_config.base_topic, 32);
}
/**
* @brief Connect to MQTT with the current config
*/
void ESPMegaIoT::connectToMqtt()
{
if (mqtt_config.mqtt_useauth)
{
ESP_LOGD("ESPMegaIoT", "Connecting to MQTT with auth");
this->connectToMqtt(network_config.hostname, mqtt_config.mqtt_server, mqtt_config.mqtt_port, mqtt_config.mqtt_user, mqtt_config.mqtt_password);
}
else
{
ESP_LOGD("ESPMegaIoT", "Connecting to MQTT without auth");
this->connectToMqtt(network_config.hostname, mqtt_config.mqtt_server, mqtt_config.mqtt_port);
}
}
/**
* @brief Connect to the network using the current config
*/
void ESPMegaIoT::connectNetwork()
{
if (network_config.useWifi)
{
if (network_config.wifiUseAuth)
this->connectToWifi(network_config.ssid, network_config.password);
else
this->connectToWifi(network_config.ssid);
if (network_config.useStaticIp)
WiFi.config(network_config.ip, network_config.gateway, network_config.subnet);
else
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
else
{
this->ethernetBegin();
if (network_config.useStaticIp)
ethernetIface->config(network_config.ip, network_config.gateway, network_config.subnet, network_config.dns1, network_config.dns2);
}
}
/**
* @brief Set the MQTT config
*
* @param mqtt_config The MQTT config struct
*/
void ESPMegaIoT::setMqttConfig(MqttConfig mqtt_config)
{
this->mqtt_config = mqtt_config;
this->base_topic_length = strlen(mqtt_config.base_topic);
}
/**
* @brief Bind an ethernet interface to the ESPMegaIoT object
*
* @param ethernetIface The ethernet interface to bind (ETH for ESPMegaPRO R3)
*/
void ESPMegaIoT::bindEthernetInterface(ETHClass *ethernetIface)
{
this->ethernetIface = ethernetIface;
}
/**
* @brief Get the IoTComponent object for a card
*
* @param card_id The id of the card
* @return The IoTComponent object for the card
*/
IoTComponent *ESPMegaIoT::getComponent(uint8_t card_id)
{
return components[card_id];
}
/**
* @brief Get the network config
*
* @warning You should not modify the returned struct directly
*
* @return The network config struct
*/
NetworkConfig *ESPMegaIoT::getNetworkConfig()
{
return &network_config;
}
/**
* @brief Get the MQTT config
*
* @warning You should not modify the returned struct directly
*
* @return The MQTT config struct
*/
MqttConfig *ESPMegaIoT::getMqttConfig()
{
return &mqtt_config;
}
/**
* @brief Check if the MQTT is connected
*
* @return True if the MQTT is connected, false otherwise
*/
bool ESPMegaIoT::mqttConnected()
{
// return mqtt_connected;
return mqtt.connected();
}
/**
* @brief Check if the network is connected
*
* @return True if the network is connected, false otherwise
*/
bool ESPMegaIoT::networkConnected()
{
if (network_config.useWifi)
return WiFi.status() == WL_CONNECTED;
else
return ethernetIface->linkUp();
}
/**
* @brief Bind a FRAM object to the ESPMegaIoT object
* @note This class is hardcode to use the FRAM address 34-300
*
* @param fram The FRAM object to bind
*/
void ESPMegaIoT::bindFRAM(FRAM *fram)
{
this->fram = fram;
}
/**
* @brief Get the Wifi IP address
*
* @return The Wifi IP address
*/
IPAddress ESPMegaIoT::getWifiIp()
{
return WiFi.localIP();
}
/**
* @brief Get the Ethernet IP Address
*
* @return The Ethernet IP Address
*/
IPAddress ESPMegaIoT::getETHIp()
{
return ETH.localIP();
}
/**
* @brief Get the IP address of the currently active network interface
*
* @return The IP address of the currently active network interface
*/
IPAddress ESPMegaIoT::getIp()
{
if (network_config.useWifi)
return this->getWifiIp();
else
return this->getETHIp();
}
/**
* @brief Get the MAC Address of the Ethernet interface
*
* @return The MAC Address of the Ethernet interface
*/
String ESPMegaIoT::getETHMac()
{
return ETH.macAddress();
}
/**
* @brief Get the MAC Address of the Wifi interface
*
* @return The MAC Address of the Wifi interface
*/
String ESPMegaIoT::getWifiMac()
{
return WiFi.macAddress();
}
/**
* @brief Get the MAC Address of the currently active network interface
*
* @return The MAC Address of the currently active network interface
*/
String ESPMegaIoT::getMac()
{
if (network_config.useWifi)
return this->getWifiMac();
else
return this->getETHMac();
}
/**
* @brief Publish a json object containing the system summary
*/
void ESPMegaIoT::publishSystemSummary() {
char payload[1024];
StaticJsonDocument<1024> doc;
JsonObject root = doc.to<JsonObject>();
root["ip"] = this->getIp().toString();
root["firmware"] = SW_VERSION;
root["sdk_version"] = SDK_VESRION;
root["board_model"] = BOARD_MODEL;
JsonArray cards = root.createNestedArray("cards");
for (int i = 0; i < 255; i++)
{
if (components[i] != NULL)
{
JsonObject card = cards.createNestedObject();
card["id"] = i;
card["type"] = components[i]->getType();
}
}
serializeJson(doc, payload);
ESP_LOGD("ESPMegaIoT", "Publishing system summary: %s", payload);
mqtt.loop();
this->publishRelative("info", payload, strlen(payload));
}

View File

@ -1,163 +0,0 @@
#pragma once
#include <ExpansionCard.hpp>
#include <AnalogCard.hpp>
#include <AnalogIoT.hpp>
#include <DigitalInputCard.hpp>
#include <DigitalInputIoT.hpp>
#include <DigitalOutputCard.hpp>
#include <DigitalOutputIoT.hpp>
#include <ClimateCard.hpp>
#include <ClimateIoT.hpp>
#include <CurrentTransformerCard.hpp>
#include <CurrentTransformerIoT.hpp>
#include <IoTComponent.hpp>
#include <PubSubClient.h>
#include <ETH.h>
#include <WiFi.h>
#include <FRAM.h>
#include <map>
#include <ArduinoJson.h>
#include <ESPMegaCommon.hpp>
// MQTT Connection Parameters
#define TCP_TIMEOUT_SEC 5
#define MQTT_RECONNECT_INTERVAL 30000
// FRAM Address for ESPMegaPROIoT
// Starts from 34
// Ends at 300 (inclusive)
// Total of 267 bytes
#define IOT_FRAM_ADDRESS 34
/**
* @brief The network configuration struct
* @note This struct will be saved to FRAM when calling saveNetworkConfig
*/
struct NetworkConfig
{
IPAddress ip; ///< The IP address
IPAddress gateway; ///< The gateway address
IPAddress subnet; ///< The subnet mask
IPAddress dns1; ///< The primary DNS server
IPAddress dns2; ///< The secondary DNS server
char hostname[32]; ///< The hostname
bool useStaticIp; ///< Whether to use a static IP, if false, DHCP will be used
bool useWifi; ///< Whether to use WiFi or Ethernet, if false, Ethernet will be used
bool wifiUseAuth; ///< Whether to use WiFi authentication, if false, ssid and password will be ignored
char ssid[32]; ///< The WiFi SSID, even if wifiUseAuth is false, this should be set
char password[32]; ///< The WiFi password, even if wifiUseAuth is false, this should be set
};
/**
* @brief The MQTT configuration struct
* @note This struct will be saved to FRAM when calling saveMqttConfig
*/
struct MqttConfig
{
char mqtt_server[32]; ///< The MQTT server address
uint16_t mqtt_port; ///< The MQTT server port
char mqtt_user[32]; ///< The MQTT username, even if mqtt_useauth is false, this should be set
char mqtt_password[32]; ///< The MQTT password, even if mqtt_useauth is false, this should be set
bool mqtt_useauth; ///< Whether to use MQTT authentication, if false, mqtt_user and mqtt_password will be ignored
char base_topic[32]; ///< The base topic for the MQTT messages
};
/**
* @brief The ESPMegaIoT class is a class that is used to interface with the ESPMegaPRO IoT module
*
* This class allows you to register IoT components and interface with them through MQTT.
* This class also manages the network and MQTT connections for you.
* Supports both WiFi and Ethernet.
* Also allows you to save and load network and MQTT configurations to and from FRAM.
* Also provides MQTT helpers for publishing and subscribing to topics.
*/
class ESPMegaIoT
{
public:
ESPMegaIoT();
~ESPMegaIoT();
void intr_begin(ExpansionCard *cards[]);
void loop();
void registerCard(uint8_t card_id);
void unregisterCard(uint8_t card_id);
void publishCard(uint8_t card_id);
// Publish topic appended with base topic
void publishRelative(const char *topic, const char *payload);
void publishRelative(const char *topic, const char *payload, unsigned int length);
// Subscribe topic appended with base topic
void subscribeRelative(const char *topic);
void subscribe(const char *topic);
void unsubscribeFromTopic(const char *topic);
void connectToWifi(const char *ssid, const char *password);
void connectToWifi(const char *ssid);
void disconnectFromWifi();
bool wifiConnected();
void ethernetBegin();
void loadNetworkConfig();
void saveNetworkConfig();
NetworkConfig* getNetworkConfig();
MqttConfig* getMqttConfig();
void setMqttConfig(MqttConfig mqtt_config);
void saveMqttConfig();
void loadMqttConfig();
void connectNetwork();
void setNetworkConfig(NetworkConfig network_config);
void connectToMqtt();
bool connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port, char *mqtt_user, char *mqtt_password);
bool connectToMqtt(char *client_id, char *mqtt_server, uint16_t mqtt_port);
bool mqttConnected();
void disconnectFromMqtt();
void publish(const char *topic, const char *payload);
void publish(const char *topic, const char *payload, unsigned int length);
uint16_t registerMqttCallback(std::function<void(char *, char *)> callback);
void unregisterMqttCallback(uint16_t handler);
uint16_t registerRelativeMqttCallback(std::function<void(char *, char *)> callback);
void unregisterRelativeMqttCallback(uint16_t handler);
uint16_t registerSubscribeCallback(std::function<void(void)> callback);
void unregisterSubscribeCallback(uint16_t handler);
void setBaseTopic(char *base_topic);
void bindEthernetInterface(ETHClass *ethernetIface);
bool networkConnected();
void bindFRAM(FRAM *fram);
void publishSystemSummary();
IoTComponent* getComponent(uint8_t card_id);
IPAddress getETHIp();
IPAddress getWifiIp();
IPAddress getIp();
String getETHMac();
String getWifiMac();
String getMac();
private:
FRAM *fram;
bool useWifi;
bool WifiUseAuth;
char ssid[32];
char password[32];
WiFiClient tcpClient;
void sessionKeepAlive();
bool mqttReconnect();
void wifiReconnect();
void mqttSubscribe();
void mqttCallback(char *topic, byte *payload, unsigned int length);
uint16_t mqtt_callbacks_handler_index;
uint16_t mqtt_relative_callbacks_handler_index;
uint16_t subscribe_callbacks_handler_index;
std::map<uint16_t, std::function<void(char*, char*)>> mqtt_callbacks;
std::map<uint16_t, std::function<void(char*, char*)>> mqtt_relative_callbacks;
std::map<uint16_t, std::function<void(void)>> subscribe_callbacks;
void publishRelative(uint8_t card_id, char *topic, char *payload);
bool active;
PubSubClient mqtt;
IoTComponent *components[255];
char payload_buffer[200];
uint8_t base_topic_length;
ExpansionCard **cards; // Points to card array in ESPMegaPRO Core
// MQTT Connection Parameters
bool mqtt_connected;
NetworkConfig network_config;
MqttConfig mqtt_config;
ETHClass *ethernetIface;
};

View File

@ -1,358 +0,0 @@
#include <ESPMegaProOS.hpp>
#include "esp_sntp.h"
// Reserve FRAM address 0 - 1000 for ESPMegaPRO Internal Use
// (34 Bytes) Address 0-33 for Built-in Digital Output Card
// (266 Bytes) Address 34-300 for ESPMegaPRO IoT Module
/**
* @brief Create a new ESPMegaPRO object
*
* @warning Only one ESPMegaPRO object can be created, creating more than one will result in undefined behavior
*/
ESPMegaPRO::ESPMegaPRO()
{
}
/**
* @brief Initializes the ESPMegaPRO object.
*
* This function initializes the ESPMegaPRO object and all of its components.
* It also initializes the built-in Digital Input and Digital Output cards.
*
* @return True if the initialization is successful, false otherwise.
*/
bool ESPMegaPRO::begin()
{
Wire.begin(14, 33);
fram.begin(FRAM_ADDRESS);
Serial.begin(115200);
this->installCard(1, &outputs);
outputs.bindFRAM(&fram, 0);
uint8_t outputPinMap[16] = {8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7};
outputs.loadPinMap(outputPinMap);
outputs.loadFromFRAM();
outputs.setAutoSaveToFRAM(true);
if (!this->installCard(0, &inputs))
{
ESP_LOGE("ESPMegaPRO", "Failed to initialize inputs");
ESP_LOGE("ESPMegaPRO", "Is this an ESPMegaPRO device?");
return false;
}
uint8_t inputPinMap[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 15, 14, 13, 12};
inputs.loadPinMap(inputPinMap);
// Detect GPIO2 Reset
gpio_num_t buttonPin = GPIO_NUM_2;
gpio_pad_select_gpio(buttonPin);
gpio_set_direction(buttonPin, GPIO_MODE_INPUT);
gpio_set_pull_mode(buttonPin, GPIO_PULLUP_ONLY);
if (gpio_get_level(buttonPin) == 0)
{
ESP_LOGW("ESPMegaPRO", "GPIO2 is low, if this condition persists for 5 more seconds, the device will factory reset");
bool shouldReset = true;
for (int i = 0; i < 50; i++)
{
vTaskDelay(100 / portTICK_PERIOD_MS);
if (gpio_get_level(buttonPin) == 1)
{
ESP_LOGI("ESPMegaPRO", "Reset condition cleared, Continuing boot process");
shouldReset = false;
break;
}
}
if (shouldReset)
{
ESP_LOGW("ESPMegaPRO", "Reset condition met, Factory Resetting device");
for (int i = 0; i < fram.getSizeBytes(); i++)
{
if (i % 1024 == 0)
ESP_LOGV("ESPMegaPRO", "Clearing FRAM Address %d", i);
fram.write8(i, 0);
}
ESP_LOGI("ESPMegaPRO", "Factory Reset Complete");
ESP_LOGI("ESPMegaPRO", "Rebooting device");
esp_restart();
}
}
// Recovery Mode
recovery.bindFRAM(&fram, 600);
recovery.begin();
return true;
}
/**
* @brief The main loop for the ESPMegaPRO object.
*
* @note This function must be called in the main loop of the program.
*
* It will call the loop() function of all installed expansion cards, the ESPMegaIoT module, and the internal display.
*
*/
void ESPMegaPRO::loop()
{
inputs.loop();
outputs.loop();
recovery.loop();
for (int i = 0; i < 255; i++)
{
if (cardInstalled[i])
{
cards[i]->loop();
}
}
if (iotEnabled)
{
iot->loop();
static int64_t lastNTPUpdate = (esp_timer_get_time() / 1000) - NTP_UPDATE_INTERVAL_MS + NTP_INITIAL_SYNC_DELAY_MS;
if ((esp_timer_get_time() / 1000) - lastNTPUpdate > NTP_UPDATE_INTERVAL_MS)
{
ESP_LOGV("ESPMegaPRO", "Updating time from NTP");
lastNTPUpdate = esp_timer_get_time() / 1000;
this->updateTimeFromNTP();
}
}
if (internalDisplayEnabled)
{
display->loop();
}
if (webServerEnabled)
{
webServer->loop();
}
}
/**
* @brief Installs an expansion card to the specified slot.
*
* @note This function automatically initializes the expansion card.
*
* @param slot The slot to install the card to.
* @param card Pointer to the ExpansionCard object.
*
* @return True if the installation is successful, false otherwise.
*/
bool ESPMegaPRO::installCard(uint8_t slot, ExpansionCard *card)
{
if (slot > 255)
return false;
if (cardInstalled[slot])
{
ESP_LOGE("ESPMegaPRO", "Card already installed at slot %d", slot);
return false;
}
if (!card->begin())
{
ESP_LOGE("ESPMegaPRO", "Failed to initialize card at slot %d", slot);
return false;
}
cards[slot] = card;
cardInstalled[slot] = true;
cardCount++;
return true;
}
/**
* @brief Updates the internal RTC from NTP.
*
* @note Network must be connected before calling this function (see ESPMegaPRO.ESPMegaIoT::connectNetwork()).
*
* @return True if the update is successful, false otherwise.
*/
bool ESPMegaPRO::updateTimeFromNTP()
{
struct tm timeinfo;
uint32_t start = esp_timer_get_time() / 1000;
time_t now;
time(&now);
localtime_r(&now, &timeinfo);
if (!(timeinfo.tm_year > (2016 - 1900)))
{
ESP_LOGI("ESPMegaPRO", "NTP is not ready yet!");
return false;
}
rtctime_t rtctime = this->getTime();
if (rtctime.hours != timeinfo.tm_hour || rtctime.minutes != timeinfo.tm_min ||
rtctime.seconds != timeinfo.tm_sec || rtctime.day != timeinfo.tm_mday ||
rtctime.month != timeinfo.tm_mon + 1 || rtctime.year != timeinfo.tm_year + 1900)
{
this->setTime(timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec,
timeinfo.tm_mday, timeinfo.tm_mon + 1, timeinfo.tm_year + 1900);
}
ESP_LOGV("ESPMegaPRO", "Time updated from NTP: %s", asctime(&timeinfo));
return true;
}
/**
* @brief Sets the timezone for the internal RTC.
*
* @note This function takes POSIX timezone strings (e.g. "EST5EDT,M3.2.0,M11.1.0").
*/
void ESPMegaPRO::setTimezone(const char* offset)
{
setenv("TZ", offset, 1);
}
/**
* @brief Gets the current time from the internal RTC.
*
* @return The current time as a rtctime_t struct.
*/
rtctime_t ESPMegaPRO::getTime()
{
tmElements_t timeElement;
RTC.read(timeElement);
rtctime_t time;
time.hours = timeElement.Hour;
time.minutes = timeElement.Minute;
time.seconds = timeElement.Second;
time.day = timeElement.Day;
time.month = timeElement.Month;
time.year = timeElement.Year + 1970;
return time;
}
/**
* @brief Sets the current time of the internal RTC.
*
* @param hours The hours.
* @param minutes The minutes.
* @param seconds The seconds.
* @param day The day.
* @param month The month.
* @param year The year.
*/
void ESPMegaPRO::setTime(int hours, int minutes, int seconds, int day, int month, int year)
{
tmElements_t timeElement;
timeElement.Hour = hours;
timeElement.Minute = minutes;
timeElement.Second = seconds;
timeElement.Day = day;
timeElement.Month = month;
timeElement.Year = year - 1970;
RTC.write(timeElement);
}
/**
* @brief Enables, Instanitates, and Initializes the ESPMegaIoT module.
*
* @note This function must be called before using the ESPMegaIoT module.
*/
void ESPMegaPRO::enableIotModule()
{
if (iotEnabled)
return;
this->iot = new ESPMegaIoT();
this->iot->bindFRAM(&fram);
this->iot->intr_begin(cards);
iotEnabled = true;
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, this->iot->getMqttConfig()->mqtt_server);
sntp_setservername(1, "pool.ntp.org");
sntp_init();
}
/**
* @brief Gets the expansion card installed at the specified slot.
*
* @param slot The slot to get the card from.
*
* @return Pointer to the ExpansionCard object, or nullptr if no card is installed at the specified slot.
*/
ExpansionCard *ESPMegaPRO::getCard(uint8_t slot)
{
if (slot > 255)
return nullptr;
if (!cardInstalled[slot])
return nullptr;
return cards[slot];
}
/**
* @brief Enables, Instanitates, and Initializes the internal display.
*
* @note &Serial is used for the internal display on ESPMegaPRO R3.
* @note This function can only be called if the ESPMegaIoT module is enabled.
* @note This function must be called before using the internal display.
*
* @param serial Pointer to the HardwareSerial object to use for the internal display (Serial for ESPMegaPRO R3).
*/
void ESPMegaPRO::enableInternalDisplay(HardwareSerial *serial)
{
if (internalDisplayEnabled)
return;
if (!iotEnabled)
{
ESP_LOGE("ESPMegaPRO", "Cannot enable internal display without IoT module enabled");
return;
}
ESP_LOGD("ESPMegaPRO", "Enabling Internal Display");
display = new InternalDisplay(serial);
ESP_LOGD("ESPMegaPRO", "Binding Internal Display to IoT Module");
auto bindedGetTime = std::bind(&ESPMegaPRO::getTime, this);
ESP_LOGD("ESPMegaPRO", "Binding Internal Display to Input/Output Cards");
display->bindInputCard(&inputs);
display->bindOutputCard(&outputs);
display->begin(this->iot, bindedGetTime);
internalDisplayEnabled = true;
ESP_LOGD("ESPMegaPRO", "Internal Display Enabled");
}
/**
* @brief Dumps the contents of the internal FRAM to the serial port.
*
* @param start The starting address.
* @param end The ending address.
*/
void ESPMegaPRO::dumpFRAMtoSerial(uint16_t start, uint16_t end)
{
for (int i = start; i <= end; i++)
{
if (i % 16 == 0)
{
Serial.printf("\n%03d: ", i);
}
Serial.printf("%03d ", this->fram.read8(i));
}
}
/**
* @brief Dumps the contents of the internal FRAM to the serial port in ASCII.
*
* @param start The starting address.
* @param end The ending address.
*/
void ESPMegaPRO::dumpFRAMtoSerialASCII(uint16_t start, uint16_t end)
{
for (int i = 0; i < 500; i++)
{
Serial.printf("%d: %c\n", i, this->fram.read8(i));
}
}
/**
* @brief Enables the internal web server.
*
* @note This function can only be called if the ESPMegaIoT module is enabled.
* @note This function can only be called once.
*
* @param port The port to use for the web server.
*/
void ESPMegaPRO::enableWebServer(uint16_t port)
{
if (!iotEnabled)
{
ESP_LOGE("ESPMegaPRO", "Cannot enable web server without IoT module enabled");
return;
}
if (webServerEnabled)
{
ESP_LOGE("ESPMegaPRO", "Web server already enabled");
return;
}
webServer = new ESPMegaWebServer(port, this->iot);
webServer->bindFRAM(&fram);
webServer->begin();
webServerEnabled = true;
}

View File

@ -1,104 +0,0 @@
#pragma once
#include <ExpansionCard.hpp>
#include <DigitalInputCard.hpp>
#include <DigitalOutputCard.hpp>
#include <ClimateCard.hpp>
#include <AnalogCard.hpp>
#include <ESPMegaIoT.hpp>
#include <Arduino.h>
#include <Wire.h>
#include <FRAM.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <time.h>
#include <TimeStructure.hpp>
#include <ESPMegaDisplay.hpp>
#include <InternalDisplay.hpp>
#include <ESPMegaWebServer.hpp>
#include <ESPMegaRecovery.hpp>
// ESPMega Pro R3 Board Address
#define FRAM_ADDRESS 0x56
#define INPUT_BANK_A_ADDRESS 0x21
#define INPUT_BANK_B_ADDRESS 0x22
#define PWM_BANK_ADDRESS 0x5F
#define RTC_ADDRESS 0x68
// Constants
#define NTP_TIMEOUT_MS 5000
#define NTP_UPDATE_INTERVAL_MS 60000
#define NTP_INITIAL_SYNC_DELAY_MS 15000
/**
* @brief The ESPMegaPRO class is the main class for the ESPMegaPRO library.
*
* This class provides functions for managing the ESPMegaPRO board, such as installing expansion cards, managing the internal RTC, and managing the internal FRAM.
* This class also provides functions for managing the ESPMegaIoT module and the internal display.
*
* This class provide a Object Oriented Programming (OOP) interface for the ESPMegaPRO board.
* If you are looking for a more simple and a more procedural interface, please use the ESPMegaPRO class in ESPMegaPRO.hpp.
* But note that the ESPMegaPRO class only interfaces with the built-in Digital Input and Digital Output cards and other onboard components.
* It does not provide an interface for expansion cards, the ESPMegaIoT module, and the internal display.
*
* @warning Only one ESPMegaPRO object can be created, creating more than one will result in undefined behavior.
*/
class ESPMegaPRO {
public:
ESPMegaPRO();
bool begin();
void loop();
bool installCard(uint8_t slot, ExpansionCard* card);
bool updateTimeFromNTP();
void enableIotModule();
void enableInternalDisplay(HardwareSerial *serial);
void enableWebServer(uint16_t port);
void setTimezone(const char* offset);
rtctime_t getTime();
void dumpFRAMtoSerial(uint16_t start, uint16_t end);
void dumpFRAMtoSerialASCII(uint16_t start, uint16_t end);
void setTime(int hours, int minutes, int seconds, int day, int month, int year);
ExpansionCard* getCard(uint8_t slot);
FRAM fram;
/**
* @brief The Digital Input Card Built-in to the ESPMegaPRO board.
* @typedef DigitalInputCard
* @note This card is installed by default at slot 0 on the ESPMegaPRO R3 board.
*/
DigitalInputCard inputs = DigitalInputCard(INPUT_BANK_A_ADDRESS, INPUT_BANK_B_ADDRESS);
/**
* @brief The Digital Output Card Built-in to the ESPMegaPRO board.
* @typedef DigitalOutputCard
* @note This card is installed by default at slot 1 on the ESPMegaPRO R3 board.
*/
DigitalOutputCard outputs = DigitalOutputCard(PWM_BANK_ADDRESS);
/**
* @brief The Display Built-in to the ESPMegaPRO board.
* @typedef InternalDisplay
* @note SKU EMG-PRO-R3-XXX-(F)-(12/24)V does not have a built-in display.
*/
InternalDisplay *display;
/**
* @brief This component is used to connect the ESPMegaPRO board to the internet and communicate with it through MQTT.
* @typedef ESPMegaIoT
* @note You must call enableIotModule() before using this component.
*/
ESPMegaIoT *iot;
/**
* @brief This component is used to create a web server on the ESPMegaPRO board.
* @typedef ESPMegaWebServer
* @note You must call enableWebServer() before using this component.
*/
ESPMegaWebServer *webServer;
/**
* @brief This component is used to enter recovery mode when the ESPMegaPRO board is in a bootloop.
* @typedef ESPMegaRecovery
*/
ESPMegaRecovery recovery;
private:
bool iotEnabled = false;
bool internalDisplayEnabled = false;
bool webServerEnabled = false;
ExpansionCard* cards[255];
bool cardInstalled[255];
uint8_t cardCount = 0;
};

View File

@ -1,15 +0,0 @@
#pragma once
#include <ESPMegaIoT.hpp>
class ESPMegaRTU {
public:
ESPMegaRTU();
~ESPMegaRTU();
virtual void subscribe();
virtual void begin(char* remote_base_topic, uint8_t remote_card_slot, ESPMegaIoT* iot);
protected:
char* remoteBaseTopic;
uint8_t remoteBaseTopicLength;
uint8_t cardSlot;
ESPMegaIoT* iot;
};

View File

@ -1,141 +0,0 @@
#include <ESPMegaRecovery.hpp>
ESPMegaRecovery::ESPMegaRecovery()
{
// Initialize all the pointers to null
this->fram = nullptr;
this->fram_address = 0;
this->bootloop_counter = 0;
this->recovery_mode = false;
this->web_server = nullptr;
this->iot = nullptr;
}
void ESPMegaRecovery::begin() {
// Retrieve the bootloop counter from the FRAM
if(this->fram != nullptr) {
this->bootloop_counter = this->fram->read8(this->fram_address);
}
// Inclement the bootloop counter
this->inclementBootloopCounter();
ESP_LOGV("ESPMegaRecovery", "Bootloop counter: %d", this->getBootloopCounter());
// If the bootloop counter is greater than 5, enter recovery mode
if(this->getBootloopCounter() > 5) {
ESP_LOGE("ESPMegaRecovery", "Bootloop detected");
// Reset the bootloop counter to prevent re-entering recovery mode
// The device might unintentionally restart multiple times
// By resetting the counter, the user can press reset once in recovery mode to exit
ESP_LOGD("ESPMegaRecovery", "Resetting bootloop counter");
this->resetBootloopCounter();
ESP_LOGW("ESPMegaRecovery", "Entering recovery mode");
this->enterRecoveryMode();
this->loop();
}
}
void ESPMegaRecovery::loop() {
// If the device is in recovery mode, block all other tasks
if(this->isRecoveryMode()) {
int i = 0;
while(true) {
if (i%10 == 0) {
ESP_LOGV("ESPMegaRecovery", "System is in recovery mode, no tasks will be executed");
ESP_LOGV("ESPMegaRecovery", "Please upload a new firmware to exit recovery mode");
}
// This code will become the new loop
delay(1000);
i++;
}
}
// Watchdog timer
static bool booted = false;
static uint32_t boot_time = millis();
if(!booted) {
if(millis() - boot_time > RECOVERY_WATCHDOG_TIMEOUT * 1000) {
ESP_LOGI("ESPMegaRecovery", "System booted successfully, resetting bootloop counter");
this->resetBootloopCounter();
booted = true;
}
}
}
void ESPMegaRecovery::enterRecoveryMode() {
// Set the recovery mode flag
this->recovery_mode = true;
// Enabling the IoT module
ESP_LOGI("ESPMegaRecovery", "Enabling the IoT module");
this->iot = new ESPMegaIoT();
this->iot->bindFRAM(this->fram);
ETH.begin();
this->iot->bindEthernetInterface(&ETH);
ESP_LOGI("ESPMegaRecovery", "Loading network configuration");
this->iot->loadNetworkConfig();
ESP_LOGI("ESPMegaRecovery", "Attempting to connect to the network");
this->iot->connectNetwork();
// Start the web server
ESP_LOGI("ESPMegaRecovery", "Starting the web server");
this->web_server = new ESPMegaWebServer(80, this->iot);
this->web_server->bindFRAM(this->fram);
this->web_server->loadCredentialsFromFRAM();
ESP_LOGI("ESPMegaRecovery", "Aquiring the web server instance");
AsyncWebServer *server = this->web_server->getServer();
server->begin();
// Add OTA update and restart endpoint
ESP_LOGI("ESPMegaRecovery", "Adding OTA update and reboot endpoints");
auto bindedDashboardHandler = std::bind(&ESPMegaWebServer::dashboardHandler, this->web_server, std::placeholders::_1);
auto bindedOtaRequestHandler = std::bind(&ESPMegaWebServer::otaRequestHandler, this->web_server, std::placeholders::_1);
auto bindedOtaUploadHandler = std::bind(&ESPMegaWebServer::otaUploadHandler, this->web_server, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6);
auto bindedDeviceInfoHandler = std::bind(&ESPMegaRecovery::getDeviceInfoHandler, this, std::placeholders::_1);
auto bindedConfigHandler = std::bind(&ESPMegaRecovery::configHandler, this, std::placeholders::_1);
server->on("/", HTTP_GET, bindedDashboardHandler);
server->on("/ota_update", HTTP_POST, bindedOtaRequestHandler, bindedOtaUploadHandler);
server->on("/reboot", HTTP_GET, std::bind(&ESPMegaWebServer::rebootHandler, this->web_server, std::placeholders::_1));
server->on("/get_device_info", HTTP_GET, bindedDeviceInfoHandler);
server->on("/config", HTTP_GET, bindedConfigHandler);
}
void ESPMegaRecovery::bindFRAM(FRAM *fram, uint32_t address) {
this->fram = fram;
this->fram_address = address;
}
uint8_t ESPMegaRecovery::getBootloopCounter() {
return this->bootloop_counter;
}
void ESPMegaRecovery::inclementBootloopCounter() {
this->bootloop_counter++;
if(this->fram != nullptr) {
this->fram->write8(this->fram_address, this->bootloop_counter);
}
}
void ESPMegaRecovery::resetBootloopCounter() {
this->bootloop_counter = 0;
if(this->fram != nullptr) {
this->fram->write8(this->fram_address, this->bootloop_counter);
}
}
bool ESPMegaRecovery::isRecoveryMode() {
return this->recovery_mode;
}
void ESPMegaRecovery::getDeviceInfoHandler(AsyncWebServerRequest *request) {
if (!request->authenticate(this->web_server->getWebUsername(), this->web_server->getWebPassword()))
{
return request->requestAuthentication();
}
StaticJsonDocument<512> doc;
doc["hostname"] = this->iot->getNetworkConfig()->hostname;
doc["ip_address"] = this->iot->getIp().toString();
doc["mac_address"] = this->iot->getMac();
doc["model"] = BOARD_MODEL;
doc["mqtt_server"] = "Recovery";
doc["mqtt_port"] = "Mode";
doc["base_topic"] = "Recovery Mode";
doc["mqtt_connected"] = "Recovery Mode";
doc["software_version"] = "EMG-SAFE-1.0.0";
doc["sdk_version"] = SDK_VESRION;
doc["idf_version"] = IDF_VER;
char buffer[512];
serializeJson(doc, buffer);
request->send(200, "application/json", buffer);
}
void ESPMegaRecovery::configHandler(AsyncWebServerRequest *request){
// Say Not Available in Recovery Mode
// Wait 3s then redirect to /
request->send(500, "text/html", "<h1>RECOVERY MODE</h1><p>Configuration is not available in recovery mode</p><script>setTimeout(function(){window.location.href = '/';}, 1500);</script>");
}

View File

@ -1,36 +0,0 @@
#include <FRAM.h>
#include <ESPMegaWebServer.hpp>
#include <esp_log.h>
/**
* @brief Recovery mode for ESPMega
* Recovery mode is a mode that is entered when the device is in a bootloop
* In this mode, the device will block all other tasks and wait for a new firmware to be uploaded over the air
* To exit recovery mode, the device must be restarted or a new firmware must be uploaded
* This is useful when the device is in a bootloop and the user can't access the device to upload a new firmware (ota bricking)
* The device is considered to be in a bootloop when it is restarted before RECOVERY_WATCHDOG_TIMEOUT seconds for 5 consecutive times
* The timer is started when the device's initialization is complete (enter loop)
*/
#define RECOVERY_WATCHDOG_TIMEOUT 15 // The time in seconds to wait for a restart before considering it a bootloop
class ESPMegaRecovery {
public:
ESPMegaRecovery();
void begin();
void loop();
void enterRecoveryMode(); // Enter recovery mode, Block all other tasks, start OTA server
void bindFRAM(FRAM* fram, uint32_t address); // Bind the FRAM block to store the bootloop counter
uint8_t getBootloopCounter(); // Get the bootloop counter
void inclementBootloopCounter(); // Increment the bootloop counter
void resetBootloopCounter(); // Reset the bootloop counter
bool isRecoveryMode(); // Check if the device is in recovery mode
private:
void getDeviceInfoHandler(AsyncWebServerRequest *request);
void configHandler(AsyncWebServerRequest *request);
FRAM* fram;
uint32_t fram_address;
uint8_t bootloop_counter;
ESPMegaIoT* iot;
ESPMegaWebServer* web_server;
bool recovery_mode;
};

View File

@ -1,9 +0,0 @@
// ESPMega TCP API
#include <Arduino.h>
class ESPMegaTCP
{
public:
void begin();
void loop();
};

View File

@ -1,489 +0,0 @@
/**
* @file ESPMegaWebServer.cpp
* @brief Implementation file for the ESPMegaWebServer class.
*
* This file contains the implementation of the ESPMegaWebServer class, which is responsible for handling web server functionality for the ESPMegaPRO firmware.
* The ESPMegaWebServer class provides methods for starting the web server, handling HTTP requests, and managing credentials and configurations.
*/
#include <ESPMegaWebServer.hpp>
/**
* @brief Construct a new ESPMegaWebServer::ESPMegaWebServer objecy
*
* @note Although you can instantiate this class directly, it is recommended to use the ESPMegaPRO.webServer object instead.
*
* @param port The TCP port to listen on
* @param iot A pointer to the ESPMegaIoT object
*/
ESPMegaWebServer::ESPMegaWebServer(uint16_t port, ESPMegaIoT *iot)
{
this->port = port;
this->iot = iot;
this->server = new AsyncWebServer(port);
this->saveConfigHandler = new AsyncCallbackJsonWebHandler("/save_config", std::bind(&ESPMegaWebServer::saveConfigJSONHandler, this, std::placeholders::_1, std::placeholders::_2));
}
/**
* @brief Destroy the ESPMegaWebServer::ESPMegaWebServer object
*/
ESPMegaWebServer::~ESPMegaWebServer()
{
delete this->server;
}
/**
* @brief Start the web server
*
* This method starts the web server and registers the necessary handlers.
*
* @note This method should be called after the ESPMegaIoT object has been initialized.
* @note This method is automatically called if you use ESPMegaPRO::enableWebServer()
*/
void ESPMegaWebServer::begin()
{
this->loadCredentialsFromFRAM();
this->server->begin();
auto bindedDashboardHandler = std::bind(&ESPMegaWebServer::dashboardHandler, this, std::placeholders::_1);
this->server->on("/", HTTP_GET, bindedDashboardHandler);
auto bindedConfigHandler = std::bind(&ESPMegaWebServer::configHandler, this, std::placeholders::_1);
this->server->on("/config", HTTP_GET, bindedConfigHandler);
this->server->addHandler(saveConfigHandler);
auto bindedOtaRequestHandler = std::bind(&ESPMegaWebServer::otaRequestHandler, this, std::placeholders::_1);
auto bindedOtaUploadHandler = std::bind(&ESPMegaWebServer::otaUploadHandler, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6);
this->server->on("/ota_update", HTTP_POST, bindedOtaRequestHandler, bindedOtaUploadHandler);
this->server->on("/reboot", HTTP_GET, std::bind(&ESPMegaWebServer::rebootHandler, this, std::placeholders::_1));
auto bindedGetConfigHandler = std::bind(&ESPMegaWebServer::getConfigHandler, this, std::placeholders::_1);
this->server->on("/get_config", HTTP_GET, bindedGetConfigHandler);
auto bindedGetDeviceInfoHandler = std::bind(&ESPMegaWebServer::getDeviceInfoHandler, this, std::placeholders::_1);
this->server->on("/get_device_info", HTTP_GET, bindedGetDeviceInfoHandler);
}
/**
* @brief The loop function for the web server
*
* @note This method is not used by the ESPMegaWebServer class as of now.
*/
void ESPMegaWebServer::loop()
{
// AsyncWebServer doesn't have a loop function
}
/**
* @brief Bind the FRAM object to the web server
*
* This method binds the FRAM object to the web server so that the web server can read and write credentials to the FRAM.
*
* @note The FRAM object must be bound to the web server before calling ESPMegaWebServer::begin()
* @note This class takes 64 bytes of FRAM starting from address 301, however address 301-400 is reserved for it.
*
* @param fram A pointer to the FRAM object
*/
void ESPMegaWebServer::bindFRAM(FRAM *fram)
{
this->fram = fram;
}
/**
* @brief Load web username and password from FRAM
*
* This method loads the web server credentials from the FRAM.
*
* @note This method is automatically called by ESPMegaWebServer::begin()
*/
void ESPMegaWebServer::loadCredentialsFromFRAM()
{
this->fram->read(301, (uint8_t *)this->webUsername, 32);
this->fram->read(333, (uint8_t *)this->webPassword, 32);
// Verify if credentials are valid
// A valid username and password is null terminated
// Scan for null terminator
bool validUsername = false;
bool validPassword = false;
for (int i = 0; i < 32; i++)
{
if (this->webUsername[i] == '\0')
{
validUsername = true;
break;
}
}
for (int i = 0; i < 32; i++)
{
if (this->webPassword[i] == '\0')
{
validPassword = true;
break;
}
}
if (!validUsername || !validPassword)
{
this->resetCredentials();
return;
}
// A valid username and password is at least 1 character long
if (strlen(this->webUsername) == 0 || strlen(this->webPassword) == 0)
{
this->resetCredentials();
return;
}
}
/**
* @brief Save web username and password to FRAM
*
* This method saves the web server credentials to the FRAM.
*/
void ESPMegaWebServer::saveCredentialsToFRAM()
{
this->fram->write(301, (uint8_t *)this->webUsername, 32);
this->fram->write(333, (uint8_t *)this->webPassword, 32);
}
/**
* @brief Reset web username and password to default
*
* This method resets the web server credentials to the default username and password.
*
* @note The default username and password is both "admin"
*/
void ESPMegaWebServer::resetCredentials()
{
// The default username and password is "admin"
strcpy(this->webUsername, "admin");
strcpy(this->webPassword, "admin");
this->saveCredentialsToFRAM();
}
/**
* @brief Get the web username
*
* @warning The returned pointer should not be freed or modified.
*
* @return The web username
*/
char *ESPMegaWebServer::getWebUsername()
{
return this->webUsername;
}
/**
* @brief Get the web password
*
* @warning The returned pointer should not be freed or modified.
*
* @return The web password
*/
char *ESPMegaWebServer::getWebPassword()
{
return this->webPassword;
}
/**
* @brief Set the web username
*
* @param username The new web username
*/
void ESPMegaWebServer::setWebUsername(const char *username)
{
strcpy(this->webUsername, username);
}
/**
* @brief Set the web password
*
* @param password The new web password
*/
void ESPMegaWebServer::setWebPassword(const char *password)
{
strcpy(this->webPassword, password);
}
/**
* @brief Handle HTTP requests to the dashboard (/) page
*
* @param request The AsyncWebServerRequest object
*/
void ESPMegaWebServer::dashboardHandler(AsyncWebServerRequest *request)
{
if (!request->authenticate(this->webUsername, this->webPassword))
{
return request->requestAuthentication();
}
request->send_P(200, "text/html", ota_html);
}
/**
* @brief Handle HTTP requests to the config (/config) page
*
* @param request The AsyncWebServerRequest object
*/
void ESPMegaWebServer::configHandler(AsyncWebServerRequest *request)
{
if (!request->authenticate(this->webUsername, this->webPassword))
{
return request->requestAuthentication();
}
request->send_P(200, "text/html", config_html);
}
/**
* @brief Handle HTTP requests to the OTA update (/ota_update) page
*
* @param request The AsyncWebServerRequest object
*/
void ESPMegaWebServer::otaRequestHandler(AsyncWebServerRequest *request)
{
// Prepare to receive firmware
if (!request->authenticate(this->webUsername, this->webPassword))
{
return request->requestAuthentication();
}
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
response->addHeader("Connection", "close");
request->send(response);
// Restart ESPMega
ESP.restart();
}
/**
* @brief Handle HTTP upload session to the OTA update (/ota_update) page
*
* @param request The AsyncWebServerRequest object
* @param filename The filename of the firmware
* @param index The index of the firmware
* @param data The firmware data
* @param len The length of the firmware data
* @param final Whether this is the final chunk of firmware
*/
void ESPMegaWebServer::otaUploadHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
// Receive firmware
if (!request->authenticate(this->webUsername, this->webPassword))
{
return request->requestAuthentication();
}
if (index == 0)
{
ESP_LOGI("ESPMegaWebServer", "OTA Update Start");
if (!Update.begin(UPDATE_SIZE_UNKNOWN))
{ // start with max available size
ESP_LOGE("ESPMegaWebServer", "OTA Update Start Error");
Update.printError(Serial);
}
}
if (Update.write(data, len) != len)
{
ESP_LOGE("ESPMegaWebServer", "OTA Update Write Error");
Update.printError(Serial);
} else {
ESP_LOGI("ESPMegaWebServer", "OTA Update Write Success: %uB", index + len);
}
if (final)
{
if (Update.end(true))
{ // true to set the size to the current progress
ESP_LOGI("ESPMegaWebServer", "OTA Update Success: %uB", index + len);
}
else
{
ESP_LOGE("ESPMegaWebServer", "OTA Update End Error");
Update.printError(Serial);
}
}
}
/**
* @brief Handle JSON POST requests to the save_config (/save_config) page
*
* @param request The AsyncWebServerRequest object
* @param json The JSON object representing the request body
*/
void ESPMegaWebServer::saveConfigJSONHandler(AsyncWebServerRequest *request, JsonVariant &json)
{
/**
* Request POST body should be a JSON object
* containing the following fields:
* ip_address: String, the IP address of the device
* netmask: String, the netmask of the device
* gateway: String, the gateway of the device
* dns: String, the DNS of the device
* hostname: String, the hostname of the device
* bms_ip: String, the IP address of the MQTT broker
* bms_port: int, the port of the MQTT broker
* bms_useauth: Boolean, true if the MQTT broker requires authentication
* bms_username: String, the username of the MQTT broker
* bms_password: String, the password of the MQTT broker
* bms_endpoint: String, the base topic of the MQTT broker
* web_username: String, the username of the web server
* web_password: String, the password of the web server
*/
ESP_LOGD("ESPMegaWebServer", "Saving config");
JsonObject root = json.as<JsonObject>();
// Network Config
NetworkConfig networkConfig;
networkConfig.useStaticIp = true;
networkConfig.useWifi = false;
IPAddress ip;
ESP_LOGD("ESPMegaWebServer", "Checking IP Address");
if (!ip.fromString(root["ip_address"].as<String>()))
{
ESP_LOGE("ESPMegaWebServer", "Invalid Config IP Address");
request->send(400, "text/plain", "Invalid IP Address");
return;
}
networkConfig.ip = ip;
ESP_LOGD("ESPMegaWebServer", "Checking Netmask");
if (!ip.fromString(root["netmask"].as<String>()))
{
ESP_LOGE("ESPMegaWebServer", "Invalid Config Netmask");
request->send(400, "text/plain", "Invalid Netmask");
return;
}
networkConfig.subnet = ip;
ESP_LOGD("ESPMegaWebServer", "Checking Gateway");
if (!ip.fromString(root["gateway"].as<String>()))
{
ESP_LOGE("ESPMegaWebServer", "Invalid Config Gateway");
request->send(400, "text/plain", "Invalid Gateway");
return;
}
networkConfig.gateway = ip;
ESP_LOGD("ESPMegaWebServer", "Checking DNS");
if (!ip.fromString(root["dns"].as<String>()))
{
ESP_LOGE("ESPMegaWebServer", "Invalid Config DNS");
request->send(400, "text/plain", "Invalid DNS");
return;
}
networkConfig.dns1 = ip;
ESP_LOGD("ESPMegaWebServer", "Setting Hostname");
strcpy(networkConfig.hostname, root["hostname"].as<String>().c_str());
// MQTT Config
MqttConfig mqttConfig;
ESP_LOGD("ESPMegaWebServer", "Setting MQTT Server");
strcpy(mqttConfig.mqtt_server, root["bms_ip"].as<String>().c_str());
ESP_LOGD("ESPMegaWebServer", "Checking MQTT Port");
uint16_t mqttPort = root["bms_port"].as<int>();
if (mqttConfig.mqtt_port <= 0 || mqttConfig.mqtt_port > 65535)
{
ESP_LOGE("ESPMegaWebServer", "Invalid Config MQTT Port");
request->send(400, "text/plain", "Invalid MQTT Port");
return;
}
mqttConfig.mqtt_port = mqttPort;
ESP_LOGD("ESPMegaWebServer", "Checking MQTT Use Auth");
mqttConfig.mqtt_useauth = root["bms_useauth"].as<bool>();
ESP_LOGD("ESPMegaWebServer", "Setting MQTT Username");
strcpy(mqttConfig.mqtt_user, root["bms_username"].as<String>().c_str());
ESP_LOGD("ESPMegaWebServer", "Setting MQTT Password");
strcpy(mqttConfig.mqtt_password, root["bms_password"].as<String>().c_str());
ESP_LOGD("ESPMegaWebServer", "Setting MQTT Base Topic");
strcpy(mqttConfig.base_topic, root["bms_endpoint"].as<String>().c_str());
// Web Server Config
ESP_LOGD("ESPMegaWebServer", "Setting Web Username");
strcpy(this->webUsername, root["web_username"].as<String>().c_str());
ESP_LOGD("ESPMegaWebServer", "Setting Web Password");
strcpy(this->webPassword, root["web_password"].as<String>().c_str());
// Commit changes to FRAM
ESP_LOGD("ESPMegaWebServer", "Committing Network Config to FRAM");
this->iot->setNetworkConfig(networkConfig);
this->iot->saveNetworkConfig();
ESP_LOGD("ESPMegaWebServer", "Committing MQTT Config to FRAM");
this->iot->setMqttConfig(mqttConfig);
this->iot->saveMqttConfig();
ESP_LOGD("ESPMegaWebServer", "Committing Web Server Config to FRAM");
this->saveCredentialsToFRAM();
ESP_LOGD("ESPMegaWebServer", "Config saved");
// Send response
request->send(200, "text/plain", "OK");
ESP.restart();
}
/**
* @brief Get the AsyncWebServer object
*
* @return The AsyncWebServer object
*/
AsyncWebServer *ESPMegaWebServer::getServer() {
return this->server;
}
/**
* @brief Request authentication from the client
*
* This method requests authentication from the client.
*
* @param request The AsyncWebServerRequest object
*/
bool ESPMegaWebServer::checkAuthentication(AsyncWebServerRequest *request) {
if (!request->authenticate(this->webUsername, this->webPassword))
{
request->requestAuthentication();
return false;
}
return true;
}
/**
* @brief Handle HTTP requests to the reboot (/reboot) page
*
* @param request The AsyncWebServerRequest object
*/
void ESPMegaWebServer::rebootHandler(AsyncWebServerRequest *request)
{
if (!request->authenticate(this->webUsername, this->webPassword))
{
return request->requestAuthentication();
}
request->send(200, "text/plain", "Rebooting ESPMega PRO...");
esp_restart();
}
void ESPMegaWebServer::getConfigHandler(AsyncWebServerRequest *request) {
if (!request->authenticate(this->webUsername, this->webPassword))
{
return request->requestAuthentication();
}
StaticJsonDocument<512> doc;
NetworkConfig *networkConfig = this->iot->getNetworkConfig();
MqttConfig *mqttConfig = this->iot->getMqttConfig();
doc["ip_address"] = networkConfig->ip.toString();
doc["netmask"] = networkConfig->subnet.toString();
doc["gateway"] = networkConfig->gateway.toString();
doc["dns"] = networkConfig->dns1.toString();
doc["hostname"] = networkConfig->hostname;
doc["bms_ip"] = mqttConfig->mqtt_server;
doc["bms_port"] = mqttConfig->mqtt_port;
doc["bms_useauth"] = mqttConfig->mqtt_useauth;
doc["bms_username"] = mqttConfig->mqtt_user;
doc["bms_password"] = mqttConfig->mqtt_password;
doc["bms_endpoint"] = mqttConfig->base_topic;
doc["web_username"] = this->webUsername;
doc["web_password"] = this->webPassword;
char buffer[512];
serializeJson(doc, buffer);
request->send(200, "application/json", buffer);
}
void ESPMegaWebServer::getDeviceInfoHandler(AsyncWebServerRequest *request) {
if (!request->authenticate(this->webUsername, this->webPassword))
{
return request->requestAuthentication();
}
StaticJsonDocument<512> doc;
doc["hostname"] = this->iot->getNetworkConfig()->hostname;
doc["ip_address"] = this->iot->getIp().toString();
doc["mac_address"] = this->iot->getMac();
doc["model"] = BOARD_MODEL;
doc["mqtt_server"] = this->iot->getMqttConfig()->mqtt_server;
doc["mqtt_port"] = this->iot->getMqttConfig()->mqtt_port;
doc["base_topic"] = this->iot->getMqttConfig()->base_topic;
doc["mqtt_connected"] = this->iot->mqttConnected() ? "Connected" : "Standalone";
doc["software_version"] = SW_VERSION;
doc["sdk_version"] = SDK_VESRION;
doc["idf_version"] = IDF_VER;
doc["uptime"] = esp_timer_get_time() / 1000000; // Uptime in seconds
char buffer[512];
serializeJson(doc, buffer);
request->send(200, "application/json", buffer);
}

View File

@ -1,60 +0,0 @@
#pragma once
#include <FS.h>
#include <ESPAsyncWebServer.h>
#include <ESPMegaIoT.hpp>
#include <Update.h>
#include <FRAM.h>
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <all_html.h>
#include <ESPMegaCommon.hpp>
/**
* @brief Provides a web server for ESPMegaPRO
*
* This class provides a web server for ESPMegaPRO. It is used to configure the device and to update the firmware.
* This class also allows to save the credentials to access the web server in the FRAM memory.
* User can also add custom endpoints to the web server.
*
* This class use FRAM address 301-400
*/
class ESPMegaWebServer
{
public:
ESPMegaWebServer(uint16_t port, ESPMegaIoT *iot);
~ESPMegaWebServer();
void begin();
void loop();
void resetCredentials();
char* getWebUsername();
char* getWebPassword();
void setWebUsername(const char* username);
void setWebPassword(const char* password);
void bindFRAM(FRAM *fram);
void loadCredentialsFromFRAM();
void saveCredentialsToFRAM();
AsyncWebServer* getServer();
bool checkAuthentication(AsyncWebServerRequest *request);
// Endpoints Handlers
void dashboardHandler(AsyncWebServerRequest *request);
void configHandler(AsyncWebServerRequest *request);
AsyncCallbackJsonWebHandler *saveConfigHandler;
void saveConfigJSONHandler(AsyncWebServerRequest *request, JsonVariant &json);
void getConfigHandler(AsyncWebServerRequest *request);
void getDeviceInfoHandler(AsyncWebServerRequest *request);
void otaRequestHandler(AsyncWebServerRequest *request);
void otaUploadHandler(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void restAPIHandler(AsyncWebServerRequest *request);
void rebootHandler(AsyncWebServerRequest *request);
private:
// FRAM
FRAM *fram;
// Credentials
char webUsername[32];
char webPassword[32];
// Web Server
AsyncWebServer *server;
uint16_t port;
// ESPMegaIoT
ESPMegaIoT *iot;
};

View File

@ -1,24 +0,0 @@
#pragma once
#include <Arduino.h>
/**
* @brief The base class for all expansion cards
*
* In order to create a new expansion card, you should create a new class that inherits from this class.
* Your class should implement the following functions:
* - begin() : Initialize the card
* - loop() : A function that is called in the main loop
* - getType() : Get the type of the card, The type should be a unique number between 0 and 255
*
* @warning This class is abstract and should not be instantiated directly.
*/
class ExpansionCard {
public:
// Instantiate the card with the specified address
ExpansionCard() {}
virtual bool begin();
// Preform a loop to refresh the input buffers
virtual void loop();
// Get the card type
virtual uint8_t getType();
};

View File

@ -1,67 +0,0 @@
#include <IRBlaster.hpp>
/**
* @brief Destroy the IRBlaster object
*/
IRBlaster::~IRBlaster()
{
ESP_ERROR_CHECK(rmt_driver_uninstall(channel));
}
/**
* @brief Send an IR signal
*
* @param data The microseconds timing array produced by IRReceiver
* @param size The number of elements in the array
*/
void IRBlaster::send(const uint16_t *data, const size_t size)
{
// Send a raw IR signal
rmt_item32_t *items = new rmt_item32_t[size / 2 + size % 2];
// data is in microseconds, we need to convert it to ticks
// If the number of elements is odd, we need to add a 0 at the end
for (size_t i = 0, j = 0; i < size; i += 2, j++)
{
items[j].level0 = 1;
items[j].duration0 = data[i];
items[j].level1 = 0;
if (i + 1 < size)
{
items[j].duration1 = data[i+1];
} else {
items[j].duration1 = 0;
}
}
ESP_ERROR_CHECK(rmt_write_items(channel, items, size / 2 + size % 2, false));
delete[] items;
}
/**
* @brief Construct a new IRBlaster object
*
* @param pin The pin to use for IR transmission
* @param channel The RMT channel to use
*/
IRBlaster::IRBlaster(const uint8_t pin, rmt_channel_t channel)
{
this->channel = channel;
gpio_num_t gpio = gpio_num_t(pin);
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, channel);
config.clk_div = 80;
config.tx_config.carrier_en = true;
config.tx_config.carrier_freq_hz = 38000;
ESP_ERROR_CHECK(rmt_config(&config));
ESP_ERROR_CHECK(rmt_driver_install(channel, 0, 0));
}
/**
* @brief Construct a new IRBlaster object
* @note This constructor uses RMT_CHANNEL_0
*
* @param pin The pin to use for IR transmission
*/
IRBlaster::IRBlaster(const uint8_t pin)
{
IRBlaster(pin, RMT_CHANNEL_0);
}

View File

@ -1,18 +0,0 @@
#pragma once
#include <driver/rmt.h>
/**
* @brief Class for sending IR signals
*
* @warning This class takes one of the RMT channels, The ESP32 has 8 RMT channels, so you can use 7 IRBlaster objects at the same time (or 6 if you use the IRReceiver class)
*/
class IRBlaster
{
public:
IRBlaster(const uint8_t pin, rmt_channel_t channel);
IRBlaster(const uint8_t pin);
~IRBlaster();
void send(const uint16_t *data, const size_t size);
private:
rmt_channel_t channel;
};

View File

@ -1,41 +0,0 @@
#include <IRReceiver.hpp>
#include <functional>
volatile unsigned int IRReceiver::irBufferPtr = 0;
volatile unsigned int IRReceiver::irBuffer[1000];
uint8_t IRReceiver::pin;
void IRReceiver::begin(uint8_t pin) {
IRReceiver::pin = pin;
IRReceiver::irBufferPtr = 0;
}
void IRReceiver::start_long_receive() {
irBufferPtr = 0;
attachInterrupt(digitalPinToInterrupt(pin), IRReceiver::handleInterrupt, CHANGE);
}
ir_data_t IRReceiver::end_long_receive() {
detachInterrupt(digitalPinToInterrupt(pin));
// The data in the array is the time between each transition, so we need to convert it to the time of each transition
ir_data_t data;
const size_t size = irBufferPtr-1;
data.data = (unsigned int*)calloc(size, sizeof(unsigned int));
if (data.data == nullptr) {
data.size = 0;
return data;
}
// The data in the array is the time between each transition, so we need to convert it to the time of each transition
for (size_t i = 1; i <= size; i++) {
data.data[i-1] = irBuffer[i] - irBuffer[i-1];
}
//memcpy(data.data, (const unsigned int*)irBuffer, (size)*sizeof(unsigned int));
Serial.println("Done");
data.size = irBufferPtr;
return data;
}
void IRAM_ATTR IRReceiver::handleInterrupt() {
if (irBufferPtr > 1000) return; //full buffer = bad data
irBuffer[irBufferPtr++] = micros(); //just continually record the time-stamp of signal transitions
}

View File

@ -1,26 +0,0 @@
#pragma once
#include <Arduino.h>
#include <map>
#include <functional>
struct ir_data_t {
unsigned int *data;
size_t size;
};
/**
* @brief Class for receiving IR signals
*
* @warning Only one IRReceiver can be used at a time
*/
class IRReceiver {
public:
static void begin(uint8_t pin);
static void start_long_receive();
static ir_data_t end_long_receive();
private:
static uint8_t pin;
static void IRAM_ATTR handleInterrupt();
static volatile unsigned int irBuffer[1000]; //stores timings - volatile because changed by ISR
static volatile unsigned int irBufferPtr; //Pointer thru irBuffer - volatile because changed by ISR
};

View File

@ -1,943 +0,0 @@
#include <InternalDisplay.hpp>
/**
* @brief Initialize the Internal Display
*
* @note You should not call this function directly, instead use ESPMegaIoT::enableInternalDisplay()
*
* @param iot The ESPMegaIoT object
* @param getRtcTime A function that returns the current time
*/
void InternalDisplay::begin(ESPMegaIoT *iot, std::function<rtctime_t()> getRtcTime)
{
this->iot = iot;
this->getRtcTime = getRtcTime;
this->mqttConfig = this->iot->getMqttConfig();
this->networkConfig = this->iot->getNetworkConfig();
// Register callbacks
auto bindedPageChangeCallback = std::bind(&InternalDisplay::handlePageChange, this, std::placeholders::_1);
this->registerPageChangeCallback(bindedPageChangeCallback);
auto bindedPayloadCallback = std::bind(&InternalDisplay::handlePayload, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->registerPayloadCallback(bindedPayloadCallback);
auto bindedTouchCallback = std::bind(&InternalDisplay::handleTouch, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->registerTouchCallback(bindedTouchCallback);
// Initialize the display
if(!this->takeSerialMutex()) return;
this->displayAdapter->begin(115200);
this->displayAdapter->setTimeout(100);
this->displayAdapter->flush();
this->giveSerialMutex();
this->reset();
delay(500);
this->jumpToPage(1);
}
/**
* @brief The main loop of the Internal Display
*
* @note You should not call this function directly, instead use ESPMega::loop()
*/
void InternalDisplay::loop()
{
// Keep reading the Serial Adapter
this->recieveSerialCommand();
// Refresh the top bar every 5 seconds
static uint32_t lastTopBarRefresh;
if (millis() - lastTopBarRefresh > INTERNAL_DISPLAY_TOP_BAR_REFRESH_INTERVAL)
{
this->updateStatusIcons(this->iot->networkConnected(), this->iot->mqttConnected());
lastTopBarRefresh = millis();
}
// Refresh the clock every 10 seconds
static uint32_t lastClockRefresh;
if (millis() - lastClockRefresh > INTERNAL_DISPLAY_CLOCK_REFRESH_INTERVAL)
{
this->updateClock();
lastClockRefresh = millis();
}
}
/**
* @brief Update the display in response to a change in the input state
*
* @param pin The pin that changed
* @param state The new state of the pin
*/
void InternalDisplay::handleInputStateChange(uint8_t pin, bool state)
{
// If the input card is binded to the display and the current page is the input page
// then update the respective input component
if (this->inputCard == nullptr || this->currentPage != INTERNAL_DISPLAY_INPUT_PAGE)
return;
// Update the input state
this->setInputMarker(pin, state);
}
/**
* @brief Update the display in response to a change in the PWM state
*
* @param pin The pin that changed
* @param state The new state of the pin
* @param value The new value of the pin
*/
void InternalDisplay::handlePwmStateChange(uint8_t pin, bool state, uint16_t value)
{
// If the output card is binded to the display and the current page is the output page
// then update the respective output component
if (this->outputCard == nullptr)
return;
if (this->currentPage == INTERNAL_DISPLAY_OUTPUT_PAGE)
{
// Update the output state
this->setOutputBar(pin, value);
this->setOutputStateColor(pin, state);
}
// Refresh the PWM Adjustment page if the current page is the PWM Adjustment page and the pin is the same
else if (this->currentPage == INTERNAL_DISPLAY_PWM_ADJUSTMENT_PAGE && this->pmwAdjustmentPin == pin)
{
this->refreshPWMAdjustment();
}
}
/**
* @brief Update the display in response to page change
*
* @param page The new page
*/
void InternalDisplay::handlePageChange(uint8_t page)
{
// Refresh the page
this->refreshPage(page);
}
/**
* @brief Save the network config to the FRAM
*/
void InternalDisplay::saveNetworkConfig()
{
// The network config page have the following components:
// ip_set -> a text input to set the ip address
// netmask_set -> a text input to set the netmask
// gateway_set -> a text input to set the gateway
// dns_set -> a text input to set the dns
// host_set -> a text input to set the hostname
// Save the ip address
IPAddress ip;
// 000.000.000.000, 16 characters, 3 dots, 3 characters per octet, 1 null terminator
char ip_buffer[30];
if (!this->getStringToBuffer("ip_set.txt", ip_buffer, 30))
{
return;
}
// Validate the ip address
if (!ip.fromString(ip_buffer))
{
return;
}
// Save the netmask
IPAddress netmask;
if (!this->getStringToBuffer("netmask_set.txt", ip_buffer, 30))
{
return;
}
// Validate the netmask
if (!netmask.fromString(ip_buffer))
{
return;
}
// Save the gateway
IPAddress gateway;
if (!this->getStringToBuffer("gateway_set.txt", ip_buffer, 30))
{
return;
}
// Validate the gateway
if (!gateway.fromString(ip_buffer))
{
return;
}
// Save the dns
IPAddress dns;
if (!this->getStringToBuffer("dns_set.txt", ip_buffer, 30))
return;
// Validate the dns
if (!dns.fromString(ip_buffer))
return;
// Save the hostname
if (!this->getStringToBuffer("host_set.txt", this->networkConfig->hostname, 32))
return;
// Write the ip address, netmask and gateway to the network config
this->networkConfig->ip = ip;
this->networkConfig->subnet = netmask;
this->networkConfig->gateway = gateway;
this->networkConfig->dns1 = dns;
this->networkConfig->dns2 = dns;
this->networkConfig->useStaticIp = true;
this->networkConfig->useWifi = false;
this->networkConfig->wifiUseAuth = false;
this->iot->saveNetworkConfig();
ESP.restart();
}
/**
* @brief Save the MQTT config to the FRAM
*/
void InternalDisplay::saveMQTTConfig()
{
// The MQTT config page have the following components:
// mqttsv_set -> a text input to set the mqtt server
// port_set -> a text input to set the mqtt port
// use_auth -> a checkbox to enable/disable mqtt authentication
// user_set -> a text input to set the mqtt username
// password_set -> a text input to set the mqtt password
// topic_set -> a text input to set the mqtt base topic
// Send the stop bytes to flush the serial buffer
this->sendStopBytes();
// Save the mqtt server
if (!this->getStringToBuffer("mqttsv_set.txt", this->mqttConfig->mqtt_server, 16))
return;
// Save the mqtt port
this->mqttConfig->mqtt_port = this->getNumber("port_set.val");
// Save the mqtt username
if (!this->getStringToBuffer("user_set.txt", this->mqttConfig->mqtt_user, 16))
return;
// Check if the password should be
char password_temp[32];
// Get the passwords
if (!this->getStringToBuffer("password_set.txt", password_temp, 16))
return;
// Check if the password should be updated
if (strcmp(password_temp, PASSWORD_OBFUSCATION_STRING))
{
strcpy(this->mqttConfig->mqtt_password, password_temp);
}
// Save the mqtt base topic
if (!this->getStringToBuffer("topic_set.txt", this->mqttConfig->base_topic, 16))
return;
// Save the mqtt use auth
uint8_t use_auth = this->getNumber("use_auth.val");
this->mqttConfig->mqtt_useauth = use_auth == 1 ? true : false;
this->iot->saveMqttConfig();
ESP.restart();
}
/**
* @brief Update the status icons on the Internal Display's top bar
*
* @param networkStatus The network status
* @param mqttStatus The MQTT status
*/
void InternalDisplay::updateStatusIcons(bool networkStatus, bool mqttStatus)
{
this->setNumber("server.pic", mqttStatus ? PIC_MQTT_CONNECTED : PIC_MQTT_DISCONNECTED);
this->setNumber("lan.pic", networkStatus ? PIC_LAN_CONNECTED : PIC_LAN_DISCONNECTED);
}
/**
* @brief Update the clock on the Internal Display's top bar
*/
void InternalDisplay::updateClock()
{
rtctime_t time = this->getRtcTime();
if(!this->takeSerialMutex()) return;
this->displayAdapter->printf("time.txt=\"%02d:%02d %s\"", time.hours % 12, time.minutes, time.hours / 12 ? "PM" : "AM");
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Send data to display element on the current page
*/
void InternalDisplay::refreshPage()
{
this->refreshPage(this->currentPage);
}
/**
* @brief Send data to display element on the specified page
*
* @note The current page must be the specified page
*
* @param page The page to refresh
*/
void InternalDisplay::refreshPage(uint8_t page)
{
switch (page)
{
case INTERNAL_DISPLAY_DASHBOARD_PAGE:
this->refreshDashboard();
break;
case INTERNAL_DISPLAY_INPUT_PAGE:
if (this->inputCard == nullptr)
{
this->jumpToPage(INTERNAL_DISPLAY_INPUT_NULL_PTR_PAGE);
break;
}
this->refreshInput();
break;
case INTERNAL_DISPLAY_OUTPUT_PAGE:
if (this->outputCard == nullptr)
{
this->jumpToPage(INTERNAL_DISPLAY_OUTPUT_NULL_PTR_PAGE);
break;
}
this->refreshOutput();
break;
case INTERNAL_DISPLAY_AC_PAGE:
if (this->climateCard == nullptr)
{
this->jumpToPage(INTERNAL_DISPLAY_CLIMATE_NULL_PTR_PAGE);
break;
}
this->refreshAC();
break;
case INTERNAL_DISPLAY_PWM_ADJUSTMENT_PAGE:
this->refreshPWMAdjustment();
break;
case INTERNAL_DISPLAY_NETWORK_CONFIG_PAGE:
this->refreshNetworkConfig();
break;
case INTERNAL_DISPLAY_MQTT_CONFIG_PAGE:
this->refreshMQTTConfig();
break;
default:
break;
}
}
/**
* @brief Send data to display element on the dashboard page
*/
void InternalDisplay::refreshDashboard()
{
// The dashboard have the following components:
// 1. Hostname
// 2. IP Address
// 3. MQTT Server with port
// 4. MQTT Connection status
this->setString("hostname.txt", this->networkConfig->hostname);
// Construct the IP address string
static char ip_address[25];
sprintf(ip_address, "%d.%d.%d.%d", this->networkConfig->ip[0], this->networkConfig->ip[1], this->networkConfig->ip[2], this->networkConfig->ip[3]);
this->setString("ip_address.txt", ip_address);
// Send the MQTT server and port
if(!this->takeSerialMutex()) return;
this->displayAdapter->print("server_address.txt=\"");
this->displayAdapter->print(this->mqttConfig->mqtt_server);
this->displayAdapter->print("\"");
this->sendStopBytes();
this->giveSerialMutex();
// Send the MQTT connection status
this->setString("status_txt.txt", this->iot->mqttConnected() ? MSG_MQTT_CONNECTED : MSG_MQTT_DISCONNECTED);
}
/**
* @brief Send data to display element on the input page
*/
void InternalDisplay::refreshInput()
{
for (uint8_t i = 0; i < 16; i++)
{
this->setInputMarker(i, this->inputCard->digitalRead(i, false));
}
}
/**
* @brief Send data to display element on the output page
*/
void InternalDisplay::refreshOutput()
{
for (uint8_t i = 0; i < 16; i++)
{
this->setOutputBar(i, this->outputCard->getValue(i));
this->setOutputStateColor(i, this->outputCard->getState(i));
}
}
/**
* @brief Send data to display element on the AC page
*/
void InternalDisplay::refreshAC()
{
if(!this->takeSerialMutex()) return;
this->displayAdapter->print("temp.txt=\"");
this->displayAdapter->print(this->climateCard->getTemperature());
this->displayAdapter->print("C\"");
this->sendStopBytes();
this->displayAdapter->print("fan_auto.pic=");
this->displayAdapter->print(this->climateCard->getFanSpeed() == AC_FAN_SPEED_AUTO ? PIC_AC_FAN_SPEED_AUTO_ACTIVE : PIC_AC_FAN_SPEED_AUTO_INACTIVE);
this->sendStopBytes();
this->displayAdapter->print("fan_low.pic=");
this->displayAdapter->print(this->climateCard->getFanSpeed() == AC_FAN_SPEED_LOW ? PIC_AC_FAN_SPEED_LOW_ACTIVE : PIC_AC_FAN_SPEED_LOW_INACTIVE);
this->sendStopBytes();
this->displayAdapter->print("fan_mid.pic=");
this->displayAdapter->print(this->climateCard->getFanSpeed() == AC_FAN_SPEED_MEDIUM ? PIC_AC_FAN_SPEED_MEDIUM_ACTIVE : PIC_AC_FAN_SPEED_MEDIUM_INACTIVE);
this->sendStopBytes();
this->displayAdapter->print("fan_high.pic=");
this->displayAdapter->print(this->climateCard->getFanSpeed() == AC_FAN_SPEED_HIGH ? PIC_AC_FAN_SPEED_HIGH_ACTIVE : PIC_AC_FAN_SPEED_HIGH_INACTIVE);
this->sendStopBytes();
this->displayAdapter->print("mode_off.pic=");
this->displayAdapter->print(this->climateCard->getMode() == AC_MODE_OFF ? PIC_AC_MODE_OFF_ACTIVE : PIC_AC_MODE_OFF_INACTIVE);
this->sendStopBytes();
this->displayAdapter->print("mode_fan.pic=");
this->displayAdapter->print(this->climateCard->getMode() == AC_MODE_FAN_ONLY ? PIC_AC_MODE_FAN_ACTIVE : PIC_AC_MODE_FAN_INACTIVE);
this->sendStopBytes();
this->displayAdapter->print("mode_cool.pic=");
this->displayAdapter->print(this->climateCard->getMode() == AC_MODE_COOL ? PIC_AC_MODE_COOL_ACTIVE : PIC_AC_MODE_COOL_INACTIVE);
this->sendStopBytes();
this->giveSerialMutex();
if (this->climateCard->getSensorType() == AC_SENSOR_TYPE_DHT22)
{
if(!this->takeSerialMutex()) return;
this->displayAdapter->print("roomtemp.txt=\"");
this->displayAdapter->print(this->climateCard->getRoomTemperature());
this->displayAdapter->print("C\"");
this->sendStopBytes();
this->displayAdapter->print("roomhumid.txt=\"");
this->displayAdapter->print(this->climateCard->getHumidity());
this->displayAdapter->print("%\"");
this->sendStopBytes();
this->giveSerialMutex();
}
else if (this->climateCard->getSensorType() == AC_SENSOR_TYPE_DS18B20)
{
if(!this->takeSerialMutex()) return;
this->displayAdapter->print("roomtemp.txt=\"");
this->displayAdapter->print(this->climateCard->getRoomTemperature());
this->displayAdapter->print("C\"");
this->sendStopBytes();
this->giveSerialMutex();
this->setString("roomhumid.txt", "N/A");
}
else
{
this->setString("roomtemp.txt", "N/A");
this->setString("roomhumid.txt", "N/A");
}
}
/**
* @brief Set the PWM status output bar value (Fullness of the bar)
*
* @param pin The pin of the PWM
* @param value The value of the PWM (0 - 4095)
*/
void InternalDisplay::setOutputBar(uint8_t pin, uint16_t value)
{
if(!this->takeSerialMutex()) return;
// Write the value to the output bar
this->displayAdapter->print("j");
this->displayAdapter->print(pin);
this->displayAdapter->print(".val=");
this->displayAdapter->print((int)(value * 100 / 4095));
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Set the PWM status output bar color to match the PWM state
*
* @param pin The pin of the PWM
* @param state The state of the PWM
*/
void InternalDisplay::setOutputStateColor(uint8_t pin, bool state)
{
if(!this->takeSerialMutex()) return;
this->displayAdapter->print("j");
this->displayAdapter->print(pin);
this->displayAdapter->print(".ppic=");
this->displayAdapter->print(state ? PIC_PWM_BAR_ON : PIC_PWM_BAR_OFF);
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Set Input Marker to match the input state
*
* @param pin The pin of the input
* @param state The state of the input
*/
void InternalDisplay::setInputMarker(uint8_t pin, bool state)
{
if(!this->takeSerialMutex()) return;
this->displayAdapter->print("I");
this->displayAdapter->print(pin);
this->displayAdapter->print(".val=");
this->displayAdapter->print(state ? 1 : 0);
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Create a new Internal Display object
*
* @param displayAdapter The HardwareSerial object that is connected to the display
*/
InternalDisplay::InternalDisplay(HardwareSerial *displayAdapter) : ESPMegaDisplay(displayAdapter, 115200, 921600, 1, 3)
{
this->currentPage = INTERNAL_DISPLAY_DASHBOARD_PAGE;
this->iot = nullptr;
this->inputCard = nullptr;
this->outputCard = nullptr;
this->climateCard = nullptr;
this->pmwAdjustmentPin = 0;
}
/**
* @brief Set the input card to be be shown on the input page
*
* @param inputCard The input card object to be shown
*/
void InternalDisplay::bindInputCard(DigitalInputCard *inputCard)
{
// Check if the input card is already binded
// If it is, then unbind it first
if (this->inputCard != nullptr)
this->unbindInputCard();
this->inputCard = inputCard;
auto bindedInputStateChangeCallback =
std::bind(&InternalDisplay::handleInputStateChange, this,
std::placeholders::_1, std::placeholders::_2);
this->bindedInputCardCallbackHandler =
this->inputCard->registerCallback(bindedInputStateChangeCallback);
}
/**
* @brief Unbind the input card from the display
*/
void InternalDisplay::unbindInputCard()
{
if (this->inputCard == nullptr)
return;
this->inputCard->unregisterCallback(this->bindedInputCardCallbackHandler);
this->inputCard = nullptr;
}
/**
* @brief Set the output card to be be shown on the output page
*
* @param outputCard The output card object to be shown
*/
void InternalDisplay::bindOutputCard(DigitalOutputCard *outputCard)
{
// Check if the output card is already binded
// If it is, then unbind it first
if (this->outputCard != nullptr)
this->unbindOutputCard();
this->outputCard = outputCard;
auto bindedPwmStateChangeCallback = std::bind(&InternalDisplay::handlePwmStateChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->bindedOutputCardCallbackHandler =
this->outputCard->registerChangeCallback(bindedPwmStateChangeCallback);
}
/**
* @brief Unbind the output card from the display
*/
void InternalDisplay::unbindOutputCard()
{
if (this->outputCard == nullptr)
return;
this->outputCard->unregisterChangeCallback(this->bindedOutputCardCallbackHandler);
this->outputCard = nullptr;
}
/**
* @brief Set the climate card to be be shown on the AC page
*
* This assume that your ClimeateCard has the mode and fan speed names in the following order:
* mode: [off, fan_only, cool]
* fan_speed: [auto, low, medium, high]
*
* @param climateCard The climate card object to be shown
*/
void InternalDisplay::bindClimateCard(ClimateCard *climateCard)
{
// Check if the climate card is already binded
// If it is, then unbind it first
if (this->climateCard != nullptr)
this->unbindClimateCard();
this->climateCard = climateCard;
auto bindedACStateChangeCallback = std::bind(&InternalDisplay::handleACStateChange, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
this->bindedClimateCardCallbackHandler =
this->climateCard->registerChangeCallback(bindedACStateChangeCallback);
}
/**
* @brief Unbind the climate card from the display
*/
void InternalDisplay::unbindClimateCard()
{
if (this->climateCard == nullptr)
return;
this->climateCard->unregisterChangeCallback(this->bindedClimateCardCallbackHandler);
this->climateCard = nullptr;
}
/**
* @brief Send data to display element on the PWM Adjustment page
*/
void InternalDisplay::refreshPWMAdjustment()
{
// The PWM Adjustment page have the following components:
// pwm_value -> a slider to adjust the PWM value
// pwm_state -> a button to toggle the PWM state
// pwm_id -> a text to show the PWM pin
// Refresh the PWM pin
this->refreshPWMAdjustmentId();
// Refresh the PWM value
this->refreshPWMAdjustmentSlider();
// Refresh the PWM state
this->refreshPWMAdjustmentState();
}
/**
* @brief Send the PWM pin id to the display on the PWM Adjustment page
*/
void InternalDisplay::refreshPWMAdjustmentId()
{
if(!this->takeSerialMutex()) return;
// Send the PWM pin
this->displayAdapter->print("pwm_id.txt=\"P");
this->displayAdapter->print(pmwAdjustmentPin);
this->displayAdapter->print("\"");
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Send the PWM value to the display on the PWM Adjustment page
*/
void InternalDisplay::refreshPWMAdjustmentSlider()
{
if(!this->takeSerialMutex()) return;
// Send the PWM value
this->displayAdapter->print("pwm_value.val=");
this->displayAdapter->print(this->outputCard->getValue(this->pmwAdjustmentPin));
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Send the PWM state to the display on the PWM Adjustment page
*/
void InternalDisplay::refreshPWMAdjustmentState()
{
if(!this->takeSerialMutex()) return;
// Send the PWM state
this->displayAdapter->print("pwm_state.txt=\"");
this->displayAdapter->print(this->outputCard->getState(this->pmwAdjustmentPin) ? MSG_PWM_ADJUSTMENT_STATE_ON : MSG_PWM_ADJUSTMENT_STATE_OFF);
this->displayAdapter->print("\"");
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Handle the touch event on the display
*
* @param page The page that the touch event occured
* @param component The component that the touch event occured
* @param type The type of the touch event
*/
void InternalDisplay::handleTouch(uint8_t page, uint8_t component, uint8_t type)
{
// Switch based on the page
switch (page)
{
case INTERNAL_DISPLAY_AC_PAGE:
this->handleACTouch(type, component);
break;
case INTERNAL_DISPLAY_PWM_ADJUSTMENT_PAGE:
this->handlePWMAdjustmentTouch(type, component);
break;
case INTERNAL_DISPLAY_NETWORK_CONFIG_PAGE:
if (type == TOUCH_TYPE_RELEASE && component == 7)
this->saveNetworkConfig();
break;
case INTERNAL_DISPLAY_MQTT_CONFIG_PAGE:
if (type == TOUCH_TYPE_RELEASE && component == 2)
this->saveMQTTConfig();
break;
default:
break;
}
}
/**
* @brief Handle the touch event on the AC page
*
* @param type The type of the touch event
* @param component The component that the touch event occured
*/
void InternalDisplay::handleACTouch(uint8_t type, uint8_t component)
{
// b1 [component 18] -> inclement AC temperature by 1
// b0 [component 17] -> declement AC temperature by 1
// fan_auto [component 4] -> set the fan speed to auto
// fan_low [component 5] -> set the fan speed to low
// fan_med [component 6] -> set the fan speed to medium
// fan_high [component 7] -> set the fan speed to high
// mode_off [component 10] -> set the mode to off
// mode_fan [component 9] -> set the mode to fan only
// mode_cool [component 8] -> set the mode to cool
// For b0 and b1, if the type is not release then return
// For other components, if the type is not press then return
if ((component == 17 || component == 18) && type != TOUCH_TYPE_RELEASE)
return;
if ((component != 17 && component != 18) && type != TOUCH_TYPE_PRESS)
return;
// Switch based on the component
switch (component)
{
case 17:
// Decrement the temperature
this->climateCard->setTemperature(this->climateCard->getTemperature() - 1);
break;
case 18:
// Increment the temperature
this->climateCard->setTemperature(this->climateCard->getTemperature() + 1);
break;
case 4:
// Set the fan speed to auto
this->climateCard->setFanSpeed(AC_FAN_SPEED_AUTO);
break;
case 5:
// Set the fan speed to low
this->climateCard->setFanSpeed(AC_FAN_SPEED_LOW);
break;
case 6:
// Set the fan speed to medium
this->climateCard->setFanSpeed(AC_FAN_SPEED_MEDIUM);
break;
case 7:
// Set the fan speed to high
this->climateCard->setFanSpeed(AC_FAN_SPEED_HIGH);
break;
case 10:
// Set the mode to off
this->climateCard->setMode(AC_MODE_OFF);
break;
case 9:
// Set the mode to fan only
this->climateCard->setMode(AC_MODE_FAN_ONLY);
break;
case 8:
// Set the mode to cool
this->climateCard->setMode(AC_MODE_COOL);
break;
default:
break;
}
}
/**
* @brief Handle the touch event on the PWM Adjustment page
*
* @param type The type of the touch event
* @param component The component that the touch event occured
*/
void InternalDisplay::handlePWMAdjustmentTouch(uint8_t type, uint8_t component)
{
// b0 [component 5] -> decrement the PWM id if its greater than 0, else set it to 15
// b1 [component 6] -> increment the PWM id if its less than 15, else set it to 0
// pwm_state [component 4] -> toggle the PWM state
// pwm_value [component 1] -> set the PWM value based on the slider value
// If the type is not release then return
if (type != TOUCH_TYPE_RELEASE)
return;
uint16_t val = 0;
// switch based on the component
switch (component)
{
case 5:
// Decrement the PWM id
this->pmwAdjustmentPin = this->pmwAdjustmentPin > 0 ? this->pmwAdjustmentPin - 1 : 15;
this->refreshPWMAdjustment();
break;
case 6:
// Increment the PWM id
this->pmwAdjustmentPin = this->pmwAdjustmentPin < 15 ? this->pmwAdjustmentPin + 1 : 0;
this->refreshPWMAdjustment();
break;
case 4:
// Toggle the PWM state
this->outputCard->setState(this->pmwAdjustmentPin, !this->outputCard->getState(this->pmwAdjustmentPin));
this->refreshPWMAdjustmentState();
break;
case 1:
// Set the PWM value
val = (uint16_t)this->getNumber("pwm_value.val");
this->outputCard->setValue(this->pmwAdjustmentPin, val);
break;
default:
break;
}
}
/**
* @brief Send data to display element on the network config page
*/
void InternalDisplay::refreshNetworkConfig()
{
if(!this->takeSerialMutex()) return;
// The network config page have the following components:
// ip_set -> a text input to set the ip address
// netmask_set -> a text input to set the netmask
// gateway_set -> a text input to set the gateway
// dns_set -> a text input to set the dns
// host_set -> a text input to set the hostname
// Refresh the ip address
this->displayAdapter->print("ip_set.txt=\"");
this->sendIpToDisplay(this->networkConfig->ip);
this->displayAdapter->print("\"");
this->sendStopBytes();
// Refresh the netmask
this->displayAdapter->print("netmask_set.txt=\"");
this->sendIpToDisplay(this->networkConfig->subnet);
this->displayAdapter->print("\"");
this->sendStopBytes();
// Refresh the gateway
this->displayAdapter->print("gateway_set.txt=\"");
this->sendIpToDisplay(this->networkConfig->gateway);
this->displayAdapter->print("\"");
this->sendStopBytes();
// Refresh the dns
this->displayAdapter->print("dns_set.txt=\"");
this->sendIpToDisplay(this->networkConfig->dns1);
this->displayAdapter->print("\"");
this->sendStopBytes();
// Refresh the hostname
this->displayAdapter->print("host_set.txt=\"");
this->displayAdapter->print(this->networkConfig->hostname);
this->displayAdapter->print("\"");
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Send data to display element on the mqtt config page
*/
void InternalDisplay::refreshMQTTConfig()
{
if(!this->takeSerialMutex()) return;
// The MQTT config page have the following components:
// mqttsv_set -> a text input to set the mqtt server
// port_set -> a text input to set the mqtt port
// use_auth -> a checkbox to enable/disable mqtt authentication
// user_set -> a text input to set the mqtt username
// password_set -> a text input to set the mqtt password
// topic_set -> a text input to set the mqtt base topic
// Refresh the mqtt server
this->displayAdapter->print("mqttsv_set.txt=\"");
this->displayAdapter->print(this->mqttConfig->mqtt_server);
this->displayAdapter->print("\"");
this->sendStopBytes();
// Refresh the mqtt port
this->displayAdapter->print("port_set.val=");
this->displayAdapter->print(this->mqttConfig->mqtt_port);
this->sendStopBytes();
// Refresh the mqtt username
this->displayAdapter->print("user_set.txt=\"");
this->displayAdapter->print(this->mqttConfig->mqtt_user);
this->displayAdapter->print("\"");
this->sendStopBytes();
// Refresh the mqtt password
this->displayAdapter->print("password_set.txt=\"");
this->displayAdapter->print(PASSWORD_OBFUSCATION_STRING);
this->displayAdapter->print("\"");
this->sendStopBytes();
// Refresh the mqtt base topic
this->displayAdapter->print("topic_set.txt=\"");
this->displayAdapter->print(this->mqttConfig->base_topic);
this->displayAdapter->print("\"");
this->sendStopBytes();
// Refresh the mqtt use auth
this->displayAdapter->print("use_auth.val=");
this->displayAdapter->print(this->mqttConfig->mqtt_useauth ? 1 : 0);
this->sendStopBytes();
this->giveSerialMutex();
}
/**
* @brief Write an ip address to the display
*
* @note This function only writes the ip address to the display, you need to send the prefix and suffix yourself
* @warning This function does not take the serial mutex, you need to take it yourself
*
* @param ip The ip address to send
*/
void InternalDisplay::sendIpToDisplay(IPAddress ip)
{
// Send the ip address
this->displayAdapter->print(ip[0]);
this->displayAdapter->print(".");
this->displayAdapter->print(ip[1]);
this->displayAdapter->print(".");
this->displayAdapter->print(ip[2]);
this->displayAdapter->print(".");
this->displayAdapter->print(ip[3]);
}
/**
* @brief Handle the AC state change
*
* @note This function is registered as a callback to the ClimateCard
*
* @param mode The new mode
* @param fan_speed The new fan speed
* @param temperature The new temperature
*/
void InternalDisplay::handleACStateChange(uint8_t mode, uint8_t fan_speed, uint8_t temperature)
{
// If the climate card is binded to the display and the current page is the AC page
// then update the respective AC component
if (this->climateCard == nullptr || this->currentPage != INTERNAL_DISPLAY_AC_PAGE)
return;
this->sendStopBytes();
// Update the AC state
this->refreshAC();
}
/**
* @brief Set the boot status text
*
* @param text The text to set
*/
void InternalDisplay::setBootStatus(const char *text)
{
if(!this->takeSerialMutex()) return;
this->displayAdapter->print("boot_state.txt=\"");
this->displayAdapter->print(text);
this->displayAdapter->print("\"");
this->sendStopBytes();
this->giveSerialMutex();
}
void InternalDisplay::handlePayload(uint8_t type, uint8_t *payload, uint8_t length) {
// If payload of type 0x92 is received
// Send the display to page 1
if (type == 0x92)
{
this->jumpToPage(1);
}
}

View File

@ -1,140 +0,0 @@
#pragma once
#include <ESPMegaDisplay.hpp>
#include <TimeStructure.hpp>
#include <ESPMegaIoT.hpp>
#include <DigitalInputCard.hpp>
#include <DigitalOutputCard.hpp>
#include <ClimateCard.hpp>
// Password Obfuscation
#define PASSWORD_OBFUSCATION_STRING "********"
// Page IDs
#define INTERNAL_DISPLAY_BOOT_PAGE 0
#define INTERNAL_DISPLAY_DASHBOARD_PAGE 1
#define INTERNAL_DISPLAY_INPUT_PAGE 2
#define INTERNAL_DISPLAY_OUTPUT_PAGE 3
#define INTERNAL_DISPLAY_AC_PAGE 4
#define INTERNAL_DISPLAY_PWM_ADJUSTMENT_PAGE 5
#define INTERNAL_DISPLAY_NETWORK_CONFIG_PAGE 6
#define INTERNAL_DISPLAY_OTA_PAGE 9
#define INTERNAL_DISPLAY_CLIMATE_NULL_PTR_PAGE 10
#define INTERNAL_DISPLAY_MQTT_CONFIG_PAGE 11
#define INTERNAL_DISPLAY_INPUT_NULL_PTR_PAGE 12
#define INTERNAL_DISPLAY_OUTPUT_NULL_PTR_PAGE 13
// Picture IDs
#define PIC_LAN_DISCONNECTED 2
#define PIC_LAN_CONNECTED 3
#define PIC_MQTT_DISCONNECTED 4
#define PIC_MQTT_CONNECTED 5
#define PIC_PWM_BAR_ON 33
#define PIC_PWM_BAR_OFF 48
#define PIC_AC_MODE_OFF_ACTIVE 24
#define PIC_AC_MODE_OFF_INACTIVE 25
#define PIC_AC_MODE_FAN_ACTIVE 22
#define PIC_AC_MODE_FAN_INACTIVE 23
#define PIC_AC_MODE_COOL_ACTIVE 12
#define PIC_AC_MODE_COOL_INACTIVE 13
#define PIC_AC_FAN_SPEED_AUTO_ACTIVE 14
#define PIC_AC_FAN_SPEED_AUTO_INACTIVE 15
#define PIC_AC_FAN_SPEED_LOW_ACTIVE 18
#define PIC_AC_FAN_SPEED_LOW_INACTIVE 19
#define PIC_AC_FAN_SPEED_MEDIUM_ACTIVE 20
#define PIC_AC_FAN_SPEED_MEDIUM_INACTIVE 21
#define PIC_AC_FAN_SPEED_HIGH_ACTIVE 16
#define PIC_AC_FAN_SPEED_HIGH_INACTIVE 17
// AC Fan Speeds and Mode Position Assumptions
#define AC_FAN_SPEED_AUTO 0
#define AC_FAN_SPEED_LOW 1
#define AC_FAN_SPEED_MEDIUM 2
#define AC_FAN_SPEED_HIGH 3
#define AC_MODE_OFF 0
#define AC_MODE_FAN_ONLY 1
#define AC_MODE_COOL 2
// Messages
#define MSG_MQTT_CONNECTED "BMS Managed"
#define MSG_MQTT_DISCONNECTED "Standalone"
#define MSG_PWM_ADJUSTMENT_STATE_ON "ON"
#define MSG_PWM_ADJUSTMENT_STATE_OFF "OFF"
// Refresh Interval
#define INTERNAL_DISPLAY_CLOCK_REFRESH_INTERVAL 15000
#define INTERNAL_DISPLAY_TOP_BAR_REFRESH_INTERVAL 5000
// Touch Types
#define TOUCH_TYPE_PRESS 0x01
#define TOUCH_TYPE_RELEASE 0x0
/**
* @brief The internal display of the ESPMegaPRO
*
* This is the display that is installed on some ESPMegaPRO Chassis. It is a 3.5" TFT LCD with a resistive touch screen.
*
* You can use this display to monitor the status of the ESPMegaPRO and also to control the various components of the
* ESPMegaPRO.
*
* If you are using a custom display, you need to create a class that inherits from ESPMegaDisplay and implement the
* methods in that class, you may refer to this class for reference.
*
* @note This class is automatically instantiated by the ESPMegaPRO and can be accessed via the `display` variable.
*/
class InternalDisplay : public ESPMegaDisplay {
public:
InternalDisplay(HardwareSerial *displayAdapter);
void begin(ESPMegaIoT *iot, std::function<rtctime_t()> getRtcTime);
void loop();
void bindInputCard(DigitalInputCard *inputCard);
void bindOutputCard(DigitalOutputCard *outputCard);
void bindClimateCard(ClimateCard *climateCard);
void unbindInputCard();
void unbindOutputCard();
void unbindClimateCard();
private:
uint8_t bindedInputCardCallbackHandler;
uint8_t bindedOutputCardCallbackHandler;
uint8_t bindedClimateCardCallbackHandler;
uint8_t bindedClimateCardSensorCallbackHandler;
DigitalInputCard *inputCard;
DigitalOutputCard *outputCard;
ClimateCard *climateCard;
void handleInputStateChange(uint8_t pin, bool state);
void handlePwmStateChange(uint8_t pin, bool state, uint16_t value);
void handlePageChange(uint8_t page);
void handlePayload(uint8_t type, uint8_t *payload, uint8_t length);
void setOutputBar(uint8_t pin, uint16_t value);
void setOutputStateColor(uint8_t pin, bool state);
void setInputMarker(uint8_t pin, bool state);
void handleACStateChange(uint8_t mode, uint8_t fan_speed, uint8_t temperature);
void saveNetworkConfig();
void saveMQTTConfig();
void updateStatusIcons(bool networkStatus, bool mqttStatus);
void updateClock();
void refreshPage();
void refreshPage(uint8_t page);
void refreshDashboard();
void refreshInput();
void refreshOutput();
void refreshAC();
void refreshPWMAdjustment();
void refreshPWMAdjustmentSlider();
void refreshPWMAdjustmentState();
void refreshPWMAdjustmentId();
void refreshNetworkConfig();
void refreshMQTTConfig();
void setBootStatus(const char* status);
void sendIpToDisplay(IPAddress ip);
uint8_t pmwAdjustmentPin;
// Touch handlers
void handleTouch(uint8_t page, uint8_t component, uint8_t type);
void handlePWMAdjustmentTouch(uint8_t component, uint8_t type);
void handleACTouch(uint8_t component, uint8_t type);
MqttConfig *mqttConfig;
NetworkConfig *networkConfig;
// Pointers to various data
ESPMegaIoT *iot;
std::function<rtctime_t()> getRtcTime;
};

View File

@ -1,25 +0,0 @@
#include <IoTComponent.hpp>
void IoTComponent::setMqttClient(PubSubClient *mqtt) {
this->mqtt = mqtt;
}
void IoTComponent::publishRelative(const char *topic, const char *payload) {
static char absolute_topic[100];
sprintf(absolute_topic, "%s/%02d/%s", base_topic, card_id, topic);
ESP_LOGD("IoTComponent", "Publishing to %s : %s", absolute_topic, payload);
mqtt->publish(absolute_topic, payload);
mqtt->loop();
}
void IoTComponent::subscribeRelative(const char *topic) {
char absolute_topic[50];
sprintf(absolute_topic, "%s/%02d/%s", base_topic, card_id, topic);
ESP_LOGD("IoTComponent", "Subscribing to %s", absolute_topic);
mqtt->subscribe(absolute_topic);
mqtt->loop();
}
void IoTComponent::loop() {
// Placeholder, Do nothing
}

View File

@ -1,39 +0,0 @@
#pragma once
#include <ExpansionCard.hpp>
#include <PubSubClient.h>
#include <esp_log.h>
/**
* @brief The IoTComponent class is a base class that is used to interface with an expansion card through MQTT.
*
* In order to create a new IoTComponent, you should create a new class that inherits from this class.
* Your class should implement the following functions:
* - begin() : Initialize the component, record the card id, ExpansionCard object, the PubSubClient object and the base topic
* - handleMqttMessage() : Handle the MQTT messages for the component
* - publishReport() : Publish all the reports for the component
* - getType() : Get the type of the component, This should return the underlying ExpansionCard type
* - subscribe() : Subscribe to the MQTT topics used by the component
* - loop() : A function that is called in the main loop
*
* Additionally, the inherited class will have access to these helper functions:
* - publishRelative() : Publish a message to a topic relative to the base topic and the card id
* - subscribeRelative() : Subscribe to a topic relative to the base topic and the card id
*
* @warning This class is abstract and should not be instantiated directly.
*/
class IoTComponent {
public:
virtual bool begin(uint8_t card_id, ExpansionCard *card, PubSubClient *mqtt, char *base_topic);
virtual void handleMqttMessage(char *topic, char *payload);
void setMqttClient(PubSubClient *mqtt);
virtual void publishReport();
virtual uint8_t getType();
virtual void subscribe();
void loop();
protected:
char *base_topic;
void publishRelative(const char *topic, const char *payload);
void subscribeRelative(const char *topic);
PubSubClient *mqtt;
uint8_t card_id;
};

View File

@ -1,219 +0,0 @@
#include <RemoteVariable.hpp>
/**
* @brief Construct a new Remote Variable object
* This constructor does not take any arguments.
* Please use the begin method to initialize the object.
*/
RemoteVariable::RemoteVariable() {
this->topic = nullptr;
this->value = nullptr;
}
/**
* @brief Destroy the Remote Variable object
* This destructor does not take any arguments.
* It will free the memory allocated for the topic and value.
*/
RemoteVariable::~RemoteVariable() {
free(this->value);
}
/**
* @brief Initialize the RemoteVariable object with value request
*
* This method is used to initialize the RemoteVariable object.
*
* @param size The maximum size of the variable in bytes
* @param topic The topic that the variable is published to
* @param iot The ESPMegaIoT object
* @param useValueRequest Whether to use value request
* @param valueRequestTopic The topic to request value
*
* @note Because the value is null terminated, the size should be the desired text length + 1
* @warning This use dynamic memory allocation, so it is important to call the destructor when the object is no longer needed
*/
void RemoteVariable::begin(size_t size, const char* topic, ESPMegaIoT* iot, bool useValueRequest, const char* valueRequestTopic) {
this->iot = iot;
this->size = size;
this->topic = topic;
this->useValueRequest = useValueRequest;
this->valueRequestTopic = valueRequestTopic;
value = (char*)calloc(size, sizeof(char));
auto bindedMqttCallback = std::bind(&RemoteVariable::mqtt_callback, this, std::placeholders::_1, std::placeholders::_2);
this->iot->registerMqttCallback(bindedMqttCallback);
auto bindedSubscribe = std::bind(&RemoteVariable::subscribe, this);
this->iot->registerSubscribeCallback(bindedSubscribe);
this->subscribe();
}
/**
* @brief Initialize the RemoteVariable object
*
* This method is used to initialize the RemoteVariable object.
*
* @param size The maximum size of the variable in bytes
* @param topic The topic that the variable is published to
* @param iot The ESPMegaIoT object
*
* @note Because the value is null terminated, the size should be the desired text length + 1
* @warning This use dynamic memory allocation, so it is important to call the destructor when the object is no longer needed
*/
void RemoteVariable::begin(size_t size, const char* topic, ESPMegaIoT* iot) {
this->begin(size, topic, iot, false, nullptr);
}
/**
* @brief Subscribe to the topic
* This method is used internally to subscribe to the topic from iot core
*/
void RemoteVariable::subscribe() {
ESP_LOGD("RemoteVariable", "Subscribing to %s", this->topic);
this->iot->subscribe(this->topic);
if(this->useValueRequest) {
ESP_LOGD("RemoteVariable", "Subscribing to %s", this->valueRequestTopic);
this->requestValue();
}
}
/**
* @brief Get the value of the variable
*
* This method is used to get the value of the variable.
*
* @return char* The null terminated string of the value
*/
char* RemoteVariable::getValue() {
return this->value;
}
/**
* @brief The MQTT callback
* This method is binded to the MQTT callback in iot core
*/
void RemoteVariable::mqtt_callback(char* topic, char* payload) {
if (strcmp(topic, this->topic) == 0) {
ESP_LOGD("RemoteVariable", "Received MQTT message from %s", topic);
strcpy(this->value, payload);
for (auto& callback : this->valueChangeCallback) {
callback.second(this->value);
}
}
}
/**
* @brief Request the value of the variable
* This method request a value update from the device that has the variable
* @note This method is only functional if the useValueRequest is set to true
*/
void RemoteVariable::requestValue() {
if(!this->useValueRequest)
return;
ESP_LOGD("RemoteVariable", "Sending request to %s", this->valueRequestTopic);
this->iot->publish(this->valueRequestTopic, "request");
}
/**
* @brief Enable the set value feature
* This method is used to enable the set value feature
* @param setValueTopic The topic to send the value to
*/
void RemoteVariable::enableSetValue(const char* setValueTopic) {
this->useSetValue = true;
this->setValueTopic = setValueTopic;
}
/**
* @brief Set the value of the variable
* This method is used to set the value of the variable
* @param value The value to set
* @note This method is only functional if the enableSetValue is already called
*/
void RemoteVariable::setValue(const char* value) {
if(!this->useSetValue)
return;
this->iot->publish(this->setValueTopic, value);
}
/**
* @brief Register a callback for value change
* This method will be called when the value of the variable changes on the remote device
* @param callback The callback function
* @return uint8_t The handler for the callback
*/
uint8_t RemoteVariable::registerCallback(std::function<void(char*)> callback) {
this->valueChangeCallback[this->valueChangeCallbackCount] = callback;
return valueChangeCallbackCount++;
}
/**
* @brief Unregister a callback
* This method is used to unregister a callback
* @param handler The handler of the callback
*/
void RemoteVariable::unregisterCallback(uint8_t handler) {
this->valueChangeCallback.erase(handler);
}
/**
* @brief Get the value of the variable as an integer
* This method is used to get the value of the variable as an integer
* @return int The value of the variable as an integer
* @note If the value is not a valid integer, it will return 0
*/
int RemoteVariable::getValueAsInt() {
return atoi(this->value);
}
/**
* @brief Get the value of the variable as a long
* This method is used to get the value of the variable as a long
* @return long The value of the variable as a long
* @note If the value is not a valid long, it will return 0
*/
long RemoteVariable::getValueAsLong() {
return atol(this->value);
}
/**
* @brief Get the value of the variable as a double
* This method is used to get the value of the variable as a double
* @return double The value of the variable as a double
* @note If the value is not a valid double, it will return 0
*/
double RemoteVariable::getValueAsDouble() {
return atof(this->value);
}
/**
* @brief Set the value of the variable as an integer
* This method is used to set the value of the variable as an integer
* @param value The value to set
*/
void RemoteVariable::setIntValue(int value) {
char buffer[this->size];
itoa(value, buffer, DEC);
this->setValue(buffer);
}
/**
* @brief Set the value of the variable as a long
* This method is used to set the value of the variable as a long
* @param value The value to set
*/
void RemoteVariable::setLongValue(long value) {
char buffer[this->size];
ltoa(value, buffer, DEC);
this->setValue(buffer);
}
/**
* @brief Set the value of the variable as a double
* This method is used to set the value of the variable as a double
* @param value The value to set
*/
void RemoteVariable::setDoubleValue(double value) {
char buffer[this->size];
snprintf(buffer, this->size, "%f", value);
this->setValue(buffer);
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <ESPMegaIoT.hpp>
#include <map>
/**
* @brief A class that create a variable that exists on other devices and can be accessed remotely
*
* This class is used to create a variable that exists on other devices and can be accessed remotely.
* Supports setting and getting values from the variable.
* Also support value request.
*/
class RemoteVariable
{
public:
RemoteVariable();
~RemoteVariable();
void begin(size_t size, const char* topic, ESPMegaIoT* iot);
void begin(size_t size, const char* topic, ESPMegaIoT* iot, bool useValueRequest, const char* valueRequestTopic);
void setValue(const char* value);
void enableSetValue(const char* setValueTopic);
void subscribe();
void requestValue();
char* getValue();
int getValueAsInt();
long getValueAsLong();
double getValueAsDouble();
void setIntValue(int value);
void setLongValue(long value);
void setDoubleValue(double value);
uint8_t registerCallback(std::function<void(char*)>);
void unregisterCallback(uint8_t handler);
private:
void mqtt_callback(char* topic, char* payload);
ESPMegaIoT* iot;
const char* topic;
char* value;
size_t size;
bool useValueRequest;
const char* valueRequestTopic;
bool useSetValue;
const char* setValueTopic;
uint8_t valueChangeCallbackCount;
std::map<uint8_t,std::function<void(char*)>> valueChangeCallback;
};

View File

@ -1,184 +0,0 @@
#include "SmartVariable.hpp"
SmartVariable::SmartVariable()
{
}
SmartVariable::~SmartVariable()
{
if (this->value != nullptr)
free(this->value);
}
void SmartVariable::begin(size_t size)
{
this->value = (char *)calloc(size, sizeof(char));
this->size = size;
}
void SmartVariable::enableIoT(ESPMegaIoT *iot, const char *topic)
{
this->iot = iot;
this->iotEnabled = true;
this->topic = topic;
ESP_LOGV("SmartVariable", "Binding MQTT Callback");
auto bindedMqttCallback = std::bind(&SmartVariable::handleMqttCallback, this, std::placeholders::_1, std::placeholders::_2);
this->iot->registerMqttCallback(bindedMqttCallback);
ESP_LOGV("SmartVariable", "Binding MQTT Subscription");
auto bindedMqttSubscription = std::bind(&SmartVariable::subscribeMqtt, this);
this->iot->registerSubscribeCallback(bindedMqttSubscription);
ESP_LOGI("SmartVariable", "Calling MQTT Subscribe");
this->subscribeMqtt();
}
void SmartVariable::enableValueRequest(const char *valueRequestTopic)
{
ESP_LOGD("SmartVariable", "Enabling Value Request");
this->useValueRequest = true;
this->valueRequestTopic = valueRequestTopic;
this->subscribeMqtt();
}
void SmartVariable::setValue(const char *value)
{
strncpy(this->value, value, this->size - 1);
this->value[this->size - 1] = '\0';
if (this->autoSave)
this->saveValue();
if (this->iotEnabled)
this->publishValue();
// Call Callbacks
for (auto const &callback : this->valueChangeCallbacks)
{
callback.second(this->value);
}
}
char *SmartVariable::getValue()
{
return this->value;
}
void SmartVariable::enableSetValue(const char *setValueTopic)
{
this->setValueEnabled = true;
this->setValueTopic = setValueTopic;
this->subscribeMqtt();
}
void SmartVariable::publishValue()
{
if (this->iotEnabled) {
if (this->value == nullptr) {
ESP_LOGE("SmartVariable", "Value is NULL");
return;
}
if (this->topic == nullptr) {
ESP_LOGE("SmartVariable", "Topic is NULL");
return;
}
ESP_LOGV("SmartVariable", "Publishing Value: %s to %s", this->value, this->topic);
this->iot->publish(this->topic, this->value);
}
}
void SmartVariable::bindFRAM(FRAM *fram, uint32_t framAddress)
{
this->bindFRAM(fram, framAddress, true);
}
void SmartVariable::bindFRAM(FRAM *fram, uint32_t framAddress, bool loadValue)
{
this->framAddress = framAddress;
this->fram = fram;
if (loadValue)
this->loadValue();
}
void SmartVariable::loadValue()
{
this->fram->read(this->framAddress, (uint8_t *)this->value, this->size);
this->setValue(this->value);
}
void SmartVariable::saveValue()
{
this->fram->write(this->framAddress, (uint8_t *)this->value, this->size);
}
void SmartVariable::setValueAutoSave(bool autoSave)
{
this->autoSave = autoSave;
}
uint16_t SmartVariable::registerCallback(std::function<void(char *)> callback)
{
this->valueChangeCallbacks[this->currentHandlerId] = callback;
return this->currentHandlerId++;
}
void SmartVariable::unregisterCallback(uint16_t handlerId)
{
this->valueChangeCallbacks.erase(handlerId);
}
void SmartVariable::handleMqttCallback(char *topic, char *payload)
{
if (!strcmp(topic, this->valueRequestTopic))
{
this->publishValue();
}
else if (!strcmp(topic, this->setValueTopic))
{
this->setValue(payload);
}
}
void SmartVariable::subscribeMqtt()
{
if (this->iotEnabled)
{
ESP_LOGV("SmartVariable", "IoT Enabled, running MQTT Subscribe");
ESP_LOGV("SmartVariable", "Value Request: %d, Set Value: %d", this->useValueRequest, this->setValueEnabled);
if (this->useValueRequest) {
if (this->valueRequestTopic == nullptr) {
ESP_LOGE("SmartVariable", "Value Request Topic is NULL");
return;
}
ESP_LOGV("SmartVariable", "Subscribing to %s", this->valueRequestTopic);
this->iot->subscribe(this->valueRequestTopic);
}
if (this->setValueEnabled) {
if (this->setValueTopic == nullptr) {
ESP_LOGE("SmartVariable", "Set Value Topic is NULL");
return;
}
ESP_LOGV("SmartVariable", "Subscribing to %s", this->setValueTopic);
this->iot->subscribe(this->setValueTopic);
}
ESP_LOGV("SmartVariable", "Publishing Value");
this->publishValue();
}
}
int32_t SmartVariable::getIntValue()
{
return atoi(this->value);
}
void SmartVariable::setIntValue(int32_t value)
{
itoa(value, this->value, 10);
this->setValue(this->value);
}
double SmartVariable::getDoubleValue()
{
return atof(this->value);
}
void SmartVariable::setDoubleValue(double value)
{
dtostrf(value, 0, 2, this->value);
this->setValue(this->value);
}

View File

@ -1,50 +0,0 @@
#pragma once
#include <FRAM.h>
#include <ESPMegaIoT.hpp>
#include <map>
/**
* @brief SmartVariable is a local variable that can be accessed remotely and have FRAM support
*/
class SmartVariable {
public:
SmartVariable();
~SmartVariable();
void begin(size_t size);
void enableIoT(ESPMegaIoT* iot, const char* topic);
void enableValueRequest(const char* valueRequestTopic);
void setValue(const char* value);
char* getValue();
void enableSetValue(const char* setValueTopic);
void publishValue();
void bindFRAM(FRAM *fram, uint32_t framAddress);
void bindFRAM(FRAM *fram, uint32_t framAddress, bool loadValue);
void loadValue();
void saveValue();
void setValueAutoSave(bool autoSave);
uint16_t registerCallback(std::function<void(char*)> callback);
void unregisterCallback(uint16_t handlerId);
int32_t getIntValue();
void setIntValue(int32_t value);
double getDoubleValue();
void setDoubleValue(double value);
protected:
ESPMegaIoT* iot;
bool iotEnabled;
const char* topic;
char* value;
size_t size;
bool useValueRequest;
const char* valueRequestTopic;
bool setValueEnabled;
const char* setValueTopic;
bool autoSave;
FRAM *fram;
uint32_t framAddress;
void handleMqttCallback(char* topic, char* payload);
void subscribeMqtt();
// Value Change Callback
uint16_t currentHandlerId;
std::map<uint16_t, std::function<void(char*)>> valueChangeCallbacks;
};

View File

@ -1,18 +0,0 @@
#pragma once
#include <stdint.h>
/**
* @brief The rtctime_t struct is a structure for storing the time.
*
* This structure is used by the ESPMegaPRO library to store the time.
*
* @warning This structure is not compatible with the Arduino Time library.
*/
struct rtctime_t {
uint8_t hours;
uint8_t minutes;
uint8_t seconds;
uint8_t day;
uint8_t month;
uint16_t year;
};

View File

@ -1,4 +0,0 @@
#pragma once
#include "config_html.h"
#include "display_html.h"
#include "ota_html.h"

View File

@ -1,821 +0,0 @@
const char config_html[] PROGMEM = {
0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x6c, 0x6f, 0x61,
0x64, 0x69, 0x6e, 0x67, 0x53, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x22,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x73, 0x70, 0x69, 0x6e,
0x6e, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
0x72, 0x22, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x64, 0x69,
0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b,
0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x63,
0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x73, 0x70, 0x69, 0x6e, 0x6e, 0x65,
0x72, 0x22, 0x3e, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x3c, 0x70, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x6c, 0x6f, 0x61, 0x64,
0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x74, 0x22, 0x3e, 0x53, 0x61, 0x76,
0x69, 0x6e, 0x67, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65,
0x20, 0x77, 0x61, 0x69, 0x74, 0x20, 0x28, 0x3c, 0x73, 0x70, 0x61, 0x6e,
0x20, 0x69, 0x64, 0x3d, 0x22, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f,
0x77, 0x6e, 0x22, 0x3e, 0x31, 0x35, 0x73, 0x3c, 0x2f, 0x73, 0x70, 0x61,
0x6e, 0x3e, 0x29, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x3c, 0x2f, 0x64,
0x69, 0x76, 0x3e, 0x0d, 0x0a, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0d,
0x0a, 0x3c, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x65, 0x6e, 0x63, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x22, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x72,
0x74, 0x2f, 0x66, 0x6f, 0x72, 0x6d, 0x2d, 0x64, 0x61, 0x74, 0x61, 0x22,
0x20, 0x69, 0x64, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f,
0x66, 0x6f, 0x72, 0x6d, 0x22, 0x20, 0x6f, 0x6e, 0x73, 0x75, 0x62, 0x6d,
0x69, 0x74, 0x3d, 0x22, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x29, 0x22, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x68, 0x31, 0x3e, 0x45, 0x53, 0x50, 0x4d,
0x65, 0x67, 0x61, 0x20, 0x50, 0x52, 0x4f, 0x3c, 0x2f, 0x68, 0x31, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x68, 0x33, 0x3e, 0x44, 0x65, 0x76, 0x69,
0x63, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x70, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65,
0x22, 0x3e, 0x49, 0x50, 0x20, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78,
0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x69, 0x70, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d,
0x22, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66,
0x5f, 0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d,
0x22, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e,
0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3e, 0x4e, 0x65,
0x74, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x4d, 0x61, 0x73, 0x6b, 0x3c, 0x2f,
0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22,
0x20, 0x69, 0x64, 0x3d, 0x22, 0x6e, 0x65, 0x74, 0x6d, 0x61, 0x73, 0x6b,
0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x6e, 0x65, 0x74, 0x6d,
0x61, 0x73, 0x6b, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22,
0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x3d, 0x22, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67,
0x20, 0x2e, 0x2e, 0x2e, 0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x70, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22,
0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65,
0x22, 0x3e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x3c, 0x2f, 0x70,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20,
0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, 0x20,
0x69, 0x64, 0x3d, 0x22, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x22,
0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x67, 0x61, 0x74, 0x65, 0x77,
0x61, 0x79, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63,
0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3d, 0x22, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20,
0x2e, 0x2e, 0x2e, 0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x3c, 0x70, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22,
0x3e, 0x44, 0x4e, 0x53, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3c,
0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74,
0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x64, 0x6e, 0x73, 0x22, 0x20, 0x6e,
0x61, 0x6d, 0x65, 0x3d, 0x22, 0x64, 0x6e, 0x73, 0x22, 0x20, 0x63, 0x6c,
0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x78,
0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22, 0x4c, 0x6f,
0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x22, 0x3e, 0x3c,
0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70, 0x20, 0x63, 0x6c,
0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f,
0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3e, 0x48, 0x6f, 0x73, 0x74, 0x6e,
0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22,
0x74, 0x65, 0x78, 0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x68, 0x6f,
0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65,
0x3d, 0x22, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x20,
0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x5f,
0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22,
0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x22,
0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70, 0x20,
0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3e, 0x42, 0x4d, 0x53,
0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2d, 0x20, 0x49, 0x50,
0x20, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3c, 0x2f, 0x70, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74,
0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, 0x20, 0x69,
0x64, 0x3d, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x69, 0x70, 0x22, 0x20, 0x6e,
0x61, 0x6d, 0x65, 0x3d, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x69, 0x70, 0x22,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66,
0x5f, 0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d,
0x22, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e,
0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3e, 0x42, 0x4d,
0x53, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2d, 0x20, 0x50,
0x6f, 0x72, 0x74, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22,
0x74, 0x65, 0x78, 0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x62, 0x6d,
0x73, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65,
0x3d, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x20,
0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x5f,
0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22,
0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x22,
0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x6c, 0x61,
0x62, 0x65, 0x6c, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63,
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, 0x3e, 0x41, 0x75,
0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x63, 0x68, 0x65, 0x63, 0x6b,
0x62, 0x6f, 0x78, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x62,
0x6d, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x61, 0x75, 0x74, 0x68, 0x22, 0x20,
0x69, 0x64, 0x3d, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x61,
0x75, 0x74, 0x68, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22,
0x79, 0x65, 0x73, 0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c,
0x73, 0x70, 0x61, 0x6e, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22,
0x63, 0x68, 0x65, 0x63, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x22, 0x3e, 0x3c,
0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x2f,
0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3e, 0x42, 0x4d,
0x53, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2d, 0x20, 0x55,
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x3c, 0x2f, 0x70, 0x3e, 0x0d,
0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, 0x20, 0x69, 0x64,
0x3d, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x62, 0x6d,
0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x20,
0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x5f,
0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22,
0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x22,
0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70, 0x20,
0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3e, 0x42, 0x4d, 0x53,
0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2d, 0x20, 0x50, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79, 0x70,
0x65, 0x3d, 0x22, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22,
0x20, 0x69, 0x64, 0x3d, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d,
0x22, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f,
0x6e, 0x66, 0x5f, 0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x3d, 0x22, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e,
0x2e, 0x2e, 0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20,
0x3c, 0x70, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3e,
0x42, 0x4d, 0x53, 0x20, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x2d,
0x20, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x3c, 0x2f, 0x70,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20,
0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, 0x20,
0x69, 0x64, 0x3d, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x65, 0x6e, 0x64, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22,
0x62, 0x6d, 0x73, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
0x22, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e,
0x66, 0x5f, 0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x3d, 0x22, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e,
0x2e, 0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c,
0x70, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22, 0x3e, 0x57,
0x65, 0x62, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65,
0x20, 0x2d, 0x20, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x3c,
0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74,
0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x77, 0x65, 0x62, 0x5f, 0x75, 0x73,
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65,
0x3d, 0x22, 0x77, 0x65, 0x62, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63,
0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x78, 0x74, 0x22, 0x20, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x3d, 0x22, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20,
0x2e, 0x2e, 0x2e, 0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x3c, 0x70, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x22,
0x3e, 0x57, 0x65, 0x62, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61,
0x63, 0x65, 0x20, 0x2d, 0x20, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,
0x64, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22,
0x77, 0x65, 0x62, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,
0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x77, 0x65, 0x62, 0x5f,
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x20, 0x63, 0x6c,
0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x5f, 0x74, 0x78,
0x74, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22, 0x4c, 0x6f,
0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x22, 0x3e, 0x3c,
0x62, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x73, 0x61, 0x76, 0x65, 0x5f, 0x62,
0x74, 0x6e, 0x22, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x73, 0x75,
0x62, 0x6d, 0x69, 0x74, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d,
0x22, 0x62, 0x74, 0x6e, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d,
0x22, 0x53, 0x61, 0x76, 0x65, 0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c,
0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d,
0x22, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x22, 0x20, 0x63, 0x6c, 0x61,
0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x22, 0x20, 0x6f, 0x6e,
0x63, 0x6c, 0x69, 0x63, 0x6b, 0x3d, 0x22, 0x77, 0x69, 0x6e, 0x64, 0x6f,
0x77, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x68,
0x72, 0x65, 0x66, 0x3d, 0x27, 0x2f, 0x27, 0x22, 0x3e, 0x42, 0x61, 0x63,
0x6b, 0x3c, 0x2f, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3e, 0x3c, 0x62,
0x72, 0x20, 0x2f, 0x3e, 0x3c, 0x62, 0x72, 0x20, 0x2f, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x62, 0x3e, 0x53, 0x49, 0x57, 0x41, 0x54, 0x20, 0x53,
0x59, 0x53, 0x54, 0x45, 0x4d, 0x20, 0x32, 0x30, 0x32, 0x33, 0x3c, 0x2f,
0x62, 0x3e, 0x0d, 0x0a, 0x3c, 0x2f, 0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x0d,
0x0a, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, 0x6e, 0x6c, 0x6f,
0x61, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x76, 0x61, 0x72, 0x20, 0x73, 0x61, 0x76, 0x65, 0x42, 0x75, 0x74, 0x74,
0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x73, 0x61, 0x76, 0x65, 0x5f, 0x62,
0x74, 0x6e, 0x22, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73,
0x61, 0x76, 0x65, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2e, 0x73, 0x74,
0x79, 0x6c, 0x65, 0x2e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x45,
0x76, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x3d, 0x20, 0x22, 0x6e, 0x6f, 0x6e,
0x65, 0x22, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x61, 0x76,
0x65, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x79, 0x6c,
0x65, 0x2e, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x20, 0x3d, 0x20,
0x22, 0x30, 0x2e, 0x36, 0x22, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x2f, 0x2f, 0x20, 0x47, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x20, 0x64, 0x61, 0x74, 0x61, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
0x65, 0x74, 0x63, 0x68, 0x28, 0x22, 0x2f, 0x67, 0x65, 0x74, 0x5f, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x72, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x3d, 0x3e, 0x20, 0x72, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x6a, 0x73, 0x6f, 0x6e, 0x28, 0x29,
0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x74, 0x68,
0x65, 0x6e, 0x28, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3d, 0x3e, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f,
0x20, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20,
0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x64, 0x61, 0x74,
0x61, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63,
0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x64,
0x61, 0x74, 0x61, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79,
0x49, 0x64, 0x28, 0x22, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d,
0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64,
0x72, 0x65, 0x73, 0x73, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79,
0x49, 0x64, 0x28, 0x22, 0x6e, 0x65, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x22,
0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x61,
0x74, 0x61, 0x2e, 0x6e, 0x65, 0x74, 0x6d, 0x61, 0x73, 0x6b, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63,
0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x67, 0x61,
0x74, 0x65, 0x77, 0x61, 0x79, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x67, 0x61, 0x74,
0x65, 0x77, 0x61, 0x79, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79,
0x49, 0x64, 0x28, 0x22, 0x64, 0x6e, 0x73, 0x22, 0x29, 0x2e, 0x76, 0x61,
0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x64,
0x6e, 0x73, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65,
0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64,
0x28, 0x22, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x29,
0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74,
0x61, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63,
0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x62, 0x6d,
0x73, 0x5f, 0x69, 0x70, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65,
0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x62, 0x6d, 0x73, 0x5f,
0x69, 0x70, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65,
0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64,
0x28, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x29,
0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74,
0x61, 0x2e, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63,
0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x62, 0x6d,
0x73, 0x5f, 0x75, 0x73, 0x65, 0x61, 0x75, 0x74, 0x68, 0x22, 0x29, 0x2e,
0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x64, 0x61,
0x74, 0x61, 0x2e, 0x62, 0x6d, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x61, 0x75,
0x74, 0x68, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65,
0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64,
0x28, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d,
0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x62, 0x6d, 0x73, 0x5f, 0x75, 0x73,
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x62, 0x6d,
0x73, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63,
0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x62, 0x6d,
0x73, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x29,
0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74,
0x61, 0x2e, 0x62, 0x6d, 0x73, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65,
0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64,
0x28, 0x22, 0x77, 0x65, 0x62, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
0x6d, 0x65, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d,
0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x77, 0x65, 0x62, 0x5f, 0x75, 0x73,
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x77, 0x65, 0x62, 0x5f, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x77, 0x65,
0x62, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x61, 0x76,
0x65, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2e, 0x73, 0x74, 0x79, 0x6c,
0x65, 0x2e, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x45, 0x76, 0x65,
0x6e, 0x74, 0x73, 0x20, 0x3d, 0x20, 0x22, 0x61, 0x75, 0x74, 0x6f, 0x22,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73,
0x61, 0x76, 0x65, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2e, 0x73, 0x74,
0x79, 0x6c, 0x65, 0x2e, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x20,
0x3d, 0x20, 0x22, 0x31, 0x22, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x7d, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x2e, 0x63, 0x61, 0x74, 0x63, 0x68, 0x28, 0x65, 0x72, 0x72, 0x6f, 0x72,
0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65,
0x20, 0x61, 0x6e, 0x79, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e,
0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x28, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x7d, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x20, 0x0d,
0x0a, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x73, 0x65, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x28,
0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x76, 0x65,
0x6e, 0x74, 0x2e, 0x70, 0x72, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x44, 0x65,
0x66, 0x61, 0x75, 0x6c, 0x74, 0x28, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x2f, 0x2f, 0x20, 0x47, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65,
0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x6d,
0x44, 0x61, 0x74, 0x61, 0x20, 0x3d, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65,
0x73, 0x73, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42,
0x79, 0x49, 0x64, 0x28, 0x22, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72,
0x65, 0x73, 0x73, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x65, 0x74, 0x6d,
0x61, 0x73, 0x6b, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74,
0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x6e, 0x65, 0x74, 0x6d, 0x61, 0x73,
0x6b, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61,
0x79, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79,
0x49, 0x64, 0x28, 0x22, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x22,
0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x64, 0x6e, 0x73, 0x3a, 0x20, 0x64, 0x6f, 0x63,
0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x64, 0x6e,
0x73, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
0x6d, 0x65, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42,
0x79, 0x49, 0x64, 0x28, 0x22, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d,
0x65, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6d, 0x73, 0x5f, 0x69, 0x70,
0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67,
0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49,
0x64, 0x28, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x69, 0x70, 0x22, 0x29, 0x2e,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x3a, 0x20,
0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74,
0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28,
0x22, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x29, 0x2e,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x62, 0x6d, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x61, 0x75, 0x74,
0x68, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e,
0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79,
0x49, 0x64, 0x28, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x61,
0x75, 0x74, 0x68, 0x22, 0x29, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65,
0x64, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6d,
0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x20,
0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74,
0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28,
0x22, 0x62, 0x6d, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
0x65, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6d, 0x73, 0x5f, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x62, 0x6d, 0x73,
0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x29, 0x2e,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x62, 0x6d, 0x73, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42,
0x79, 0x49, 0x64, 0x28, 0x22, 0x62, 0x6d, 0x73, 0x5f, 0x65, 0x6e, 0x64,
0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75,
0x65, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x65,
0x62, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x3a, 0x20,
0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74,
0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28,
0x22, 0x77, 0x65, 0x62, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,
0x65, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x65, 0x62, 0x5f, 0x70, 0x61,
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x3a, 0x20, 0x64, 0x6f, 0x63, 0x75,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x77, 0x65, 0x62,
0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x29, 0x2e,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20,
0x53, 0x65, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x50, 0x4f, 0x53,
0x54, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x28, 0x22, 0x2f, 0x73,
0x61, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x2c,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x3a, 0x20, 0x22, 0x50, 0x4f, 0x53, 0x54, 0x22,
0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x61,
0x64, 0x65, 0x72, 0x73, 0x3a, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
0x74, 0x2d, 0x54, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x61, 0x70,
0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73,
0x6f, 0x6e, 0x22, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x64,
0x79, 0x3a, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x66, 0x6f, 0x72, 0x6d, 0x44, 0x61,
0x74, 0x61, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x74, 0x68, 0x65, 0x6e,
0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20, 0x3d, 0x3e,
0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x6a, 0x73,
0x6f, 0x6e, 0x28, 0x29, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x64, 0x61, 0x74, 0x61, 0x20,
0x3d, 0x3e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x2f, 0x2f, 0x20, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x20,
0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x20, 0x64, 0x61, 0x74, 0x61, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x6c,
0x6f, 0x67, 0x28, 0x64, 0x61, 0x74, 0x61, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2e, 0x63, 0x61, 0x74, 0x63, 0x68, 0x28, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x48, 0x61, 0x6e,
0x64, 0x6c, 0x65, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x73, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x28, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x2f, 0x2f, 0x20, 0x53, 0x68, 0x6f, 0x77, 0x20, 0x74, 0x68,
0x65, 0x20, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x70,
0x69, 0x6e, 0x6e, 0x65, 0x72, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64,
0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45,
0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x27,
0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x70, 0x69, 0x6e, 0x6e,
0x65, 0x72, 0x27, 0x29, 0x2e, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x2e, 0x64,
0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x27, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x27, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2f, 0x2f, 0x20, 0x53, 0x74, 0x61, 0x72, 0x74, 0x20, 0x74, 0x68,
0x65, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63,
0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65,
0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x27, 0x63, 0x6f,
0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x27, 0x29, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x74, 0x69, 0x6d, 0x65,
0x4c, 0x65, 0x66, 0x74, 0x20, 0x3d, 0x20, 0x31, 0x35, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x63, 0x6f, 0x75, 0x6e,
0x74, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x20, 0x3d,
0x20, 0x73, 0x65, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x29,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69,
0x6d, 0x65, 0x4c, 0x65, 0x66, 0x74, 0x2d, 0x2d, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f,
0x77, 0x6e, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x65,
0x6e, 0x74, 0x20, 0x3d, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x4c, 0x65, 0x66,
0x74, 0x20, 0x2b, 0x20, 0x22, 0x73, 0x22, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x74, 0x69, 0x6d, 0x65,
0x4c, 0x65, 0x66, 0x74, 0x20, 0x3c, 0x3d, 0x20, 0x30, 0x29, 0x20, 0x63,
0x6c, 0x65, 0x61, 0x72, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c,
0x28, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x54, 0x69,
0x6d, 0x65, 0x72, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x2c, 0x20, 0x31, 0x30, 0x30, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x57, 0x61, 0x69, 0x74, 0x20,
0x66, 0x6f, 0x72, 0x20, 0x31, 0x35, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e,
0x64, 0x73, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x65, 0x74, 0x54,
0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74,
0x69, 0x6f, 0x6e, 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x52, 0x65, 0x64, 0x69, 0x72,
0x65, 0x63, 0x74, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20,
0x68, 0x6f, 0x6d, 0x65, 0x70, 0x61, 0x67, 0x65, 0x20, 0x62, 0x61, 0x73,
0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x46, 0x6f, 0x72, 0x6d, 0x27, 0x73,
0x20, 0x49, 0x50, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77,
0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x2e, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x28, 0x22,
0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x22, 0x20, 0x2b, 0x20, 0x66,
0x6f, 0x72, 0x6d, 0x44, 0x61, 0x74, 0x61, 0x2e, 0x69, 0x70, 0x5f, 0x61,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x7d, 0x2c, 0x20, 0x31, 0x35, 0x30, 0x30, 0x30, 0x29, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x3c, 0x2f, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x3e, 0x0d, 0x0a, 0x3c, 0x73, 0x74, 0x79, 0x6c, 0x65,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69,
0x67, 0x5f, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65,
0x3a, 0x20, 0x31, 0x32, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20,
0x62, 0x6f, 0x6c, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c,
0x65, 0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x6c,
0x69, 0x67, 0x6e, 0x2d, 0x73, 0x65, 0x6c, 0x66, 0x3a, 0x20, 0x6c, 0x65,
0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72,
0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67,
0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x68, 0x72, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c,
0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72,
0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x2e, 0x35,
0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72,
0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x30, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3a,
0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74,
0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x79, 0x6c,
0x65, 0x3a, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x74, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69,
0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x33,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x20, 0x20, 0x23, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x34, 0x70,
0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64,
0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72,
0x67, 0x69, 0x6e, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x20, 0x61, 0x75,
0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e,
0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x35, 0x70, 0x78,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a,
0x20, 0x23, 0x66, 0x31, 0x66, 0x31, 0x66, 0x31, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69,
0x6e, 0x67, 0x3a, 0x20, 0x30, 0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x62, 0x6f,
0x64, 0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61,
0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x69, 0x6d, 0x61,
0x67, 0x65, 0x3a, 0x20, 0x75, 0x72, 0x6c, 0x28, 0x22, 0x68, 0x74, 0x74,
0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x73, 0x2e, 0x73, 0x69, 0x77, 0x61,
0x74, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x61, 0x72, 0x6f, 0x6e, 0x61, 0x5f, 0x62, 0x67, 0x2e, 0x70, 0x6e, 0x67,
0x22, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63,
0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65,
0x3a, 0x20, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c,
0x79, 0x3a, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69,
0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74,
0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x34, 0x70, 0x78, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x23, 0x37, 0x37, 0x37, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x23, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63,
0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x43, 0x43, 0x43, 0x43, 0x43,
0x43, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x23, 0x35, 0x45, 0x35, 0x45, 0x35, 0x45, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f,
0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f,
0x6c, 0x69, 0x64, 0x20, 0x23, 0x64, 0x64, 0x64, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67,
0x68, 0x74, 0x3a, 0x20, 0x34, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67,
0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x20, 0x20, 0x23, 0x62, 0x61, 0x72, 0x2c, 0x0d, 0x0a, 0x20,
0x20, 0x23, 0x70, 0x72, 0x67, 0x62, 0x61, 0x72, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x44,
0x39, 0x44, 0x39, 0x44, 0x39, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75,
0x73, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x23, 0x62, 0x61, 0x72, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x23, 0x32, 0x39, 0x43, 0x44, 0x31, 0x46, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x25,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68,
0x74, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x61, 0x28,
0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35,
0x35, 0x2c, 0x20, 0x30, 0x2e, 0x39, 0x35, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68,
0x3a, 0x20, 0x32, 0x35, 0x38, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x37, 0x35,
0x70, 0x78, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x33,
0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f,
0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a,
0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20,
0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x62, 0x74, 0x6e, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x23, 0x43, 0x41, 0x33, 0x44, 0x33,
0x44, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x23, 0x34, 0x31, 0x37, 0x64, 0x66,
0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x3a, 0x20, 0x34, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69,
0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x31, 0x30,
0x70, 0x78, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a,
0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x63, 0x6f,
0x6e, 0x66, 0x5f, 0x74, 0x78, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64,
0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x65, 0x32, 0x65,
0x32, 0x65, 0x32, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d,
0x0a, 0x20, 0x20, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x73, 0x69, 0x7a, 0x65,
0x3a, 0x20, 0x34, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69,
0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e,
0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c,
0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x72, 0x65, 0x6c, 0x61,
0x74, 0x69, 0x76, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70,
0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3a,
0x20, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x31, 0x32, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x77, 0x65,
0x62, 0x6b, 0x69, 0x74, 0x2d, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x73, 0x65,
0x6c, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x6d, 0x6f, 0x7a, 0x2d, 0x75, 0x73,
0x65, 0x72, 0x2d, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x3a, 0x20, 0x6e,
0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x6d,
0x73, 0x2d, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x73, 0x65, 0x6c, 0x65, 0x63,
0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x75, 0x73, 0x65, 0x72, 0x2d, 0x73, 0x65, 0x6c, 0x65, 0x63,
0x74, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74,
0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74,
0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74,
0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x61, 0x63,
0x69, 0x74, 0x79, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68,
0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e,
0x63, 0x68, 0x65, 0x63, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x20, 0x7b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
0x6e, 0x3a, 0x20, 0x61, 0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3a,
0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x3a, 0x20, 0x32, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x32,
0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61,
0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c,
0x6f, 0x72, 0x3a, 0x20, 0x23, 0x65, 0x65, 0x65, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x63, 0x6f, 0x6e,
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72,
0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x7e, 0x2e, 0x63, 0x68, 0x65, 0x63,
0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x63, 0x63, 0x63, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e,
0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x3a, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x7e,
0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x23, 0x32, 0x31, 0x39, 0x36, 0x46, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x63, 0x68, 0x65, 0x63,
0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x3a, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65,
0x6e, 0x74, 0x3a, 0x20, 0x22, 0x22, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61,
0x62, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x6e,
0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d,
0x0a, 0x20, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
0x72, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x3a, 0x63, 0x68, 0x65, 0x63,
0x6b, 0x65, 0x64, 0x7e, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6d, 0x61,
0x72, 0x6b, 0x3a, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61,
0x69, 0x6e, 0x65, 0x72, 0x20, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6d,
0x61, 0x72, 0x6b, 0x3a, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x7b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x20, 0x39,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x70,
0x3a, 0x20, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x35, 0x70, 0x78, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a,
0x20, 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x73, 0x6f, 0x6c, 0x69,
0x64, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x64,
0x74, 0x68, 0x3a, 0x20, 0x30, 0x20, 0x33, 0x70, 0x78, 0x20, 0x33, 0x70,
0x78, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x77,
0x65, 0x62, 0x6b, 0x69, 0x74, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66,
0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x28,
0x34, 0x35, 0x64, 0x65, 0x67, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2d, 0x6d, 0x73, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f,
0x72, 0x6d, 0x3a, 0x20, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x28, 0x34,
0x35, 0x64, 0x65, 0x67, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x72,
0x6f, 0x74, 0x61, 0x74, 0x65, 0x28, 0x34, 0x35, 0x64, 0x65, 0x67, 0x29,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20,
0x2e, 0x73, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
0x3a, 0x20, 0x66, 0x69, 0x78, 0x65, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x34, 0x30, 0x25, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x20, 0x34,
0x34, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x6c, 0x61, 0x74, 0x65, 0x28, 0x2d, 0x35, 0x30, 0x25, 0x2c, 0x20,
0x2d, 0x35, 0x30, 0x25, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x70, 0x78,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68,
0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31,
0x36, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x23, 0x66,
0x33, 0x66, 0x33, 0x66, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20,
0x31, 0x36, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x23,
0x33, 0x34, 0x39, 0x38, 0x64, 0x62, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69,
0x75, 0x73, 0x3a, 0x20, 0x35, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x61, 0x6e, 0x69, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a,
0x20, 0x73, 0x70, 0x69, 0x6e, 0x20, 0x32, 0x73, 0x20, 0x6c, 0x69, 0x6e,
0x65, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x65,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20,
0x40, 0x6b, 0x65, 0x79, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x73, 0x20, 0x73,
0x70, 0x69, 0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x30,
0x25, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74,
0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x72, 0x6f,
0x74, 0x61, 0x74, 0x65, 0x28, 0x30, 0x64, 0x65, 0x67, 0x29, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x31, 0x30, 0x30, 0x25, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x6f, 0x72,
0x6d, 0x3a, 0x20, 0x72, 0x6f, 0x74, 0x61, 0x74, 0x65, 0x28, 0x33, 0x36,
0x30, 0x64, 0x65, 0x67, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20,
0x23, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x78, 0x74,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a,
0x20, 0x34, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20,
0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66,
0x69, 0x78, 0x65, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74,
0x6f, 0x70, 0x3a, 0x20, 0x36, 0x35, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x20, 0x35, 0x30, 0x25, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66,
0x6f, 0x72, 0x6d, 0x3a, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61,
0x74, 0x65, 0x28, 0x2d, 0x35, 0x30, 0x25, 0x2c, 0x20, 0x2d, 0x35, 0x30,
0x25, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x20, 0x20, 0x2e, 0x73, 0x70, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x2d, 0x63,
0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x66, 0x6c, 0x65, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6a, 0x75, 0x73, 0x74, 0x69, 0x66, 0x79, 0x2d, 0x63, 0x6f, 0x6e, 0x74,
0x65, 0x6e, 0x74, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x2d,
0x69, 0x74, 0x65, 0x6d, 0x73, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65,
0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x6f, 0x73, 0x69,
0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x66, 0x69, 0x78, 0x65, 0x64, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7a, 0x2d, 0x69, 0x6e, 0x64, 0x65,
0x78, 0x3a, 0x20, 0x39, 0x39, 0x39, 0x39, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20,
0x31, 0x30, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68,
0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x61, 0x28, 0x30,
0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x30, 0x2e, 0x39, 0x29,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x73, 0x65,
0x6d, 0x69, 0x2d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65,
0x6e, 0x74, 0x20, 0x62, 0x6c, 0x61, 0x63, 0x6b, 0x20, 0x62, 0x61, 0x63,
0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x2a, 0x2f, 0x0d, 0x0a,
0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, 0x65,
0x3e
, 0x00};

View File

@ -1,426 +0,0 @@
const char display_html[] PROGMEM = {
0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x73, 0x72, 0x63, 0x3d,
0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x6a, 0x61,
0x78, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6a, 0x61, 0x78, 0x2f, 0x6c, 0x69,
0x62, 0x73, 0x2f, 0x6a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2f, 0x33, 0x2e,
0x32, 0x2e, 0x31, 0x2f, 0x6a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x6d,
0x69, 0x6e, 0x2e, 0x6a, 0x73, 0x22, 0x3e, 0x3c, 0x2f, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x3e, 0x0d, 0x0a, 0x3c, 0x66, 0x6f, 0x72, 0x6d, 0x20,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x3d, 0x22, 0x50, 0x4f, 0x53, 0x54,
0x22, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x23, 0x22,
0x20, 0x65, 0x6e, 0x63, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x6d, 0x75,
0x6c, 0x74, 0x69, 0x70, 0x61, 0x72, 0x74, 0x2f, 0x66, 0x6f, 0x72, 0x6d,
0x2d, 0x64, 0x61, 0x74, 0x61, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x75,
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x31, 0x3e, 0x45, 0x53,
0x50, 0x4d, 0x65, 0x67, 0x61, 0x20, 0x50, 0x52, 0x4f, 0x3c, 0x2f, 0x68,
0x31, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x33, 0x3e,
0x4c, 0x43, 0x44, 0x20, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x20,
0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x3c, 0x2f,
0x68, 0x33, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x72,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x68, 0x33, 0x3e, 0x55,
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x54, 0x46, 0x54, 0x20, 0x46, 0x69,
0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20,
0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x20,
0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22, 0x20,
0x6f, 0x6e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x22, 0x73, 0x75,
0x62, 0x28, 0x74, 0x68, 0x69, 0x73, 0x29, 0x22, 0x20, 0x73, 0x74, 0x79,
0x6c, 0x65, 0x3d, 0x22, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x22, 0x20, 0x2c, 0x20, 0x61, 0x63, 0x63,
0x65, 0x70, 0x74, 0x3d, 0x22, 0x2e, 0x74, 0x66, 0x74, 0x22, 0x20, 0x2f,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x6c, 0x61, 0x62, 0x65,
0x6c, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x22, 0x20, 0x66, 0x6f, 0x72, 0x3d, 0x22, 0x66,
0x69, 0x6c, 0x65, 0x22, 0x3e, 0x43, 0x68, 0x6f, 0x6f, 0x73, 0x65, 0x20,
0x66, 0x69, 0x6c, 0x65, 0x2e, 0x2e, 0x2e, 0x3c, 0x2f, 0x6c, 0x61, 0x62,
0x65, 0x6c, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x62, 0x75,
0x74, 0x74, 0x6f, 0x6e, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d,
0x22, 0x62, 0x74, 0x6e, 0x22, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d,
0x22, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x6f, 0x6e, 0x63,
0x6c, 0x69, 0x63, 0x6b, 0x3d, 0x22, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64,
0x46, 0x69, 0x72, 0x6d, 0x77, 0x61, 0x72, 0x65, 0x28, 0x29, 0x22, 0x20,
0x2f, 0x3e, 0x3c, 0x62, 0x72, 0x20, 0x2f, 0x3e, 0x3c, 0x62, 0x72, 0x20,
0x2f, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x64, 0x69, 0x76,
0x20, 0x69, 0x64, 0x3d, 0x22, 0x70, 0x72, 0x67, 0x22, 0x3e, 0x3c, 0x2f,
0x64, 0x69, 0x76, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x62,
0x72, 0x20, 0x2f, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x64,
0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x70, 0x72, 0x67, 0x62, 0x61,
0x72, 0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x62, 0x61,
0x72, 0x22, 0x3e, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x3c, 0x62, 0x72, 0x20, 0x2f, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x3c, 0x62, 0x3e, 0x53, 0x49, 0x57, 0x41, 0x54, 0x20,
0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x20, 0x32, 0x30, 0x32, 0x33, 0x3c,
0x2f, 0x62, 0x3e, 0x0d, 0x0a, 0x3c, 0x2f, 0x66, 0x6f, 0x72, 0x6d, 0x3e,
0x0d, 0x0a, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x20, 0x73, 0x75, 0x62, 0x28, 0x6f, 0x62, 0x6a, 0x29, 0x20, 0x7b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72,
0x20, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20,
0x6f, 0x62, 0x6a, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x73, 0x70,
0x6c, 0x69, 0x74, 0x28, 0x22, 0x5c, 0x5c, 0x22, 0x29, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x66, 0x69, 0x6c,
0x65, 0x2d, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x22, 0x29, 0x2e, 0x69, 0x6e,
0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22,
0x20, 0x20, 0x20, 0x22, 0x20, 0x2b, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x4e,
0x61, 0x6d, 0x65, 0x5b, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65,
0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x20, 0x2d, 0x20, 0x31, 0x5d,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x20, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x46, 0x69, 0x72, 0x6d, 0x77,
0x61, 0x72, 0x65, 0x28, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x66, 0x69, 0x6c,
0x65, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42,
0x79, 0x49, 0x64, 0x28, 0x27, 0x66, 0x69, 0x6c, 0x65, 0x27, 0x29, 0x2e,
0x66, 0x69, 0x6c, 0x65, 0x73, 0x5b, 0x30, 0x5d, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x72,
0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x20,
0x46, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x28, 0x29,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76,
0x61, 0x72, 0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x77, 0x69,
0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
0x6e, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x6e, 0x61, 0x6d, 0x65, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20,
0x50, 0x61, 0x74, 0x68, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x20, 0x61, 0x74, 0x20,
0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20,
0x2f, 0x20, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x65, 0x67,
0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x2c, 0x20, 0x72, 0x65, 0x6d, 0x6f,
0x76, 0x65, 0x20, 0x69, 0x74, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x70, 0x61,
0x74, 0x68, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
0x28, 0x30, 0x2c, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x6c, 0x65, 0x6e,
0x67, 0x74, 0x68, 0x20, 0x2d, 0x20, 0x31, 0x30, 0x29, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x61, 0x64,
0x65, 0x72, 0x2e, 0x6f, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x3d, 0x20,
0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x29,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x64, 0x61, 0x74, 0x61,
0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x55, 0x69, 0x6e, 0x74, 0x38,
0x41, 0x72, 0x72, 0x61, 0x79, 0x28, 0x65, 0x2e, 0x74, 0x61, 0x72, 0x67,
0x65, 0x74, 0x2e, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x29, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x76, 0x61, 0x72, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20,
0x64, 0x61, 0x74, 0x61, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x53,
0x69, 0x7a, 0x65, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x36, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x76, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x3d, 0x20,
0x30, 0x3b, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x24, 0x2e, 0x61, 0x6a, 0x61, 0x78,
0x28, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x72, 0x6c, 0x3a,
0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x2b, 0x20, 0x27, 0x6f, 0x74, 0x61,
0x2f, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x27, 0x2c, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x27, 0x50, 0x4f, 0x53,
0x54, 0x27, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x61, 0x74,
0x61, 0x3a, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x2e, 0x73, 0x74, 0x72, 0x69,
0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x7b, 0x20, 0x73, 0x69, 0x7a, 0x65,
0x3a, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x7d, 0x29, 0x2c, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54,
0x79, 0x70, 0x65, 0x3a, 0x20, 0x27, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x2c,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73,
0x73, 0x3a, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x28, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x2f, 0x2f, 0x20, 0x53, 0x65, 0x6e, 0x64, 0x20, 0x74, 0x68,
0x65, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x6e, 0x20, 0x63, 0x68,
0x75, 0x6e, 0x6b, 0x73, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73,
0x65, 0x6e, 0x64, 0x4e, 0x65, 0x78, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b,
0x28, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x20, 0x3c, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x29, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x63,
0x68, 0x75, 0x6e, 0x6b, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e,
0x73, 0x75, 0x62, 0x61, 0x72, 0x72, 0x61, 0x79, 0x28, 0x69, 0x6e, 0x64,
0x65, 0x78, 0x2c, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x2b, 0x20,
0x63, 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x69, 0x7a, 0x65, 0x29, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x63, 0x68, 0x75,
0x6e, 0x6b, 0x41, 0x72, 0x72, 0x61, 0x79, 0x20, 0x3d, 0x20, 0x41, 0x72,
0x72, 0x61, 0x79, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x28, 0x63, 0x68, 0x75,
0x6e, 0x6b, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x24, 0x2e,
0x61, 0x6a, 0x61, 0x78, 0x28, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x75, 0x72, 0x6c, 0x3a, 0x20, 0x70, 0x61, 0x74,
0x68, 0x20, 0x2b, 0x20, 0x27, 0x6f, 0x74, 0x61, 0x2f, 0x77, 0x72, 0x69,
0x74, 0x65, 0x27, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x27, 0x50, 0x4f, 0x53,
0x54, 0x27, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x2e,
0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x69, 0x66, 0x79, 0x28, 0x7b, 0x20,
0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e,
0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x2c, 0x20, 0x64, 0x61, 0x74, 0x61,
0x3a, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x41, 0x72, 0x72, 0x61, 0x79,
0x20, 0x7d, 0x29, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70,
0x65, 0x3a, 0x20, 0x27, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x27, 0x2c, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x63, 0x63,
0x65, 0x73, 0x73, 0x3a, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x64, 0x65,
0x78, 0x20, 0x2b, 0x3d, 0x20, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x69,
0x7a, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x55, 0x70, 0x64, 0x61,
0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72,
0x65, 0x73, 0x73, 0x20, 0x62, 0x61, 0x72, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72,
0x20, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x4d,
0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x69, 0x6e,
0x64, 0x65, 0x78, 0x20, 0x2f, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x2a,
0x20, 0x31, 0x30, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x24, 0x28, 0x27, 0x23,
0x62, 0x61, 0x72, 0x27, 0x29, 0x2e, 0x63, 0x73, 0x73, 0x28, 0x27, 0x77,
0x69, 0x64, 0x74, 0x68, 0x27, 0x2c, 0x20, 0x70, 0x65, 0x72, 0x63, 0x65,
0x6e, 0x74, 0x20, 0x2b, 0x20, 0x27, 0x25, 0x27, 0x29, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x24, 0x28, 0x27, 0x23, 0x70, 0x72, 0x67, 0x27, 0x29, 0x2e, 0x68, 0x74,
0x6d, 0x6c, 0x28, 0x22, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e,
0x67, 0x3a, 0x20, 0x22, 0x20, 0x2b, 0x20, 0x70, 0x65, 0x72, 0x63, 0x65,
0x6e, 0x74, 0x20, 0x2b, 0x20, 0x22, 0x25, 0x22, 0x20, 0x2b, 0x27, 0x20,
0x28, 0x27, 0x20, 0x2b, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x2b,
0x20, 0x27, 0x2f, 0x27, 0x20, 0x2b, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20,
0x2b, 0x20, 0x27, 0x29, 0x27, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x65, 0x6e,
0x64, 0x4e, 0x65, 0x78, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x28, 0x29,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x2f, 0x2f, 0x20, 0x45, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20,
0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x24, 0x2e, 0x61, 0x6a, 0x61, 0x78, 0x28, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x72, 0x6c, 0x3a, 0x20, 0x70,
0x61, 0x74, 0x68, 0x20, 0x2b, 0x20, 0x27, 0x6f, 0x74, 0x61, 0x2f, 0x65,
0x6e, 0x64, 0x27, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x27, 0x50, 0x4f, 0x53,
0x54, 0x27, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73,
0x65, 0x6e, 0x64, 0x4e, 0x65, 0x78, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b,
0x28, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x72, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x41,
0x73, 0x41, 0x72, 0x72, 0x61, 0x79, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72,
0x28, 0x66, 0x69, 0x6c, 0x65, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x7d, 0x0d, 0x0a, 0x3c, 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x3e, 0x0d, 0x0a, 0x0d, 0x0a, 0x3c, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x72, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70,
0x6c, 0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c,
0x6f, 0x72, 0x3a, 0x20, 0x23, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61,
0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c,
0x6f, 0x72, 0x3a, 0x20, 0x23, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x2e,
0x35, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74,
0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72,
0x67, 0x69, 0x6e, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3a, 0x20, 0x61, 0x75,
0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68,
0x74, 0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72,
0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x6e, 0x73, 0x65,
0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68,
0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20,
0x33, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x23, 0x66, 0x69, 0x6c, 0x65,
0x2d, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a,
0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20,
0x34, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61,
0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67,
0x69, 0x6e, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x20, 0x61, 0x75, 0x74,
0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31,
0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20,
0x23, 0x66, 0x31, 0x66, 0x31, 0x66, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72,
0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30,
0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x64,
0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d,
0x69, 0x6d, 0x61, 0x67, 0x65, 0x3a, 0x20, 0x75, 0x72, 0x6c, 0x28, 0x22,
0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x73, 0x2e, 0x73,
0x69, 0x77, 0x61, 0x74, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x6f, 0x6e, 0x61, 0x5f, 0x62, 0x67, 0x2e,
0x70, 0x6e, 0x67, 0x22, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x63, 0x6f, 0x76,
0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79,
0x3a, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66,
0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x34,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x37, 0x37, 0x37,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x23, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e,
0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x43, 0x43,
0x43, 0x43, 0x43, 0x43, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x35,
0x45, 0x35, 0x45, 0x35, 0x45, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a,
0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78,
0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x23, 0x64, 0x64, 0x64, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69,
0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34,
0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e,
0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c,
0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73,
0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x23, 0x62, 0x61, 0x72, 0x2c, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x23, 0x70, 0x72, 0x67, 0x62, 0x61, 0x72, 0x20, 0x7b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63,
0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x23, 0x44, 0x39, 0x44, 0x39, 0x44, 0x39, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72,
0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20,
0x31, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x23, 0x62, 0x61, 0x72,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63,
0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x32, 0x39, 0x43, 0x44, 0x31,
0x46, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x25, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67,
0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x66, 0x6f, 0x72, 0x6d, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
0x6e, 0x64, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35,
0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20,
0x30, 0x2e, 0x39, 0x35, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74,
0x68, 0x3a, 0x20, 0x32, 0x35, 0x38, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69,
0x6e, 0x3a, 0x20, 0x37, 0x35, 0x70, 0x78, 0x20, 0x61, 0x75, 0x74, 0x6f,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70,
0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x33, 0x30, 0x70, 0x78,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62,
0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73,
0x3a, 0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c,
0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x2e, 0x62, 0x74, 0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x23, 0x43, 0x41, 0x33, 0x44,
0x33, 0x44, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63,
0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62,
0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x23,
0x34, 0x31, 0x37, 0x64, 0x66, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70,
0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20,
0x31, 0x30, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34,
0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64,
0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69,
0x6e, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x20, 0x61, 0x75, 0x74, 0x6f,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66,
0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x35,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x3c, 0x2f, 0x73, 0x74,
0x79, 0x6c, 0x65, 0x3e
, 0x00};

View File

@ -1,339 +0,0 @@
<div id="loadingSpinner" class="spinner-container" style="display: none;">
<div class="spinner"></div>
<p id="loadingText">Saving Configuration Please wait (<span id="countdown">15s</span>)</p>
</div>
</div>
<form enctype="multipart/form-data" id="config_form" onsubmit="send_config(event)">
<h1>ESPMega PRO</h1>
<h3>Device Configurations</h3>
<p class="config_title">IP Address</p>
<input type="text" id="ip_address" name="ip_address" class="conf_txt" value="Loading ..."><br>
<p class="config_title">Network Mask</p>
<input type="text" id="netmask" name="netmask" class="conf_txt" value="Loading ..."><br>
<p class="config_title">Gateway</p>
<input type="text" id="gateway" name="gateway" class="conf_txt" value="Loading ..."><br>
<p class="config_title">DNS Server</p>
<input type="text" id="dns" name="dns" class="conf_txt" value="Loading ..."><br>
<p class="config_title">Hostname</p>
<input type="text" id="hostname" name="hostname" class="conf_txt" value="Loading ..."><br>
<p class="config_title">BMS Server - IP Address</p>
<input type="text" id="bms_ip" name="bms_ip" class="conf_txt" value="Loading ..."><br>
<p class="config_title">BMS Server - Port</p>
<input type="text" id="bms_port" name="bms_port" class="conf_txt" value="Loading ..."><br>
<label class="container">Authentication
<input type="checkbox" name="bms_useauth" id="bms_useauth" value="yes">
<span class="checkmark"></span>
</label>
<p class="config_title">BMS Server - Username</p>
<input type="text" id="bms_username" name="bms_username" class="conf_txt" value="Loading ..."><br>
<p class="config_title">BMS Server - Password</p>
<input type="password" id="bms_password" name="bms_password" class="conf_txt" value="Loading ..."><br>
<p class="config_title">BMS Server - Endpoint</p>
<input type="text" id="bms_endpoint" name="bms_endpoint" class="conf_txt" value="Loading ..."><br>
<p class="config_title">Web Interface - Username</p>
<input type="text" id="web_username" name="web_username" class="conf_txt" value="Loading ..."><br>
<p class="config_title">Web Interface - Password</p>
<input type="password" id="web_password" name="web_password" class="conf_txt" value="Loading ..."><br>
<input id="save_btn" type="submit" class="btn" value="Save">
<button type="button" class="conf" onclick="window.location.href='/'">Back</button><br /><br />
<b>SIWAT SYSTEM 2023</b>
</form>
<script>
window.onload = function () {
var saveButton = document.getElementById("save_btn");
saveButton.style.pointerEvents = "none";
saveButton.style.opacity = "0.6";
// Get the configuration data
fetch("/get_config")
.then(response => response.json())
.then(data => {
// Handle the response data
console.log(data);
document.getElementById("ip_address").value = data.ip_address;
document.getElementById("netmask").value = data.netmask;
document.getElementById("gateway").value = data.gateway;
document.getElementById("dns").value = data.dns;
document.getElementById("hostname").value = data.hostname;
document.getElementById("bms_ip").value = data.bms_ip;
document.getElementById("bms_port").value = data.bms_port;
document.getElementById("bms_useauth").checked = data.bms_useauth;
document.getElementById("bms_username").value = data.bms_username;
document.getElementById("bms_password").value = data.bms_password;
document.getElementById("bms_endpoint").value = data.bms_endpoint;
document.getElementById("web_username").value = data.web_username;
document.getElementById("web_password").value = data.web_password;
saveButton.style.pointerEvents = "auto";
saveButton.style.opacity = "1";
})
.catch(error => {
// Handle any errors
console.error(error);
});
}
function send_config() {
event.preventDefault();
// Get the form data
var formData = {
ip_address: document.getElementById("ip_address").value,
netmask: document.getElementById("netmask").value,
gateway: document.getElementById("gateway").value,
dns: document.getElementById("dns").value,
hostname: document.getElementById("hostname").value,
bms_ip: document.getElementById("bms_ip").value,
bms_port: document.getElementById("bms_port").value,
bms_useauth: document.getElementById("bms_useauth").checked,
bms_username: document.getElementById("bms_username").value,
bms_password: document.getElementById("bms_password").value,
bms_endpoint: document.getElementById("bms_endpoint").value,
web_username: document.getElementById("web_username").value,
web_password: document.getElementById("web_password").value
};
// Send the POST request
fetch("/save_config", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(formData)
})
.then(response => response.json())
.then(data => {
// Handle the response data
console.log(data);
})
.catch(error => {
// Handle any errors
console.error(error);
});
// Show the loading spinner
document.getElementById('loadingSpinner').style.display = 'block';
// Start the countdown
var countdown = document.getElementById('countdown');
var timeLeft = 15;
var countdownTimer = setInterval(function () {
timeLeft--;
countdown.textContent = timeLeft + "s";
if (timeLeft <= 0) clearInterval(countdownTimer);
}, 1000);
// Wait for 15 seconds
setTimeout(function () {
// Redirect user to homepage based on Form's IP
window.location.replace("http://" + formData.ip_address);
}, 15000);
}
</script>
<style>
p.config_title {
font-size: 12;
font-weight: bold;
text-align: left;
align-self: left;
margin-bottom: 0;
margin-top: 0;
}
hr {
display: block;
color: #aaaaaa;
background-color: #aaaaaa;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: auto;
margin-right: auto;
border-style: inset;
border-width: 0px;
height: 3px;
}
#file-input,
input {
width: 100%;
height: 44px;
border-radius: 4px;
margin: 10px auto;
font-size: 15px;
}
input {
background: #f1f1f1;
border: 0;
padding: 0 15px;
}
body {
background-image: url("https://fs.siwatsystem.com/arona_bg.png");
background-size: cover;
font-family: sans-serif;
font-size: 14px;
color: #777;
}
#file-input {
background-color: #CCCCCC;
color: #5E5E5E;
padding: 0;
border: 1px solid #ddd;
line-height: 44px;
text-align: center;
display: block;
cursor: pointer;
}
#bar,
#prgbar {
background-color: #D9D9D9;
border-radius: 10px;
}
#bar {
background-color: #29CD1F;
width: 0%;
height: 10px;
}
form {
background: rgba(255, 255, 255, 0.95);
max-width: 258px;
margin: 75px auto;
padding: 30px;
border-radius: 15px;
text-align: center;
}
.btn {
background: #CA3D3D;
color: #fff;
cursor: pointer;
}
.conf {
background: #417df3;
color: #fff;
cursor: pointer;
width: 100%;
height: 44px;
border-radius: 4px;
margin: 10px auto;
font-size: 15px;
border: 0;
}
.conf_txt {
background-color: #e2e2e2;
}
.checkbox {
size: 4;
display: inline-block;
}
.container {
display: block;
position: relative;
padding-left: 0px;
margin-bottom: 12px;
cursor: pointer;
font-size: 20px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 25px;
width: 25px;
background-color: #eee;
}
.container:hover input~.checkmark {
background-color: #ccc;
}
.container input:checked~.checkmark {
background-color: #2196F3;
}
.checkmark:after {
content: "";
position: absolute;
display: none;
}
.container input:checked~.checkmark:after {
display: block;
}
.container .checkmark:after {
left: 9px;
top: 5px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.spinner {
position: fixed;
top: 40%;
left: 44%;
transform: translate(-50%, -50%);
width: 100px;
height: 100px;
border: 16px solid #f3f3f3;
border-top: 16px solid #3498db;
border-radius: 50%;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
#loadingText {
color: #fff;
font-size: 40px;
text-align: center;
position: fixed;
top: 65%;
left: 50%;
transform: translate(-50%, -50%);
}
.spinner-container {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
/* semi-transparent black background */
}
</style>

View File

@ -1,164 +0,0 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<form method="POST" action="#" enctype="multipart/form-data" id="upload_form">
<h1>ESPMega PRO</h1>
<h3>LCD Display Management</h3>
<hr>
<h3>Upload TFT Firmware</h3>
<input type="file" name="update" id="file" onchange="sub(this)" style="display: none" , accept=".tft" />
<label id="file-input" for="file">Choose file...</label>
<input type="button" class="btn" value="Upload" onclick="uploadFirmware()" /><br /><br />
<div id="prg"></div>
<br />
<div id="prgbar">
<div id="bar"></div>
</div>
<br />
<b>SIWAT SYSTEM 2023</b>
</form>
<script>
function sub(obj) {
var fileName = obj.value.split("\\");
document.getElementById("file-input").innerHTML =
" " + fileName[fileName.length - 1];
}
function uploadFirmware() {
var file = document.getElementById('file').files[0];
var reader = new FileReader();
var path = window.location.pathname;
// Path have index.html at the end and / at the beginning, remove it
path = path.substring(0, path.length - 10);
reader.onload = function (e) {
var data = new Uint8Array(e.target.result);
var size = data.length;
var chunkSize = 256;
var index = 0;
$.ajax({
url: path + 'ota/begin',
type: 'POST',
data: JSON.stringify({ size: size }),
contentType: 'application/json',
success: function () {
// Send the data in chunks
function sendNextChunk() {
if (index < size) {
var chunk = data.subarray(index, index + chunkSize);
var chunkArray = Array.from(chunk);
$.ajax({
url: path + 'ota/write',
type: 'POST',
data: JSON.stringify({ size: chunk.length, data: chunkArray }),
contentType: 'application/json',
success: function () {
index += chunkSize;
// Update the progress bar
var percent = Math.floor(index / size * 100);
$('#bar').css('width', percent + '%');
$('#prg').html("Uploading: " + percent + "%" +' (' + index + '/' + size + ')');
sendNextChunk();
}
});
} else {
// End the update
$.ajax({
url: path + 'ota/end',
type: 'POST'
});
}
}
sendNextChunk();
}
})
};
reader.readAsArrayBuffer(file);
}
</script>
<style>
hr {
display: block;
color: #aaaaaa;
background-color: #aaaaaa;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: auto;
margin-right: auto;
border-style: inset;
border-width: 0px;
height: 3px;
}
#file-input,
input {
width: 100%;
height: 44px;
border-radius: 4px;
margin: 10px auto;
font-size: 15px;
}
input {
background: #f1f1f1;
border: 0;
padding: 0 15px;
}
body {
background-image: url("https://fs.siwatsystem.com/arona_bg.png");
background-size: cover;
font-family: sans-serif;
font-size: 14px;
color: #777;
}
#file-input {
background-color: #CCCCCC;
color: #5E5E5E;
padding: 0;
border: 1px solid #ddd;
line-height: 44px;
text-align: center;
display: block;
cursor: pointer;
}
#bar,
#prgbar {
background-color: #D9D9D9;
border-radius: 10px;
}
#bar {
background-color: #29CD1F;
width: 0%;
height: 10px;
}
form {
background: rgba(255, 255, 255, 0.95);
max-width: 258px;
margin: 75px auto;
padding: 30px;
border-radius: 15px;
text-align: center;
}
.btn {
background: #CA3D3D;
color: #fff;
cursor: pointer;
}
.conf {
background: #417df3;
color: #fff;
cursor: pointer;
width: 100%;
height: 44px;
border-radius: 4px;
margin: 10px auto;
font-size: 15px;
border: 0;
}
</style>

View File

@ -1,227 +0,0 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<form method="POST" action="#" enctype="multipart/form-data" id="upload_form">
<h1>ESPMega PRO</h1>
<h3>Device Information</h3>
<p style="text-align: left">
Hostname
<span style="float: right" id="hostname">Loading ...</span>
</p>
<p style="text-align: left">
IP Address
<span style="float: right" id="ip_address">Loading ...</span>
</p>
<p style="text-align: left">
MAC Address
<span style="float: right" id="mac_address">Loading ...</span>
</p>
<p style="text-align: left">
Uptime
<span style="float: right" id="uptime">Loading ...</span>
</p>
<p style="text-align: left">
Model
<span style="float: right" id="model">Loading ...</span>
</p>
<p style="text-align: left">
FW Version
<span style="float: right" id="fw_version">Loading ...</span>
</p>
<p style="text-align: left">
SDK Version
<span style="float: right" id="sdk_version">Loading ...</span>
</p>
<p style="text-align: left">
IDF Version
<span style="float: right" id="idf_version">Loading ...</span>
</p>
<p style="text-align: left">
API Server
<span style="float: right" id="api_server">Loading ...</span>
</p>
<p style="text-align: left">
API Endpoint
<span style="float: right" id="api_endpoint">Loading ...</span>
</p>
<p style="text-align: left">
Centrally Managed
<span style="float: right" id="centrally_managed">Loading ...</span>
</p>
<button type="button" class="conf" onclick="window.location.href='config'">Settings</button><br /><br />
<hr>
<h3>Upload Software Package</h3>
<input type="file" name="update" id="file" onchange="sub(this)" style="display: none" accept=".bin" />
<label id="file-input" for="file">Choose file...</label>
<input type="submit" class="btn" value="Program" /><br /><br />
<div id="prg"></div>
<br />
<div id="prgbar">
<div id="bar"></div>
</div>
<br />
<hr>
<h3>Device Control</h3>
<input type="button" class="btn" value="Reboot" onclick="window.location.href='/reboot'" />
<b>SIWAT SYSTEM 2023</b>
</form>
<script>
window.onload = function () {
fetch("/get_device_info")
.then(response => response.json())
.then(data => {
console.log(data);
document.getElementById("hostname").innerHTML = data.hostname;
document.getElementById("ip_address").innerHTML = data.ip_address;
document.getElementById("mac_address").innerHTML = data.mac_address;
document.getElementById("model").innerHTML = data.model;
document.getElementById("fw_version").innerHTML = data.software_version;
document.getElementById("sdk_version").innerHTML = data.sdk_version;
document.getElementById("idf_version").innerHTML = data.idf_version;
document.getElementById("api_server").innerHTML = data.mqtt_server + ":" + data.mqtt_port;
document.getElementById("api_endpoint").innerHTML = data.base_topic;
document.getElementById("centrally_managed").innerHTML = data.mqtt_connected;
var uptime = data.uptime;
var uptime_string = "";
// Uptime is in seconds, convert it to X days, HH:MM:SS
var days = Math.floor(uptime / (24 * 3600));
uptime -= days * 24 * 3600;
var hours = Math.floor(uptime / 3600);
uptime -= hours * 3600;
var minutes = Math.floor(uptime / 60);
uptime -= minutes * 60;
var seconds = uptime;
if (days > 0) uptime_string += days + " days, ";
uptime_string += hours.toString().padStart(2, "0") + ":" + minutes.toString().padStart(2, "0") + ":" + seconds.toString().padStart(2, "0");
document.getElementById("uptime").innerHTML = uptime_string;
}).catch(error => {
console.error(error);
});
};
function sub(obj) {
var fileName = obj.value.split("\\");
document.getElementById("file-input").innerHTML =
" " + fileName[fileName.length - 1];
}
$("form").submit(function (e) {
e.preventDefault();
var form = $("#upload_form")[0];
var data = new FormData(form);
$.ajax({
url: "/ota_update",
type: "POST",
data: data,
contentType: false,
processData: false,
xhr: function () {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener(
"progress",
function (evt) {
if (evt.lengthComputable) {
var per = evt.loaded / evt.total;
if (Math.round(per * 100) < 100) {
$("#prg").html("Updating . . . (" + Math.round(per * 100) + "%)");
}
else {
$("#prg").html("Update Completed, Rebooting . . .");
}
$("#bar").css("width", Math.round(per * 100) + "%");
}
},
false
);
return xhr;
},
success: function (d, s) {
console.log("success!");
},
error: function (a, b, c) { },
});
});
</script>
<style>
hr {
display: block;
color: #aaaaaa;
background-color: #aaaaaa;
margin-top: 0.5em;
margin-bottom: 0.5em;
margin-left: auto;
margin-right: auto;
border-style: inset;
border-width: 0px;
height: 3px;
}
#file-input,
input {
width: 100%;
height: 44px;
border-radius: 4px;
margin: 10px auto;
font-size: 15px;
}
input {
background: #f1f1f1;
border: 0;
padding: 0 15px;
}
body {
background-image: url("https://fs.siwatsystem.com/arona_bg.png");
background-size: cover;
font-family: sans-serif;
font-size: 14px;
color: #777;
}
#file-input {
background-color: #CCCCCC;
color: #5E5E5E;
padding: 0;
border: 1px solid #ddd;
line-height: 44px;
text-align: center;
display: block;
cursor: pointer;
}
#bar,
#prgbar {
background-color: #D9D9D9;
border-radius: 10px;
}
#bar {
background-color: #29CD1F;
width: 0%;
height: 10px;
}
form {
background: rgba(255, 255, 255, 0.95);
max-width: 258px;
margin: 75px auto;
padding: 30px;
border-radius: 15px;
text-align: center;
}
.btn {
background: #CA3D3D;
color: #fff;
cursor: pointer;
}
.conf {
background: #417df3;
color: #fff;
cursor: pointer;
width: 100%;
height: 44px;
border-radius: 4px;
margin: 10px auto;
font-size: 15px;
border: 0;
}
</style>

View File

@ -1,567 +0,0 @@
const char ota_html[] PROGMEM = {
0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x73, 0x72, 0x63, 0x3d,
0x22, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x61, 0x6a, 0x61,
0x78, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73,
0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6a, 0x61, 0x78, 0x2f, 0x6c, 0x69,
0x62, 0x73, 0x2f, 0x6a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2f, 0x33, 0x2e,
0x32, 0x2e, 0x31, 0x2f, 0x6a, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x6d,
0x69, 0x6e, 0x2e, 0x6a, 0x73, 0x22, 0x3e, 0x3c, 0x2f, 0x73, 0x63, 0x72,
0x69, 0x70, 0x74, 0x3e, 0x0d, 0x0a, 0x3c, 0x66, 0x6f, 0x72, 0x6d, 0x20,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x3d, 0x22, 0x50, 0x4f, 0x53, 0x54,
0x22, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x3d, 0x22, 0x23, 0x22,
0x20, 0x65, 0x6e, 0x63, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x6d, 0x75,
0x6c, 0x74, 0x69, 0x70, 0x61, 0x72, 0x74, 0x2f, 0x66, 0x6f, 0x72, 0x6d,
0x2d, 0x64, 0x61, 0x74, 0x61, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x75,
0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x68, 0x31, 0x3e, 0x45, 0x53, 0x50, 0x4d,
0x65, 0x67, 0x61, 0x20, 0x50, 0x52, 0x4f, 0x3c, 0x2f, 0x68, 0x31, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x68, 0x33, 0x3e, 0x44, 0x65, 0x76, 0x69,
0x63, 0x65, 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c,
0x70, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78,
0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66,
0x74, 0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x48, 0x6f, 0x73,
0x74, 0x6e, 0x61, 0x6d, 0x65, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c,
0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22,
0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74,
0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
0x6d, 0x65, 0x22, 0x3e, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20,
0x2e, 0x2e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70,
0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74,
0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x49, 0x50, 0x20, 0x41,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d,
0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68,
0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x69, 0x70, 0x5f, 0x61, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3e, 0x4c, 0x6f, 0x61, 0x64, 0x69,
0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x3c, 0x70, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74,
0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c,
0x65, 0x66, 0x74, 0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4d,
0x41, 0x43, 0x20, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74,
0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20,
0x72, 0x69, 0x67, 0x68, 0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x6d,
0x61, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x3e,
0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x3c,
0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x2f,
0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70, 0x20, 0x73, 0x74, 0x79,
0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69,
0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x22, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74,
0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20,
0x72, 0x69, 0x67, 0x68, 0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x75,
0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x3e, 0x4c, 0x6f, 0x61, 0x64, 0x69,
0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x3c, 0x70, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74,
0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c,
0x65, 0x66, 0x74, 0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x4d,
0x6f, 0x64, 0x65, 0x6c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x73,
0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66,
0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x22,
0x20, 0x69, 0x64, 0x3d, 0x22, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x22, 0x3e,
0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x3c,
0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x2f,
0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70, 0x20, 0x73, 0x74, 0x79,
0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69,
0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x22, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x46, 0x57, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69,
0x6f, 0x6e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x73, 0x70, 0x61,
0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66, 0x6c, 0x6f,
0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x22, 0x20, 0x69,
0x64, 0x3d, 0x22, 0x66, 0x77, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x6e, 0x22, 0x3e, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e,
0x2e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70, 0x20,
0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d,
0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x22,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x53, 0x44, 0x4b, 0x20, 0x56,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d,
0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68,
0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x73, 0x64, 0x6b, 0x5f, 0x76,
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x3e, 0x4c, 0x6f, 0x61, 0x64,
0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61,
0x6e, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x70, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22,
0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20,
0x6c, 0x65, 0x66, 0x74, 0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x49, 0x44, 0x46, 0x20, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73,
0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a,
0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22,
0x69, 0x64, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22,
0x3e, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e,
0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c,
0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70, 0x20, 0x73, 0x74,
0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c,
0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x22, 0x3e, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x41, 0x50, 0x49, 0x20, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x73, 0x70,
0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x66, 0x6c,
0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x22, 0x20,
0x69, 0x64, 0x3d, 0x22, 0x61, 0x70, 0x69, 0x5f, 0x73, 0x65, 0x72, 0x76,
0x65, 0x72, 0x22, 0x3e, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20,
0x2e, 0x2e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70,
0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74,
0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x41, 0x50, 0x49, 0x20,
0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c,
0x65, 0x3d, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69,
0x67, 0x68, 0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x61, 0x70, 0x69,
0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x22, 0x3e, 0x4c,
0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x2e, 0x2e, 0x3c, 0x2f,
0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x70,
0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x70, 0x20, 0x73, 0x74, 0x79, 0x6c,
0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67,
0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x22, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x43, 0x65, 0x6e, 0x74, 0x72, 0x61, 0x6c, 0x6c, 0x79,
0x20, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x64, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c,
0x65, 0x3d, 0x22, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69,
0x67, 0x68, 0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x63, 0x65, 0x6e,
0x74, 0x72, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x64, 0x22, 0x3e, 0x4c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20,
0x2e, 0x2e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x2f, 0x70, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x62,
0x75, 0x74, 0x74, 0x6f, 0x6e, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22,
0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73,
0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x22, 0x20, 0x6f, 0x6e, 0x63,
0x6c, 0x69, 0x63, 0x6b, 0x3d, 0x22, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77,
0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x68, 0x72,
0x65, 0x66, 0x3d, 0x27, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x27, 0x22,
0x3e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x3c, 0x2f, 0x62,
0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3e, 0x3c, 0x62, 0x72, 0x20, 0x2f, 0x3e,
0x3c, 0x62, 0x72, 0x20, 0x2f, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x68,
0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x68, 0x33, 0x3e, 0x55, 0x70,
0x6c, 0x6f, 0x61, 0x64, 0x20, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72,
0x65, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x3c, 0x2f, 0x68,
0x33, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22,
0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x75, 0x70, 0x64, 0x61, 0x74,
0x65, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x22,
0x20, 0x6f, 0x6e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x22, 0x73,
0x75, 0x62, 0x28, 0x74, 0x68, 0x69, 0x73, 0x29, 0x22, 0x20, 0x73, 0x74,
0x79, 0x6c, 0x65, 0x3d, 0x22, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79,
0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x22, 0x20, 0x61, 0x63, 0x63, 0x65,
0x70, 0x74, 0x3d, 0x22, 0x2e, 0x62, 0x69, 0x6e, 0x22, 0x20, 0x2f, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x69,
0x64, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x22, 0x20, 0x66, 0x6f, 0x72, 0x3d, 0x22, 0x66, 0x69, 0x6c, 0x65,
0x22, 0x3e, 0x43, 0x68, 0x6f, 0x6f, 0x73, 0x65, 0x20, 0x66, 0x69, 0x6c,
0x65, 0x2e, 0x2e, 0x2e, 0x3c, 0x2f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74,
0x79, 0x70, 0x65, 0x3d, 0x22, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x22,
0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x62, 0x74, 0x6e, 0x22,
0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22, 0x50, 0x72, 0x6f, 0x67,
0x72, 0x61, 0x6d, 0x22, 0x20, 0x2f, 0x3e, 0x3c, 0x62, 0x72, 0x20, 0x2f,
0x3e, 0x3c, 0x62, 0x72, 0x20, 0x2f, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c,
0x64, 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x70, 0x72, 0x67, 0x22,
0x3e, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c,
0x62, 0x72, 0x20, 0x2f, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x64, 0x69,
0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x70, 0x72, 0x67, 0x62, 0x61, 0x72,
0x22, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x64, 0x69, 0x76,
0x20, 0x69, 0x64, 0x3d, 0x22, 0x62, 0x61, 0x72, 0x22, 0x3e, 0x3c, 0x2f,
0x64, 0x69, 0x76, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x2f, 0x64, 0x69,
0x76, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x62, 0x72, 0x20, 0x2f, 0x3e,
0x0d, 0x0a, 0x20, 0x20, 0x3c, 0x68, 0x72, 0x3e, 0x0d, 0x0a, 0x20, 0x20,
0x3c, 0x68, 0x33, 0x3e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x20, 0x43,
0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x3c, 0x2f, 0x68, 0x33, 0x3e, 0x0d,
0x0a, 0x20, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x22, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x22, 0x20,
0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x62, 0x74, 0x6e, 0x22, 0x20,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x3d, 0x22, 0x52, 0x65, 0x62, 0x6f, 0x6f,
0x74, 0x22, 0x20, 0x6f, 0x6e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x3d, 0x22,
0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x74,
0x69, 0x6f, 0x6e, 0x2e, 0x68, 0x72, 0x65, 0x66, 0x3d, 0x27, 0x2f, 0x72,
0x65, 0x62, 0x6f, 0x6f, 0x74, 0x27, 0x22, 0x20, 0x2f, 0x3e, 0x0d, 0x0a,
0x20, 0x20, 0x3c, 0x62, 0x3e, 0x53, 0x49, 0x57, 0x41, 0x54, 0x20, 0x53,
0x59, 0x53, 0x54, 0x45, 0x4d, 0x20, 0x32, 0x30, 0x32, 0x33, 0x3c, 0x2f,
0x62, 0x3e, 0x0d, 0x0a, 0x3c, 0x2f, 0x66, 0x6f, 0x72, 0x6d, 0x3e, 0x0d,
0x0a, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0d, 0x0a, 0x20,
0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, 0x6e, 0x6c, 0x6f,
0x61, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f,
0x6e, 0x20, 0x28, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x66, 0x65, 0x74, 0x63, 0x68, 0x28, 0x22, 0x2f, 0x67, 0x65, 0x74, 0x5f,
0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22,
0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x74, 0x68,
0x65, 0x6e, 0x28, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x20,
0x3d, 0x3e, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e,
0x6a, 0x73, 0x6f, 0x6e, 0x28, 0x29, 0x29, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2e, 0x74, 0x68, 0x65, 0x6e, 0x28, 0x64, 0x61, 0x74,
0x61, 0x20, 0x3d, 0x3e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e,
0x6c, 0x6f, 0x67, 0x28, 0x64, 0x61, 0x74, 0x61, 0x29, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x68, 0x6f, 0x73,
0x74, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x29, 0x2e, 0x69, 0x6e, 0x6e, 0x65,
0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61,
0x2e, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x69, 0x70, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x29, 0x2e, 0x69, 0x6e,
0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d, 0x20, 0x64, 0x61,
0x74, 0x61, 0x2e, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74,
0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28,
0x22, 0x6d, 0x61, 0x63, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x22, 0x29, 0x2e, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c,
0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x6d, 0x61, 0x63, 0x5f,
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65,
0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x6d, 0x6f, 0x64, 0x65, 0x6c,
0x22, 0x29, 0x2e, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c,
0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x6d, 0x6f, 0x64, 0x65,
0x6c, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74,
0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28,
0x22, 0x66, 0x77, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22,
0x29, 0x2e, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20,
0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x73, 0x6f, 0x66, 0x74, 0x77,
0x61, 0x72, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f,
0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x73,
0x64, 0x6b, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x29,
0x2e, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d,
0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x73, 0x64, 0x6b, 0x5f, 0x76, 0x65,
0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74,
0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42,
0x79, 0x49, 0x64, 0x28, 0x22, 0x69, 0x64, 0x66, 0x5f, 0x76, 0x65, 0x72,
0x73, 0x69, 0x6f, 0x6e, 0x22, 0x29, 0x2e, 0x69, 0x6e, 0x6e, 0x65, 0x72,
0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e,
0x69, 0x64, 0x66, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f,
0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c,
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x61,
0x70, 0x69, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x29, 0x2e,
0x69, 0x6e, 0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d, 0x20,
0x64, 0x61, 0x74, 0x61, 0x2e, 0x6d, 0x71, 0x74, 0x74, 0x5f, 0x73, 0x65,
0x72, 0x76, 0x65, 0x72, 0x20, 0x2b, 0x20, 0x22, 0x3a, 0x22, 0x20, 0x2b,
0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x6d, 0x71, 0x74, 0x74, 0x5f, 0x70,
0x6f, 0x72, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67,
0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49,
0x64, 0x28, 0x22, 0x61, 0x70, 0x69, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x22, 0x29, 0x2e, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x48,
0x54, 0x4d, 0x4c, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x62,
0x61, 0x73, 0x65, 0x5f, 0x74, 0x6f, 0x70, 0x69, 0x63, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x6f, 0x63, 0x75,
0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x63, 0x65, 0x6e,
0x74, 0x72, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x6d, 0x61, 0x6e, 0x61, 0x67,
0x65, 0x64, 0x22, 0x29, 0x2e, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x48, 0x54,
0x4d, 0x4c, 0x20, 0x3d, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x6d, 0x71,
0x74, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76,
0x61, 0x72, 0x20, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x3d, 0x20,
0x64, 0x61, 0x74, 0x61, 0x2e, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61,
0x72, 0x20, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x72,
0x69, 0x6e, 0x67, 0x20, 0x3d, 0x20, 0x22, 0x22, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x55, 0x70,
0x74, 0x69, 0x6d, 0x65, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x73,
0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x76,
0x65, 0x72, 0x74, 0x20, 0x69, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x58, 0x20,
0x64, 0x61, 0x79, 0x73, 0x2c, 0x20, 0x48, 0x48, 0x3a, 0x4d, 0x4d, 0x3a,
0x53, 0x53, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x76, 0x61, 0x72, 0x20, 0x64, 0x61, 0x79, 0x73, 0x20, 0x3d, 0x20, 0x4d,
0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x75, 0x70,
0x74, 0x69, 0x6d, 0x65, 0x20, 0x2f, 0x20, 0x28, 0x32, 0x34, 0x20, 0x2a,
0x20, 0x33, 0x36, 0x30, 0x30, 0x29, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65,
0x20, 0x2d, 0x3d, 0x20, 0x64, 0x61, 0x79, 0x73, 0x20, 0x2a, 0x20, 0x32,
0x34, 0x20, 0x2a, 0x20, 0x33, 0x36, 0x30, 0x30, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x68,
0x6f, 0x75, 0x72, 0x73, 0x20, 0x3d, 0x20, 0x4d, 0x61, 0x74, 0x68, 0x2e,
0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65,
0x20, 0x2f, 0x20, 0x33, 0x36, 0x30, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x70, 0x74, 0x69, 0x6d,
0x65, 0x20, 0x2d, 0x3d, 0x20, 0x68, 0x6f, 0x75, 0x72, 0x73, 0x20, 0x2a,
0x20, 0x33, 0x36, 0x30, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x6d, 0x69, 0x6e, 0x75,
0x74, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x4d, 0x61, 0x74, 0x68, 0x2e, 0x66,
0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x20,
0x2f, 0x20, 0x36, 0x30, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x2d,
0x3d, 0x20, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x20, 0x2a, 0x20,
0x36, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x76, 0x61, 0x72, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73,
0x20, 0x3d, 0x20, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28,
0x64, 0x61, 0x79, 0x73, 0x20, 0x3e, 0x20, 0x30, 0x29, 0x20, 0x75, 0x70,
0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20,
0x2b, 0x3d, 0x20, 0x64, 0x61, 0x79, 0x73, 0x20, 0x2b, 0x20, 0x22, 0x20,
0x64, 0x61, 0x79, 0x73, 0x2c, 0x20, 0x22, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65,
0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x2b, 0x3d, 0x20, 0x68,
0x6f, 0x75, 0x72, 0x73, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e,
0x67, 0x28, 0x29, 0x2e, 0x70, 0x61, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74,
0x28, 0x32, 0x2c, 0x20, 0x22, 0x30, 0x22, 0x29, 0x20, 0x2b, 0x20, 0x22,
0x3a, 0x22, 0x20, 0x2b, 0x20, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73,
0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x2e,
0x70, 0x61, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x28, 0x32, 0x2c, 0x20,
0x22, 0x30, 0x22, 0x29, 0x20, 0x2b, 0x20, 0x22, 0x3a, 0x22, 0x20, 0x2b,
0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x2e, 0x74, 0x6f, 0x53,
0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, 0x2e, 0x70, 0x61, 0x64, 0x53,
0x74, 0x61, 0x72, 0x74, 0x28, 0x32, 0x2c, 0x20, 0x22, 0x30, 0x22, 0x29,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64,
0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45,
0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22,
0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x29, 0x2e, 0x69, 0x6e, 0x6e,
0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d, 0x20, 0x75, 0x70, 0x74,
0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x2e, 0x63, 0x61,
0x74, 0x63, 0x68, 0x28, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x20, 0x3d, 0x3e,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x2e, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x28, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x7d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x20, 0x73, 0x75, 0x62, 0x28, 0x6f, 0x62, 0x6a, 0x29, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x66,
0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x6f, 0x62,
0x6a, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x73, 0x70, 0x6c, 0x69,
0x74, 0x28, 0x22, 0x5c, 0x5c, 0x22, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67,
0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49,
0x64, 0x28, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x22, 0x29, 0x2e, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d,
0x4c, 0x20, 0x3d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22,
0x20, 0x20, 0x20, 0x22, 0x20, 0x2b, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x4e,
0x61, 0x6d, 0x65, 0x5b, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65,
0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x20, 0x2d, 0x20, 0x31, 0x5d,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x24, 0x28,
0x22, 0x66, 0x6f, 0x72, 0x6d, 0x22, 0x29, 0x2e, 0x73, 0x75, 0x62, 0x6d,
0x69, 0x74, 0x28, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20,
0x28, 0x65, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65,
0x2e, 0x70, 0x72, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x66, 0x61,
0x75, 0x6c, 0x74, 0x28, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x76, 0x61, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x3d, 0x20, 0x24,
0x28, 0x22, 0x23, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x66, 0x6f,
0x72, 0x6d, 0x22, 0x29, 0x5b, 0x30, 0x5d, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x3d,
0x20, 0x6e, 0x65, 0x77, 0x20, 0x46, 0x6f, 0x72, 0x6d, 0x44, 0x61, 0x74,
0x61, 0x28, 0x66, 0x6f, 0x72, 0x6d, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x24, 0x2e, 0x61, 0x6a, 0x61, 0x78, 0x28, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x75, 0x72, 0x6c, 0x3a, 0x20, 0x22,
0x2f, 0x6f, 0x74, 0x61, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22,
0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70,
0x65, 0x3a, 0x20, 0x22, 0x50, 0x4f, 0x53, 0x54, 0x22, 0x2c, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x61, 0x74, 0x61, 0x3a, 0x20,
0x64, 0x61, 0x74, 0x61, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65,
0x3a, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x44,
0x61, 0x74, 0x61, 0x3a, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78, 0x68, 0x72, 0x3a, 0x20,
0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x29, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76,
0x61, 0x72, 0x20, 0x78, 0x68, 0x72, 0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77,
0x20, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x58, 0x4d, 0x4c, 0x48,
0x74, 0x74, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x29,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x78,
0x68, 0x72, 0x2e, 0x75, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x2e, 0x61, 0x64,
0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e,
0x65, 0x72, 0x28, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x22, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73,
0x22, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28,
0x65, 0x76, 0x74, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28,
0x65, 0x76, 0x74, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x43, 0x6f,
0x6d, 0x70, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x29, 0x20, 0x7b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x76, 0x61, 0x72, 0x20, 0x70, 0x65, 0x72, 0x20, 0x3d,
0x20, 0x65, 0x76, 0x74, 0x2e, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20,
0x2f, 0x20, 0x65, 0x76, 0x74, 0x2e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x28, 0x4d, 0x61, 0x74, 0x68,
0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x70, 0x65, 0x72, 0x20, 0x2a,
0x20, 0x31, 0x30, 0x30, 0x29, 0x20, 0x3c, 0x20, 0x31, 0x30, 0x30, 0x29,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x24, 0x28, 0x22, 0x23,
0x70, 0x72, 0x67, 0x22, 0x29, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x28, 0x22,
0x55, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x20, 0x2e,
0x20, 0x2e, 0x20, 0x28, 0x22, 0x20, 0x2b, 0x20, 0x4d, 0x61, 0x74, 0x68,
0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x70, 0x65, 0x72, 0x20, 0x2a,
0x20, 0x31, 0x30, 0x30, 0x29, 0x20, 0x2b, 0x20, 0x22, 0x25, 0x29, 0x22,
0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65,
0x6c, 0x73, 0x65, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x24,
0x28, 0x22, 0x23, 0x70, 0x72, 0x67, 0x22, 0x29, 0x2e, 0x68, 0x74, 0x6d,
0x6c, 0x28, 0x22, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x43, 0x6f,
0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x52, 0x65, 0x62,
0x6f, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x20, 0x2e, 0x20, 0x2e,
0x22, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x24, 0x28, 0x22, 0x23, 0x62, 0x61, 0x72, 0x22, 0x29, 0x2e, 0x63, 0x73,
0x73, 0x28, 0x22, 0x77, 0x69, 0x64, 0x74, 0x68, 0x22, 0x2c, 0x20, 0x4d,
0x61, 0x74, 0x68, 0x2e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x28, 0x70, 0x65,
0x72, 0x20, 0x2a, 0x20, 0x31, 0x30, 0x30, 0x29, 0x20, 0x2b, 0x20, 0x22,
0x25, 0x22, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x61, 0x6c,
0x73, 0x65, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x78, 0x68, 0x72, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x2c, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73,
0x3a, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28,
0x64, 0x2c, 0x20, 0x73, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65,
0x2e, 0x6c, 0x6f, 0x67, 0x28, 0x22, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73,
0x73, 0x21, 0x22, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x7d, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65,
0x72, 0x72, 0x6f, 0x72, 0x3a, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69,
0x6f, 0x6e, 0x20, 0x28, 0x61, 0x2c, 0x20, 0x62, 0x2c, 0x20, 0x63, 0x29,
0x20, 0x7b, 0x20, 0x7d, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d,
0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x29, 0x3b, 0x0d, 0x0a, 0x3c,
0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0x0d, 0x0a, 0x3c, 0x73,
0x74, 0x79, 0x6c, 0x65, 0x3e, 0x0d, 0x0a, 0x20, 0x20, 0x68, 0x72, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c,
0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23,
0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72,
0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x2e, 0x35,
0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72,
0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x30, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x6c, 0x65, 0x66, 0x74, 0x3a,
0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74,
0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x79, 0x6c,
0x65, 0x3a, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x74, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x77, 0x69,
0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x33,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x20, 0x20, 0x23, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x2c, 0x0d, 0x0a, 0x20, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68,
0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x34, 0x34, 0x70,
0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64,
0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72,
0x67, 0x69, 0x6e, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x20, 0x61, 0x75,
0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e,
0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x35, 0x70, 0x78,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a,
0x20, 0x23, 0x66, 0x31, 0x66, 0x31, 0x66, 0x31, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69,
0x6e, 0x67, 0x3a, 0x20, 0x30, 0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x62, 0x6f,
0x64, 0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61,
0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x69, 0x6d, 0x61,
0x67, 0x65, 0x3a, 0x20, 0x75, 0x72, 0x6c, 0x28, 0x22, 0x68, 0x74, 0x74,
0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x66, 0x73, 0x2e, 0x73, 0x69, 0x77, 0x61,
0x74, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x61, 0x72, 0x6f, 0x6e, 0x61, 0x5f, 0x62, 0x67, 0x2e, 0x70, 0x6e, 0x67,
0x22, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63,
0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65,
0x3a, 0x20, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c,
0x79, 0x3a, 0x20, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69,
0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74,
0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x34, 0x70, 0x78, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x23, 0x37, 0x37, 0x37, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x23, 0x66, 0x69, 0x6c, 0x65, 0x2d, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63,
0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x43, 0x43, 0x43, 0x43, 0x43,
0x43, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x23, 0x35, 0x45, 0x35, 0x45, 0x35, 0x45, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f,
0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f,
0x6c, 0x69, 0x64, 0x20, 0x23, 0x64, 0x64, 0x64, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67,
0x68, 0x74, 0x3a, 0x20, 0x34, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67,
0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69,
0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x20, 0x20, 0x23, 0x62, 0x61, 0x72, 0x2c, 0x0d, 0x0a, 0x20,
0x20, 0x23, 0x70, 0x72, 0x67, 0x62, 0x61, 0x72, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x44,
0x39, 0x44, 0x39, 0x44, 0x39, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75,
0x73, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x23, 0x62, 0x61, 0x72, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x23, 0x32, 0x39, 0x43, 0x44, 0x31, 0x46, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x30, 0x25,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68,
0x74, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x72, 0x67, 0x62, 0x61, 0x28,
0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x35,
0x35, 0x2c, 0x20, 0x30, 0x2e, 0x39, 0x35, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68,
0x3a, 0x20, 0x32, 0x35, 0x38, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x37, 0x35,
0x70, 0x78, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x33,
0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f,
0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a,
0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20,
0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x62, 0x74, 0x6e, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x23, 0x43, 0x41, 0x33, 0x44, 0x33,
0x44, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x23, 0x34, 0x31, 0x37, 0x64, 0x66,
0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x3a, 0x20, 0x34, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69,
0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x31, 0x30,
0x70, 0x78, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a,
0x20, 0x31, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, 0x65,
0x3e
, 0x00};

View File

@ -1,18 +0,0 @@
{
"configurations": [
{
"name": "windows-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "gcc",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "windows-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

View File

@ -1,24 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": true,
"cwd": "d:/Git/ESPMegaPRO-v3-SDK/Template Project/src",
"program": "d:/Git/ESPMegaPRO-v3-SDK/Template Project/src/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

View File

@ -1,58 +0,0 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false
}

View File

@ -1,26 +0,0 @@
#include <ESPMegaPRO_OOP.hpp>
ESPMegaPRO espmega = ESPMegaPRO();
void setup() {
espmega.begin();
//espmega.fram.write8(301, 25);
// Dump FRAM to a prettified table
for (int i = 0; i < 500; i++) {
if (i % 16 == 0) {
Serial.printf("\n%03d: ", i);
}
Serial.printf("%03d ", espmega.fram.read8(i));
}
// Dump FRAM again but treat it as a long string
Serial.printf("\n\n");
for (int i = 0; i < 500; i++) {
Serial.printf("%d: %c\n", i,espmega.fram.read8(i));
}
}
void loop() {
espmega.loop();
}

View File

@ -1,505 +0,0 @@
//
// FILE: MultiSpeedI2CScanner.ino
// AUTHOR: Rob Tillaart
// VERSION: 0.1.17
// PURPOSE: I2C scanner at different speeds
// DATE: 2013-11-05
// URL: https://github.com/RobTillaart/MultiSpeedI2CScanner
// URL: http://forum.arduino.cc/index.php?topic=197360
//
#include <Arduino.h>
#include <Wire.h>
// FOR INTERNAL I2C BUS NANO 33 BLE
// #define WIRE_IMPLEMENT_WIRE1 1
// extern TwoWire Wire1;
void I2Cscan();
void displayHelp();
void reset();
void setAddress();
void setSpeed(char sp);
char getCommand();
TwoWire *wire;
const char version[] = "0.1.16";
// INTERFACE COUNT (TESTED TEENSY 3.5 AND ARDUINO DUE ONLY)
int wirePortCount = 1;
int selectedWirePort = 0;
// scans devices from 50 to 800 KHz I2C speeds.
// speed lower than 50 and above 400 can cause problems
long speed[10] = { 100, 200, 300, 400 };
int speeds;
int addressStart = 8;
int addressEnd = 119;
// DELAY BETWEEN TESTS
// for delay between tests of found devices.
#ifndef RESTORE_LATENCY
#define RESTORE_LATENCY 5
#endif
bool delayFlag = false;
// MINIMIZE OUTPUT
bool printAll = true;
bool header = true;
bool disableIRQ = false;
// STATE MACHINE
enum states {
STOP, ONCE, CONT, HELP
};
states state = STOP;
// TIMING
uint32_t startScan;
uint32_t stopScan;
///////////////////////////////////////////////////////////////////////////
//
// MAIN CODE
//
void setup()
{
Serial.begin(115200);
while (!Serial);
Wire.begin(14,33);
#if defined WIRE_IMPLEMENT_WIRE1 || WIRE_INTERFACES_COUNT > 1
Wire1.begin();
wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE2 || WIRE_INTERFACES_COUNT > 2
Wire2.begin();
wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE3 || WIRE_INTERFACES_COUNT > 3
Wire3.begin();
wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE4 || WIRE_INTERFACES_COUNT > 4
Wire4.begin();
wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE5 || WIRE_INTERFACES_COUNT > 5
Wire5.begin();
wirePortCount++;
#endif
wire = &Wire;
Serial.println();
reset();
}
void loop()
{
yield();
char command = getCommand();
switch (command)
{
case '@':
selectedWirePort = (selectedWirePort + 1) % wirePortCount;
Serial.print(F("<I2C PORT=Wire"));
Serial.print(selectedWirePort);
Serial.println(F(">"));
switch (selectedWirePort)
{
case 0:
wire = &Wire;
break;
#if defined WIRE_IMPLEMENT_WIRE1 || WIRE_INTERFACES_COUNT > 1
case 1:
wire = &Wire1;
break;
#endif
#if defined WIRE_IMPLEMENT_WIRE2 || WIRE_INTERFACES_COUNT > 2
case 2:
wire = &Wire2;
break;
#endif
#if defined WIRE_IMPLEMENT_WIRE3 || WIRE_INTERFACES_COUNT > 3
case 3:
wire = &Wire3;
break;
#endif
#if defined WIRE_IMPLEMENT_WIRE4 || WIRE_INTERFACES_COUNT > 4
case 4:
wire = &Wire4;
break;
#endif
#if defined WIRE_IMPLEMENT_WIRE5 || WIRE_INTERFACES_COUNT > 5
case 5:
wire = &Wire5;
break;
#endif
}
break;
case 's':
state = ONCE;
break;
case 'c':
state = CONT;
break;
case 'd':
delayFlag = !delayFlag;
Serial.print(F("<delay="));
Serial.println(delayFlag ? F("5>") : F("0>"));
break;
case 'e':
// eeprom test TODO
break;
case 'h':
header = !header;
Serial.print(F("<header="));
Serial.println(header ? F("yes>") : F("no>"));
break;
case 'p':
printAll = !printAll;
Serial.print(F("<print="));
Serial.println(printAll ? F("all>") : F("found>"));
break;
case 'i':
disableIRQ = !disableIRQ;
Serial.print(F("<irq="));
Serial.println(disableIRQ ? F("diabled>") : F("enabled>"));
break;
case '0':
case '1':
case '2':
case '4':
case '8':
case '9':
case 'M':
case 'N':
case 'O':
case 'P':
setSpeed(command);
break;
case 'r':
reset();
break;
case 'a':
setAddress();
break;
case 'q':
case '?':
state = HELP;
break;
default:
break;
}
switch (state)
{
case ONCE:
I2Cscan();
state = HELP;
break;
case CONT:
I2Cscan();
delay(1000);
break;
case HELP:
displayHelp();
state = STOP;
break;
case STOP:
break;
default: // ignore all non commands
break;
}
}
//////////////////////////////////////////////////////////////////////
void reset()
{
setSpeed('9');
selectedWirePort = 0;
addressStart = 8;
addressEnd = 119;
delayFlag = false;
printAll = true;
header = true;
disableIRQ = false;
state = STOP;
displayHelp();
}
void setAddress()
{
if (addressStart == 0)
{
addressStart = 8;
addressEnd = 119;
}
else
{
addressStart = 0;
addressEnd = 127;
}
Serial.print(F("<address Range = "));
Serial.print(addressStart);
Serial.print(F(".."));
Serial.print(addressEnd);
Serial.println(F(">"));
}
void setSpeed(char sp)
{
switch (sp)
{
case '1':
speed[0] = 100;
speeds = 1;
break;
case '2':
speed[0] = 200;
speeds = 1;
break;
case '4':
speed[0] = 400;
speeds = 1;
break;
case '8':
speed[0] = 800;
speeds = 1;
break;
case '9': // limited to 400 KHz
speeds = 8;
for (int i = 1; i <= speeds; i++) speed[i - 1] = i * 50;
break;
case '0': // limited to 800 KHz
speeds = 8;
for (int i = 1; i <= speeds; i++) speed[i - 1] = i * 100;
break;
// new in 0.1.10 - experimental
case 'M':
speed[0] = 1000;
speeds = 1;
break;
case 'N':
speed[0] = 3400;
speeds = 1;
break;
case 'O':
speed[0] = 5000;
speeds = 1;
break;
case 'P':
speed[0] = 100;
speed[1] = 400;
speed[2] = 1000;
speed[3] = 3400;
speed[4] = 5000;
speeds = 5;
break;
}
Serial.print("<speeds =");
for (int i = 0; i < speeds; i++)
{
Serial.print(' ');
Serial.print(speed[i]);
}
Serial.println(" >");
}
char getCommand()
{
char c = '\0';
if (Serial.available())
{
c = Serial.read();
}
return c;
}
void displayHelp()
{
Serial.print(F("\nArduino MultiSpeed I2C Scanner - "));
Serial.println(version);
Serial.println();
Serial.print(F("I2C ports: "));
Serial.print(wirePortCount);
Serial.print(F(" Current: Wire"));
Serial.println(selectedWirePort);
Serial.println(F("\t@ = toggle Wire - Wire1 .. Wire5 [e.g. TEENSY or Arduino Due]"));
Serial.println(F("Scan mode:"));
Serial.println(F("\ts = single scan"));
Serial.println(F("\tc = continuous scan - 1 second delay"));
Serial.println(F("\tq = quit continuous scan"));
Serial.println(F("\td = toggle latency delay between successful tests. 0 - 5 ms"));
Serial.println(F("\ti = toggle enable/disable interrupts"));
Serial.println(F("Output:"));
Serial.println(F("\tp = toggle printAll - printFound."));
Serial.println(F("\th = toggle header - noHeader."));
Serial.println(F("\ta = toggle address range, 0..127 - 8..119 (default)"));
Serial.println(F("Speeds:"));
Serial.println(F("\t0 = 100..800 KHz - step 100 (warning - can block!!)"));
Serial.println(F("\t1 = 100 KHz"));
Serial.println(F("\t2 = 200 KHz"));
Serial.println(F("\t4 = 400 KHz"));
Serial.println(F("\t9 = 50..400 KHz - step 50 < DEFAULT >"));
Serial.println();
Serial.println(F("\t!! HIGH SPEEDS - WARNING - can block - not applicable for UNO"));
Serial.println(F("\t8 = 800 KHz"));
Serial.println(F("\tM = 1000 KHz"));
Serial.println(F("\tN = 3400 KHz"));
Serial.println(F("\tO = 5000 KHz"));
Serial.println(F("\tP = 100 400 1000 3400 5000 KHz (standards)"));
Serial.println(F("\n\tr = reset to startup defaults."));
Serial.println(F("\t? = help - this page"));
Serial.println();
}
void I2Cscan()
{
startScan = millis();
uint8_t count = 0;
if (disableIRQ)
{
noInterrupts();
}
if (header)
{
Serial.print(F("TIME\tDEC\tHEX\t"));
for (uint8_t s = 0; s < speeds; s++)
{
Serial.print(F("\t"));
Serial.print(speed[s]);
}
Serial.println(F("\t[KHz]"));
for (uint8_t s = 0; s < speeds + 5; s++)
{
Serial.print(F("--------"));
}
Serial.println();
delay(100);
}
for (uint8_t address = addressStart; address <= addressEnd; address++)
{
bool printLine = printAll;
bool found[speeds];
bool fnd = false;
for (uint8_t s = 0; s < speeds ; s++)
{
yield(); // keep ESP happy
#if ARDUINO < 158 && defined (TWBR)
uint16_t PREV_TWBR = TWBR;
TWBR = (F_CPU / (speed[s] * 1000) - 16) / 2;
if (TWBR < 2)
{
Serial.println("ERROR: not supported speed");
TWBR = PREV_TWBR;
return;
}
#else
wire->setClock(speed[s] * 1000UL);
#endif
wire->beginTransmission (address);
found[s] = (wire->endTransmission () == 0);
fnd |= found[s];
// give device 5 millis
if (fnd && delayFlag) delay(RESTORE_LATENCY);
}
if (fnd) count++;
printLine |= fnd;
if (printLine)
{
Serial.print(millis());
Serial.print(F("\t"));
Serial.print(address, DEC);
Serial.print(F("\t0x"));
if (address < 0x10) Serial.print(0, HEX);
Serial.print(address, HEX);
Serial.print(F("\t"));
for (uint8_t s = 0; s < speeds ; s++)
{
Serial.print(F("\t"));
Serial.print(found[s] ? F("V") : F("."));
}
Serial.println();
}
}
/*
// FOOTER
if (header)
{
for (uint8_t s = 0; s < speeds + 5; s++)
{
Serial.print(F("--------"));
}
Serial.println();
Serial.print(F("TIME\tDEC\tHEX\t"));
for (uint8_t s = 0; s < speeds; s++)
{
Serial.print(F("\t"));
Serial.print(speed[s]);
}
Serial.println(F("\t[KHz]"));
}
*/
stopScan = millis();
if (header)
{
Serial.println();
Serial.print(count);
Serial.print(F(" devices found in "));
Serial.print(stopScan - startScan);
Serial.println(F(" milliseconds."));
}
interrupts();
}
// -- END OF FILE --

View File

@ -1,382 +0,0 @@
/**
* @file main.cpp
* @brief Test firmware for the ESPMegaPRO OOP library
*/
#include <ESPMegaProOS.hpp>
#include <InternalDisplay.hpp>
#include <ETH.h>
#include <ClimateCard.hpp>
#include <ESPMegaDisplayOTA.hpp>
#include <RemoteVariable.hpp>
#include <CurrentTransformerCard.hpp>
#include <AnalogCard.hpp>
#include <SmartVariable.hpp>
#define INPUT_DEBOUNCE_TIME_MS 1000
// #define FRAM_DEBUG
// #define MQTT_DEBUG
// #define RTC_DEBUG
// #define WRITE_DEFAULT_NETCONF
// #define CLIMATE_CARD_ENABLE
#define MQTT_CARD_REGISTER
// #define DISPLAY_ENABLE
#define WEB_SERVER_ENABLE
// #define LCD_OTA_ENABLE
// #define REMOTE_VARIABLE_ENABLE
// #define CT_ENABLE
// #define SMART_VARIABLE_ENABLE
#define EXTERNAL_DIGITAL_OUTPUT_CARD_ENABLE
#define EXTERNAL_DIGITAL_INPUT_CARD_ENABLE
// Demo PLC firmware using the ESPMegaPRO OOP library
ESPMegaPRO espmega = ESPMegaPRO();
#ifdef EXTERNAL_DIGITAL_OUTPUT_CARD_ENABLE
DigitalOutputCard externalDigitalOutputCard = DigitalOutputCard(1, 0, 1, 1, 0);
#endif
#ifdef EXTERNAL_DIGITAL_INPUT_CARD_ENABLE
void handleExternalDigitalInput(uint8_t pin, uint8_t state)
{
Serial.printf("Digital Input External %d: %d\n", pin, state);
}
DigitalInputCard externalDigitalInputCard = DigitalInputCard(0, 0, 1, 0, 1, 1);
#endif
// Remote Variable
#ifdef REMOTE_VARIABLE_ENABLE
RemoteVariable testVar = RemoteVariable();
#endif
#ifdef LCD_OTA_ENABLE
ESPMegaDisplayOTA internalDisplayOTA = ESPMegaDisplayOTA();
#endif
#ifdef CT_ENABLE
float adc2current(uint16_t adcValue)
{
return (adcValue - 2048) * 0.0005;
}
AnalogCard analogCard = AnalogCard();
float voltage = 220.0;
CurrentTransformerCard ct = CurrentTransformerCard(&analogCard, 0, &voltage, adc2current, 1000);
#endif
#ifdef SMART_VARIABLE_ENABLE
SmartVariable smartVar = SmartVariable();
#endif
#ifdef CLIMATE_CARD_ENABLE
// Climate Card
const char *mode_names[] = {"off", "fan_only", "cool"};
const char *fan_speed_names[] = {"auto"};
const uint16_t code_off[] = {3478, 1717, 452, 418, 452, 1285, 452, 418, 452, 424, 452, 419, 451, 420, 452, 419, 451, 424, 451, 420, 451, 420, 452, 419, 451, 424, 451, 419, 451, 1285, 452, 419, 452, 424, 451, 419, 452, 419, 452, 419, 452, 423, 452, 418, 451, 1284, 452, 1284, 451, 1289, 452, 419, 452, 418, 452, 1285, 452, 423, 451, 420, 451, 420, 452, 419, 452, 423, 451, 420, 452, 420, 451, 419, 452, 423, 452, 419, 452, 419, 452, 419, 452, 423, 452, 420, 451, 420, 451, 420, 451, 423, 452, 419, 452, 419, 452, 420, 451, 423, 452, 419, 452, 420, 451, 419, 452, 423, 452, 419, 452, 419, 452, 420, 451, 423, 452, 418, 452, 1283, 452, 1285, 452, 424, 451, 420, 451, 420, 451, 420, 451, 419, 451, 9924, 3478, 1716, 452, 419, 451, 1284, 453, 420, 450, 425, 450, 420, 452, 419, 452, 419, 452, 423, 452, 420, 451, 420, 451, 420, 451, 423, 452, 418, 451, 1286, 451, 420, 451, 423, 452, 420, 451, 420, 451, 420, 451, 424, 451, 418, 451, 1285, 503, 1232, 451, 1289, 452, 420, 451, 418, 452, 1285, 451, 424, 451, 421, 450, 420, 451, 421, 450, 424, 451, 420, 451, 421, 450, 420, 451, 424, 451, 420, 451, 420, 451, 419, 452, 424, 451, 420, 451, 420, 451, 419, 450, 1290, 451, 419, 450, 1287, 504, 367, 451, 423, 451, 419, 450, 1285, 451, 1286, 450, 423, 451, 1284, 451, 1286, 506, 365, 506, 369, 451, 420, 451, 419, 506, 366, 505, 369, 505, 367, 450, 420, 506, 364, 506, 1236, 508, 1227, 506, 1230, 508, 1228, 506, 1234, 511, 358, 506, 1231, 505, 365, 504, 1235, 507, 1230, 505, 364, 508, 1228, 505, 1236, 507, 364, 505, 366, 506, 365, 507, 368, 506, 366, 505, 365, 451, 420, 506, 369, 506, 365, 506, 365, 506, 364, 507, 368, 506, 365, 505, 1229, 508, 1228, 451, 1289, 507, 364, 507, 365, 506, 365, 504, 370, 451, 421, 506, 366, 504, 366, 450, 425, 504, 365, 450, 1285, 451, 1285, 450, 1291, 504, 366, 505, 367, 504, 367, 450, 425, 506, 365, 450, 421, 450, 421, 510, 366, 449, 421, 450, 421, 506, 365, 504, 371, 506, 364, 506, 366, 505, 366, 506, 367, 451, 1286, 506, 366, 449, 422, 532, 342, 451, 420, 507, 364, 507, 363, 504, 1238, 506, 365, 504, 367, 505, 366, 506, 370, 508, 364, 450, 422, 449, 421, 506, 372, 506, 364, 506, 366, 450, 421, 450, 427, 450, 421, 450, 421, 532, 339, 450, 425, 507, 1229, 450, 1285, 506, 1229, 451, 1292, 507, 364, 507, 365, 535, 336, 450, 422, 506};
const uint16_t code_cool_auto[3][439] = {
{3477, 1718, 451, 419, 451, 1286, 450, 421, 451, 425, 449, 421, 451, 421, 450, 420, 451, 424, 451, 420, 451, 421, 450, 421, 450, 424, 451, 419, 451, 1286, 451, 420, 451, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 419, 451, 1285, 450, 1285, 451, 1289, 452, 420, 450, 420, 450, 1287, 450, 424, 451, 421, 450, 420, 451, 420, 451, 424, 451, 421, 450, 421, 450, 421, 450, 425, 450, 421, 450, 421, 450, 421, 451, 424, 451, 420, 450, 421, 451, 420, 451, 424, 451, 420, 450, 421, 451, 421, 450, 424, 451, 421, 450, 420, 451, 421, 450, 424, 451, 421, 450, 421, 450, 421, 451, 423, 451, 419, 451, 1285, 450, 1286, 451, 424, 451, 420, 451, 420, 451, 421, 451, 418, 451, 9926, 3477, 1717, 451, 419, 451, 1286, 450, 421, 451, 423, 452, 421, 450, 420, 451, 420, 451, 424, 451, 420, 451, 420, 451, 421, 450, 424, 451, 419, 451, 1285, 452, 420, 451, 424, 450, 421, 451, 420, 451, 420, 451, 424, 451, 419, 450, 1285, 451, 1285, 451, 1289, 451, 421, 450, 419, 451, 1286, 451, 424, 451, 420, 451, 420, 451, 420, 451, 425, 450, 421, 450, 420, 451, 421, 450, 424, 451, 420, 451, 421, 450, 421, 450, 425, 451, 420, 451, 420, 451, 418, 451, 1290, 451, 419, 450, 1286, 452, 420, 450, 425, 451, 420, 451, 420, 451, 420, 451, 422, 451, 1285, 451, 1286, 450, 420, 452, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 420, 451, 420, 451, 418, 451, 1292, 451, 1284, 452, 1285, 450, 1285, 451, 1290, 451, 419, 450, 1287, 450, 419, 451, 1289, 450, 1287, 450, 419, 451, 1284, 451, 1290, 451, 420, 451, 421, 450, 421, 450, 424, 451, 421, 450, 421, 451, 420, 451, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 419, 450, 1284, 452, 1285, 451, 1289, 451, 420, 451, 420, 451, 420, 451, 424, 451, 420, 452, 420, 450, 421, 450, 425, 451, 418, 451, 1285, 451, 1284, 451, 1290, 450, 421, 451, 420, 451, 421, 450, 424, 451, 421, 450, 420, 451, 421, 450, 424, 451, 420, 451, 421, 450, 421, 450, 424, 451, 420, 452, 419, 451, 421, 451, 423, 450, 1286, 451, 420, 451, 420, 451, 424, 451, 420, 451, 420, 451, 419, 451, 1290, 451, 420, 451, 421, 451, 420, 451, 426, 451, 420, 451, 420, 451, 420, 451, 426, 452, 420, 451, 419, 451, 421, 451, 425, 452, 420, 451, 420, 451, 420, 451, 425, 451, 1286, 450, 421, 451, 418, 451, 1292, 451, 420, 452, 419, 452, 420, 451, 420, 451}, // 24
{3478, 1717, 452, 417, 452, 1285, 452, 420, 451, 424, 451, 420, 451, 419, 452, 420, 451, 424, 451, 420, 452, 419, 451, 420, 452, 423, 452, 418, 451, 1286, 451, 420, 452, 423, 451, 420, 451, 419, 452, 420, 452, 423, 451, 419, 451, 1284, 452, 1283, 452, 1289, 452, 419, 452, 418, 452, 1285, 452, 423, 452, 419, 452, 419, 452, 420, 451, 423, 452, 418, 453, 419, 452, 419, 452, 423, 452, 420, 451, 420, 451, 420, 451, 424, 451, 420, 451, 420, 451, 420, 452, 423, 451, 420, 451, 420, 452, 419, 452, 423, 452, 419, 452, 420, 451, 419, 452, 423, 452, 419, 452, 419, 452, 420, 456, 419, 451, 418, 452, 1283, 452, 1286, 451, 423, 452, 420, 451, 419, 452, 420, 451, 418, 452, 9925, 3477, 1718, 451, 418, 451, 1286, 451, 420, 452, 423, 452, 419, 452, 419, 452, 420, 451, 423, 452, 419, 452, 420, 451, 420, 451, 423, 452, 418, 452, 1285, 452, 419, 452, 423, 451, 420, 452, 419, 452, 420, 502, 372, 452, 417, 452, 1284, 452, 1284, 451, 1289, 452, 420, 451, 418, 452, 1285, 452, 422, 453, 418, 453, 419, 452, 420, 451, 423, 452, 420, 451, 419, 452, 420, 452, 422, 452, 419, 452, 420, 452, 419, 452, 424, 450, 420, 452, 419, 452, 418, 451, 1289, 452, 418, 452, 1285, 452, 419, 452, 423, 451, 419, 451, 1285, 452, 419, 452, 422, 452, 1284, 451, 1285, 452, 420, 451, 424, 451, 420, 451, 420, 451, 419, 453, 423, 451, 420, 452, 419, 451, 419, 451, 1292, 451, 1284, 452, 1284, 451, 1284, 452, 1289, 452, 417, 452, 1285, 452, 418, 452, 1287, 452, 1285, 452, 418, 452, 1283, 452, 1290, 451, 419, 452, 419, 452, 420, 451, 423, 452, 419, 452, 420, 451, 420, 451, 423, 452, 420, 452, 419, 452, 419, 452, 423, 452, 417, 452, 1284, 452, 1283, 452, 1289, 452, 419, 452, 419, 452, 420, 451, 423, 452, 420, 451, 420, 451, 420, 452, 423, 452, 417, 452, 1284, 452, 1283, 452, 1289, 452, 420, 451, 419, 452, 420, 451, 423, 452, 419, 452, 420, 451, 420, 451, 423, 452, 419, 452, 420, 451, 420, 451, 424, 451, 419, 452, 420, 452, 419, 452, 422, 451, 1286, 451, 420, 451, 420, 451, 424, 451, 420, 452, 418, 453, 417, 452, 1291, 451, 419, 452, 419, 452, 422, 449, 426, 451, 420, 451, 420, 451, 420, 452, 425, 451, 420, 452, 419, 452, 420, 451, 424, 453, 419, 452, 419, 452, 420, 452, 424, 451, 1284, 451, 1285, 453, 418, 451, 1291, 452, 420, 451, 420, 451, 420, 451, 421, 451}, // 25
{3478, 1717, 451, 419, 451, 1286, 451, 420, 451, 424, 451, 421, 450, 421, 450, 421, 451, 423, 451, 420, 452, 420, 451, 420, 451, 424, 451, 418, 451, 1286, 451, 420, 452, 423, 451, 421, 450, 421, 451, 420, 451, 424, 450, 419, 451, 1285, 451, 1284, 451, 1290, 451, 420, 451, 419, 451, 1286, 451, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 420, 451, 421, 450, 420, 451, 424, 451, 420, 451, 421, 450, 420, 451, 425, 450, 421, 450, 421, 450, 421, 451, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 419, 451, 1285, 450, 1287, 451, 424, 450, 420, 451, 420, 452, 420, 451, 418, 451, 9926, 3477, 1717, 451, 419, 451, 1286, 451, 420, 451, 424, 451, 421, 450, 420, 451, 420, 451, 424, 451, 420, 451, 420, 452, 419, 452, 424, 450, 419, 451, 1286, 451, 420, 451, 424, 451, 420, 451, 420, 451, 421, 450, 424, 451, 419, 451, 1285, 450, 1285, 451, 1290, 450, 421, 451, 418, 451, 1287, 451, 423, 451, 420, 451, 421, 450, 421, 451, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 420, 451, 420, 451, 420, 451, 424, 451, 421, 450, 420, 451, 419, 451, 1290, 450, 420, 450, 1286, 451, 421, 451, 423, 451, 421, 451, 418, 451, 1286, 451, 423, 451, 1284, 451, 1286, 451, 420, 451, 424, 451, 420, 451, 420, 451, 420, 451, 425, 450, 420, 451, 421, 450, 419, 451, 1292, 451, 1284, 451, 1286, 450, 1285, 451, 1290, 450, 419, 451, 1286, 451, 419, 451, 1288, 451, 1286, 456, 414, 450, 1285, 451, 1290, 450, 421, 451, 420, 451, 420, 451, 424, 451, 420, 451, 421, 450, 421, 450, 425, 450, 421, 450, 421, 451, 420, 451, 424, 451, 419, 450, 1285, 450, 1286, 450, 1290, 451, 420, 451, 420, 451, 420, 451, 424, 451, 420, 451, 421, 450, 421, 451, 424, 450, 419, 451, 1285, 450, 1285, 451, 1290, 450, 421, 451, 420, 451, 420, 451, 425, 450, 421, 450, 421, 450, 420, 451, 425, 450, 420, 451, 421, 451, 420, 451, 424, 450, 421, 450, 421, 451, 421, 450, 423, 450, 1286, 451, 421, 450, 421, 450, 425, 451, 419, 452, 420, 450, 420, 450, 1291, 451, 420, 451, 420, 451, 421, 450, 426, 451, 421, 450, 421, 451, 420, 451, 426, 451, 420, 451, 420, 451, 420, 451, 427, 450, 420, 452, 420, 450, 421, 451, 425, 450, 1287, 450, 419, 451, 1285, 450, 1293, 450, 421, 451, 420, 451, 420, 451, 421, 451} // 26
};
size_t getInfraredCode(uint8_t mode, uint8_t fan_speed, uint8_t temperature_index, const uint16_t **codePtr)
{
if (mode == 0)
{
// Off
*codePtr = &(code_off[0]);
return sizeof(code_off) / sizeof(uint16_t);
}
else if (mode == 1)
{
*codePtr = &(code_off[0]);
return sizeof(code_off) / sizeof(uint16_t);
}
else if (mode == 2)
{
// Cool
if (fan_speed == 0)
{
// Auto
Serial.printf("Returning cool auto code at temperature index %d\n", temperature_index);
*codePtr = &(code_cool_auto[temperature_index][0]);
return sizeof(code_cool_auto[temperature_index]) / sizeof(uint16_t);
}
else
{
// Didn't capture the other fan speeds yet
// Will do that later
*codePtr = &(code_off[0]);
return sizeof(code_off) / sizeof(uint16_t);
}
}
return 0;
}
AirConditioner ac = {
.max_temperature = 26,
.min_temperature = 24,
.modes = 3,
.mode_names = mode_names,
.fan_speeds = 4,
.fan_speed_names = fan_speed_names,
.getInfraredCode = &getInfraredCode};
ClimateCard climateCard = ClimateCard(4, ac, RMT_CHANNEL_1);
#endif
void input_change_callback(uint8_t pin, uint8_t value)
{
Serial.print("Input change callback: ");
Serial.print(pin);
Serial.print(" ");
Serial.println(value);
espmega.outputs.toggleState(pin);
}
void mqtt_callback(char *topic, char *payload)
{
Serial.print("MQTT Callback: ");
Serial.print(topic);
Serial.print(" ");
Serial.println(payload);
}
#ifdef WRITE_DEFAULT_NETCONF
void setNetworkConfig()
{
NetworkConfig config = {
.ip = {192, 168, 0, 249},
.gateway = {192, 168, 0, 1},
.subnet = {255, 255, 255, 0},
.dns1 = {10, 192, 1, 1},
.dns2 = {10, 192, 1, 1},
.useStaticIp = true,
.useWifi = false,
.wifiUseAuth = false,
};
strcpy(config.ssid, "ssid");
strcpy(config.password, "password");
strcpy(config.hostname, "espmega");
Serial.println("Setting network config");
espmega.iot->setNetworkConfig(config);
espmega.iot->saveNetworkConfig();
}
void setMqttConfig()
{
MqttConfig config = {
.mqtt_port = 1883,
.mqtt_useauth = false};
strcpy(config.mqtt_server, "192.168.0.26");
strcpy(config.base_topic, "/espmegacud");
espmega.iot->setMqttConfig(config);
espmega.iot->saveMqttConfig();
}
#endif
void setup()
{
ESP_LOGI("Initializer", "Starting ESPMegaPRO OOP demo");
espmega.begin();
#ifdef EXTERNAL_DIGITAL_OUTPUT_CARD_ENABLE
espmega.installCard(6, &externalDigitalOutputCard);
#endif
#ifdef EXTERNAL_DIGITAL_INPUT_CARD_ENABLE
espmega.installCard(7, &externalDigitalInputCard);
externalDigitalInputCard.registerCallback(handleExternalDigitalInput);
#endif
espmega.setTimezone("UTC-7");
ESP_LOGI("Initializer", "Enabling IOT module");
espmega.enableIotModule();
ESP_LOGI("Initializer", "Enabling Ethernet");
ETH.begin();
ESP_LOGI("Initializer", "Binding Ethernet to IOT module");
espmega.iot->bindEthernetInterface(&ETH);
#ifdef WRITE_DEFAULT_NETCONF
setNetworkConfig();
#else
ESP_LOGI("Initializer", "Loading network config");
espmega.iot->loadNetworkConfig();
#endif
ESP_LOGI("Initializer", "Connecting to network");
espmega.iot->connectNetwork();
#ifdef WRITE_DEFAULT_NETCONF
setMqttConfig();
#else
ESP_LOGI("Initializer", "Loading MQTT config");
espmega.iot->loadMqttConfig();
#endif
ESP_LOGI("Initializer", "Connecting to MQTT");
espmega.iot->connectToMqtt();
espmega.iot->registerMqttCallback(mqtt_callback);
// Set debounce time
for (uint8_t i = 0; i < 16; i++)
{
espmega.inputs.setDebounceTime(i, INPUT_DEBOUNCE_TIME_MS);
}
// Set all pin values to 4095
for (uint8_t i = 0; i < 16; i++)
{
espmega.outputs.setValue(i, 4095);
}
#ifdef MQTT_CARD_REGISTER
ESP_LOGI("Initializer", "Registering cards 0");
espmega.iot->registerCard(0);
ESP_LOGI("Initializer", "Registering cards 1");
espmega.iot->registerCard(1);
#ifdef EXTERNAL_DIGITAL_OUTPUT_CARD_ENABLE
ESP_LOGI("Initializer", "Registering cards 6");
espmega.iot->registerCard(6);
#endif
#ifdef EXTERNAL_DIGITAL_INPUT_CARD_ENABLE
ESP_LOGI("Initializer", "Registering cards 7");
espmega.iot->registerCard(7);
#endif
#endif
ESP_LOGI("Initializer", "Registering Input change callback");
espmega.inputs.registerCallback(input_change_callback);
#ifdef CLIMATE_CARD_ENABLE
ESP_LOGI("Initializer", "Installing climate card");
espmega.installCard(2, &climateCard);
ESP_LOGI("Initializer", "Binding climate card to FRAM");
climateCard.bindFRAM(&espmega.fram, 1001);
ESP_LOGI("Initializer", "Loading climate card state from FRAM");
climateCard.loadStateFromFRAM();
ESP_LOGI("Initializer", "Enabling climate card FRAM autosave");
climateCard.setFRAMAutoSave(true);
ESP_LOGI("Initializer", "Registering cards 2");
espmega.iot->registerCard(2);
#endif
#ifdef DISPLAY_ENABLE
ESP_LOGI("Initializer", "Enabling internal display");
espmega.enableInternalDisplay(&Serial);
ESP_LOGI("Initializer", "Binding climate card to internal display");
#ifdef CLIMATE_CARD_ENABLE
espmega.display->bindClimateCard(&climateCard);
#endif
#endif
#ifdef WEB_SERVER_ENABLE
ESP_LOGI("Initializer", "Enabling web server");
espmega.enableWebServer(80);
espmega.webServer->setWebUsername("admin");
espmega.webServer->setWebPassword("Passw0rd");
espmega.webServer->saveCredentialsToFRAM();
#endif
#ifdef LCD_OTA_ENABLE
internalDisplayOTA.begin("/display", espmega.display, espmega.webServer);
#endif
#ifdef REMOTE_VARIABLE_ENABLE
ESP_LOGI("Initializer", "Initializing testvar");
testVar.begin(32, "/xm/fan_speed", espmega.iot, true, "/pm/request_fan_speed");
testVar.enableSetValue("/pm/request_switch_state");
#endif
#ifdef CT_ENABLE
ESP_LOGI("Initializer", "Initializing analog card");
analogCard.begin();
ESP_LOGI("Initializer", "Initializing current transformer");
ct.begin();
ct.bindFRAM(&espmega.fram, 7000);
ct.loadEnergy();
ct.setEnergyAutoSave(true);
espmega.iot->registerCard(3);
#endif
#ifdef SMART_VARIABLE_ENABLE
ESP_LOGI("Initializer", "Initializing smart variable");
smartVar.begin(16);
ESP_LOGI("Initializer", "Binding smart variable to FRAM");
smartVar.bindFRAM(&espmega.fram, 8000);
ESP_LOGI("Initializer", "Enabling smart variable autosave");
smartVar.setValueAutoSave(true);
ESP_LOGI("Initializer", "Enabling IoT for smart variable");
smartVar.enableIoT(espmega.iot, "/smartvar");
ESP_LOGI("Initializer", "Enabling smart variable set value");
smartVar.enableSetValue("/smartvar/set");
ESP_LOGI("Initializer", "Enabling smart variable value request");
smartVar.enableValueRequest("/smartvar/request");
#endif
ESP_LOGI("Initializer", "Setup complete");
}
void loop()
{
espmega.loop();
#ifdef FRAM_DEBUG
// Every 20 seconds, dump FRAM 0-500 to serial
static uint32_t last_fram_dump = 0;
if (millis() - last_fram_dump >= 20000)
{
last_fram_dump = millis();
Serial.println("Dumping FRAM");
espmega.dumpFRAMtoSerial(0, 500);
Serial.println("Dumping FRAM ASCII");
espmega.dumpFRAMtoSerialASCII(0, 500);
}
#endif
// Every 5 seconds, publish "I'm alive" to MQTT
#ifdef MQTT_DEBUG
static uint32_t last_mqtt_publish = 0;
if (millis() - last_mqtt_publish >= 5000)
{
last_mqtt_publish = millis();
espmega.iot->publish("/espmegai/alive", "true");
}
static uint32_t last_mqtt_status = 0;
if (millis() - last_mqtt_status >= 1000)
{
last_mqtt_status = millis();
Serial.print("MQTT Status: ");
Serial.println(espmega.iot->mqttConnected() ? "Connected" : "Disconnected");
}
#endif
#ifdef REMOTE_VARIABLE_ENABLE
// Print out testvar value every 5 seconds
static uint32_t last_testvar_print = 0;
if (millis() - last_testvar_print >= 5000)
{
last_testvar_print = millis();
if (testVar.getValue() != nullptr)
ESP_LOGI("TestVar", "Value: %s", testVar.getValue());
testVar.setValue("Hello World");
}
#endif
#ifdef CT_ENABLE
ct.loop();
static uint32_t last_ct_print = 0;
if (millis() - last_ct_print >= 1000)
{
last_ct_print = millis();
Serial.print("Current: ");
Serial.println(ct.getCurrent());
}
#endif
#ifdef SMART_VARIABLE_ENABLE
static uint32_t last_smartvar_print = 0;
if (millis() - last_smartvar_print >= 1000)
{
last_smartvar_print = millis();
Serial.print("SmartVar: ");
Serial.println(smartVar.getValue());
}
static bool last_smartvar_state = false;
static uint32_t last_smartvar_state_change = 0;
if (millis() - last_smartvar_state_change >= 5000)
{
last_smartvar_state_change = millis();
last_smartvar_state = !last_smartvar_state;
smartVar.setValue(last_smartvar_state ? "true" : "false");
}
#endif
#ifdef RTC_DEBUG
static uint32_t last_time_print = 0;
if (millis() - last_time_print >= 1000)
{
last_time_print = millis();
rtctime_t time = espmega.getTime();
Serial.printf("Time: %02d:%02d:%02d %02d/%02d/%04d\n", time.hours, time.minutes, time.seconds, time.day, time.month, time.year);
}
#endif
}

View File

@ -1,25 +0,0 @@
#include <ESPMegaProOS.hpp>
ESPMegaPRO espmega = ESPMegaPRO();
void setup() {
espmega.begin();
uint8_t outputPinMap[16] = {8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7};
espmega.outputs.loadPinMap(outputPinMap);
}
void loop() {
espmega.loop();
// Loop through all outputs, turning them on one at a time
// for 1.5 seconds each
for (uint8_t i = 0; i < 16; i++) {
Serial.printf("Turning on output %d\n", i);
espmega.outputs.digitalWrite(i, true);
// Print out the state of all outputs
for (uint8_t j = 0; j < 16; j++) {
Serial.printf("Output %d: %d\n", j, espmega.outputs.getState(j));
}
delay(1500);
espmega.outputs.digitalWrite(i, false);
}
}

View File

@ -1,29 +0,0 @@
#include <ESPMegaProOS.hpp>
#include <IRBlaster.hpp>
#include <IRReceiver.hpp>
ESPMegaPRO espmega = ESPMegaPRO();
IRBlaster irBlaster = IRBlaster(4);
uint16_t data[] = { 2441, 579, 621, 580, 620, 580, 620, 580, 621, 579, 621, 579, 1220, 580, 1220, 580, 1247, 554, 619, 580, 621, 580, 619, 581, 619, 26392, 2420, 580, 621, 580, 620, 579, 620, 581, 619, 581, 620, 580, 1219, 581, 1220, 580, 1220, 581, 619, 580, 620, 580, 620, 580, 621, 26385, 2421, 580, 621, 580, 620, 580, 620, 579, 621, 579, 621, 579, 1221, 579, 1221, 580, 1219, 580, 621, 580, 620, 580, 621, 579, 621, 26385, 2421, 580, 621, 579, 620, 580, 620, 580, 621, 579, 621, 579, 1221, 579, 1221, 580, 1219, 580, 621, 580, 620, 580, 620, 580, 621, 26384, 2422, 580, 621, 579, 621, 579, 620, 580, 620, 580, 621, 579, 1221, 579, 1221, 580, 1220, 580, 620, 580, 620, 579, 620, 581, 620};
void setup() {
espmega.begin();
IRReceiver::begin(15);
irBlaster.send(data, sizeof(data)/sizeof(uint16_t));
}
void loop() {
Serial.println("Starting long receive");
IRReceiver::start_long_receive();
delay(5000);
Serial.println("Ending long receive");
ir_data_t data = IRReceiver::end_long_receive();
Serial.printf("Recived %d data points\n", data.size);
for (size_t i = 0; i < data.size; i++) {
Serial.print(data.data[i]);
Serial.print(i == data.size-1 ? "\n" : ", ");
}
free(data.data);
}

View File

@ -1,18 +0,0 @@
{
"configurations": [
{
"name": "windows-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "gcc",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "windows-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

View File

@ -1,24 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": true,
"cwd": "d:/Git/ESPMegaPRO-v3-SDK/IRLearner",
"program": "d:/Git/ESPMegaPRO-v3-SDK/IRLearner/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

View File

@ -1,59 +0,0 @@
{
"C_Cpp_Runner.cCompilerPath": "gcc",
"C_Cpp_Runner.cppCompilerPath": "g++",
"C_Cpp_Runner.debuggerPath": "gdb",
"C_Cpp_Runner.cStandard": "",
"C_Cpp_Runner.cppStandard": "",
"C_Cpp_Runner.msvcBatchPath": "",
"C_Cpp_Runner.useMsvc": false,
"C_Cpp_Runner.warnings": [
"-Wall",
"-Wextra",
"-Wpedantic",
"-Wshadow",
"-Wformat=2",
"-Wcast-align",
"-Wconversion",
"-Wsign-conversion",
"-Wnull-dereference"
],
"C_Cpp_Runner.msvcWarnings": [
"/W4",
"/permissive-",
"/w14242",
"/w14287",
"/w14296",
"/w14311",
"/w14826",
"/w44062",
"/w44242",
"/w14905",
"/w14906",
"/w14263",
"/w44265",
"/w14928"
],
"C_Cpp_Runner.enableWarnings": true,
"C_Cpp_Runner.warningsAsError": false,
"C_Cpp_Runner.compilerArgs": [],
"C_Cpp_Runner.linkerArgs": [],
"C_Cpp_Runner.includePaths": [],
"C_Cpp_Runner.includeSearch": [
"*",
"**/*"
],
"C_Cpp_Runner.excludeSearch": [
"**/build",
"**/build/**",
"**/.*",
"**/.*/**",
"**/.vscode",
"**/.vscode/**"
],
"C_Cpp_Runner.useAddressSanitizer": false,
"C_Cpp_Runner.useUndefinedSanitizer": false,
"C_Cpp_Runner.useLeakSanitizer": false,
"C_Cpp_Runner.showCompilationTime": false,
"C_Cpp_Runner.useLinkTimeOptimization": false,
"C_Cpp_Runner.msvcSecureNoWarnings": false
}

View File

View File

@ -12,5 +12,6 @@
platform = espressif32
board = wt32-eth01
framework = arduino
lib_deps = siwats/ESPMegaPROR3@^2.0.7
lib_deps = siwats/ESPMegaPROR3@^1.3.0
z3t0/IRremote@^4.2.0
monitor_speed = 115200

32
IRLearner/src/main.cpp Normal file
View File

@ -0,0 +1,32 @@
#include <ESPMegaPRO.h>
// Infrared Transciever
#define IR_RECIEVE_PIN 35
#define IR_SEND_PIN 17
//#define MARK_EXCESS_MICROS 20
#define RAW_BUFFER_LENGTH 1024
uint16_t rawTicks[] = {2404, 597, 1204, 596, 603, 597, 1203, 598, 603, 597, 1203, 597, 603, 597, 603, 597, 1203, 596, 604, 597, 604, 596, 603, 597, 603, 25810, 2402, 597, 1204, 596, 604, 597, 1203, 597, 603, 597, 1203, 597, 603, 597, 603, 597, 1203, 597, 603, 597, 603, 597, 603, 598, 602, 25804, 2404, 596, 1204, 597, 603, 597, 1203, 597, 603, 597, 1203, 597, 603, 597, 603, 597, 1204, 596, 603, 597, 603, 597, 603, 597, 604, 25803, 2404, 597, 1203, 597, 603, 597, 1203, 597, 603, 597, 1203, 597, 603, 597, 603, 598, 1202, 598, 602, 597, 603, 598, 602, 598, 602, 25805, 2403, 597, 1203, 597, 603, 597, 1203, 597, 603, 598, 1202, 597, 603, 597, 603, 597, 1203, 597, 603, 598, 602, 598, 602, 598, 602, 25804, 2404, 597, 1202, 598, 602, 598, 1202, 598, 602, 598, 1202, 599, 602, 598, 601, 598, 1203, 597, 602, 599, 602, 598, 602, 598, 602, 25804, 2403, 598, 1202, 599, 601, 599, 1202, 598, 601, 598, 1202, 599, 601, 599, 602, 598, 1202, 598, 602, 598, 602, 597, 603, 598, 602, 25805, 2403, 598, 1201, 599, 601, 599, 1202, 598, 602, 598, 1202, 598, 602, 599, 601, 598, 1203, 597, 602, 599, 602, 598, 602, 597, 603};
#include <IRremote.hpp>
void setup()
{
Serial.begin(115200);
// IrReceiver.begin(IR_RECIEVE_PIN);
IrSender.begin(IR_SEND_PIN);
}
void loop()
{
// if (IrReceiver.decode())
// {
// Serial.println();
// IrReceiver.printIRSendUsage(&Serial);
// IrReceiver.compensateAndPrintIRResultAsCArray(&Serial, false);
// Serial.println();
// Serial.println();
// IrReceiver.resume();
// }
//IrSender.sendRaw(ir_code_a,sizeof(ir_code_a)/sizeof(ir_code_a[0]),NEC_KHZ);
// IrSender.sendRaw(ir_code_b,sizeof(ir_code_b)/sizeof(ir_code_b[0]),NEC_KHZ);
IrSender.sendRaw(rawTicks,sizeof(rawTicks)/sizeof(rawTicks[0]),NEC_KHZ);
//IrSender.sendSony(0x1, 0x15, 2, 12);
delay(500);
}

73
IRLearner/src/main.cpp.d Normal file
View File

@ -0,0 +1,73 @@
#include <ESPMegaPRO.h>
/*
Author: AnalysIR
Revision: 1.0
This code is provided to overcome an issue with Arduino IR libraries
It allows you to capture raw timings for signals longer than 255 marks & spaces.
Typical use case is for long Air conditioner signals.
You can use the output to plug back into IRremote, to resend the signal.
This Software was written by AnalysIR.
Usage: Free to use, subject to conditions posted on blog below.
Please credit AnalysIR and provide a link to our website/blog, where possible.
Copyright AnalysIR 2014
Please refer to the blog posting for conditions associated with use.
http://www.analysir.com/blog/2014/03/19/air-conditioners-problems-recording-long-infrared-remote-control-signals-arduino/
Connections:
IR Receiver Arduino
V+ -> +5v
GND -> GND
Signal Out -> Digital Pin 2
(If using a 3V Arduino, you may connect V+ to +3V)
*/
void rxIR_Interrupt_Handler();
#define LEDPIN 17
//you may increase this value on Arduinos with greater than 2k SRAM
#define maxLen 800
volatile unsigned int irBuffer[maxLen]; //stores timings - volatile because changed by ISR
volatile unsigned int x = 0; //Pointer thru irBuffer - volatile because changed by ISR
void setup() {
Serial.begin(115200); //change BAUD rate as required
attachInterrupt(digitalPinToInterrupt(35), rxIR_Interrupt_Handler, CHANGE);//set up ISR for receiving IR signal
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(F("Press the button on the remote now - once only"));
delay(5000); // pause 5 secs
if (x) { //if a signal is captured
digitalWrite(LEDPIN, HIGH);//visual indicator that signal received
Serial.println();
Serial.print(F("Raw: (")); //dump raw header format - for library
Serial.print((x - 1));
Serial.print(F(") "));
detachInterrupt(0);//stop interrupts & capture until finshed here
for (int i = 1; i < x; i++) { //now dump the times
if (!(i & 0x1)) Serial.print(F("-"));
Serial.print(irBuffer[i] - irBuffer[i - 1]);
Serial.print(F(", "));
}
x = 0;
Serial.println();
Serial.println();
digitalWrite(LEDPIN, LOW);//end of visual indicator, for this time
attachInterrupt(0, rxIR_Interrupt_Handler, CHANGE);//re-enable ISR for receiving IR signal
}
}
void rxIR_Interrupt_Handler() {
if (x > maxLen) return; //ignore if irBuffer is already full
irBuffer[x++] = micros(); //just continually record the time-stamp of signal transitions
}

1
IRLearner/src/test.txt Normal file
View File

@ -0,0 +1 @@
{438, 430, 435, 430, 436, 430, 435, 430, 436, 430, 435, 25095, 3467, 1727, 436, 1295, 436, 430, 435, 430, 436, 430, 435, 1295, 436, 430, 436, 430, 436, 429, 435, 431, 436, 1294, 436, 429, 436, 1295, 437, 1294, 437, 429, 436, 1295, 437, 1294, 436, 1295, 436, 1296, 435, 1295, 436, 430, 436, 429, 436, 1295, 436, 432, 434, 429, 436, 429, 436, 430, 436, 429, 436, 431, 434, 431, 435, 430, 436, 430, 436, 429, 436, 1295, 436, 429, 436, 1296, 435, 430, 436, 430, 436, 429, 436, 1295, 436, 1295, 435, 430, 436, 430, 436, 429, 436, 430, 436, 1295, 436, 429, 436, 430, 435, 430, 436, 430, 435, 430, 436, 429, 436, 430, 436, 430, 435, 429, 437, 429, 436, 430, 436, 1295, 436, 1295, 435, 1296, 436, 430, 435, 430, 435, 1296, 436, 1295, 436, 1296, 435, 35481, 3466, 1728, 436, 1296, 435, 429, 436, 430, 436, 429, 436, 1295, 436, 429, 436, 430, 436, 430, 435, 430, 436, 1295, 436, 429, 436, 1295, 436, 1295, 436, 430, 436, 1295, 436, 1295, 436, 1295, 436, 1295, 436, 1295, 436, 429, 436, 430, 436, 1295, 436, 429, 436, 429, 436, 430, 436, 430, 436, 429, 436, 430, 435, 430, 436, 430, 435, 430, 436, 430, 435, 430, 436, 1294, 437, 430, 436, 429, 436, 429, 436, 430, 436, 1295, 436, 429, 437, 429, 436, 430, 435, 431, 435, 429, 436, 430, 436, 430, 436, 429, 436, 430, 435, 430, 436, 430, 436, 429, 435, 430, 437, 1295, 435, 430, 436, 430, 435, 431, 435, 429, 437, 429, 436, 1295, 436, 429, 436, 429, 437, 1295, 436, 1295, 436, 429, 436, 35481, 3466, 1728, 436, 1295, 436, 429, 436, 430, 436, 430, 435, 1296, 436, 429, 436, 429, 436, 430, 436, 429, 436, 1295, 436, 430, 436, 1294, 437, 1296, 435, 429, 436, 1294, 437, 1295, 436, 1295, 436, 1296, 435, 1295, 436, 430, 436, 429, 436, 1295, 435, 431, 435, 430, 436, 430, 435, 430, 436, 430, 435, 430, 436, 430, 435, 430, 436, 430, 435, 430, 436, 429, 437, 429, 436, 429, 437, 429, 436, 429, 436, 430, 435, 430, 436, 429, 436, 1296, 435, 430, 436, 430, 436, 1295, 436, 1295, 436, 1294, 437, 429, 436, 431, 434, 430, 436, 430, 436, 1295, 436, 429, 436, 1295, 436, 1295, 436, 430, 435, 430, 436, 430, 435, 430, 436, 430, 435, 430, 436, 429, 436, 429, 437, 430, 436, 429, 436, 1295, 435, 1296, 436, 1295, 436, 1295, 436, 1295, 436, 1295, 436, 1295, 436, 430, 435, 430, 436, 430, 435, 429, 437, 429, 436, 430, 436, 429, 436, 430, 436, 429, 436, 430, 435, 431, 435, 429, 436, 430, 436, 429, 436, 430, 436, 430, 435, 430, 436, 429, 436, 1295, 436, 1296, 435, 430, 436, 430, 436, 430, 435, 430, 435, 430, 436, 430, 436, 429, 436, 429, 436, 430, 436, 429, 436, 1296, 435, 1295, 437, 429, 436, 429, 437, 429, 436, 429, 437, 429, 436, 430, 435, 430, 436, 429, 436, 430, 436, 429, 436, 430, 436, 429, 436, 429, 436, 430, 436, 430, 437, 427, 437, 430, 435, 1296, 436, 429, 436, 1295, 436, 430, 435, 430, 436, 430, 436, 1295, 435, 1296, 435, 430, 436, 430, 435, 430, 436, 430, 436, 429, 436, 430, 435, 430, 436, 430, 435, 430, 436, 429, 436, 430, 436, 1295, 436, 430, 436, 430, 435, 430, 435, 430, 436, 1295, 436, 430, 436, 430, 435, 430, 435, 1295, 436, 1295, 436, 430, 436, 429, 436}

1
IRLearner/src/test2.txt Normal file
View File

@ -0,0 +1 @@
2404, 597, 1204, 596, 603, 597, 1203, 598, 603, 597, 1203, 597, 603, 597, 603, 597, 1203, 596, 604, 597, 604, 596, 603, 597, 603, 25810, 2402, 597, 1204, 596, 604, 597, 1203, 597, 603, 597, 1203, 597, 603, 597, 603, 597, 1203, 597, 603, 597, 603, 597, 603, 598, 602, 25804, 2404, 596, 1204, 597, 603, 597, 1203, 597, 603, 597, 1203, 597, 603, 597, 603, 597, 1204, 596, 603, 597, 603, 597, 603, 597, 604, 25803, 2404, 597, 1203, 597, 603, 597, 1203, 597, 603, 597, 1203, 597, 603, 597, 603, 598, 1202, 598, 602, 597, 603, 598, 602, 598, 602, 25805, 2403, 597, 1203, 597, 603, 597, 1203, 597, 603, 598, 1202, 597, 603, 597, 603, 597, 1203, 597, 603, 598, 602, 598, 602, 598, 602, 25804, 2404, 597, 1202, 598, 602, 598, 1202, 598, 602, 598, 1202, 599, 602, 598, 601, 598, 1203, 597, 602, 599, 602, 598, 602, 598, 602, 25804, 2403, 598, 1202, 599, 601, 599, 1202, 598, 601, 598, 1202, 599, 601, 599, 602, 598, 1202, 598, 602, 598, 602, 597, 603, 598, 602, 25805, 2403, 598, 1201, 599, 601, 599, 1202, 598, 602, 598, 1202, 598, 602, 599, 601, 598, 1203, 597, 602, 599, 602, 598, 602, 597, 603,

Some files were not shown because too many files have changed in this diff Show More