Compare commits

..

No commits in common. "main" and "R3.3b" have entirely different histories.
main ... R3.3b

119 changed files with 224 additions and 13695 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,10 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
]
}

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,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

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,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

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,11 +0,0 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html

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,4 @@
platform = espressif32
board = wt32-eth01
framework = arduino
lib_deps = siwats/ESPMegaPROR3@^2.0.7
monitor_speed = 115200
lib_deps = siwats/espmegapror3

View File

@ -0,0 +1,51 @@
#include <ESPMegaPRO.h>
// Inputs
const int DEBOUNCE_TIME_MS = 50;
const int virtual_interrupt_pins[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int virtual_interupt_state[16];
unsigned long virtual_interupt_timer[16];
// Outputs
const int pwm_pins[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int pwm_states[16];
int pwm_values[16];
// Forward declaration
void virtual_interrupt_loop();
void virtual_interrupt_callback(int pin, int state);
void setup()
{
ESPMega_begin();
}
void loop()
{
ESPMega_loop();
}
void virtual_interrupt_callback(int pin, int state)
{
}
void virtual_interrupt_loop()
{
for (int i = 0; i < 16; i++)
{
int current_pin_value = ESPMega_digitalRead(virtual_interrupt_pins[i]);
if (virtual_interupt_state[i] != current_pin_value)
{
if (millis() - virtual_interupt_timer[i] > DEBOUNCE_TIME_MS)
{
virtual_interupt_state[i] = current_pin_value;
virtual_interrupt_callback(i, current_pin_value);
}
}
else
{
virtual_interupt_timer[i] = millis();
}
yield();
}
}

15
Template Project/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"files.associations": {
"array": "cpp",
"deque": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"string_view": "cpp",
"initializer_list": "cpp",
"adafruit_ads1x15.h": "c"
},
"cmake.configureOnOpen": true,
"cmake.sourceDirectory": "D:/Git/ESPMegaPRO-v3-SDK/Template Project/.pio/libdeps/wt32-eth01/Adafruit BusIO"
}

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