diff --git a/Arduino_Sketch/HASwitchPlate/HASwitchPlate.ino b/Arduino_Sketch/HASwitchPlate/HASwitchPlate.ino deleted file mode 100644 index 7508c07..0000000 --- a/Arduino_Sketch/HASwitchPlate/HASwitchPlate.ino +++ /dev/null @@ -1,3905 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////////////////////////// -// _____ _____ _____ _____ -// | | | _ | __| _ | -// | | |__ | __| -// |__|__|__|__|_____|__| -// Home Automation Switch Plate -// https://github.com/aderusha/HASwitchPlate -// -// Copyright (c) 2022 Allen Derusha allen@derusha.org -// -// MIT License -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of this hardware, -// software, and associated documentation files (the "Product"), to deal in the Product without -// restriction, including without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Product, and to permit persons to whom the -// Product is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all copies or -// substantial portions of the Product. -// -// THE PRODUCT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE PRODUCT OR THE USE OR OTHER DEALINGS IN THE PRODUCT. -//////////////////////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// These defaults may be overwritten with values saved by the web interface -char wifiSSID[32] = ""; -char wifiPass[64] = ""; -char mqttServer[128] = ""; -char mqttPort[6] = "1883"; -char mqttUser[128] = ""; -char mqttPassword[128] = ""; -char mqttFingerprint[60] = ""; -char haspNode[16] = "plate01"; -char groupName[16] = "plates"; -char hassDiscovery[128] = "homeassistant"; -char configUser[32] = "admin"; -char configPassword[32] = ""; -char motionPinConfig[3] = "0"; -char nextionBaud[7] = "115200"; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -const float haspVersion = 1.06; // Current HASPone software release version -const uint16_t mqttMaxPacketSize = 2048; // Size of buffer for incoming MQTT message -byte nextionReturnBuffer[128]; // Byte array to pass around data coming from the panel -uint8_t nextionReturnIndex = 0; // Index for nextionReturnBuffer -int8_t nextionActivePage = -1; // Track active LCD page -bool lcdConnected = false; // Set to true when we've heard something from the LCD -const char wifiConfigPass[9] = "hasplate"; // First-time config WPA2 password -const char wifiConfigAP[14] = "HASwitchPlate"; // First-time config SSID -bool shouldSaveConfig = false; // Flag to save json config to SPIFFS -bool nextionReportPage0 = false; // If false, don't report page 0 sendme -const unsigned long updateCheckInterval = 43200000; // Time in msec between update checks (12 hours) -unsigned long updateCheckTimer = updateCheckInterval; // Timer for update check -unsigned long updateCheckFirstRun = 60000; // First-run check offset -bool updateEspAvailable = false; // Flag for update check to report new ESP FW version -float updateEspAvailableVersion; // Float to hold the new ESP FW version number -bool updateLcdAvailable = false; // Flag for update check to report new LCD FW version -unsigned long debugTimer = 0; // Clock for debug performance profiling -bool debugSerialEnabled = true; // Enable USB serial debug output -const unsigned long debugSerialBaud = 115200; // Desired baud rate for serial debug output -bool debugTelnetEnabled = false; // Enable telnet debug output -bool nextionBufferOverrun = false; // Set to true if an overrun error was encountered -bool nextionAckEnable = false; // Wait for each Nextion command to be acked before continuing -bool nextionAckReceived = false; // Ack was received -bool rebootOnp0b1 = false; // When true, reboot device on button press of p[0].b[1] -bool rebootOnLongPress = true; // When true, reboot device on long press of any button -unsigned long rebootOnLongPressTimer = 0; // Clock for long press reboot timer -unsigned long rebootOnLongPressTimeout = 10000; // Timeout value for long press reboot timer -const unsigned long nextionAckTimeout = 1000; // Timeout to wait for an ack before throwing error -unsigned long nextionAckTimer = 0; // Timer to track Nextion ack -const unsigned long telnetInputMax = 128; // Size of user input buffer for user telnet session -bool motionEnabled = false; // Motion sensor is enabled -bool mdnsEnabled = true; // mDNS enabled -bool ignoreTouchWhenOff = false; // Ignore touch events when backlight is off and instead send mqtt msg -bool beepEnabled = false; // Keypress beep enabled -unsigned long beepOnTime = 1000; // milliseconds of on-time for beep -unsigned long beepOffTime = 1000; // milliseconds of off-time for beep -unsigned int beepCounter; // Count the number of beeps -uint8_t beepPin = D2; // define beep pin output -uint8_t motionPin = 0; // GPIO input pin for motion sensor if connected and enabled -bool motionActive = false; // Motion is being detected -const unsigned long motionLatchTimeout = 1000; // Latch time for motion sensor -const unsigned long motionBufferTimeout = 100; // Trigger threshold time for motion sensor -unsigned long lcdVersion = 0; // Int to hold current LCD FW version number -unsigned long updateLcdAvailableVersion; // Int to hold the new LCD FW version number -bool lcdVersionQueryFlag = false; // Flag to set if we've queried lcdVersion -const String lcdVersionQuery = "p[0].b[2].val"; // Object ID for lcdVersion in HMI -uint8_t lcdBacklightDim = 0; // Backlight dimmer value -bool lcdBacklightOn = 0; // Backlight on/off -bool lcdBacklightQueryFlag = false; // Flag to set if we've queried lcdBacklightDim -bool startupCompleteFlag = false; // Startup process has completed -const unsigned long statusUpdateInterval = 300000; // Time in msec between publishing MQTT status updates (5 minutes) -unsigned long statusUpdateTimer = 0; // Timer for status update -const unsigned long connectTimeout = 300; // Timeout for WiFi and MQTT connection attempts in seconds -const unsigned long reConnectTimeout = 60; // Timeout for WiFi reconnection attempts in seconds -byte espMac[6]; // Byte array to store our MAC address -bool mqttTlsEnabled = false; // Enable MQTT client TLS connections -bool mqttPingCheck = false; // MQTT broker ping check result -bool mqttPortCheck = false; // MQTT broke port check result -String mqttClientId; // Auto-generated MQTT ClientID -String mqttGetSubtopic; // MQTT subtopic for incoming commands requesting .val -String mqttStateTopic; // MQTT topic for outgoing panel interactions -String mqttStateJSONTopic; // MQTT topic for outgoing panel interactions in JSON format -String mqttCommandTopic; // MQTT topic for incoming panel commands -String mqttGroupCommandTopic; // MQTT topic for incoming group panel commands -String mqttStatusTopic; // MQTT topic for publishing device connectivity state -String mqttSensorTopic; // MQTT topic for publishing device information in JSON format -String mqttLightCommandTopic; // MQTT topic for incoming panel backlight on/off commands -String mqttLightStateTopic; // MQTT topic for outgoing panel backlight on/off state -String mqttLightBrightCommandTopic; // MQTT topic for incoming panel backlight dimmer commands -String mqttLightBrightStateTopic; // MQTT topic for outgoing panel backlight dimmer state -String mqttMotionStateTopic; // MQTT topic for outgoing motion sensor state -String nextionModel; // Record reported model number of LCD panel -const byte nextionSuffix[] = {0xFF, 0xFF, 0xFF}; // Standard suffix for Nextion commands -uint8_t nextionMaxPages = 11; // Maximum number of pages in Nextion project -uint32_t tftFileSize = 0; // Filesize for TFT firmware upload -const uint8_t nextionResetPin = D6; // Pin for Nextion power rail switch (GPIO12/D6) -const unsigned long nextionSpeeds[] = {2400, - 4800, - 9600, - 19200, - 31250, - 38400, - 57600, - 115200, - 230400, - 250000, - 256000, - 512000, - 921600}; // Valid serial speeds for Nextion communication -const uint8_t nextionSpeedsLength = sizeof(nextionSpeeds) / sizeof(nextionSpeeds[0]); // Size of our list of speeds - -WiFiClientSecure mqttClientSecure; // TLS-enabled WiFiClient for MQTT -WiFiClient wifiClient; // Standard WiFiClient -MQTTClient mqttClient(mqttMaxPacketSize); // MQTT client -ESP8266WebServer webServer(80); // Admin web server on port 80 -ESP8266HTTPUpdateServer httpOTAUpdate; // Arduino OTA server -WiFiServer telnetServer(23); // Telnet server (if enabled) -WiFiClient telnetClient; // Telnet client -MDNSResponder::hMDNSService hMDNSService; // mDNS -EspSaveCrash SaveCrash; // Save crash details to flash - -// URL for auto-update check of "version.json" -const char UPDATE_URL[] PROGMEM = "https://raw.githubusercontent.com/HASwitchPlate/HASPone/main/update/version.json"; -// Additional CSS style to match Hass theme -const char HASP_STYLE[] PROGMEM = ""; -// Default link to compiled Arduino firmware image -String espFirmwareUrl = "https://raw.githubusercontent.com/HASwitchPlate/HASPone/main/Arduino_Sketch/HASwitchPlate.ino.d1_mini.bin"; -// Default link to compiled Nextion firmware images -String lcdFirmwareUrl = "https://raw.githubusercontent.com/HASwitchPlate/HASPone/main/Nextion_HMI/HASwitchPlate.tft"; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void setup() -{ // System setup - debugPrint(String(F("\n\n================================================================================\n"))); - debugPrintln(String(F("SYSTEM: Starting HASPone v")) + String(haspVersion)); - debugPrintln(String(F("SYSTEM: heapFree: ")) + String(ESP.getFreeHeap()) + String(F(" heapMaxFreeBlockSize: ")) + String(ESP.getMaxFreeBlockSize())); - debugPrintln(String(F("SYSTEM: Last reset reason: ")) + String(ESP.getResetInfo())); - if (SaveCrash.count()) - { - debugPrint(String(F("SYSTEM: Crashdump data discovered:"))); - debugPrintCrash(); - } - debugPrint(String(F("================================================================================\n\n"))); - - pinMode(nextionResetPin, OUTPUT); // Take control over the power switch for the LCD - digitalWrite(nextionResetPin, HIGH); // Power on the LCD - configRead(); // Check filesystem for a saved config.json - Serial.begin(atoi(nextionBaud)); // Serial - LCD RX (after swap), debug TX - Serial1.begin(atoi(nextionBaud)); // Serial1 - LCD TX, no RX - Serial.swap(); // Swap to allow hardware UART comms to LCD - - if (!nextionConnect()) - { - if (lcdConnected) - { - debugPrintln(F("HMI: LCD responding but initialization wasn't completed. Continuing program load anyway.")); - } - else - { - debugPrintln(F("HMI: LCD not responding, continuing program load")); - } - } - - espWifiConnect(); // Start up networking - - if ((configPassword[0] != '\0') && (configUser[0] != '\0')) - { // Start the webserver with our assigned password if it's been configured... - httpOTAUpdate.setup(&webServer, "/update", configUser, configPassword); - } - else - { // or without a password if not - httpOTAUpdate.setup(&webServer, "/update"); - } - - webServer.on("/", webHandleRoot); - webServer.on("/saveConfig", webHandleSaveConfig); - webServer.on("/resetConfig", webHandleResetConfig); - webServer.on("/resetBacklight", webHandleResetBacklight); - webServer.on("/firmware", webHandleFirmware); - webServer.on("/espfirmware", webHandleEspFirmware); - webServer.on( - "/lcdupload", HTTP_POST, []() - { webServer.send(200); }, - webHandleLcdUpload); - webServer.on("/tftFileSize", webHandleTftFileSize); - webServer.on("/lcddownload", webHandleLcdDownload); - webServer.on("/lcdOtaSuccess", webHandleLcdUpdateSuccess); - webServer.on("/lcdOtaFailure", webHandleLcdUpdateFailure); - webServer.on("/reboot", webHandleReboot); - webServer.onNotFound(webHandleNotFound); - webServer.begin(); - debugPrintln(String(F("HTTP: Server started @ http://")) + WiFi.localIP().toString()); - - espSetupOta(); // Start OTA firmware update - - motionSetup(); // Setup motion sensor if configured - - mqttConnect(); // Connect to MQTT - - if (mdnsEnabled) - { // Setup mDNS service discovery if enabled - hMDNSService = MDNS.addService(haspNode, "http", "tcp", 80); - if (debugTelnetEnabled) - { - MDNS.addService(haspNode, "telnet", "tcp", 23); - } - MDNS.addServiceTxt(hMDNSService, "app_name", "HASwitchPlate"); - MDNS.addServiceTxt(hMDNSService, "app_version", String(haspVersion).c_str()); - MDNS.update(); - } - - if (beepEnabled) - { // Setup beep/tactile output if configured - pinMode(beepPin, OUTPUT); - } - - if (debugTelnetEnabled) - { // Setup telnet server for remote debug output - telnetServer.setNoDelay(true); - telnetServer.begin(); - debugPrintln(String(F("TELNET: debug server enabled at telnet:")) + WiFi.localIP().toString()); - } - - debugPrintln(F("SYSTEM: System init complete.")); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void loop() -{ // Main execution loop - while ((WiFi.status() != WL_CONNECTED) || (WiFi.localIP().toString() == "0.0.0.0")) - { // Check WiFi is connected and that we have a valid IP, retry until we do. - if (WiFi.status() == WL_CONNECTED) - { // If we're currently connected, disconnect so we can try again - WiFi.disconnect(); - } - espWifiReconnect(); - } - - if (!mqttClient.connected()) - { // Check MQTT connection - debugPrintln(String(F("MQTT: not connected, connecting."))); - mqttConnect(); - } - nextionHandleInput(); // Nextion serial communications loop - mqttClient.loop(); // MQTT client loop - ArduinoOTA.handle(); // Arduino OTA loop - webServer.handleClient(); // webServer loop - telnetHandleClient(); // telnet client loop - motionHandle(); // motion sensor loop - beepHandle(); // beep feedback loop - - if (mdnsEnabled) - { - MDNS.update(); - } - - if ((millis() - statusUpdateTimer) >= statusUpdateInterval) - { // Run periodic status update - statusUpdateTimer = millis(); - mqttStatusUpdate(); - } - - if (((millis() - updateCheckTimer) >= updateCheckInterval) && (millis() > updateCheckFirstRun)) - { // Run periodic update check - updateCheckTimer = millis(); - if (updateCheck()) - { // Publish new status if updateCheck() worked and reset the timer - statusUpdateTimer = millis(); - mqttStatusUpdate(); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Functions - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void mqttConnect() -{ // MQTT connection and subscriptions - - static bool mqttFirstConnect = true; // For the first connection, we want to send an OFF/ON state to - // trigger any automations, but skip that if we reconnect while - // still running the sketch - rebootOnp0b1 = true; - static uint8_t mqttReconnectCount = 0; - unsigned long mqttConnectTimer = 0; - const unsigned long mqttConnectTimeout = 5000; - - // Check to see if we have a broker configured and notify the user if not - if (strcmp(mqttServer, "") == 0) - { - nextionSendCmd("page 0"); - nextionSetAttr("p[0].b[1].font", "6"); - nextionSetAttr("p[0].b[1].txt", "\"WiFi Connected!\\r " + String(WiFi.SSID()) + "\\rIP: " + WiFi.localIP().toString() + "\\r\\rConfigure MQTT:\\rhttp://" + WiFi.localIP().toString() + "\""); - while (strcmp(mqttServer, "") == 0) - { // Handle other stuff while we're waiting for MQTT to be configured - yield(); - nextionHandleInput(); // Nextion serial communications loop - ArduinoOTA.handle(); // Arduino OTA loop - webServer.handleClient(); // webServer loop - telnetHandleClient(); // telnet client loop - motionHandle(); // motion sensor loop - beepHandle(); // beep feedback loop - } - } - - if (mqttTlsEnabled) - { // Create MQTT service object with TLS connection - mqttClient.begin(mqttServer, atoi(mqttPort), mqttClientSecure); - if (strcmp(mqttFingerprint, "") == 0) - { - debugPrintln(String(F("MQTT: Configuring MQTT TLS connection without fingerprint validation."))); - mqttClientSecure.setInsecure(); - } - else - { - debugPrintln(String(F("MQTT: Configuring MQTT TLS connection with fingerprint validation."))); - mqttClientSecure.allowSelfSignedCerts(); - mqttClientSecure.setFingerprint(mqttFingerprint); - } - mqttClientSecure.setBufferSizes(512, 512); - } - else - { // Create MQTT service object without TLS connection - debugPrintln(String(F("MQTT: Configuring MQTT connection without TLS."))); - mqttClient.begin(mqttServer, atoi(mqttPort), wifiClient); - } - - mqttClient.onMessage(mqttProcessInput); // Setup MQTT callback function - - // MQTT topic string definitions - mqttStateTopic = "hasp/" + String(haspNode) + "/state"; - mqttStateJSONTopic = "hasp/" + String(haspNode) + "/state/json"; - mqttCommandTopic = "hasp/" + String(haspNode) + "/command"; - mqttGroupCommandTopic = "hasp/" + String(groupName) + "/command"; - mqttStatusTopic = "hasp/" + String(haspNode) + "/status"; - mqttSensorTopic = "hasp/" + String(haspNode) + "/sensor"; - mqttLightCommandTopic = "hasp/" + String(haspNode) + "/light/switch"; - mqttLightStateTopic = "hasp/" + String(haspNode) + "/light/state"; - mqttLightBrightCommandTopic = "hasp/" + String(haspNode) + "/brightness/set"; - mqttLightBrightStateTopic = "hasp/" + String(haspNode) + "/brightness/state"; - mqttMotionStateTopic = "hasp/" + String(haspNode) + "/motion/state"; - - const String mqttCommandSubscription = mqttCommandTopic + "/#"; - const String mqttGroupCommandSubscription = mqttGroupCommandTopic + "/#"; - const String mqttLightSubscription = mqttLightCommandTopic + "/#"; - const String mqttLightBrightSubscription = mqttLightBrightCommandTopic + "/#"; - - // Generate an MQTT client ID as haspNode + our MAC address - mqttClientId = String(haspNode) + "-" + String(espMac[0], HEX) + String(espMac[1], HEX) + String(espMac[2], HEX) + String(espMac[3], HEX) + String(espMac[4], HEX) + String(espMac[5], HEX); - nextionSendCmd("page 0"); - nextionSetAttr("p[0].b[1].font", "6"); - nextionSetAttr("p[0].b[1].txt", "\"WiFi Connected!\\r " + String(WiFi.SSID()) + "\\rIP: " + WiFi.localIP().toString() + "\\r\\rMQTT Connecting:\\r " + String(mqttServer) + "\""); - if (mqttTlsEnabled) - { - debugPrintln(String(F("MQTT: Attempting connection to broker ")) + String(mqttServer) + String(F(" on port ")) + String(mqttPort) + String(F(" with TLS enabled as clientID ")) + mqttClientId); - } - else - { - debugPrintln(String(F("MQTT: Attempting connection to broker ")) + String(mqttServer) + String(F(" on port ")) + String(mqttPort) + String(F(" with TLS disabled as clientID ")) + mqttClientId); - } - - // Set keepAlive, cleanSession, timeout - mqttClient.setOptions(30, true, mqttConnectTimeout); - - // declare LWT - mqttClient.setWill(mqttStatusTopic.c_str(), "OFF", true, 1); - - while (!mqttClient.connected()) - { // Loop until we're connected to MQTT - mqttConnectTimer = millis(); - mqttClient.connect(mqttClientId.c_str(), mqttUser, mqttPassword, false); - - if (mqttClient.connected()) - { // Attempt to connect to broker, setting last will and testament - // Update panel with MQTT status - nextionSetAttr("p[0].b[1].txt", "\"WiFi Connected!\\r " + String(WiFi.SSID()) + "\\rIP: " + WiFi.localIP().toString() + "\\r\\rMQTT Connected:\\r " + String(mqttServer) + "\""); - debugPrintln(F("MQTT: connected")); - - // Reset our diagnostic booleans - mqttPingCheck = true; - mqttPortCheck = true; - - // Subscribe to our incoming topics - if (mqttClient.subscribe(mqttCommandSubscription)) - { - debugPrintln(String(F("MQTT: subscribed to ")) + mqttCommandSubscription); - } - if (mqttClient.subscribe(mqttGroupCommandSubscription)) - { - debugPrintln(String(F("MQTT: subscribed to ")) + mqttGroupCommandSubscription); - } - if (mqttClient.subscribe(mqttLightSubscription)) - { - debugPrintln(String(F("MQTT: subscribed to ")) + mqttLightSubscription); - } - if (mqttClient.subscribe(mqttLightBrightSubscription)) - { - debugPrintln(String(F("MQTT: subscribed to ")) + mqttLightBrightSubscription); - } - - // Publish discovery configuration - mqttDiscovery(); - - // Publish backlight status - if (lcdBacklightOn) - { - debugPrintln(String(F("MQTT OUT: '")) + mqttLightStateTopic + String(F("' : 'ON'"))); - mqttClient.publish(mqttLightStateTopic, "ON", true, 1); - } - else - { - debugPrintln(String(F("MQTT OUT: '")) + mqttLightStateTopic + String(F("' : 'OFF'"))); - mqttClient.publish(mqttLightStateTopic, "OFF", true, 1); - } - debugPrintln(String(F("MQTT OUT: '")) + mqttLightBrightStateTopic + String(F("' : ")) + String(lcdBacklightDim)); - mqttClient.publish(mqttLightBrightStateTopic, String(lcdBacklightDim), true, 1); - - if (mqttFirstConnect) - { // Force any subscribed clients to toggle OFF/ON when we first connect to - // make sure we get a full panel refresh at power on. Sending OFF, - // "ON" will be sent by the mqttStatusTopic subscription action below. - mqttFirstConnect = false; - debugPrintln(String(F("MQTT OUT: '")) + mqttStatusTopic + "' : 'OFF'"); - mqttClient.publish(mqttStatusTopic, "OFF", true, 0); - } - - if (mqttClient.subscribe(mqttStatusTopic)) - { - debugPrintln(String(F("MQTT: subscribed to ")) + mqttStatusTopic); - } - mqttClient.loop(); - } - else - { // Retry until we give up and restart after connectTimeout seconds - mqttReconnectCount++; - if (mqttReconnectCount * mqttConnectTimeout * 6 > (connectTimeout * 1000)) - { - debugPrintln(String(F("MQTT: connection attempt ")) + String(mqttReconnectCount) + String(F(" failed with rc: ")) + String(mqttClient.returnCode()) + String(F(" and error: ")) + String(mqttClient.lastError()) + String(F(". Restarting device."))); - espReset(); - } - yield(); - webServer.handleClient(); - - String mqttCheckResult = "Ping: FAILED"; - String mqttCheckResultNextion = "MQTT Check..."; - - debugPrintln(String(F("MQTT: connection attempt ")) + String(mqttReconnectCount) + String(F(" failed with rc ")) + String(mqttClient.returnCode()) + String(F(" and error: ")) + String(mqttClient.lastError())); - nextionSetAttr("p[0].b[1].txt", String(F("\"WiFi Connected!\\r ")) + String(WiFi.SSID()) + String(F("\\rIP: ")) + WiFi.localIP().toString() + String(F("\\r\\rMQTT Failed:\\r ")) + String(mqttServer) + String(F("\\rRC: ")) + String(mqttClient.returnCode()) + String(F(" Error: ")) + String(mqttClient.lastError()) + String(F("\\r")) + mqttCheckResultNextion + String(F("\""))); - - mqttPingCheck = Ping.ping(mqttServer, 4); - yield(); - webServer.handleClient(); - mqttPortCheck = wifiClient.connect(mqttServer, atoi(mqttPort)); - yield(); - webServer.handleClient(); - - mqttCheckResultNextion = "Ping: "; - if (mqttPingCheck) - { - mqttCheckResult = "Ping: SUCCESS"; - mqttCheckResultNextion = "Ping: "; - } - if (mqttPortCheck) - { - mqttCheckResult += " Port: SUCCESS"; - mqttCheckResultNextion += " Port: "; - } - else - { - mqttCheckResult += " Port: FAILED"; - mqttCheckResultNextion += " Port: "; - } - debugPrintln(String(F("MQTT: connection checks: ")) + mqttCheckResult + String(F(". Trying again in 30 seconds."))); - nextionSetAttr("p[0].b[1].txt", String(F("\"WiFi Connected!\\r ")) + String(WiFi.SSID()) + String(F("\\rIP: ")) + WiFi.localIP().toString() + String(F("\\r\\rMQTT Failed:\\r ")) + String(mqttServer) + String(F("\\rRC: ")) + String(mqttClient.returnCode()) + String(F(" Error: ")) + String(mqttClient.lastError()) + String(F("\\r")) + mqttCheckResultNextion + String(F("\""))); - - while (millis() < (mqttConnectTimer + (mqttConnectTimeout * 6))) - { - yield(); - nextionHandleInput(); // Nextion serial communications loop - ArduinoOTA.handle(); // Arduino OTA loop - webServer.handleClient(); // webServer loop - telnetHandleClient(); // telnet client loop - motionHandle(); // motion sensor loop - beepHandle(); // beep feedback loop - } - } - } - rebootOnp0b1 = false; - if (nextionActivePage < 0) - { // We never picked up a message giving us a page number, so we'll just go to the default page - debugPrintln(String(F("DEBUG: NextionActivePage not received from MQTT, setting to 0"))); - String mqttButtonJSONEvent = String(F("{\"event\":\"page\",\"value\":0}")); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent, false, 0); - String mqttPageTopic = mqttStateTopic + "/page"; - debugPrintln(String(F("MQTT OUT: '")) + mqttPageTopic + String(F("' : '0'"))); - mqttClient.publish(mqttPageTopic, "0", false, 0); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void mqttProcessInput(String &strTopic, String &strPayload) -{ // Handle incoming commands from MQTT - - // strTopic: homeassistant/haswitchplate/devicename/command/p[1].b[4].txt - // strPayload: "Lights On" - // subTopic: p[1].b[4].txt - - // Incoming Namespace (replace /device/ with /group/ for group commands) - // '[...]/device/command' -m '' == No command requested, respond with mqttStatusUpdate() - // '[...]/device/command' -m 'dim=50' == nextionSendCmd("dim=50") - // '[...]/device/command/json' -m '["dim=5", "page 1"]' == nextionSendCmd("dim=50"), nextionSendCmd("page 1") - // '[...]/device/command/p[1].b[4].txt' -m '' == nextionGetAttr("p[1].b[4].txt") - // '[...]/device/command/p[1].b[4].txt' -m '"Lights On"' == nextionSetAttr("p[1].b[4].txt", "\"Lights On\"") - // '[...]/device/brightness/set' -m '50' == nextionSendCmd("dims=50") - // '[...]/device/light/switch' -m 'OFF' == nextionSendCmd("dims=0") - // '[...]/device/command/page' -m '1' == nextionSendCmd("page 1") - // '[...]/device/command/statusupdate' -m '' == mqttStatusUpdate() - // '[...]/device/command/discovery' -m '' == call mqttDiscovery() - // '[...]/device/command/lcdupdate' -m 'http://192.168.0.10/local/HASwitchPlate.tft' == nextionOtaStartDownload("http://192.168.0.10/local/HASwitchPlate.tft") - // '[...]/device/command/lcdupdate' -m '' == nextionOtaStartDownload("lcdFirmwareUrl") - // '[...]/device/command/espupdate' -m 'http://192.168.0.10/local/HASwitchPlate.ino.d1_mini.bin' == espStartOta("http://192.168.0.10/local/HASwitchPlate.ino.d1_mini.bin") - // '[...]/device/command/espupdate' -m '' == espStartOta("espFirmwareUrl") - // '[...]/device/command/beep' -m '100,200,3' == beep on for 100msec, off for 200msec, repeat 3 times - // '[...]/device/command/hassdiscovery' -m 'homeassistant' == hassDiscovery = homeassistant - // '[...]/device/command/nextionmaxpages' -m '11' == nextionmaxpages = 11 - // '[...]/device/command/nextionbaud' -m '921600' == nextionBaud = 921600 - // '[...]/device/command/debugserialenabled' -m 'true' == enable serial debug output - // '[...]/device/command/debugtelnetenabled' -m 'true' == enable telnet debug output - // '[...]/device/command/mdnsenabled' -m 'true' == enable mDNS responder - // '[...]/device/command/beepenabled' -m 'true' == enable beep output on keypress - // '[...]/device/command/ignoretouchwhenoff' -m 'true' == disable actions on keypress - - debugPrintln(String(F("MQTT IN: '")) + strTopic + String(F("' : '")) + strPayload + String(F("'"))); - - if (((strTopic == mqttCommandTopic) || (strTopic == mqttGroupCommandTopic)) && (strPayload == "")) - { // '[...]/device/command' -m '' = No command requested, respond with mqttStatusUpdate() - mqttStatusUpdate(); // return status JSON via MQTT - } - else if (strTopic == mqttCommandTopic || strTopic == mqttGroupCommandTopic) - { // '[...]/device/command' -m 'dim=50' == nextionSendCmd("dim=50") - nextionSendCmd(strPayload); - } - else if (strTopic == (mqttCommandTopic + "/page") || strTopic == (mqttGroupCommandTopic + "/page")) - { // '[...]/device/command/page' -m '1' == nextionSendCmd("page 1") - if (strPayload == "") - { - nextionSendCmd("sendme"); - } - else - { - nextionActivePage = strPayload.toInt(); - nextionSendCmd("page " + strPayload); - } - } - else if (strTopic == (mqttCommandTopic + "/json") || strTopic == (mqttGroupCommandTopic + "/json")) - { // '[...]/device/command/json' -m '["dim=5", "page 1"]' = nextionSendCmd("dim=50"), nextionSendCmd("page 1") - if (strPayload != "") - { - nextionParseJson(strPayload); // Send to nextionParseJson() - } - } - else if (strTopic == (mqttCommandTopic + "/statusupdate") || strTopic == (mqttGroupCommandTopic + "/statusupdate")) - { // '[...]/device/command/statusupdate' == mqttStatusUpdate() - mqttStatusUpdate(); // return status JSON via MQTT - } - else if (strTopic == (mqttCommandTopic + "/discovery") || strTopic == (mqttGroupCommandTopic + "/discovery")) - { // '[...]/device/command/discovery' == mqttDiscovery() - mqttDiscovery(); // send Home Assistant discovery message via MQTT - } - else if (strTopic == (mqttCommandTopic + "/hassdiscovery") || strTopic == (mqttGroupCommandTopic + "/hassdiscovery")) - { // '[...]/device/command/hassdiscovery' -m 'homeassistant' == hassDiscovery = homeassistant - strPayload.toCharArray(hassDiscovery, 128); // set hassDiscovery to value provided in payload - configSave(); - mqttDiscovery(); // send Home Assistant discovery message on new discovery topic via MQTT - } - else if ((strTopic == (mqttCommandTopic + "/nextionmaxpages") || strTopic == (mqttGroupCommandTopic + "/nextionmaxpages")) && (strPayload.toInt() < 256) && (strPayload.toInt() > 0)) - { // '[...]/device/command/nextionmaxpages' -m '11' == nextionmaxpages = 11 - nextionMaxPages = strPayload.toInt(); // set nextionMaxPages to value provided in payload - configSave(); - mqttDiscovery(); // send Home Assistant discovery message via MQTT - } - else if ((strTopic == (mqttCommandTopic + "/nextionbaud") || strTopic == (mqttGroupCommandTopic + "/nextionbaud")) && - ((strPayload.toInt() == 2400) || - (strPayload.toInt() == 4800) || - (strPayload.toInt() == 9600) || - (strPayload.toInt() == 19200) || - (strPayload.toInt() == 31250) || - (strPayload.toInt() == 38400) || - (strPayload.toInt() == 57600) || - (strPayload.toInt() == 115200) || - (strPayload.toInt() == 230400) || - (strPayload.toInt() == 250000) || - (strPayload.toInt() == 256000) || - (strPayload.toInt() == 512000) || - (strPayload.toInt() == 921600))) - { // '[...]/device/command/nextionbaud' -m '921600' == nextionBaud = 921600 - strPayload.toCharArray(nextionBaud, 7); // set nextionBaud to value provided in payload - nextionAckEnable = false; - nextionSendCmd("bauds=" + strPayload); // send baud rate to nextion - nextionAckEnable = true; - Serial.flush(); - Serial1.flush(); - Serial.end(); - Serial1.end(); - Serial.begin(atoi(nextionBaud)); // Serial - LCD RX (after swap), debug TX - Serial1.begin(atoi(nextionBaud)); // Serial1 - LCD TX, no RX - Serial.swap(); // Swap to allow hardware UART comms to LCD - configSave(); - } - else if (strTopic == (mqttCommandTopic + "/debugserialenabled") || strTopic == (mqttGroupCommandTopic + "/debugserialenabled")) - { // '[...]/device/command/debugserialenabled' -m 'true' == enable serial debug output - if (strPayload.equalsIgnoreCase("true")) - { - debugSerialEnabled = true; - configSave(); - } - else if (strPayload.equalsIgnoreCase("false")) - { - debugSerialEnabled = false; - configSave(); - } - } - else if (strTopic == (mqttCommandTopic + "/debugtelnetenabled") || strTopic == (mqttGroupCommandTopic + "/debugtelnetenabled")) - { // '[...]/device/command/debugtelnetenabled' -m 'true' == enable telnet debug output - if (strPayload.equalsIgnoreCase("true")) - { - debugTelnetEnabled = true; - configSave(); - } - else if (strPayload.equalsIgnoreCase("false")) - { - debugTelnetEnabled = false; - configSave(); - } - } - else if (strTopic == (mqttCommandTopic + "/mdnsenabled") || strTopic == (mqttGroupCommandTopic + "/mdnsenabled")) - { // '[...]/device/command/mdnsenabled' -m 'true' == enable mDNS responder - if (strPayload.equalsIgnoreCase("true")) - { - mdnsEnabled = true; - configSave(); - } - else if (strPayload.equalsIgnoreCase("false")) - { - mdnsEnabled = false; - configSave(); - } - } - else if (strTopic == (mqttCommandTopic + "/beepenabled") || strTopic == (mqttGroupCommandTopic + "/beepenabled")) - { // '[...]/device/command/beepenabled' -m 'true' == enable beep output on keypress - if (strPayload.equalsIgnoreCase("true")) - { - beepEnabled = true; - configSave(); - } - else if (strPayload.equalsIgnoreCase("false")) - { - beepEnabled = false; - configSave(); - } - } - else if (strTopic == (mqttCommandTopic + "/ignoretouchwhenoff") || strTopic == (mqttGroupCommandTopic + "/ignoretouchwhenoff")) - { // '[...]/device/command/ignoretouchwhenoff' -m 'true' == disable actions on keypress - if (strPayload.equalsIgnoreCase("true")) - { - ignoreTouchWhenOff = true; - configSave(); - } - else if (strPayload.equalsIgnoreCase("false")) - { - ignoreTouchWhenOff = false; - configSave(); - } - } - else if (strTopic == (mqttCommandTopic + "/lcdupdate") || strTopic == (mqttGroupCommandTopic + "/lcdupdate")) - { // '[...]/device/command/lcdupdate' -m 'http://192.168.0.10/local/HASwitchPlate.tft' == nextionOtaStartDownload("http://192.168.0.10/local/HASwitchPlate.tft") - if (strPayload == "") - { - nextionOtaStartDownload(lcdFirmwareUrl); - } - else - { - nextionOtaStartDownload(strPayload); - } - } - else if (strTopic == (mqttCommandTopic + "/espupdate") || strTopic == (mqttGroupCommandTopic + "/espupdate")) - { // '[...]/device/command/espupdate' -m 'http://192.168.0.10/local/HASwitchPlate.ino.d1_mini.bin' == espStartOta("http://192.168.0.10/local/HASwitchPlate.ino.d1_mini.bin") - if (strPayload == "") - { - espStartOta(espFirmwareUrl); - } - else - { - espStartOta(strPayload); - } - } - else if (strTopic == (mqttCommandTopic + "/reboot") || strTopic == (mqttGroupCommandTopic + "/reboot")) - { // '[...]/device/command/reboot' == reboot microcontroller - debugPrintln(F("MQTT: Rebooting device")); - espReset(); - } - else if (strTopic == (mqttCommandTopic + "/lcdreboot") || strTopic == (mqttGroupCommandTopic + "/lcdreboot")) - { // '[...]/device/command/lcdreboot' == reboot LCD panel - debugPrintln(F("MQTT: Rebooting LCD")); - nextionReset(); - } - else if (strTopic == (mqttCommandTopic + "/factoryreset") || strTopic == (mqttGroupCommandTopic + "/factoryreset")) - { // '[...]/device/command/factoryreset' == clear all saved settings - configClearSaved(); - } - else if (strTopic == (mqttCommandTopic + "/beep") || strTopic == (mqttGroupCommandTopic + "/beep")) - { // '[...]/device/command/beep' == activate beep function - String mqttvar1 = getSubtringField(strPayload, ',', 0); - String mqttvar2 = getSubtringField(strPayload, ',', 1); - String mqttvar3 = getSubtringField(strPayload, ',', 2); - - beepOnTime = mqttvar1.toInt(); - beepOffTime = mqttvar2.toInt(); - beepCounter = mqttvar3.toInt(); - } - else if (strTopic == (mqttCommandTopic + "/crashtest")) - { // '[...]/device/command/crashtest' -m 'divzero' == divide by zero - if (strPayload == "divzero") - { - debugPrintln(String(F("DEBUG: attempt to divide by zero"))); - int result, zero; - zero = 0; - result = 1 / zero; - debugPrintln(String(F("DEBUG: div zero result: ")) + String(result)); - } - else if (strPayload == "nullptr") - { // '[...]/device/command/crashtest' -m 'nullptr' == dereference a null pointer - debugPrintln(String(F("DEBUG: attempt to dereference null pointer"))); - int *nullPointer = NULL; - debugPrintln(String(F("DEBUG: dereference null pointer: ")) + String(*nullPointer)); - } - else if (strPayload == "wdt") - { // '[...]/device/command/crashtest' -m 'wdt' == trigger soft WDT - debugPrintln(String(F("DEBUG: enter tight loop and cause WDT"))); - while (true) - { - } - } - } - else if (strTopic.startsWith(mqttCommandTopic) && (strPayload == "")) - { // '[...]/device/command/p[1].b[4].txt' -m '' == nextionGetAttr("p[1].b[4].txt") - String subTopic = strTopic.substring(mqttCommandTopic.length() + 1); - mqttGetSubtopic = "/" + subTopic; - nextionGetAttr(subTopic); - } - else if (strTopic.startsWith(mqttGroupCommandTopic) && (strPayload == "")) - { // '[...]/group/command/p[1].b[4].txt' -m '' == nextionGetAttr("p[1].b[4].txt") - String subTopic = strTopic.substring(mqttGroupCommandTopic.length() + 1); - mqttGetSubtopic = "/" + subTopic; - nextionGetAttr(subTopic); - } - else if (strTopic.startsWith(mqttCommandTopic)) - { // '[...]/device/command/p[1].b[4].txt' -m '"Lights On"' == nextionSetAttr("p[1].b[4].txt", "\"Lights On\"") - String subTopic = strTopic.substring(mqttCommandTopic.length() + 1); - nextionSetAttr(subTopic, strPayload); - } - else if (strTopic.startsWith(mqttGroupCommandTopic)) - { // '[...]/group/command/p[1].b[4].txt' -m '"Lights On"' == nextionSetAttr("p[1].b[4].txt", "\"Lights On\"") - String subTopic = strTopic.substring(mqttGroupCommandTopic.length() + 1); - nextionSetAttr(subTopic, strPayload); - } - else if (strTopic == mqttLightBrightCommandTopic) - { // change the brightness from the light topic - nextionSetAttr("dim", strPayload); - nextionSetAttr("dims", "dim"); - lcdBacklightDim = strPayload.toInt(); - debugPrintln(String(F("MQTT OUT: '")) + mqttLightBrightStateTopic + String(F("' : '")) + strPayload + String(F("'"))); - mqttClient.publish(mqttLightBrightStateTopic, strPayload, true, 0); - } - else if (strTopic == mqttLightCommandTopic && strPayload == "OFF") - { // set the panel dim OFF from the light topic, saving current dim level first - nextionSetAttr("dims", "dim"); - nextionSetAttr("dim", "0"); - lcdBacklightOn = 0; - debugPrintln(String(F("MQTT OUT: '")) + mqttLightStateTopic + String(F("' : 'OFF'"))); - mqttClient.publish(mqttLightStateTopic, "OFF", true, 0); - } - else if (strTopic == mqttLightCommandTopic && strPayload == "ON") - { // set the panel dim ON from the light topic, restoring saved dim level - nextionSetAttr("dim", "dims"); - nextionSetAttr("sleep", "0"); - lcdBacklightOn = 1; - debugPrintln(String(F("MQTT OUT: '")) + mqttLightStateTopic + String(F("' : 'ON'"))); - mqttClient.publish(mqttLightStateTopic, "ON", true, 0); - } - else if (strTopic == mqttStatusTopic && strPayload == "OFF") - { // catch a dangling LWT from a previous connection if it appears - debugPrintln(String(F("MQTT OUT: '")) + mqttStatusTopic + String(F("' : 'ON'"))); - mqttClient.publish(mqttStatusTopic, "ON", true, 0); - mqttClient.publish(mqttStateJSONTopic, String(F("{\"event_type\":\"hasp_device\",\"event\":\"online\"}"))); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F(" : {\"event_type\":\"hasp_device\",\"event\":\"online\"}"))); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void mqttStatusUpdate() -{ // Periodically publish system status - String mqttSensorPayload = "{"; - mqttSensorPayload += String(F("\"espVersion\":")) + String(haspVersion) + String(F(",")); - if (updateEspAvailable) - { - mqttSensorPayload += String(F("\"updateEspAvailable\":true,")); - } - else - { - mqttSensorPayload += String(F("\"updateEspAvailable\":false,")); - } - if (lcdConnected) - { - mqttSensorPayload += String(F("\"lcdConnected\":true,")); - } - else - { - mqttSensorPayload += String(F("\"lcdConnected\":false,")); - } - mqttSensorPayload += String(F("\"lcdVersion\":\"")) + String(lcdVersion) + String(F("\",")); - if (updateLcdAvailable) - { - mqttSensorPayload += String(F("\"updateLcdAvailable\":true,")); - } - else - { - mqttSensorPayload += String(F("\"updateLcdAvailable\":false,")); - } - mqttSensorPayload += String(F("\"espUptime\":")) + String(long(millis() / 1000)) + String(F(",")); - mqttSensorPayload += String(F("\"signalStrength\":")) + String(WiFi.RSSI()) + String(F(",")); - mqttSensorPayload += String(F("\"haspName\":\"")) + String(haspNode) + String(F("\",")); - mqttSensorPayload += String(F("\"haspIP\":\"")) + WiFi.localIP().toString() + String(F("\",")); - mqttSensorPayload += String(F("\"haspClientID\":\"")) + mqttClientId + String(F("\",")); - mqttSensorPayload += String(F("\"haspMac\":\"")) + String(espMac[0], HEX) + String(F(":")) + String(espMac[1], HEX) + String(F(":")) + String(espMac[2], HEX) + String(F(":")) + String(espMac[3], HEX) + String(F(":")) + String(espMac[4], HEX) + String(F(":")) + String(espMac[5], HEX) + String(F("\",")); - mqttSensorPayload += String(F("\"haspManufacturer\":\"HASwitchPlate\",\"haspModel\":\"HASPone v1.0.0\",")); - mqttSensorPayload += String(F("\"heapFree\":")) + String(ESP.getFreeHeap()) + String(F(",")); - mqttSensorPayload += String(F("\"heapFragmentation\":")) + String(ESP.getHeapFragmentation()) + String(F(",")); - mqttSensorPayload += String(F("\"heapMaxFreeBlockSize\":")) + String(ESP.getMaxFreeBlockSize()) + String(F(",")); - mqttSensorPayload += String(F("\"espCore\":\"")) + String(ESP.getCoreVersion()) + String(F("\"")); - mqttSensorPayload += "}"; - - // Publish sensor JSON - mqttClient.publish(mqttSensorTopic, mqttSensorPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttSensorTopic + String(F("' : '")) + mqttSensorPayload + String(F("'"))); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void mqttDiscovery() -{ // Publish Home Assistant discovery messages - - String macAddress = String(espMac[0], HEX) + String(F(":")) + String(espMac[1], HEX) + String(F(":")) + String(espMac[2], HEX) + String(F(":")) + String(espMac[3], HEX) + String(F(":")) + String(espMac[4], HEX) + String(F(":")) + String(espMac[5], HEX); - - // light discovery for backlight - String mqttDiscoveryTopic = String(hassDiscovery) + String(F("/light/")) + String(haspNode) + String(F("/config")); - String mqttDiscoveryPayload = String(F("{\"name\":\"")) + String(haspNode) + String(F(" backlight\",\"command_topic\":\"")) + mqttLightCommandTopic + String(F("\",\"state_topic\":\"")) + mqttLightStateTopic + String(F("\",\"brightness_state_topic\":\"")) + mqttLightBrightStateTopic + String(F("\",\"brightness_command_topic\":\"")) + mqttLightBrightCommandTopic + String(F("\",\"availability_topic\":\"")) + mqttStatusTopic + String(F("\",\"brightness_scale\":100,\"unique_id\":\"")) + mqttClientId + String(F("-backlight\",\"payload_on\":\"ON\",\"payload_off\":\"OFF\",\"payload_available\":\"ON\",\"payload_not_available\":\"OFF\",\"device\":{\"identifiers\":[\"")) + mqttClientId + String(F("\"],\"name\":\"")) + String(haspNode) + String(F("\",\"manufacturer\":\"HASwitchPlate\",\"model\":\"HASPone v1.0.0\",\"sw_version\":")) + String(haspVersion) + String(F("}}")); - mqttClient.publish(mqttDiscoveryTopic, mqttDiscoveryPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttDiscoveryTopic + String(F("' : '")) + String(mqttDiscoveryPayload) + String(F("'"))); - - // sensor discovery for device telemetry - mqttDiscoveryTopic = String(hassDiscovery) + String(F("/sensor/")) + String(haspNode) + String(F("/config")); - mqttDiscoveryPayload = String(F("{\"name\":\"")) + String(haspNode) + String(F(" sensor\",\"json_attributes_topic\":\"")) + mqttSensorTopic + String(F("\",\"state_topic\":\"")) + mqttStatusTopic + String(F("\",\"unique_id\":\"")) + mqttClientId + String(F("-sensor\",\"icon\":\"mdi:cellphone-text\",\"device\":{\"identifiers\":[\"")) + mqttClientId + String(F("\"],\"name\":\"")) + String(haspNode) + String(F("\",\"manufacturer\":\"HASwitchPlate\",\"model\":\"HASPone v1.0.0\",\"sw_version\":")) + String(haspVersion) + String(F("}}")); - mqttClient.publish(mqttDiscoveryTopic, mqttDiscoveryPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttDiscoveryTopic + String(F("' : '")) + String(mqttDiscoveryPayload) + String(F("'"))); - - // number discovery for active page - mqttDiscoveryTopic = String(hassDiscovery) + String(F("/number/")) + String(haspNode) + String(F("/config")); - mqttDiscoveryPayload = String(F("{\"name\":\"")) + String(haspNode) + String(F(" active page\",\"command_topic\":\"")) + mqttCommandTopic + String(F("/page\",\"state_topic\":\"")) + mqttStateTopic + String(F("/page\",\"step\":1,\"min\":0,\"max\":")) + String(nextionMaxPages) + String(F(",\"retain\":true,\"optimistic\":true,\"icon\":\"mdi:page-next-outline\",\"unique_id\":\"")) + mqttClientId + String(F("-page\",\"device\":{\"identifiers\":[\"")) + mqttClientId + String(F("\"],\"name\":\"")) + String(haspNode) + String(F("\",\"manufacturer\":\"HASwitchPlate\",\"model\":\"HASPone v1.0.0\",\"sw_version\":")) + String(haspVersion) + String(F("}}")); - mqttClient.publish(mqttDiscoveryTopic, mqttDiscoveryPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttDiscoveryTopic + String(F("' : '")) + String(mqttDiscoveryPayload) + String(F("'"))); - - // AlwaysOn topic for RGB lights - mqttClient.publish((String(F("hasp/")) + String(haspNode) + String(F("/alwayson"))), "ON", true, 1); - debugPrintln(String(F("MQTT OUT: 'hasp/")) + String(haspNode) + String(F("/alwayson' : 'ON'"))); - - // rgb light discovery for selectedforegroundcolor - mqttDiscoveryTopic = String(hassDiscovery) + String(F("/light/")) + String(haspNode) + String(F("/selectedforegroundcolor/config")); - mqttDiscoveryPayload = String(F("{\"name\":\"")) + String(haspNode) + String(F(" selected foreground color\",\"command_topic\":\"hasp/")) + String(haspNode) + String(F("/light/selectedforegroundcolor/switch\",\"state_topic\":\"hasp/")) + String(haspNode) + String(F("/alwayson\",\"rgb_command_topic\":\"hasp/")) + String(haspNode) + String(F("/light/selectedforegroundcolor/rgb\",\"rgb_command_template\":\"{{(red|bitwise_and(248)*256)+(green|bitwise_and(252)*8)+(blue|bitwise_and(248)/8)|int }}\",\"retain\":true,\"unique_id\":\"")) + mqttClientId + String(F("-selectedforegroundcolor\",\"device\":{\"identifiers\":[\"")) + mqttClientId + String(F("\"],\"name\":\"")) + String(haspNode) + String(F("\",\"manufacturer\":\"HASwitchPlate\",\"model\":\"HASPone v1.0.0\",\"sw_version\":")) + String(haspVersion) + String(F("}}")); - mqttClient.publish(mqttDiscoveryTopic, mqttDiscoveryPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttDiscoveryTopic + String(F("' : '")) + String(mqttDiscoveryPayload) + String(F("'"))); - - // rgb light discovery for selectedbackgroundcolor - mqttDiscoveryTopic = String(hassDiscovery) + String(F("/light/")) + String(haspNode) + String(F("/selectedbackgroundcolor/config")); - mqttDiscoveryPayload = String(F("{\"name\":\"")) + String(haspNode) + String(F(" selected background color\",\"command_topic\":\"hasp/")) + String(haspNode) + String(F("/light/selectedbackgroundcolor/switch\",\"state_topic\":\"hasp/")) + String(haspNode) + String(F("/alwayson\",\"rgb_command_topic\":\"hasp/")) + String(haspNode) + String(F("/light/selectedbackgroundcolor/rgb\",\"rgb_command_template\":\"{{(red|bitwise_and(248)*256)+(green|bitwise_and(252)*8)+(blue|bitwise_and(248)/8)|int }}\",\"retain\":true,\"unique_id\":\"")) + mqttClientId + String(F("-selectedbackgroundcolor\",\"device\":{\"identifiers\":[\"")) + mqttClientId + String(F("\"],\"name\":\"")) + String(haspNode) + String(F("\",\"manufacturer\":\"HASwitchPlate\",\"model\":\"HASPone v1.0.0\",\"sw_version\":")) + String(haspVersion) + String(F("}}")); - mqttClient.publish(mqttDiscoveryTopic, mqttDiscoveryPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttDiscoveryTopic + String(F("' : '")) + String(mqttDiscoveryPayload) + String(F("'"))); - - // rgb light discovery for unselectedforegroundcolor - mqttDiscoveryTopic = String(hassDiscovery) + String(F("/light/")) + String(haspNode) + String(F("/unselectedforegroundcolor/config")); - mqttDiscoveryPayload = String(F("{\"name\":\"")) + String(haspNode) + String(F(" unselected foreground color\",\"command_topic\":\"hasp/")) + String(haspNode) + String(F("/light/unselectedforegroundcolor/switch\",\"state_topic\":\"hasp/")) + String(haspNode) + String(F("/alwayson\",\"rgb_command_topic\":\"hasp/")) + String(haspNode) + String(F("/light/unselectedforegroundcolor/rgb\",\"rgb_command_template\":\"{{(red|bitwise_and(248)*256)+(green|bitwise_and(252)*8)+(blue|bitwise_and(248)/8)|int }}\",\"retain\":true,\"unique_id\":\"")) + mqttClientId + String(F("-unselectedforegroundcolor\",\"device\":{\"identifiers\":[\"")) + mqttClientId + String(F("\"],\"name\":\"")) + String(haspNode) + String(F("\",\"manufacturer\":\"HASwitchPlate\",\"model\":\"HASPone v1.0.0\",\"sw_version\":")) + String(haspVersion) + String(F("}}")); - mqttClient.publish(mqttDiscoveryTopic, mqttDiscoveryPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttDiscoveryTopic + String(F("' : '")) + String(mqttDiscoveryPayload) + String(F("'"))); - - // rgb light discovery for unselectedbackgroundcolor - mqttDiscoveryTopic = String(hassDiscovery) + String(F("/light/")) + String(haspNode) + String(F("/unselectedbackgroundcolor/config")); - mqttDiscoveryPayload = String(F("{\"name\":\"")) + String(haspNode) + String(F(" unselected background color\",\"command_topic\":\"hasp/")) + String(haspNode) + String(F("/light/unselectedbackgroundcolor/switch\",\"state_topic\":\"hasp/")) + String(haspNode) + String(F("/alwayson\",\"rgb_command_topic\":\"hasp/")) + String(haspNode) + String(F("/light/unselectedbackgroundcolor/rgb\",\"rgb_command_template\":\"{{(red|bitwise_and(248)*256)+(green|bitwise_and(252)*8)+(blue|bitwise_and(248)/8)|int }}\",\"retain\":true,\"unique_id\":\"")) + mqttClientId + String(F("-unselectedbackgroundcolor\",\"device\":{\"identifiers\":[\"")) + mqttClientId + String(F("\"],\"name\":\"")) + String(haspNode) + String(F("\",\"manufacturer\":\"HASwitchPlate\",\"model\":\"HASPone v1.0.0\",\"sw_version\":")) + String(haspVersion) + String(F("}}")); - mqttClient.publish(mqttDiscoveryTopic, mqttDiscoveryPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttDiscoveryTopic + String(F("' : '")) + String(mqttDiscoveryPayload) + String(F("'"))); - - if (motionEnabled) - { // binary_sensor for motion - String mqttDiscoveryTopic = String(hassDiscovery) + String(F("/binary_sensor/")) + String(haspNode) + String(F("-motion/config")); - String mqttDiscoveryPayload = String(F("{\"device_class\":\"motion\",\"name\":\"")) + String(haspNode) + String(F(" motion\",\"state_topic\":\"")) + mqttMotionStateTopic + String(F("\",\"unique_id\":\"")) + mqttClientId + String(F("-motion\",\"payload_on\":\"ON\",\"payload_off\":\"OFF\",\"device\":{\"identifiers\":[\"")) + mqttClientId + String(F("\"],\"name\":\"")) + String(haspNode) + String(F("\",\"manufacturer\":\"HASwitchPlate\",\"model\":\"HASPone v1.0.0\",\"sw_version\":")) + String(haspVersion) + String(F("}}")); - mqttClient.publish(mqttDiscoveryTopic, mqttDiscoveryPayload, true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttDiscoveryTopic + String(F("' : '")) + String(mqttDiscoveryPayload) + String(F("'"))); - } - -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionHandleInput() -{ // Handle incoming serial data from the Nextion panel - // This will collect serial data from the panel and place it into the global buffer - // nextionReturnBuffer[nextionReturnIndex] - unsigned long handlerTimeout = millis() + 100; - bool nextionCommandComplete = false; - static uint8_t nextionTermByteCnt = 0; // counter for our 3 consecutive 0xFFs - - while (Serial.available() && !nextionCommandComplete && (millis() < handlerTimeout)) - { - byte nextionCommandByte = Serial.read(); - if (nextionCommandByte == 0xFF) - { // check to see if we have one of 3 consecutive 0xFF which indicates the end of a command - nextionTermByteCnt++; - if (nextionTermByteCnt >= 3) - { // We have received a complete command - lcdConnected = true; - nextionCommandComplete = true; - nextionTermByteCnt = 0; // reset counter - } - } - else - { - nextionTermByteCnt = 0; // reset counter if a non-term byte was encountered - } - nextionReturnBuffer[nextionReturnIndex] = nextionCommandByte; - nextionReturnIndex++; - if (nextionCommandComplete) - { - nextionAckReceived = true; - nextionProcessInput(); - } - yield(); - } - if (millis() > handlerTimeout) - { - debugPrintln(String(F("HMI ERROR: nextionHandleInput timeout"))); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionProcessInput() -{ // Process complete incoming serial command from the Nextion panel - // Command reference: https://www.itead.cc/wiki/Nextion_Instruction_Set#Format_of_Device_Return_Data - // tl;dr: command byte, command data, 0xFF 0xFF 0xFF - - if (nextionReturnBuffer[0] == 0x01) - { // Instruction Successful - quietly ignore this as it will be returned after every command issued, - // and processing it + spitting out serial output is a huge drag on performance if serial debug is enabled. - - // debugPrintln(String(F("HMI IN: [Instruction Successful] 0x")) + String(nextionReturnBuffer[0], HEX)); - // if (mqttClient.connected()) - // { - // String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Instruction Successful\"}")); - // mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - // debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - // } - nextionReturnIndex = 0; // Done handling the buffer, reset index back to 0 - return; // skip the rest of the tests below and return immediately - } - - debugPrintln(String(F("HMI IN: [")) + String(nextionReturnIndex) + String(F(" bytes]: ")) + printHex8(nextionReturnBuffer, nextionReturnIndex)); - - if (nextionReturnBuffer[0] == 0x00 && nextionReturnBuffer[1] == 0x00 && nextionReturnBuffer[2] == 0x00) - { // Nextion Startup - debugPrintln(String(F("HMI IN: [Nextion Startup] 0x00 0x00 0x00"))); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x00 0x00 0x00\",\"return_code_description\":\"Nextion Startup\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x00) - { // Invalid Instruction - debugPrintln(String(F("HMI IN: [Invalid Instruction] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Instruction\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x02) - { // Invalid Component ID - debugPrintln(String(F("HMI IN: [Invalid Component ID] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Component ID\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x03) - { // Invalid Page ID - debugPrintln(String(F("HMI IN: [Invalid Page ID] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Page ID\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x04) - { // Invalid Picture ID - debugPrintln(String(F("HMI IN: [Invalid Picture ID] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Picture ID\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x05) - { // Invalid Font ID - debugPrintln(String(F("HMI IN: [Invalid Font ID ] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Font ID \"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x06) - { // Invalid File Operation - debugPrintln(String(F("HMI IN: [Invalid File Operation] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid File Operation\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x09) - { // Invalid CRC - debugPrintln(String(F("HMI IN: [Invalid CRC] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid CRC\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x11) - { // Invalid Baud rate Setting - debugPrintln(String(F("HMI IN: [Invalid Baud rate Setting] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Baud rate Setting\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x12) - { // Invalid Waveform ID or Channel # - debugPrintln(String(F("HMI IN: [Invalid Waveform ID or Channel #] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Waveform ID or Channel #\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x1A) - { // Invalid Variable name or attribute - debugPrintln(String(F("HMI IN: [Invalid Variable name or attribute] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Variable name or attribute\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x1B) - { // Invalid Variable Operation - debugPrintln(String(F("HMI IN: [Invalid Variable Operation] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Variable Operation\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x1C) - { // Assignment failed to assign - debugPrintln(String(F("HMI IN: [Assignment failed to assign] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Assignment failed to assign\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x1D) - { // EEPROM Operation failed - debugPrintln(String(F("HMI IN: [EEPROM Operation failed] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"EEPROM Operation failed\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x1E) - { // Invalid Quantity of Parameters - debugPrintln(String(F("HMI IN: [Invalid Quantity of Parameters] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Invalid Quantity of Parameters\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x1F) - { // IO Operation failed - debugPrintln(String(F("HMI IN: [IO Operation failed] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"IO Operation failed\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x20) - { // Escape Character Invalid - debugPrintln(String(F("HMI IN: [Escape Character Invalid] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Escape Character Invalid\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x23) - { // Variable name too long - debugPrintln(String(F("HMI IN: [Variable name too long] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Variable name too long\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x24) - { // Serial Buffer Overflow - debugPrintln(String(F("HMI IN: [Serial Buffer Overflow] 0x")) + String(nextionReturnBuffer[0], HEX)); - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"nextion_return_data\",\"return_code\":\"0x")) + String(nextionReturnBuffer[0], HEX) + String(F("\",\"return_code_description\":\"Serial Buffer Overflow\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - - else if (nextionReturnBuffer[0] == 0x65) - { // Handle incoming touch command - // 0x65+Page ID+Component ID+TouchEvent+End - // Return this data when the touch event created by the user is pressed. - // Definition of TouchEvent: Press Event 0x01, Release Event 0X00 - // Example: 0x65 0x00 0x02 0x01 0xFF 0xFF 0xFF - // Meaning: Touch Event, Page 0, Object 2, Press - String nextionPage = String(nextionReturnBuffer[1]); - String nextionButtonID = String(nextionReturnBuffer[2]); - byte nextionButtonAction = nextionReturnBuffer[3]; - - if (nextionButtonAction == 0x01) - { - debugPrintln(String(F("HMI IN: [Button ON] 'p[")) + nextionPage + "].b[" + nextionButtonID + "]'"); - if (mqttClient.connected()) - { - // Only process touch events if screen backlight is on and configured to do so. - if (ignoreTouchWhenOff && !lcdBacklightOn) - { - String mqttButtonJSONEvent = String(F("{\"event_type\":\"button_press_disabled\",\"event\":\"p[")) + nextionPage + String(F("].b[")) + nextionButtonID + String(F("]\",\"value\":\"ON\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - else - { - String mqttButtonTopic = mqttStateTopic + "/p[" + nextionPage + "].b[" + nextionButtonID + "]"; - mqttClient.publish(mqttButtonTopic, "ON"); - debugPrintln(String(F("MQTT OUT: '")) + mqttButtonTopic + "' : 'ON'"); - String mqttButtonJSONEvent = String(F("{\"event_type\":\"button_short_press\",\"event\":\"p[")) + nextionPage + String(F("].b[")) + nextionButtonID + String(F("]\",\"value\":\"ON\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - if (beepEnabled) - { - beepOnTime = 500; - beepOffTime = 100; - beepCounter = 1; - } - if (rebootOnp0b1 && (nextionPage == "0") && (nextionButtonID == "1")) - { - debugPrintln(String(F("HMI IN: p[0].b[1] pressed during HASPone configuration, rebooting."))); - espReset(); - } - if (rebootOnLongPress) - { - rebootOnLongPressTimer = millis(); - } - } - else if (nextionButtonAction == 0x00) - { - debugPrintln(String(F("HMI IN: [Button OFF] 'p[")) + nextionPage + "].b[" + nextionButtonID + "]'"); - if (mqttClient.connected()) - { - // Only process touch events if screen backlight is on and configured to do so. - if (ignoreTouchWhenOff && !lcdBacklightOn) - { - String mqttButtonJSONEvent = String(F("{\"event_type\":\"button_release_disabled\",\"event\":\"p[")) + nextionPage + String(F("].b[")) + nextionButtonID + String(F("]\",\"value\":\"ON\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - else - { - String mqttButtonTopic = mqttStateTopic + "/p[" + nextionPage + "].b[" + nextionButtonID + "]"; - mqttClient.publish(mqttButtonTopic, "OFF"); - debugPrintln(String(F("MQTT OUT: '")) + mqttButtonTopic + "' : 'OFF'"); - String mqttButtonJSONEvent = String(F("{\"event_type\":\"button_short_release\",\"event\":\"p[")) + nextionPage + String(F("].b[")) + nextionButtonID + String(F("]\",\"value\":\"OFF\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - // Now see if this object has a .val that might have been updated. Works for sliders, - // two-state buttons, etc, returns 0 for normal buttons - mqttGetSubtopic = "/p[" + nextionPage + "].b[" + nextionButtonID + "].val"; - // This right here is dicey. We're done w/ this command so reset the index allowing this to be kinda-reentrant - // because the call to nextionGetAttr is going to call us back. - nextionReturnIndex = 0; - nextionGetAttr("p[" + nextionPage + "].b[" + nextionButtonID + "].val"); - } - } - if (rebootOnLongPress && (millis() - rebootOnLongPressTimer > rebootOnLongPressTimeout)) - { - debugPrintln(String(F("HMI IN: Button long press, rebooting."))); - espReset(); - } - rebootOnLongPressTimer = millis(); - } - } - else if (nextionReturnBuffer[0] == 0x66) - { // Handle incoming "sendme" page number - // 0x66+PageNum+End - // Example: 0x66 0x02 0xFF 0xFF 0xFF - // Meaning: page 2 - String nextionPage = String(nextionReturnBuffer[1]); - debugPrintln(String(F("HMI IN: [sendme Page] '")) + nextionPage + String(F("'"))); - if ((nextionPage != "0") || nextionReportPage0) - { // If we have a new page AND ( (it's not "0") OR (we've set the flag to report 0 anyway) ) - - if (mqttClient.connected()) - { - String mqttButtonJSONEvent = String(F("{\"event\":\"page\",\"value\":")) + nextionPage + String(F("}")); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - String mqttPageTopic = mqttStateTopic + "/page"; - debugPrintln(String(F("MQTT OUT: '")) + mqttPageTopic + String(F("' : '")) + nextionPage + String(F("'"))); - mqttClient.publish(mqttPageTopic, nextionPage, false, 0); - } - } - } - else if (nextionReturnBuffer[0] == 0x67 || nextionReturnBuffer[0] == 0x68) - { // Handle touch coordinate data - // 0X67+Coordinate X High+Coordinate X Low+Coordinate Y High+Coordinate Y Low+TouchEvent+End - // Example: 0X67 0X00 0X7A 0X00 0X1E 0X01 0XFF 0XFF 0XFF - // Meaning: Coordinate (122,30), Touch Event: Press - // issue Nextion command "sendxy=1" to enable this output - // 0x68 is the same, but returned when the screen touch has awakened the screen from sleep - uint16_t xCoord = nextionReturnBuffer[1]; - xCoord = xCoord * 256 + nextionReturnBuffer[2]; - uint16_t yCoord = nextionReturnBuffer[3]; - yCoord = yCoord * 256 + nextionReturnBuffer[4]; - String xyCoord = String(xCoord) + String(',') + String(yCoord); - byte nextionTouchAction = nextionReturnBuffer[5]; - if (nextionTouchAction == 0x01) - { - debugPrintln(String(F("HMI IN: [Touch ON] '")) + xyCoord + String(F("'"))); - if (mqttClient.connected()) - { - String mqttTouchTopic = mqttStateTopic + "/touchOn"; - mqttClient.publish(mqttTouchTopic, xyCoord); - debugPrintln(String(F("MQTT OUT: '")) + mqttTouchTopic + String(F("' : '")) + xyCoord + String(F("'"))); - String mqttButtonJSONEvent = String(F("{\"event_type\":\"button_short_press\",\"event\":\"touchxy\",\"touch_event\":\"ON\",\"touchx\":\"")) + String(xCoord) + String(F("\",\"touchy\":\"")) + String(yCoord) + String(F("\",\"screen_state\":\"")); - if (nextionReturnBuffer[0] == 0x67) - { - mqttButtonJSONEvent += "awake\"}"; - } - else - { - mqttButtonJSONEvent += "asleep\"}"; - } - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionTouchAction == 0x00) - { - debugPrintln(String(F("HMI IN: [Touch OFF] '")) + xyCoord + String(F("'"))); - if (mqttClient.connected()) - { - String mqttTouchTopic = mqttStateTopic + "/touchOff"; - mqttClient.publish(mqttTouchTopic, xyCoord); - debugPrintln(String(F("MQTT OUT: '")) + mqttTouchTopic + String(F("' : '")) + xyCoord + String(F("'"))); - String mqttButtonJSONEvent = String(F("{\"event_type\":\"button_short_press\",\"event\":\"touchxy\",\"touch_event\":\"OFF\",\"touchx\":\"")) + String(xCoord) + String(F("\",\"touchy\":\"")) + String(yCoord) + String(F("\",\"screen_state\":\"")); - if (nextionReturnBuffer[0] == 0x67) - { - mqttButtonJSONEvent += "awake\"}"; - } - else - { - mqttButtonJSONEvent += "asleep\"}"; - } - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - } - else if (nextionReturnBuffer[0] == 0x70) - { // Handle get string return - // 0x70+ASCII string+End - // Example: 0x70 0x41 0x42 0x43 0x44 0x31 0x32 0x33 0x34 0xFF 0xFF 0xFF - // Meaning: String data, ABCD1234 - String getString; - for (int i = 1; i < nextionReturnIndex - 3; i++) - { // convert the payload into a string - getString += (char)nextionReturnBuffer[i]; - } - debugPrintln(String(F("HMI IN: [String Return] '")) + getString + String(F("'"))); - if (mqttClient.connected()) - { - if (mqttGetSubtopic == "") - { // If there's no outstanding request for a value, publish to mqttStateTopic - mqttClient.publish(mqttStateTopic, getString); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateTopic + String(F("' : '")) + getString + String(F("'"))); - } - else - { // Otherwise, publish the to saved mqttGetSubtopic and then reset mqttGetSubtopic - String mqttReturnTopic = mqttStateTopic + mqttGetSubtopic; - mqttClient.publish(mqttReturnTopic, getString); - debugPrintln(String(F("MQTT OUT: '")) + mqttReturnTopic + String(F("' : '")) + getString + String(F("'"))); - String mqttButtonJSONEvent = String(F("{\"event\":\"")) + mqttGetSubtopic.substring(1) + String(F("\",\"value\":\"")) + getString + String(F("\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - mqttGetSubtopic = ""; - } - } - } - else if (nextionReturnBuffer[0] == 0x71) - { // Handle get int return - // 0x71+byte1+byte2+byte3+byte4+End (4 byte little endian) - // Example: 0x71 0x7B 0x00 0x00 0x00 0xFF 0xFF 0xFF - // Meaning: Integer data, 123 - long getInt = nextionReturnBuffer[4]; - getInt = getInt * 256 + nextionReturnBuffer[3]; - getInt = getInt * 256 + nextionReturnBuffer[2]; - getInt = getInt * 256 + nextionReturnBuffer[1]; - String getString = String(getInt); - debugPrintln(String(F("HMI IN: [Int Return] '")) + getString + String(F("'"))); - - if (lcdVersionQueryFlag) - { - lcdVersion = getInt; - lcdVersionQueryFlag = false; - debugPrintln(String(F("HMI IN: lcdVersion '")) + String(lcdVersion) + String(F("'"))); - } - else if (lcdBacklightQueryFlag) - { - lcdBacklightDim = getInt; - lcdBacklightQueryFlag = false; - if (lcdBacklightDim > 0) - { - lcdBacklightOn = 1; - } - else - { - lcdBacklightOn = 0; - } - debugPrintln(String(F("HMI IN: lcdBacklightDim '")) + String(lcdBacklightDim) + String(F("'"))); - } - else if (mqttGetSubtopic == "") - { - if (mqttClient.connected()) - { - mqttClient.publish(mqttStateTopic, getString); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateTopic + String(F("' : '")) + getString + String(F("'"))); - } - } - // Otherwise, publish the to saved mqttGetSubtopic and then reset mqttGetSubtopic - else - { - if (mqttClient.connected()) - { - String mqttReturnTopic = mqttStateTopic + mqttGetSubtopic; - mqttClient.publish(mqttReturnTopic, getString); - debugPrintln(String(F("MQTT OUT: '")) + mqttReturnTopic + String(F("' : '")) + getString + String(F("'"))); - String mqttButtonJSONEvent = String(F("{\"event\":\"")) + mqttGetSubtopic.substring(1) + String(F("\",\"value\":")) + getString + String(F("}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - mqttGetSubtopic = ""; - } - } - else if (nextionReturnBuffer[0] == 0x63 && nextionReturnBuffer[1] == 0x6f && nextionReturnBuffer[2] == 0x6d && nextionReturnBuffer[3] == 0x6f && nextionReturnBuffer[4] == 0x6b) - { // Catch 'comok' response to 'connect' command: https://www.itead.cc/blog/nextion-hmi-upload-protocol - String comokField; - uint8_t comokFieldCount = 0; - byte comokFieldSeperator = 0x2c; // "," - - for (uint8_t i = 0; i <= nextionReturnIndex; i++) - { // cycle through each byte looking for our field seperator - if (nextionReturnBuffer[i] == comokFieldSeperator) - { // Found the end of a field, so do something with it. Maybe. - if (comokFieldCount == 2) - { - nextionModel = comokField; - debugPrintln(String(F("HMI IN: nextionModel: ")) + nextionModel); - } - comokFieldCount++; - comokField = ""; - } - else - { - comokField += String(char(nextionReturnBuffer[i])); - } - } - } - else if (nextionReturnBuffer[0] == 0x86) - { // Returned when Nextion enters sleep automatically. Using sleep=1 will not return an 0x86 - // 0x86+End - if (mqttClient.connected()) - { - lcdBacklightOn = 0; - mqttClient.publish(mqttLightStateTopic, "OFF", true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttLightStateTopic + String(F("' : 'OFF'"))); - String mqttButtonJSONEvent = String(F("{\"event\":\"sleep\",\"value\":\"ON\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x87) - { // Returned when Nextion leaves sleep automatically. Using sleep=0 will not return an 0x87 - // 0x87+End - if (mqttClient.connected()) - { - lcdBacklightOn = 1; - mqttClient.publish(mqttLightStateTopic, "ON", true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttLightStateTopic + String(F("' : 'ON'"))); - mqttClient.publish(mqttLightBrightStateTopic, String(lcdBacklightDim), true, 1); - debugPrintln(String(F("MQTT OUT: '")) + mqttLightBrightStateTopic + String(F("' : ")) + String(lcdBacklightDim)); - String mqttButtonJSONEvent = String(F("{\"event\":\"sleep\",\"value\":\"OFF\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else if (nextionReturnBuffer[0] == 0x88) - { // Returned when Nextion powers on - // 0x88+End - debugPrintln(F("HMI: Nextion panel connected.")); - } - nextionReturnIndex = 0; // Done handling the buffer, reset index back to 0 -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionSendCmd(const String &nextionCmd) -{ // Send a raw command to the Nextion panel - Serial1.print(nextionCmd); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.flush(); - debugPrintln(String(F("HMI OUT: ")) + nextionCmd); - - if (nextionAckEnable) - { - nextionAckReceived = false; - nextionAckTimer = millis(); - - while ((!nextionAckReceived) && (millis() - nextionAckTimer < nextionAckTimeout)) - { - nextionHandleInput(); - } - if (!nextionAckReceived) - { - debugPrintln(String(F("HMI ERROR: Nextion Ack timeout"))); - String mqttButtonJSONEvent = String(F("{\"event\":\"nextionError\",\"value\":\"Nextion Ack timeout\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else - { - nextionHandleInput(); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionSetAttr(const String &hmiAttribute, const String &hmiValue) -{ // Set the value of a Nextion component attribute - Serial1.print(hmiAttribute); - Serial1.print("="); - Serial1.print(hmiValue); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.flush(); - debugPrintln(String(F("HMI OUT: '")) + hmiAttribute + "=" + hmiValue + String(F("'"))); - if (nextionAckEnable) - { - nextionAckReceived = false; - nextionAckTimer = millis(); - - while ((!nextionAckReceived) || (millis() - nextionAckTimer > nextionAckTimeout)) - { - nextionHandleInput(); - } - if (!nextionAckReceived) - { - debugPrintln(String(F("HMI ERROR: Nextion Ack timeout"))); - String mqttButtonJSONEvent = String(F("{\"event\":\"nextionError\",\"value\":\"Nextion Ack timeout\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else - { - nextionHandleInput(); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionGetAttr(const String &hmiAttribute) -{ // Get the value of a Nextion component attribute - // This will only send the command to the panel requesting the attribute, the actual - // return of that value will be handled by nextionProcessInput and placed into mqttGetSubtopic - Serial1.print("get "); - Serial1.print(hmiAttribute); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.flush(); - debugPrintln(String(F("HMI OUT: 'get ")) + hmiAttribute + String(F("'"))); - if (nextionAckEnable) - { - nextionAckReceived = false; - nextionAckTimer = millis(); - - while ((!nextionAckReceived) || (millis() - nextionAckTimer > nextionAckTimeout)) - { - nextionHandleInput(); - } - if (!nextionAckReceived) - { - debugPrintln(String(F("HMI ERROR: Nextion Ack timeout"))); - String mqttButtonJSONEvent = String(F("{\"event\":\"nextionError\",\"value\":\"Nextion Ack timeout\"}")); - mqttClient.publish(mqttStateJSONTopic, mqttButtonJSONEvent); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F("' : '")) + mqttButtonJSONEvent + String(F("'"))); - } - } - else - { - nextionHandleInput(); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionParseJson(const String &strPayload) -{ // Parse an incoming JSON array into individual Nextion commands - DynamicJsonDocument nextionCommands(mqttMaxPacketSize + 1024); - DeserializationError jsonError = deserializeJson(nextionCommands, strPayload); - - if (jsonError) - { // Couldn't parse incoming JSON command - String jsonErrorDescription = String(F("Failed to parse incoming JSON command with error:")) + String(jsonError.c_str()) + String(F(" memoryUsage: ")) + String(nextionCommands.memoryUsage()) + String(F(" capacity: ")) + String(nextionCommands.capacity()); - debugPrintln(String(F("MQTT: [ERROR] ")) + jsonErrorDescription); - mqttClient.publish(mqttStateJSONTopic, String(F("{\"event\":\"jsonError\",\"event_source\":\"nextionParseJson()\",\"event_description\":\"")) + jsonErrorDescription + String(F("\"}"))); - } - else - { - for (uint8_t i = 0; i < nextionCommands.size(); i++) - { - nextionSendCmd(nextionCommands[i]); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionOtaStartDownload(const String &lcdOtaUrl) -{ // Upload firmware to the Nextion LCD via HTTP download - - uint32_t lcdOtaFileSize = 0; - String lcdOtaNextionCmd; - uint32_t lcdOtaChunkCounter = 0; - uint16_t lcdOtaPartNum = 0; - uint32_t lcdOtaTransferred = 0; - uint8_t lcdOtaPercentComplete = 0; - const uint32_t lcdOtaTimeout = 30000; // timeout for receiving new data in milliseconds - static uint32_t lcdOtaTimer = 0; // timer for lcdOtaTimeout - - HTTPClient lcdOtaHttp; - WiFiClientSecure lcdOtaWifiSecure; - WiFiClient lcdOtaWifi; - if (lcdOtaUrl.startsWith(F("https"))) - { - debugPrintln("LCDOTA: Attempting firmware update from HTTPS host: " + lcdOtaUrl); - - lcdOtaHttp.begin(lcdOtaWifiSecure, lcdOtaUrl); - lcdOtaWifiSecure.setInsecure(); - lcdOtaWifiSecure.setBufferSizes(512, 512); - } - else - { - debugPrintln("LCDOTA: Attempting firmware update from HTTP host: " + lcdOtaUrl); - lcdOtaHttp.begin(lcdOtaWifi, lcdOtaUrl); - } - - int lcdOtaHttpReturn = lcdOtaHttp.GET(); - if (lcdOtaHttpReturn > 0) - { // HTTP header has been sent and Server response header has been handled - debugPrintln(String(F("LCDOTA: HTTP GET return code:")) + String(lcdOtaHttpReturn)); - if (lcdOtaHttpReturn == HTTP_CODE_OK) - { // file found at server - int32_t lcdOtaRemaining = lcdOtaHttp.getSize(); // get length of document (is -1 when Server sends no Content-Length header) - lcdOtaFileSize = lcdOtaRemaining; - static uint16_t lcdOtaParts = (lcdOtaRemaining / 4096) + 1; - static const uint16_t lcdOtaBufferSize = 1024; // upload data buffer before sending to UART - static uint8_t lcdOtaBuffer[lcdOtaBufferSize] = {}; - - debugPrintln(String(F("LCDOTA: File found at Server. Size ")) + String(lcdOtaRemaining) + String(F(" bytes in ")) + String(lcdOtaParts) + String(F(" 4k chunks."))); - - WiFiUDP::stopAll(); // Keep mDNS responder and MQTT traffic from breaking things - if (mqttClient.connected()) - { - debugPrintln(F("LCDOTA: LCD firmware upload starting, closing MQTT connection.")); - mqttClient.publish(mqttStatusTopic, "OFF", true, 0); - debugPrintln(String(F("MQTT OUT: '")) + mqttStatusTopic + String(F("' : 'OFF'"))); - mqttClient.disconnect(); - } - - WiFiClient *stream = lcdOtaHttp.getStreamPtr(); // get tcp stream - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); // Send empty command - Serial1.flush(); - nextionHandleInput(); - String lcdOtaNextionCmd = "whmi-wri " + String(lcdOtaFileSize) + "," + String(nextionBaud) + ",0"; - debugPrintln(String(F("LCDOTA: Sending LCD upload command: ")) + lcdOtaNextionCmd); - Serial1.print(lcdOtaNextionCmd); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.flush(); - - if (nextionOtaResponse()) - { - debugPrintln(F("LCDOTA: LCD upload command accepted.")); - } - else - { - debugPrintln(F("LCDOTA: LCD upload command FAILED. Restarting device.")); - espReset(); - } - debugPrintln(F("LCDOTA: Starting update")); - lcdOtaTimer = millis(); - while (lcdOtaHttp.connected() && (lcdOtaRemaining > 0 || lcdOtaRemaining == -1)) - { // Write incoming data to panel as it arrives - uint16_t lcdOtaHttpSize = stream->available(); // get available data size - - if (lcdOtaHttpSize) - { - uint16_t lcdOtaChunkSize = 0; - if ((lcdOtaHttpSize <= lcdOtaBufferSize) && (lcdOtaHttpSize <= (4096 - lcdOtaChunkCounter))) - { - lcdOtaChunkSize = lcdOtaHttpSize; - } - else if ((lcdOtaBufferSize <= lcdOtaHttpSize) && (lcdOtaBufferSize <= (4096 - lcdOtaChunkCounter))) - { - lcdOtaChunkSize = lcdOtaBufferSize; - } - else - { - lcdOtaChunkSize = 4096 - lcdOtaChunkCounter; - } - stream->readBytes(lcdOtaBuffer, lcdOtaChunkSize); - Serial1.flush(); // make sure any previous writes the UART have completed - Serial1.write(lcdOtaBuffer, lcdOtaChunkSize); // now send buffer to the UART - lcdOtaChunkCounter += lcdOtaChunkSize; - if (lcdOtaChunkCounter >= 4096) - { - Serial1.flush(); - lcdOtaPartNum++; - lcdOtaTransferred += lcdOtaChunkCounter; - lcdOtaPercentComplete = (lcdOtaTransferred * 100) / lcdOtaFileSize; - lcdOtaChunkCounter = 0; - if (nextionOtaResponse()) - { // We've completed a chunk - debugPrintln(String(F("LCDOTA: Part ")) + String(lcdOtaPartNum) + String(F(" OK, ")) + String(lcdOtaPercentComplete) + String(F("% complete"))); - lcdOtaTimer = millis(); - } - else - { - debugPrintln(String(F("LCDOTA: Part ")) + String(lcdOtaPartNum) + String(F(" FAILED, ")) + String(lcdOtaPercentComplete) + String(F("% complete"))); - debugPrintln(F("LCDOTA: failure")); - delay(2000); // extra delay while the LCD does its thing - espReset(); - } - } - else - { - delay(20); - } - if (lcdOtaRemaining > 0) - { - lcdOtaRemaining -= lcdOtaChunkSize; - } - } - delay(10); - if ((lcdOtaTimer > 0) && ((millis() - lcdOtaTimer) > lcdOtaTimeout)) - { // Our timer expired so reset - debugPrintln(F("LCDOTA: ERROR: LCD upload timeout. Restarting.")); - espReset(); - } - } - lcdOtaPartNum++; - lcdOtaTransferred += lcdOtaChunkCounter; - if ((lcdOtaTransferred == lcdOtaFileSize) && nextionOtaResponse()) - { - debugPrintln(String(F("LCDOTA: Success, wrote ")) + String(lcdOtaTransferred) + String(F(" of ")) + String(tftFileSize) + String(F(" bytes."))); - uint32_t lcdOtaDelay = millis(); - debugPrintln(F("LCDOTA: Waiting 5 seconds to allow LCD to apply updates we've sent.")); - while ((millis() - lcdOtaDelay) < 5000) - { // extra 5sec delay while the LCD handles any local firmware updates from new versions of code sent to it - webServer.handleClient(); - yield(); - } - espReset(); - } - else - { - debugPrintln(String(F("LCDOTA: Failure, lcdOtaTransferred: ")) + String(lcdOtaTransferred) + String(F(" lcdOtaFileSize: ")) + String(lcdOtaFileSize)); - espReset(); - } - } - } - else - { - debugPrintln(String(F("LCDOTA: HTTP GET failed, error code ")) + lcdOtaHttp.errorToString(lcdOtaHttpReturn)); - espReset(); - } - lcdOtaHttp.end(); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -bool nextionOtaResponse() -{ // Monitor the serial port for a 0x05 response within our timeout - unsigned long nextionCommandTimeout = 2000; // timeout for receiving termination string in milliseconds - unsigned long nextionCommandTimer = millis(); // record current time for our timeout - bool otaSuccessVal = false; - while ((millis() - nextionCommandTimer) < nextionCommandTimeout) - { - if (Serial.available()) - { - byte inByte = Serial.read(); - if (inByte == 0x5) - { - otaSuccessVal = true; - break; - } - } - else - { - delay(1); - } - } - return otaSuccessVal; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -bool nextionConnect() -{ - const unsigned long nextionCheckTimeout = 2000; // Max time in msec for nextion connection check - unsigned long nextionCheckTimer = millis(); // Timer for nextion connection checks - - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - - if (!lcdConnected) - { // Check for some traffic from our LCD - debugPrintln(F("HMI: Waiting for LCD connection")); - while (((millis() - nextionCheckTimer) <= nextionCheckTimeout) && !lcdConnected) - { - nextionHandleInput(); - } - } - if (!lcdConnected) - { // No response from the display using the configured speed, so scan all possible speeds - nextionSetSpeed(); - - nextionCheckTimer = millis(); // Reset our timer - debugPrintln(F("HMI: Waiting again for LCD connection")); - while (((millis() - nextionCheckTimer) <= nextionCheckTimeout) && !lcdConnected) - { - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - nextionHandleInput(); - } - if (!lcdConnected) - { - debugPrintln(F("HMI: LCD connection timed out")); - return false; - } - } - - // Query backlight status. This should always succeed under simulation or non-HASPone HMI - lcdBacklightQueryFlag = true; - debugPrintln(F("HMI: Querying LCD backlight status")); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - nextionSendCmd("get dim"); - while (((millis() - nextionCheckTimer) <= nextionCheckTimeout) && lcdBacklightQueryFlag) - { - nextionHandleInput(); - } - if (lcdBacklightQueryFlag) - { // Our flag is still set, meaning we never got a response. - debugPrintln(F("HMI: LCD backlight query timed out")); - lcdBacklightQueryFlag = false; - return false; - } - - // We are now communicating with the panel successfully. Enable ACK checking for all future commands. - nextionAckEnable = true; - nextionSendCmd("bkcmd=3"); - - // This check depends on the HMI having been designed with a version number in the object - // defined in lcdVersionQuery. It's OK if this fails, it just means the HMI project is - // not utilizing the version capability that the HASPone project makes use of. - lcdVersionQueryFlag = true; - debugPrintln(F("HMI: Querying LCD firmware version number")); - nextionSendCmd("get " + lcdVersionQuery); - while (((millis() - nextionCheckTimer) <= nextionCheckTimeout) && lcdVersionQueryFlag) - { - nextionHandleInput(); - } - if (lcdVersionQueryFlag) - { // Our flag is still set, meaning we never got a response. This should only happen if - // there's a problem. Non-HASPone projects should pass this check with lcdVersion = 0 - debugPrintln(F("HMI: LCD version query timed out")); - lcdVersionQueryFlag = false; - return false; - } - - if (nextionModel.length() == 0) - { // Check for LCD model via `connect`. The Nextion simulator does not support this command, - // so if we're running under that environment this process should timeout. - debugPrintln(F("HMI: Querying LCD model information")); - nextionSendCmd("connect"); - while (((millis() - nextionCheckTimer) <= nextionCheckTimeout) && (nextionModel.length() == 0)) - { - nextionHandleInput(); - } - } - return true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionSetSpeed() -{ - debugPrintln(String(F("HMI: No Nextion response, attempting to set serial speed to ")) + String(nextionBaud)); - for (unsigned int nextionSpeedsIndex = 0; nextionSpeedsIndex < nextionSpeedsLength; nextionSpeedsIndex++) - { - debugPrintln(String(F("HMI: Sending bauds=")) + String(nextionBaud) + " @" + String(nextionSpeeds[nextionSpeedsIndex]) + " baud"); - Serial1.flush(); - Serial1.begin(nextionSpeeds[nextionSpeedsIndex]); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.print("bauds=" + String(nextionBaud)); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.flush(); - } - Serial1.begin(atoi(nextionBaud)); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionReset() -{ - debugPrintln(F("HMI: Rebooting LCD")); - digitalWrite(nextionResetPin, LOW); - Serial1.print("rest"); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.flush(); - delay(100); - digitalWrite(nextionResetPin, HIGH); - - unsigned long lcdResetTimer = millis(); - const unsigned long lcdResetTimeout = 5000; - - lcdConnected = false; - while (!lcdConnected && (millis() < (lcdResetTimer + lcdResetTimeout))) - { - nextionHandleInput(); - } - if (lcdConnected) - { - debugPrintln(F("HMI: Rebooting LCD completed")); - if (nextionActivePage) - { - nextionSendCmd("page " + String(nextionActivePage)); - } - } - else - { - debugPrintln(F("ERROR: Rebooting LCD completed, but LCD is not responding.")); - } - mqttClient.publish(mqttStatusTopic, "OFF", true, 0); - debugPrintln(String(F("MQTT OUT: '")) + mqttStatusTopic + String(F("' : 'OFF'"))); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void nextionUpdateProgress(const unsigned int &progress, const unsigned int &total) -{ - uint8_t progressPercent = (float(progress) / float(total)) * 100; - nextionSetAttr("p[0].b[4].val", String(progressPercent)); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void espWifiConnect() -{ // Connect to WiFi - rebootOnp0b1 = true; - - nextionSetAttr("p[0].b[1].font", "6"); - if (lcdVersion < 1 || lcdVersion > 2) - { - nextionSendCmd("page 0"); - } - - WiFi.persistent(false); - enableWiFiAtBootTime(); - WiFi.macAddress(espMac); // Read our MAC address and save it to espMac - WiFi.hostname(haspNode); // Assign our hostname before connecting to WiFi - WiFi.setAutoReconnect(true); // Tell WiFi to autoreconnect if connection has dropped - WiFi.setSleepMode(WIFI_NONE_SLEEP); // Disable WiFi sleep modes to prevent occasional disconnects - WiFi.mode(WIFI_STA); // Set the radio to Station - - if (String(wifiSSID) == "") - { // If the sketch has no hard-coded wifiSSID, attempt to use saved creds or use WiFiManager to collect required information from the user. - - // First, check if we have saved wifi creds and try to connect manually. - if (WiFi.SSID() != "") - { - nextionSetAttr("p[0].b[1].txt", "\"WiFi Connecting...\\r " + String(WiFi.SSID()) + "\""); - unsigned long connectTimer = millis() + 10000; - - debugPrintln(String(F("WIFI: Connecting to previously-saved SSID: ")) + String(WiFi.SSID())); - - WiFi.begin(); - while ((WiFi.status() != WL_CONNECTED) && (millis() < connectTimer)) - { - yield(); - } - - unsigned int connectCounter = 0; - unsigned int connectRetries = 4; - unsigned int connectTime = 10000; - while ((WiFi.status() != WL_CONNECTED) && (connectCounter <= connectRetries)) - { - connectCounter++; - debugPrintln(String(F("WIFI: Connect failed, retry attempt ")) + String(connectCounter)); - WiFi.mode(WIFI_OFF); // Force the radio off, and then - delay(100); - WiFi.mode(WIFI_STA); // toggle it back on again - connectTimer = millis() + connectTime; - WiFi.begin(); - while ((WiFi.status() != WL_CONNECTED) && (millis() < connectTimer)) - { - yield(); - } - - if (WiFi.localIP().toString() == "(IP unset)") - { // Check if we have our IP yet - debugPrintln(F("WIFI: Failed to lease address from DHCP, disconnecting and trying again")); - WiFi.disconnect(); - } - } - } - - if (WiFi.status() != WL_CONNECTED) - { // We gave it a shot, still couldn't connect, so let WiFiManager run to make one last - // connection attempt and then flip to AP mode to collect credentials from the user. - WiFi.persistent(true); - WiFiManagerParameter custom_haspNodeHeader("
HASPone Node"); - WiFiManagerParameter custom_haspNode("haspNode", "
Node Name (required: lowercase letters, numbers, and _ only)", haspNode, 15, " maxlength=15 required pattern='[a-z0-9_]*'"); - WiFiManagerParameter custom_groupName("groupName", "Group Name (required)", groupName, 15, " maxlength=15 required"); - WiFiManagerParameter custom_mqttHeader("

MQTT"); - WiFiManagerParameter custom_mqttServer("mqttServer", "
MQTT Broker (required, IP address is preferred)", mqttServer, 127, " maxlength=127"); - WiFiManagerParameter custom_mqttPort("mqttPort", "MQTT Port (required)", mqttPort, 5, " maxlength=5 type='number'"); - WiFiManagerParameter custom_mqttUser("mqttUser", "MQTT User (optional)", mqttUser, 127, " maxlength=127"); - WiFiManagerParameter custom_mqttPassword("mqttPassword", "MQTT Password (optional)", mqttPassword, 127, " maxlength=127 type='password'"); - String mqttTlsEnabled_value = "F"; - if (mqttTlsEnabled) - { - mqttTlsEnabled_value = "T"; - } - String mqttTlsEnabled_checked = "type=\"checkbox\""; - if (mqttTlsEnabled) - { - mqttTlsEnabled_checked = "type=\"checkbox\" checked=\"true\""; - } - WiFiManagerParameter custom_mqttTlsEnabled("mqttTlsEnabled", "MQTT TLS enabled:", mqttTlsEnabled_value.c_str(), 2, mqttTlsEnabled_checked.c_str()); - WiFiManagerParameter custom_mqttFingerprint("mqttFingerprint", "
MQTT TLS Fingerprint (optional, enter as 01:23:AB:CD, etc)", mqttFingerprint, 59, " min length=59 maxlength=59"); - WiFiManagerParameter custom_configHeader("

Admin access"); - WiFiManagerParameter custom_configUser("configUser", "
Config User (required)", configUser, 15, " maxlength=31"); - WiFiManagerParameter custom_configPassword("configPassword", "Config Password (optional)", configPassword, 31, " maxlength=31 type='password'"); - WiFiManagerParameter custom_hassHeader("

Home Assistant integration"); - WiFiManagerParameter custom_hassDiscovery("hassDiscovery", "
Home Assistant Discovery topic (required, should probably be \"homeassistant\")", hassDiscovery, 127, " maxlength=127"); - - WiFiManager wifiManager; - wifiManager.setSaveConfigCallback(configSaveCallback); // set config save notify callback - wifiManager.setCustomHeadElement(HASP_STYLE); // add custom style - wifiManager.addParameter(&custom_haspNodeHeader); - wifiManager.addParameter(&custom_haspNode); - wifiManager.addParameter(&custom_groupName); - wifiManager.addParameter(&custom_mqttHeader); - wifiManager.addParameter(&custom_mqttServer); - wifiManager.addParameter(&custom_mqttPort); - wifiManager.addParameter(&custom_mqttUser); - wifiManager.addParameter(&custom_mqttPassword); - wifiManager.addParameter(&custom_mqttTlsEnabled); - wifiManager.addParameter(&custom_mqttFingerprint); - wifiManager.addParameter(&custom_configHeader); - wifiManager.addParameter(&custom_configUser); - wifiManager.addParameter(&custom_configPassword); - wifiManager.addParameter(&custom_hassHeader); - wifiManager.addParameter(&custom_hassDiscovery); - - // Timeout config portal after connectTimeout seconds, useful if configured wifi network was temporarily unavailable - wifiManager.setTimeout(connectTimeout); - - wifiManager.setAPCallback(espWifiConfigCallback); - - // Fetches SSID and pass from EEPROM and tries to connect - // If it does not connect it starts an access point with the specified name - // and goes into a blocking loop awaiting configuration. - if (!wifiManager.autoConnect(wifiConfigAP, wifiConfigPass)) - { // Reset and try again - debugPrintln(F("WIFI: Failed to connect and hit timeout")); - espReset(); - } - - // Read updated parameters - strcpy(mqttServer, custom_mqttServer.getValue()); - strcpy(mqttPort, custom_mqttPort.getValue()); - strcpy(mqttUser, custom_mqttUser.getValue()); - strcpy(mqttPassword, custom_mqttPassword.getValue()); - if (strcmp(custom_mqttTlsEnabled.getValue(), "T") == 0) - { - mqttTlsEnabled = true; - } - else - { - mqttTlsEnabled = false; - } - strcpy(mqttFingerprint, custom_mqttFingerprint.getValue()); - strcpy(haspNode, custom_haspNode.getValue()); - strcpy(groupName, custom_groupName.getValue()); - strcpy(configUser, custom_configUser.getValue()); - strcpy(configPassword, custom_configPassword.getValue()); - strcpy(hassDiscovery, custom_hassDiscovery.getValue()); - if (shouldSaveConfig) - { // Save the custom parameters to FS - configSave(); - } - } - } - else - { // wifiSSID has been defined, so attempt to connect to it - debugPrintln(String(F("Connecting to WiFi network: ")) + String(wifiSSID)); - WiFi.mode(WIFI_STA); - WiFi.begin(wifiSSID, wifiPass); - - unsigned long wifiReconnectTimer = millis(); - while (WiFi.status() != WL_CONNECTED) - { - delay(1); - if (millis() >= (wifiReconnectTimer + (connectTimeout * 1000))) - { // If we've been trying to reconnect for connectTimeout seconds, reboot and try again - debugPrintln(F("WIFI: Failed to connect and hit timeout")); - espReset(); - } - } - } - - // If you get here you have connected to WiFi - nextionSetAttr("p[0].b[1].font", "6"); - nextionSetAttr("p[0].b[1].txt", "\"WiFi Connected!\\r " + String(WiFi.SSID()) + "\\rIP: " + WiFi.localIP().toString() + "\""); - debugPrintln(String(F("WIFI: Connected successfully and assigned IP: ")) + WiFi.localIP().toString()); - if (nextionActivePage) - { - nextionSendCmd("page " + String(nextionActivePage)); - } - - rebootOnp0b1 = false; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void espWifiReconnect() -{ // Existing WiFi connection dropped, try to reconnect - debugPrintln(F("Reconnecting to WiFi network...")); - WiFi.mode(WIFI_STA); - WiFi.begin(wifiSSID, wifiPass); - - unsigned long wifiReconnectTimer = millis(); - while (WiFi.status() != WL_CONNECTED) - { - delay(1); - if (millis() >= (wifiReconnectTimer + (reConnectTimeout * 1000))) - { // If we've been trying to reconnect for reConnectTimeout seconds, reboot and try again - debugPrintln(F("WIFI: Failed to reconnect and hit timeout")); - espReset(); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void espWifiConfigCallback(WiFiManager *myWiFiManager) -{ // Notify the user that we're entering config mode - debugPrintln(F("WIFI: Failed to connect to assigned AP, entering config mode")); - if (lcdVersion < 1 || lcdVersion > 2) - { - nextionSendCmd("page 0"); - } - nextionSetAttr("p[0].b[1].font", "6"); - nextionSetAttr("p[0].b[1].txt", "\" HASPone Setup\\r AP: " + String(wifiConfigAP) + "\\rPassword: " + String(wifiConfigPass) + "\\r\\r\\r\\r\\r\\r\\r http://192.168.4.1\""); - nextionSendCmd("vis 3,1"); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void espSetupOta() -{ // Update ESP firmware from network via Arduino OTA - - ArduinoOTA.setHostname(haspNode); - ArduinoOTA.setPassword(configPassword); - ArduinoOTA.setRebootOnSuccess(false); - - ArduinoOTA.onStart([]() - { - debugPrintln(F("ESP OTA: update start")); - nextionSetAttr("p[0].b[1].txt", "\"\\rHASPone update:\\r\\r\\r \""); - nextionSendCmd("page 0"); - nextionSendCmd("vis 4,1"); }); - ArduinoOTA.onEnd([]() - { - debugPrintln(F("ESP OTA: update complete")); - nextionSetAttr("p[0].b[1].txt", "\"\\rHASPone update:\\r\\r Complete!\\rRestarting.\""); - nextionSendCmd("vis 4,1"); - delay(1000); - espReset(); }); - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) - { nextionUpdateProgress(progress, total); }); - ArduinoOTA.onError([](ota_error_t error) - { - debugPrintln(String(F("ESP OTA: ERROR code ")) + String(error)); - if (error == OTA_AUTH_ERROR) - debugPrintln(F("ESP OTA: ERROR - Auth Failed")); - else if (error == OTA_BEGIN_ERROR) - debugPrintln(F("ESP OTA: ERROR - Begin Failed")); - else if (error == OTA_CONNECT_ERROR) - debugPrintln(F("ESP OTA: ERROR - Connect Failed")); - else if (error == OTA_RECEIVE_ERROR) - debugPrintln(F("ESP OTA: ERROR - Receive Failed")); - else if (error == OTA_END_ERROR) - debugPrintln(F("ESP OTA: ERROR - End Failed")); - nextionSendCmd("vis 4,0"); - nextionSetAttr("p[0].b[1].txt", "\"HASPone update:\\r FAILED\\rerror: " + String(error) + "\""); - delay(1000); - nextionSendCmd("page " + String(nextionActivePage)); }); - ArduinoOTA.begin(); - debugPrintln(F("ESP OTA: Over the Air firmware update ready")); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void espStartOta(const String &espOtaUrl) -{ // Update ESP firmware from HTTP/HTTPS URL - - nextionSetAttr("p[0].b[1].txt", "\"\\rHASPone update:\\r\\r\\r \""); - nextionSendCmd("page 0"); - nextionSendCmd("vis 4,1"); - - WiFiUDP::stopAll(); // Keep mDNS responder from breaking things - delay(1); - ESPhttpUpdate.rebootOnUpdate(false); - ESPhttpUpdate.onProgress(nextionUpdateProgress); - t_httpUpdate_return espOtaUrlReturnCode; - if (espOtaUrl.startsWith(F("https"))) - { - debugPrintln(String(F("ESPFW: Attempting firmware update from HTTPS host: ")) + espOtaUrl); - WiFiClientSecure wifiEspOtaClientSecure; - wifiEspOtaClientSecure.setInsecure(); - wifiEspOtaClientSecure.setBufferSizes(512, 512); - espOtaUrlReturnCode = ESPhttpUpdate.update(wifiEspOtaClientSecure, espOtaUrl); - } - else - { - debugPrintln(String(F("ESPFW: Attempting firmware update from HTTP host: ")) + espOtaUrl); - espOtaUrlReturnCode = ESPhttpUpdate.update(wifiClient, espOtaUrl); - } - - switch (espOtaUrlReturnCode) - { - case HTTP_UPDATE_FAILED: - debugPrintln(String(F("ESPFW: HTTP_UPDATE_FAILED error ")) + String(ESPhttpUpdate.getLastError()) + " " + ESPhttpUpdate.getLastErrorString()); - nextionSendCmd("vis 4,0"); - nextionSetAttr("p[0].b[1].txt", "\"HASPone update:\\r FAILED\\rerror: " + ESPhttpUpdate.getLastErrorString() + "\""); - break; - - case HTTP_UPDATE_NO_UPDATES: - debugPrintln(F("ESPFW: HTTP_UPDATE_NO_UPDATES")); - nextionSendCmd("vis 4,0"); - nextionSetAttr("p[0].b[1].txt", "\"HASPone update:\\rNo update\""); - break; - - case HTTP_UPDATE_OK: - debugPrintln(F("ESPFW: HTTP_UPDATE_OK")); - nextionSetAttr("p[0].b[1].txt", "\"\\rHASPone update:\\r\\r Complete!\\rRestarting.\""); - nextionSendCmd("vis 4,1"); - delay(1000); - espReset(); - } - delay(1000); - nextionSendCmd("page " + String(nextionActivePage)); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void espReset() -{ - debugPrintln(F("RESET: HASPone reset")); - if (mqttClient.connected()) - { - mqttClient.publish(mqttStateJSONTopic, String(F("{\"event_type\":\"hasp_device\",\"event\":\"offline\"}"))); - debugPrintln(String(F("MQTT OUT: '")) + mqttStateJSONTopic + String(F(" : {\"event_type\":\"hasp_device\",\"event\":\"offline\"}"))); - mqttClient.publish(mqttStatusTopic, "OFF", true, 0); - mqttClient.disconnect(); - debugPrintln(String(F("MQTT OUT: '")) + mqttStatusTopic + String(F("' : 'OFF'"))); - } - debugPrintln(F("HMI: Rebooting LCD")); - digitalWrite(nextionResetPin, LOW); - Serial1.print("rest"); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.flush(); - delay(500); - ESP.reset(); - delay(5000); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void configRead() -{ // Read saved config.json from SPIFFS - debugPrintln(F("SPIFFS: mounting SPIFFS")); - if (SPIFFS.begin()) - { - if (SPIFFS.exists("/config.json")) - { // File exists, reading and loading - debugPrintln(F("SPIFFS: reading /config.json")); - // debugPrintFile("/config.json"); - File configFile = SPIFFS.open("/config.json", "r"); - if (configFile) - { - DynamicJsonDocument jsonConfigValues(1536); - DeserializationError jsonError = deserializeJson(jsonConfigValues, configFile); - - if (jsonError) - { // Couldn't parse the saved config - debugPrintln(String(F("SPIFFS: [ERROR] Failed to parse /config.json: ")) + String(jsonError.c_str())); - } - else - { - if (!jsonConfigValues["mqttServer"].isNull()) - { - strcpy(mqttServer, jsonConfigValues["mqttServer"]); - } - if (!jsonConfigValues["mqttPort"].isNull()) - { - strcpy(mqttPort, jsonConfigValues["mqttPort"]); - } - if (!jsonConfigValues["mqttUser"].isNull()) - { - strcpy(mqttUser, jsonConfigValues["mqttUser"]); - } - if (!jsonConfigValues["mqttPassword"].isNull()) - { - strcpy(mqttPassword, jsonConfigValues["mqttPassword"]); - } - if (!jsonConfigValues["mqttFingerprint"].isNull()) - { - strcpy(mqttFingerprint, jsonConfigValues["mqttFingerprint"]); - } - if (!jsonConfigValues["haspNode"].isNull()) - { - strcpy(haspNode, jsonConfigValues["haspNode"]); - } - if (!jsonConfigValues["groupName"].isNull()) - { - strcpy(groupName, jsonConfigValues["groupName"]); - } - if (!jsonConfigValues["configUser"].isNull()) - { - strcpy(configUser, jsonConfigValues["configUser"]); - } - if (!jsonConfigValues["configPassword"].isNull()) - { - strcpy(configPassword, jsonConfigValues["configPassword"]); - } - if (!jsonConfigValues["hassDiscovery"].isNull()) - { - strcpy(hassDiscovery, jsonConfigValues["hassDiscovery"]); - } - if (strcmp(hassDiscovery, "") == 0) - { // Cover off any edge case where this value winds up being empty - debugPrintln(F("SPIFFS: [WARNING] /config.json has empty hassDiscovery value, setting to 'homeassistant'")); - strcpy(hassDiscovery, "homeassistant"); - } - if (!jsonConfigValues["nextionBaud"].isNull()) - { - strcpy(nextionBaud, jsonConfigValues["nextionBaud"]); - } - if (strcmp(nextionBaud, "") == 0) - { // Cover off any edge case where this value winds up being empty - debugPrintln(F("SPIFFS: [WARNING] /config.json has empty nextionBaud value, setting to '115200'")); - strcpy(nextionBaud, "115200"); - } - if (!jsonConfigValues["nextionMaxPages"].isNull()) - { - nextionMaxPages = jsonConfigValues["nextionMaxPages"]; - } - if (nextionMaxPages < 1) - { // Cover off any edge case where this value winds up being zero or negative - debugPrintln(F("SPIFFS: [WARNING] /config.json has nextionMaxPages value of zero or negative, setting to '11'")); - nextionMaxPages = 11; - } - if (!jsonConfigValues["motionPinConfig"].isNull()) - { - strcpy(motionPinConfig, jsonConfigValues["motionPinConfig"]); - } - if (!jsonConfigValues["mqttTlsEnabled"].isNull()) - { - if (jsonConfigValues["mqttTlsEnabled"]) - { - mqttTlsEnabled = true; - } - else - { - mqttTlsEnabled = false; - } - } - if (!jsonConfigValues["debugSerialEnabled"].isNull()) - { - if (jsonConfigValues["debugSerialEnabled"]) - { - debugSerialEnabled = true; - } - else - { - debugSerialEnabled = false; - } - } - if (!jsonConfigValues["debugTelnetEnabled"].isNull()) - { - if (jsonConfigValues["debugTelnetEnabled"]) - { - debugTelnetEnabled = true; - } - else - { - debugTelnetEnabled = false; - } - } - if (!jsonConfigValues["mdnsEnabled"].isNull()) - { - if (jsonConfigValues["mdnsEnabled"]) - { - mdnsEnabled = true; - } - else - { - mdnsEnabled = false; - } - } - if (!jsonConfigValues["beepEnabled"].isNull()) - { - if (jsonConfigValues["beepEnabled"]) - { - beepEnabled = true; - } - else - { - beepEnabled = false; - } - } - if (!jsonConfigValues["ignoreTouchWhenOff"].isNull()) - { - if (jsonConfigValues["ignoreTouchWhenOff"]) - { - ignoreTouchWhenOff = true; - } - else - { - ignoreTouchWhenOff = false; - } - } - String jsonConfigValuesStr; - serializeJson(jsonConfigValues, jsonConfigValuesStr); - debugPrintln(String(F("SPIFFS: read ")) + String(configFile.size()) + String(F(" bytes and parsed json:")) + jsonConfigValuesStr); - } - } - else - { - debugPrintln(F("SPIFFS: [ERROR] Failed to read /config.json")); - } - } - else - { - debugPrintln(F("SPIFFS: [WARNING] /config.json not found, will be created on first config save")); - } - } - else - { - debugPrintln(F("SPIFFS: [ERROR] Failed to mount FS")); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void configSaveCallback() -{ // Callback notifying us of the need to save config - debugPrintln(F("SPIFFS: Configuration changed, flagging for save")); - shouldSaveConfig = true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void configSave() -{ // Save the custom parameters to config.json - debugPrintln(F("SPIFFS: Saving config")); - DynamicJsonDocument jsonConfigValues(2048); - - jsonConfigValues["mqttServer"] = mqttServer; - jsonConfigValues["mqttPort"] = mqttPort; - jsonConfigValues["mqttUser"] = mqttUser; - jsonConfigValues["mqttPassword"] = mqttPassword; - jsonConfigValues["mqttTlsEnabled"] = mqttTlsEnabled; - jsonConfigValues["mqttFingerprint"] = mqttFingerprint; - jsonConfigValues["haspNode"] = haspNode; - jsonConfigValues["groupName"] = groupName; - jsonConfigValues["configUser"] = configUser; - jsonConfigValues["configPassword"] = configPassword; - jsonConfigValues["hassDiscovery"] = hassDiscovery; - jsonConfigValues["nextionBaud"] = nextionBaud; - jsonConfigValues["nextionMaxPages"] = nextionMaxPages; - jsonConfigValues["motionPinConfig"] = motionPinConfig; - jsonConfigValues["debugSerialEnabled"] = debugSerialEnabled; - jsonConfigValues["debugTelnetEnabled"] = debugTelnetEnabled; - jsonConfigValues["mdnsEnabled"] = mdnsEnabled; - jsonConfigValues["beepEnabled"] = beepEnabled; - jsonConfigValues["ignoreTouchWhenOff"] = ignoreTouchWhenOff; - - debugPrintln(String(F("SPIFFS: mqttServer = ")) + String(mqttServer)); - debugPrintln(String(F("SPIFFS: mqttPort = ")) + String(mqttPort)); - debugPrintln(String(F("SPIFFS: mqttUser = ")) + String(mqttUser)); - debugPrintln(String(F("SPIFFS: mqttPassword = ")) + String(mqttPassword)); - debugPrintln(String(F("SPIFFS: mqttTlsEnabled = ")) + String(mqttTlsEnabled)); - debugPrintln(String(F("SPIFFS: mqttFingerprint = ")) + String(mqttFingerprint)); - debugPrintln(String(F("SPIFFS: haspNode = ")) + String(haspNode)); - debugPrintln(String(F("SPIFFS: groupName = ")) + String(groupName)); - debugPrintln(String(F("SPIFFS: configUser = ")) + String(configUser)); - debugPrintln(String(F("SPIFFS: configPassword = ")) + String(configPassword)); - debugPrintln(String(F("SPIFFS: hassDiscovery = ")) + String(hassDiscovery)); - debugPrintln(String(F("SPIFFS: nextionBaud = ")) + String(nextionBaud)); - debugPrintln(String(F("SPIFFS: nextionMaxPages = ")) + String(nextionMaxPages)); - debugPrintln(String(F("SPIFFS: motionPinConfig = ")) + String(motionPinConfig)); - debugPrintln(String(F("SPIFFS: debugSerialEnabled = ")) + String(debugSerialEnabled)); - debugPrintln(String(F("SPIFFS: debugTelnetEnabled = ")) + String(debugTelnetEnabled)); - debugPrintln(String(F("SPIFFS: mdnsEnabled = ")) + String(mdnsEnabled)); - debugPrintln(String(F("SPIFFS: beepEnabled = ")) + String(beepEnabled)); - debugPrintln(String(F("SPIFFS: ignoreTouchWhenOff = ")) + String(ignoreTouchWhenOff)); - - File configFile = SPIFFS.open("/config.json", "w"); - if (!configFile) - { - debugPrintln(F("SPIFFS: Failed to open config file for writing")); - } - else - { - serializeJson(jsonConfigValues, configFile); - configFile.print("\n\n\n"); - configFile.flush(); - delay(10); - configFile.close(); - } - debugPrintFile("/config.json"); - shouldSaveConfig = false; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void configClearSaved() -{ // Clear out all local storage - nextionSetAttr("dims", "100"); - nextionSendCmd("page 0"); - nextionSetAttr("p[0].b[1].txt", "\"Resetting\\rsystem...\""); - debugPrintln(F("RESET: Formatting SPIFFS")); - SPIFFS.format(); - debugPrintln(F("RESET: Clearing WiFiManager settings...")); - WiFi.disconnect(); - WiFiManager wifiManager; - wifiManager.resetSettings(); - EEPROM.begin(512); - debugPrintln(F("Clearing EEPROM...")); - for (uint16_t i = 0; i < EEPROM.length(); i++) - { - EEPROM.write(i, 0); - } - nextionSetAttr("p[0].b[1].txt", "\"Rebooting\\rsystem...\""); - debugPrintln(F("RESET: Rebooting device")); - espReset(); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleNotFound() -{ // webServer 404 - debugPrintln(String(F("HTTP: Sending 404 to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " 404"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(404, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

404: File Not Found

One of us appears to have done something horribly wrong.
URI: ")); - webServer.sendContent(webServer.uri()); - webServer.sendContent(F("
Method: ")); - webServer.sendContent((webServer.method() == HTTP_GET) ? F("GET") : F("POST")); - webServer.sendContent(F("
Arguments: ")); - webServer.sendContent(String(webServer.args())); - for (uint8_t i = 0; i < webServer.args(); i++) - { - webServer.sendContent(F("
")); - webServer.sendContent(String(webServer.argName(i))); - webServer.sendContent(F(": ")); - webServer.sendContent(String(webServer.arg(i))); - } - webServer.sendContent(""); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleRoot() -{ // http://plate01/ - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending root page to client connected from: ")) + webServer.client().remoteIP().toString()); - - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode)); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent(httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent_P(HTTP_HEAD_END); - - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F("

")); - - webServer.sendContent(F("
")); - webServer.sendContent(F("WiFi SSID (required)
WiFi Password (required)")); - webServer.sendContent(F("

HASPone Node Name (required. lowercase letters, numbers, and _ only)
Group Name (required)

MQTT Broker (required, IP address is preferred)
MQTT Port (required)
MQTT User (optional)
MQTT Password (optional)")); - - webServer.sendContent(F("
MQTT TLS enabled:
MQTT TLS Fingerpint (leave blank to disable fingerprint checking)")); - - webServer.sendContent(F("

HASPone Admin Username (optional)
HASPone Admin Password (optional)

Home Assistant discovery topic (required, should probably be \"homeassistant\")
Nextion project page count (required, probably \"11\")

")); - // Big menu of possible serial speeds - if ((lcdVersion != 1) && (lcdVersion != 2)) - { // HASPone lcdVersion 1 and 2 have `bauds=115200` in the pre-init script of page 0. Don't show this option if either of those two versions are running. - webServer.sendContent(F("LCD Serial Speed: 
")); - } - - webServer.sendContent(F("USB serial debug output enabled:
Telnet debug output enabled:
mDNS enabled:
Motion Sensor Pin: ")); - webServer.sendContent(F("
Keypress beep enabled on D2:
Ignore touchevents when backlight is off:

")); - - if (updateEspAvailable) - { - webServer.sendContent(F("

HASPone Update available!

")); - webServer.sendContent(F("
")); - webServer.sendContent(F("
")); - } - - webServer.sendContent(F("
")); - webServer.sendContent(F("
")); - - webServer.sendContent(F("
")); - webServer.sendContent(F("
")); - - webServer.sendContent(F("
")); - webServer.sendContent(F("
")); - - webServer.sendContent(F("
")); - webServer.sendContent(F("
")); - - webServer.sendContent(F("
MQTT Status: ")); - if (mqttClient.connected()) - { // Check MQTT connection - webServer.sendContent(F("Connected")); - } - else - { - webServer.sendContent(F("Disconnected
MQTT return code: ")); - webServer.sendContent(String(mqttClient.returnCode())); - webServer.sendContent(F("
MQTT last error: ")); - webServer.sendContent(String(mqttClient.lastError())); - webServer.sendContent(F("
MQTT broker ping check: ")); - if (mqttPingCheck) - { - webServer.sendContent(F("SUCCESS")); - } - else - { - webServer.sendContent(F("FAILED")); - } - webServer.sendContent(F("
MQTT broker port check: ")); - if (mqttPortCheck) - { - webServer.sendContent(F("SUCCESS")); - } - else - { - webServer.sendContent(F("FAILED")); - } - } - webServer.sendContent(F("
MQTT ClientID: ")); - if (mqttClientId != "") - { - webServer.sendContent(mqttClientId); - } - webServer.sendContent(F("
HASPone FW Version: ")); - webServer.sendContent(String(haspVersion)); - webServer.sendContent(F("
LCD Model: ")); - if (nextionModel != "") - { - webServer.sendContent(nextionModel); - } - webServer.sendContent(F("
LCD FW Version: ")); - webServer.sendContent(String(lcdVersion)); - webServer.sendContent(F("
LCD Active Page: ")); - webServer.sendContent(String(nextionActivePage)); - webServer.sendContent(F("
LCD Serial Speed: ")); - webServer.sendContent(nextionBaud); - webServer.sendContent(F("
CPU Frequency: ")); - webServer.sendContent(String(ESP.getCpuFreqMHz())); - webServer.sendContent(F("MHz")); - webServer.sendContent(F("
Sketch Size: ")); - webServer.sendContent(String(ESP.getSketchSize())); - webServer.sendContent(F(" bytes")); - webServer.sendContent(F("
Free Sketch Space: ")); - webServer.sendContent(String(ESP.getFreeSketchSpace())); - webServer.sendContent(F(" bytes")); - webServer.sendContent(F("
Heap Free: ")); - webServer.sendContent(String(ESP.getFreeHeap())); - webServer.sendContent(F("
Heap Fragmentation: ")); - webServer.sendContent(String(ESP.getHeapFragmentation())); - webServer.sendContent(F("
ESP core version: ")); - webServer.sendContent(ESP.getCoreVersion()); - webServer.sendContent(F("
IP Address: ")); - webServer.sendContent(WiFi.localIP().toString()); - webServer.sendContent(F("
Signal Strength: ")); - webServer.sendContent(String(WiFi.RSSI())); - webServer.sendContent(F("
Uptime: ")); - webServer.sendContent(String(long(millis() / 1000))); - webServer.sendContent(F("
Last reset: ")); - webServer.sendContent(ESP.getResetInfo()); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleSaveConfig() -{ // http://plate01/saveConfig - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /saveConfig page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " Saving configuration"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent_P(HTTP_HEAD_END); - - bool shouldSaveWifi = false; - // Check required values - if ((webServer.arg("wifiSSID") != "") && (webServer.arg("wifiSSID") != String(WiFi.SSID()))) - { // Handle WiFi SSID - shouldSaveConfig = true; - shouldSaveWifi = true; - webServer.arg("wifiSSID").toCharArray(wifiSSID, 32); - } - if ((webServer.arg("wifiPass") != String(wifiPass)) && (webServer.arg("wifiPass") != String("********"))) - { // Handle WiFi password - shouldSaveConfig = true; - shouldSaveWifi = true; - webServer.arg("wifiPass").toCharArray(wifiPass, 64); - } - - if (webServer.arg("haspNode") != "" && webServer.arg("haspNode") != String(haspNode)) - { // Handle haspNode - shouldSaveConfig = true; - String lowerHaspNode = webServer.arg("haspNode"); - lowerHaspNode.toLowerCase(); - lowerHaspNode.toCharArray(haspNode, 16); - } - if (webServer.arg("groupName") != "" && webServer.arg("groupName") != String(groupName)) - { // Handle groupName - shouldSaveConfig = true; - webServer.arg("groupName").toCharArray(groupName, 16); - } - - if (webServer.arg("mqttServer") != "" && webServer.arg("mqttServer") != String(mqttServer)) - { // Handle mqttServer - shouldSaveConfig = true; - webServer.arg("mqttServer").toCharArray(mqttServer, 128); - } - if (webServer.arg("mqttPort") != "" && webServer.arg("mqttPort") != String(mqttPort)) - { // Handle mqttPort - shouldSaveConfig = true; - webServer.arg("mqttPort").toCharArray(mqttPort, 6); - } - if (webServer.arg("mqttUser") != String(mqttUser)) - { // Handle mqttUser - shouldSaveConfig = true; - webServer.arg("mqttUser").toCharArray(mqttUser, 128); - } - if (webServer.arg("mqttPassword") != String("********")) - { // Handle mqttPassword - shouldSaveConfig = true; - webServer.arg("mqttPassword").toCharArray(mqttPassword, 128); - } - if ((webServer.arg("mqttTlsEnabled") == String("on")) && !mqttTlsEnabled) - { // mqttTlsEnabled was disabled but should now be enabled - shouldSaveConfig = true; - mqttTlsEnabled = true; - } - else if ((webServer.arg("mqttTlsEnabled") == String("")) && mqttTlsEnabled) - { // mqttTlsEnabled was enabled but should now be disabled - shouldSaveConfig = true; - mqttTlsEnabled = false; - } - if (webServer.arg("mqttFingerprint") != String(mqttFingerprint)) - { // Handle mqttFingerprint - shouldSaveConfig = true; - webServer.arg("mqttFingerprint").toCharArray(mqttFingerprint, 60); - } - if (webServer.arg("configUser") != String(configUser)) - { // Handle configUser - shouldSaveConfig = true; - webServer.arg("configUser").toCharArray(configUser, 32); - } - if (webServer.arg("configPassword") != String("********")) - { // Handle configPassword - shouldSaveConfig = true; - webServer.arg("configPassword").toCharArray(configPassword, 32); - } - if (webServer.arg("hassDiscovery") != String(hassDiscovery)) - { // Handle hassDiscovery - shouldSaveConfig = true; - webServer.arg("hassDiscovery").toCharArray(hassDiscovery, 128); - } - if ((webServer.arg("nextionMaxPages") != String(nextionMaxPages)) && (webServer.arg("nextionMaxPages").toInt() < 256) && (webServer.arg("nextionMaxPages").toInt() > 0)) - { - shouldSaveConfig = true; - nextionMaxPages = webServer.arg("nextionMaxPages").toInt(); - } - if (webServer.arg("nextionBaud") != String(nextionBaud)) - { // Handle nextionBaud - shouldSaveConfig = true; - webServer.arg("nextionBaud").toCharArray(nextionBaud, 7); - } - if (webServer.arg("motionPinConfig") != String(motionPinConfig)) - { // Handle motionPinConfig - shouldSaveConfig = true; - webServer.arg("motionPinConfig").toCharArray(motionPinConfig, 3); - } - if ((webServer.arg("debugSerialEnabled") == String("on")) && !debugSerialEnabled) - { // debugSerialEnabled was disabled but should now be enabled - shouldSaveConfig = true; - debugSerialEnabled = true; - } - else if ((webServer.arg("debugSerialEnabled") == String("")) && debugSerialEnabled) - { // debugSerialEnabled was enabled but should now be disabled - shouldSaveConfig = true; - debugSerialEnabled = false; - } - if ((webServer.arg("debugTelnetEnabled") == String("on")) && !debugTelnetEnabled) - { // debugTelnetEnabled was disabled but should now be enabled - shouldSaveConfig = true; - debugTelnetEnabled = true; - } - else if ((webServer.arg("debugTelnetEnabled") == String("")) && debugTelnetEnabled) - { // debugTelnetEnabled was enabled but should now be disabled - shouldSaveConfig = true; - debugTelnetEnabled = false; - } - if ((webServer.arg("mdnsEnabled") == String("on")) && !mdnsEnabled) - { // mdnsEnabled was disabled but should now be enabled - shouldSaveConfig = true; - mdnsEnabled = true; - } - else if ((webServer.arg("mdnsEnabled") == String("")) && mdnsEnabled) - { // mdnsEnabled was enabled but should now be disabled - shouldSaveConfig = true; - mdnsEnabled = false; - } - if ((webServer.arg("beepEnabled") == String("on")) && !beepEnabled) - { // beepEnabled was disabled but should now be enabled - shouldSaveConfig = true; - beepEnabled = true; - } - else if ((webServer.arg("beepEnabled") == String("")) && beepEnabled) - { // beepEnabled was enabled but should now be disabled - shouldSaveConfig = true; - beepEnabled = false; - } - if ((webServer.arg("ignoreTouchWhenOff") == String("on")) && !ignoreTouchWhenOff) - { // ignoreTouchWhenOff was disabled but should now be enabled - shouldSaveConfig = true; - ignoreTouchWhenOff = true; - } - else if ((webServer.arg("ignoreTouchWhenOff") == String("")) && ignoreTouchWhenOff) - { // ignoreTouchWhenOff was enabled but should now be disabled - shouldSaveConfig = true; - ignoreTouchWhenOff = false; - } - - if (shouldSaveConfig) - { // Config updated, notify user and trigger write to SPIFFS - - webServer.sendContent(F("")); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F("

")); - webServer.sendContent(F("
Saving updated configuration values and restarting device")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(F("")); - configSave(); - if (shouldSaveWifi) - { - debugPrintln(String(F("CONFIG: Attempting connection to SSID: ")) + webServer.arg("wifiSSID")); - espWifiConnect(); - } - espReset(); - } - else - { // No change found, notify user and link back to config page - webServer.sendContent(F("")); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F("

")); - webServer.sendContent(F("
No changes found, returning to home page")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(F("")); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleResetConfig() -{ // http://plate01/resetConfig - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /resetConfig page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " Resetting configuration"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent_P(HTTP_HEAD_END); - - if (webServer.arg("confirm") == "yes") - { // User has confirmed, so reset everything - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F("

Resetting all saved settings and restarting device into WiFi AP mode")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); - delay(100); - configClearSaved(); - } - else - { - webServer.sendContent(F("

Warning

This process will reset all settings to the default values and restart the device. You may need to connect to the WiFi AP displayed on the panel to re-configure the device before accessing it again.")); - webServer.sendContent(F("


")); - webServer.sendContent(F("

")); - webServer.sendContent(F("


")); - webServer.sendContent(F("
")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleResetBacklight() -{ // http://plate01/resetBacklight - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /resetBacklight page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " Backlight reset"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent(F("")); - webServer.sendContent_P(HTTP_HEAD_END); - - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F("

")); - webServer.sendContent(F("
Resetting backlight to 100%")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); - debugPrintln(F("HTTP: Resetting backlight to 100%")); - nextionSetAttr("dims", "100"); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleFirmware() -{ // http://plate01/firmware - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /firmware page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " Firmware updates"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent_P(HTTP_HEAD_END); - - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F(" Firmware updates

Note: If updating firmware for both the ESP8266 and the Nextion LCD, you'll want to update the ESP8266 first followed by the Nextion LCD

")); - - // Display main firmware page - webServer.sendContent(F("
")); - if (updateEspAvailable) - { - webServer.sendContent(F("HASPone ESP8266 update available!")); - } - webServer.sendContent(F("
Update ESP8266 from URL")); - webServer.sendContent(F("


")); - - webServer.sendContent(F("
")); - webServer.sendContent(F("Update ESP8266 from file")); - webServer.sendContent(F("

")); - - webServer.sendContent(F("


WARNING!

")); - webServer.sendContent(F("Nextion LCD firmware updates can be risky. If interrupted, the LCD will display an error message until a successful firmware update has completed. ")); - webServer.sendContent(F("

Note: Failed LCD firmware updates on HASPone hardware prior to v1.0 may require a hard power cycle of the device, via a circuit breaker or by physically disconnecting the device.")); - - webServer.sendContent(F("

")); - if (updateLcdAvailable) - { - webServer.sendContent(F("HASPone LCD update available!")); - } - webServer.sendContent(F("
Update Nextion LCD from URL http only")); - webServer.sendContent(F("


")); - - webServer.sendContent(F("
")); - webServer.sendContent(F("
Update Nextion LCD from file")); - webServer.sendContent(F("

")); - - // Javascript to collect the filesize of the LCD upload and send it to /tftFileSize - webServer.sendContent(F("")); - - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleEspFirmware() -{ // http://plate01/espfirmware - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - - debugPrintln(String(F("HTTP: Sending /espfirmware page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " ESP8266 firmware update"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent(F("")); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F(" ESP8266 firmware update

")); - webServer.sendContent(F("
Updating ESP firmware from: ")); - webServer.sendContent(webServer.arg("espFirmware")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); - - debugPrintln("ESPFW: Attempting ESP firmware update from: " + String(webServer.arg("espFirmware"))); - espStartOta(webServer.arg("espFirmware")); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleLcdUpload() -{ // http://plate01/lcdupload - // Upload firmware to the Nextion LCD via HTTP upload - - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - - static uint32_t lcdOtaTransferred = 0; - static uint32_t lcdOtaRemaining; - static uint16_t lcdOtaParts; - const uint32_t lcdOtaTimeout = 30000; // timeout for receiving new data in milliseconds - static uint32_t lcdOtaTimer = 0; // timer for upload timeout - - HTTPUpload &upload = webServer.upload(); - - if (tftFileSize == 0) - { - debugPrintln(String(F("LCDOTA: FAILED, no filesize sent."))); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " LCD update error"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent(F("")); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F(" LCD update FAILED

")); - webServer.sendContent(F("No update file size reported. You must use a modern browser with Javascript enabled.")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); - } - else if ((lcdOtaTimer > 0) && ((millis() - lcdOtaTimer) > lcdOtaTimeout)) - { // Our timer expired so reset - debugPrintln(F("LCDOTA: ERROR: LCD upload timeout. Restarting.")); - espReset(); - } - else if (upload.status == UPLOAD_FILE_START) - { - WiFiUDP::stopAll(); // Keep mDNS responder from breaking things - - debugPrintln(String(F("LCDOTA: Attempting firmware upload"))); - debugPrintln(String(F("LCDOTA: upload.filename: ")) + String(upload.filename)); - debugPrintln(String(F("LCDOTA: TFTfileSize: ")) + String(tftFileSize)); - - lcdOtaRemaining = tftFileSize; - lcdOtaParts = (lcdOtaRemaining / 4096) + 1; - debugPrintln(String(F("LCDOTA: File upload beginning. Size ")) + String(lcdOtaRemaining) + String(F(" bytes in ")) + String(lcdOtaParts) + String(F(" 4k chunks."))); - - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); // Send empty command to LCD - Serial1.flush(); - nextionHandleInput(); - - String lcdOtaNextionCmd = "whmi-wri " + String(tftFileSize) + "," + String(nextionBaud) + ",0"; - debugPrintln(String(F("LCDOTA: Sending LCD upload command: ")) + lcdOtaNextionCmd); - Serial1.print(lcdOtaNextionCmd); - Serial1.write(nextionSuffix, sizeof(nextionSuffix)); - Serial1.flush(); - - if (nextionOtaResponse()) - { - debugPrintln(F("LCDOTA: LCD upload command accepted")); - } - else - { - debugPrintln(F("LCDOTA: LCD upload command FAILED.")); - espReset(); - } - lcdOtaTimer = millis(); - } - else if (upload.status == UPLOAD_FILE_WRITE) - { // Handle upload data - static int lcdOtaChunkCounter = 0; - static uint16_t lcdOtaPartNum = 0; - static int lcdOtaPercentComplete = 0; - static const uint16_t lcdOtaBufferSize = 1024; // upload data buffer before sending to UART - static uint8_t lcdOtaBuffer[lcdOtaBufferSize] = {}; - uint16_t lcdOtaUploadIndex = 0; - int32_t lcdOtaPacketRemaining = upload.currentSize; - - while (lcdOtaPacketRemaining > 0) - { // Write incoming data to panel as it arrives - // determine chunk size as lowest value of lcdOtaPacketRemaining, lcdOtaBufferSize, or 4096 - lcdOtaChunkCounter - uint16_t lcdOtaChunkSize = 0; - if ((lcdOtaPacketRemaining <= lcdOtaBufferSize) && (lcdOtaPacketRemaining <= (4096 - lcdOtaChunkCounter))) - { - lcdOtaChunkSize = lcdOtaPacketRemaining; - } - else if ((lcdOtaBufferSize <= lcdOtaPacketRemaining) && (lcdOtaBufferSize <= (4096 - lcdOtaChunkCounter))) - { - lcdOtaChunkSize = lcdOtaBufferSize; - } - else - { - lcdOtaChunkSize = 4096 - lcdOtaChunkCounter; - } - - for (uint16_t i = 0; i < lcdOtaChunkSize; i++) - { // Load up the UART buffer - lcdOtaBuffer[i] = upload.buf[lcdOtaUploadIndex]; - lcdOtaUploadIndex++; - } - Serial1.flush(); // Clear out current UART buffer - Serial1.write(lcdOtaBuffer, lcdOtaChunkSize); // And send the most recent data - lcdOtaChunkCounter += lcdOtaChunkSize; - lcdOtaTransferred += lcdOtaChunkSize; - if (lcdOtaChunkCounter >= 4096) - { - Serial1.flush(); - lcdOtaPartNum++; - lcdOtaPercentComplete = (lcdOtaTransferred * 100) / tftFileSize; - lcdOtaChunkCounter = 0; - if (nextionOtaResponse()) - { - debugPrintln(String(F("LCDOTA: Part ")) + String(lcdOtaPartNum) + String(F(" OK, ")) + String(lcdOtaPercentComplete) + String(F("% complete"))); - } - else - { - debugPrintln(String(F("LCDOTA: Part ")) + String(lcdOtaPartNum) + String(F(" FAILED, ")) + String(lcdOtaPercentComplete) + String(F("% complete"))); - } - } - else - { - delay(10); - } - if (lcdOtaRemaining > 0) - { - lcdOtaRemaining -= lcdOtaChunkSize; - } - if (lcdOtaPacketRemaining > 0) - { - lcdOtaPacketRemaining -= lcdOtaChunkSize; - } - } - - if (lcdOtaTransferred >= tftFileSize) - { - if (nextionOtaResponse()) - { - debugPrintln(String(F("LCDOTA: Success, wrote ")) + String(lcdOtaTransferred) + " of " + String(tftFileSize) + " bytes."); - webServer.sendHeader("Location", "/lcdOtaSuccess"); - webServer.send(303); - uint32_t lcdOtaDelay = millis(); - while ((millis() - lcdOtaDelay) < 5000) - { // extra 5sec delay while the LCD handles any local firmware updates from new versions of code sent to it - webServer.handleClient(); - delay(1); - } - espReset(); - } - else - { - debugPrintln(F("LCDOTA: Failure")); - webServer.sendHeader("Location", "/lcdOtaFailure"); - webServer.send(303); - uint32_t lcdOtaDelay = millis(); - while ((millis() - lcdOtaDelay) < 1000) - { // extra 1sec delay for client to grab failure page - webServer.handleClient(); - delay(1); - } - espReset(); - } - } - lcdOtaTimer = millis(); - } - else if (upload.status == UPLOAD_FILE_END) - { // Upload completed - if (lcdOtaTransferred >= tftFileSize) - { - if (nextionOtaResponse()) - { // YAY WE DID IT - debugPrintln(String(F("LCDOTA: Success, wrote ")) + String(lcdOtaTransferred) + " of " + String(tftFileSize) + " bytes."); - webServer.sendHeader("Location", "/lcdOtaSuccess"); - webServer.send(303); - uint32_t lcdOtaDelay = millis(); - while ((millis() - lcdOtaDelay) < 5000) - { // extra 5sec delay while the LCD handles any local firmware updates from new versions of code sent to it - webServer.handleClient(); - yield(); - } - espReset(); - } - else - { - debugPrintln(F("LCDOTA: Failure")); - webServer.sendHeader("Location", "/lcdOtaFailure"); - webServer.send(303); - uint32_t lcdOtaDelay = millis(); - while ((millis() - lcdOtaDelay) < 1000) - { // extra 1sec delay for client to grab failure page - webServer.handleClient(); - yield(); - } - espReset(); - } - } - } - else if (upload.status == UPLOAD_FILE_ABORTED) - { // Something went kablooey - debugPrintln(F("LCDOTA: ERROR: upload.status returned: UPLOAD_FILE_ABORTED")); - debugPrintln(F("LCDOTA: Failure")); - webServer.sendHeader("Location", "/lcdOtaFailure"); - webServer.send(303); - uint32_t lcdOtaDelay = millis(); - while ((millis() - lcdOtaDelay) < 1000) - { // extra 1sec delay for client to grab failure page - webServer.handleClient(); - yield(); - } - espReset(); - } - else - { // Something went weird, we should never get here... - debugPrintln(String(F("LCDOTA: upload.status returned: ")) + String(upload.status)); - debugPrintln(F("LCDOTA: Failure")); - webServer.sendHeader("Location", "/lcdOtaFailure"); - webServer.send(303); - uint32_t lcdOtaDelay = millis(); - while ((millis() - lcdOtaDelay) < 1000) - { // extra 1sec delay for client to grab failure page - webServer.handleClient(); - yield(); - } - espReset(); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleLcdUpdateSuccess() -{ // http://plate01/lcdOtaSuccess - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /lcdOtaSuccess page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " LCD firmware update success"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent(F("")); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F(" LCD update success

")); - webServer.sendContent(F("Restarting HASwitchPlate to apply changes...")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleLcdUpdateFailure() -{ // http://plate01/lcdOtaFailure - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /lcdOtaFailure page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " LCD firmware update failed"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent(F("")); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F(" LCD update failed :(

")); - webServer.sendContent(F("Restarting HASwitchPlate to apply changes...")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleLcdDownload() -{ // http://plate01/lcddownload - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /lcddownload page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " LCD firmware update"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F(" LCD update

")); - webServer.sendContent(F("
Updating LCD firmware from: ")); - webServer.sendContent(webServer.arg("lcdFirmware")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); - nextionOtaStartDownload(webServer.arg("lcdFirmware")); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleTftFileSize() -{ // http://plate01/tftFileSize - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /tftFileSize page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " TFT Filesize"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_HEAD_END); - tftFileSize = webServer.arg("tftFileSize").toInt(); - debugPrintln(String(F("WEB: Received tftFileSize: ")) + String(tftFileSize)); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void webHandleReboot() -{ // http://plate01/reboot - if (configPassword[0] != '\0') - { // Request HTTP auth if configPassword is set - if (!webServer.authenticate(configUser, configPassword)) - { - return webServer.requestAuthentication(); - } - } - debugPrintln(String(F("HTTP: Sending /reboot page to client connected from: ")) + webServer.client().remoteIP().toString()); - String httpHeader = FPSTR(HTTP_HEAD_START); - httpHeader.replace("{v}", "HASPone " + String(haspNode) + " reboot"); - webServer.setContentLength(CONTENT_LENGTH_UNKNOWN); - webServer.send(200, "text/html", httpHeader); - webServer.sendContent_P(HTTP_SCRIPT); - webServer.sendContent_P(HTTP_STYLE); - webServer.sendContent_P(HASP_STYLE); - webServer.sendContent(F("")); - webServer.sendContent_P(HTTP_HEAD_END); - webServer.sendContent(F("

")); - webServer.sendContent(haspNode); - webServer.sendContent(F(" Reboot

")); - webServer.sendContent(F("
Rebooting device")); - webServer.sendContent_P(HTTP_END); - webServer.sendContent(""); - nextionSendCmd("page 0"); - nextionSetAttr("p[0].b[1].txt", "\"Rebooting...\""); - espReset(); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -bool updateCheck() -{ // firmware update check - WiFiClientSecure wifiUpdateClientSecure; - HTTPClient updateClient; - debugPrintln(String(F("UPDATE: Checking update URL: ")) + FPSTR(UPDATE_URL)); - - wifiUpdateClientSecure.setInsecure(); - wifiUpdateClientSecure.setBufferSizes(512, 512); - updateClient.begin(wifiUpdateClientSecure, UPDATE_URL); - - int httpCode = updateClient.GET(); // start connection and send HTTP header - if (httpCode != HTTP_CODE_OK) - { - debugPrintln(String(F("UPDATE: Update check failed: ")) + updateClient.errorToString(httpCode)); - return false; - } - - DynamicJsonDocument updateJson(2048); - DeserializationError jsonError = deserializeJson(updateJson, updateClient.getString()); - updateClient.end(); - - if (jsonError) - { // Couldn't parse the returned JSON, so bail - debugPrintln(String(F("UPDATE: JSON parsing failed: ")) + String(jsonError.c_str())); - mqttClient.publish(mqttStateJSONTopic, String(F("{\"event\":\"jsonError\",\"event_source\":\"updateCheck()\",\"event_description\":\"Failed to parse incoming JSON command with error: ")) + String(jsonError.c_str()) + String(F("\"}"))); - return false; - } - else - { - if (!updateJson["d1_mini"]["version"].isNull()) - { - updateEspAvailableVersion = updateJson["d1_mini"]["version"].as(); - debugPrintln(String(F("UPDATE: updateEspAvailableVersion: ")) + String(updateEspAvailableVersion)); - espFirmwareUrl = updateJson["d1_mini"]["firmware"].as(); - if (updateEspAvailableVersion > haspVersion) - { - updateEspAvailable = true; - debugPrintln(String(F("UPDATE: New ESP version available: ")) + String(updateEspAvailableVersion)); - } - } - if (nextionModel && !updateJson[nextionModel]["version"].isNull()) - { - updateLcdAvailableVersion = updateJson[nextionModel]["version"].as(); - debugPrintln(String(F("UPDATE: updateLcdAvailableVersion: ")) + String(updateLcdAvailableVersion)); - lcdFirmwareUrl = updateJson[nextionModel]["firmware"].as(); - if (updateLcdAvailableVersion > lcdVersion) - { - updateLcdAvailable = true; - debugPrintln(String(F("UPDATE: New LCD version available: ")) + String(updateLcdAvailableVersion)); - } - } - debugPrintln(F("UPDATE: Update check completed")); - } - return true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void motionSetup() -{ - if (strcmp(motionPinConfig, "D0") == 0) - { - motionEnabled = true; - motionPin = D0; - pinMode(motionPin, INPUT); - } - else if (strcmp(motionPinConfig, "D1") == 0) - { - motionEnabled = true; - motionPin = D1; - pinMode(motionPin, INPUT); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void motionHandle() -{ // Monitor motion sensor - if (motionEnabled) - { // Check on our motion sensor - static unsigned long motionLatchTimer = 0; // Timer for motion sensor latch - static unsigned long motionBufferTimer = millis(); // Timer for motion sensor buffer - static bool motionActiveBuffer = motionActive; - bool motionRead = digitalRead(motionPin); - - if (motionRead != motionActiveBuffer) - { // if we've changed state - motionBufferTimer = millis(); - motionActiveBuffer = motionRead; - } - else if (millis() > (motionBufferTimer + motionBufferTimeout)) - { - if ((motionActiveBuffer && !motionActive) && (millis() > (motionLatchTimer + motionLatchTimeout))) - { - motionLatchTimer = millis(); - mqttClient.publish(mqttMotionStateTopic, "ON"); - debugPrintln(String(F("MQTT OUT: '")) + mqttMotionStateTopic + String(F("' : 'ON'"))); - motionActive = motionActiveBuffer; - debugPrintln("MOTION: Active"); - } - else if ((!motionActiveBuffer && motionActive) && (millis() > (motionLatchTimer + motionLatchTimeout))) - { - motionLatchTimer = millis(); - mqttClient.publish(mqttMotionStateTopic, "OFF"); - debugPrintln(String(F("MQTT OUT: '")) + mqttMotionStateTopic + String(F("' : 'OFF'"))); - motionActive = motionActiveBuffer; - debugPrintln("MOTION: Inactive"); - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void beepHandle() -{ // Handle beep/tactile feedback - if (beepEnabled) - { - static bool beepState = false; // beep currently engaged - static unsigned long beepPrevMillis = 0; // store last time beep was updated - if ((beepState == true) && (millis() - beepPrevMillis >= beepOnTime) && ((beepCounter > 0))) - { - beepState = false; // Turn it off - beepPrevMillis = millis(); // Remember the time - analogWrite(beepPin, 254); // start beep for beepOnTime - if (beepCounter > 0) - { // Update the beep counter. - beepCounter--; - } - } - else if ((beepState == false) && (millis() - beepPrevMillis >= beepOffTime) && ((beepCounter >= 0))) - { - beepState = true; // turn it on - beepPrevMillis = millis(); // Remember the time - analogWrite(beepPin, 0); // stop beep for beepOffTime - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void telnetHandleClient() -{ // Basic telnet client handling code from: https://gist.github.com/tablatronix/4793677ca748f5f584c95ec4a2b10303 - if (debugTelnetEnabled) - { // Only do any of this if we're actually enabled - static unsigned long telnetInputIndex = 0; - if (telnetServer.hasClient()) - { // client is connected - if (!telnetClient || !telnetClient.connected()) - { - if (telnetClient) - { - telnetClient.stop(); // client disconnected - } - telnetClient = telnetServer.available(); // ready for new client - telnetInputIndex = 0; // reset input buffer index - } - else - { - telnetServer.available().stop(); // have client, block new connections - } - } - // Handle client input from telnet connection. - if (telnetClient && telnetClient.connected() && telnetClient.available()) - { // client input processing - static char telnetInputBuffer[telnetInputMax]; - - if (telnetClient.available()) - { - char telnetInputByte = telnetClient.read(); // Read client byte - if (telnetInputByte == 5) - { // If the telnet client sent a bunch of control commands on connection (which end in ENQUIRY/0x05), ignore them and restart the buffer - telnetInputIndex = 0; - } - else if (telnetInputByte == 13) - { // telnet line endings should be CRLF: https://tools.ietf.org/html/rfc5198#appendix-C - // If we get a CR just ignore it - } - else if (telnetInputByte == 10) - { // We've caught a LF (DEC 10), send buffer contents to the Nextion - telnetInputBuffer[telnetInputIndex] = 0; // null terminate our char array - nextionSendCmd(String(telnetInputBuffer)); - telnetInputIndex = 0; - } - else if (telnetInputIndex < telnetInputMax) - { // If we have room left in our buffer add the current byte - telnetInputBuffer[telnetInputIndex] = telnetInputByte; - telnetInputIndex++; - } - } - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void debugPrintln(const String &debugText) -{ // Debug output line of text to our debug targets - const String debugTimeText = "[+" + String(float(millis()) / 1000, 3) + "s] "; - if (debugSerialEnabled) - { - Serial.print(debugTimeText); - Serial.println(debugText); - SoftwareSerial debugSerial(-1, 1); // -1==nc for RX, 1==TX pin - debugSerial.begin(debugSerialBaud); - debugSerial.print(debugTimeText); - debugSerial.println(debugText); - debugSerial.flush(); - } - if (debugTelnetEnabled) - { - if (telnetClient.connected()) - { - telnetClient.print(debugTimeText); - telnetClient.println(debugText); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void debugPrint(const String &debugText) -{ // Debug output a string to our debug targets. - // Try to avoid using this function if at all possible. When connected to telnet, printing each - // character requires a full TCP round-trip + acknowledgement back and execution halts while this - // happens. Far better to put everything into a line and send it all out in one packet using - // debugPrintln. - if (debugSerialEnabled) - Serial.print(debugText); - { - SoftwareSerial debugSerial(-1, 1); // -1==nc for RX, 1==TX pin - debugSerial.begin(debugSerialBaud); - debugSerial.print(debugText); - debugSerial.flush(); - } - if (debugTelnetEnabled) - { - if (telnetClient.connected()) - { - telnetClient.print(debugText); - } - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void debugPrintCrash() -{ // Debug output line of text to our debug targets - SoftwareSerial debugSerial(-1, 1); // -1==nc for RX, 1==TX pin - debugSerial.begin(debugSerialBaud); - SaveCrash.print(debugSerial); - SaveCrash.clear(); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -void debugPrintFile(const String &fileName) -{ // Debug output line of text to our debug targets - File debugFile = SPIFFS.open(fileName, "r"); - if (debugFile) - { - uint16_t lineCount = 1; - while (debugFile.available()) - { - debugPrintln(F("SPIFFS: file:") + fileName + F(" line:") + String(lineCount) + F(" data:") + debugFile.readStringUntil('\n')); - lineCount++; - } - debugFile.close(); - } - else - { - debugPrintln("SPIFFS: Error opening file for read: " + fileName); - } -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Submitted by benmprojects to handle "beep" commands. Split -// incoming String by separator, return selected field as String -// Original source: https://arduino.stackexchange.com/a/1237 -String getSubtringField(String data, char separator, int index) -{ - int found = 0; - int strIndex[] = {0, -1}; - int maxIndex = data.length(); - - for (int i = 0; i <= maxIndex && found <= index; i++) - { - if (data.charAt(i) == separator || i == maxIndex) - { - found++; - strIndex[0] = strIndex[1] + 1; - strIndex[1] = (i == maxIndex) ? i + 1 : i; - } - } - return found > index ? data.substring(strIndex[0], strIndex[1]) : ""; -} - -//////////////////////////////////////////////////////////////////////////////// -String printHex8(byte *data, uint8_t length) -{ // returns input bytes as printable hex values in the format 0x01 0x23 0xFF - - String hex8String; - for (int i = 0; i < length; i++) - { - hex8String += "0x"; - if (data[i] < 0x10) - { - hex8String += "0"; - } - hex8String += String(data[i], HEX); - if (i != (length - 1)) - { - hex8String += " "; - } - } - // hex8String.toUpperCase(); - return hex8String; -}