// Copyright (c) 2020 Lukasz Chodyla // Distributed under the MIT License. // See accompanying file LICENSE.txt for the full license. #include "MqtriggerApp.h" #include "HttpHandlers.h" #include "nx/software/SystemSettings.h" #include "nx/software/SystemSettingsApi.h" #include "nx/software/AppSettings.h" #include "nx/software/AppSettingsApi.h" #include "nx/software/CommandParser.h" #include "nx/firmware/Storage.h" #include "nx/firmware/Wifi.h" #include "nx/firmware/MqTriggerHttpServer.h" #include "nx/firmware/MqttClient.h" #include "nx/firmware/SntpClient.h" #include "nx/firmware/StatusIndicator.h" #include "nx/firmware/Restarter.h" #include "nx/firmware/GpioTrigger.h" #include "nx/firmware/Buttons.h" #include #include #include #include #include #define TAG "APP" #define SNTP_RETRIES 8 #define API_QOS 2 #define HB_QOS 1 #define BUTTON_SERVICE_GPIO_NUM 15 #define BUTTON_SERVICE_ID 1 #define SERVICE_BUTTON_DEFAULT_RESET_HOLD_SEC 10 static const char* DEVICE_NAME_PREFIX = "mqt-"; static const uint8_t TRIGGER_GPIO[] = {27, 26, 25, 33}; // ------ static void restoreDefaultsOnStartupButtonHold(void); static void startWifi(void); static void onCommandRequest(const uint8_t* content, size_t ctLen, const char** response, size_t* respLen); static void onWifiConnected(void); static void onWifiError(void); static void onAppSettingsUpdate(void); static void onMqttConnected(); static void onMqttDisconnected(); static void onMqttError(); static void onMqttMessage(const char* msg); static void hbTask(void*); static void connWatchdogTask(void*); static SystemSettings* systemSettings = NULL; static AppSettings* appSettings = NULL; static WifiSettings wifiSettings; static MqttSettings mqttSettings; static bool wifiStarted = false; static bool mqttStarted = false; static bool serviceMode = false; static const MqTriggerHttpCallbacks httpCallbacks = { .getRoot = rootHandler, .getJs = jsHandler, .getCss = cssHandler, .getSysSetForm = sysFormHandler, .getAppSetForm = appFormHandler, .getSysSet = nxApiGetSystemSettings, .postSysSet = nxApiUpdateSystemSettings, .getAppSet = nxApiGetAppSettings, .postAppSet = nxApiUpdateAppSettings, .postCmd = onCommandRequest }; static Button buttons[] = { { .id = BUTTON_SERVICE_ID, .btnGpio = BUTTON_SERVICE_GPIO_NUM, .pullGpio = 0, .inverted = false, .isPressed = false, .isBouncing = false, .debounceMs = 50 } }; static void restoreDefaultsOnStartupButtonHold(void) { vTaskDelay(1000 / portTICK_PERIOD_MS); // possible debouncing capacitor wait if (nxIsButtonPressed(BUTTON_SERVICE_ID)) { ESP_LOGI(TAG, "SERVICE BUTTON IS PRESSED"); } else { ESP_LOGI(TAG, "SERVICE BUTTON IS NOT PRESSED"); } const uint16_t stepMs = 500; uint16_t steps = (SERVICE_BUTTON_DEFAULT_RESET_HOLD_SEC * 1000) / stepMs; serviceMode = nxIsButtonPressed(BUTTON_SERVICE_ID); while (nxIsButtonPressed(BUTTON_SERVICE_ID)) { if (steps == 0) { ESP_LOGW(TAG, "RESTORING DEFAULT SYSTEM AND DISPLAY SETTINGS"); nxRestoreAppDefaultSettings(); nxRestoreSystemDefaultSettings(); vTaskDelay(1000 / portTICK_PERIOD_MS); esp_restart(); } ESP_LOGW(TAG, "HOLDING SERVICE BUTTON [COUNTDOWN: %i]", steps); vTaskDelay(stepMs / portTICK_PERIOD_MS); steps -= 1; } } static void startWifi(void) { wifiSettings = (struct WifiSettings){ .wname = systemSettings->wifiSsid, .wpass = systemSettings->wifiPassword, .devicePrefix = DEVICE_NAME_PREFIX, .usePowerSave = &(systemSettings->wifiPowerSave), .useStaticAddr = &(systemSettings->useStaticAddr), .ip4addr = systemSettings->ip4addr, .ip4gw = systemSettings->ip4gw, .ip4mask = systemSettings->ip4mask, .dns = systemSettings->dnsAddr }; wifiSettings.mode = serviceMode ? NX_WIFI_MODE_AP : NX_WIFI_MODE_STA; ESP_LOGI(TAG, "Initializing WiFi"); if (!nxInitWifi(&wifiSettings)) { nxUpdateStatus(STATUS_SYSTEM_ERROR); } nxEnablePowerSavingStatus(systemSettings->wifiPowerSave); } static void handleCommand(const char* cmd, const char* args[], uint8_t argc) { ESP_LOGI(TAG, "Handling command %s with %i args", cmd, argc); if (strcmp(cmd, "trigger") == 0 && argc >= 2) { uint32_t durationMs = atoi(args[1]); nxTriggerGpio(atoi(args[0]), durationMs); } else if (strcmp(cmd, "on") == 0 && argc >= 1) { nxUpdateTriggerGpio(atoi(args[0]), 1); } else if (strcmp(cmd, "off") == 0 && argc >= 1) { nxUpdateTriggerGpio(atoi(args[0]), 0); } else if (strcmp(cmd, "setsys") == 0 && argc >= 1) { nxApiUpdateSystemSettings((uint8_t*)args[0], strlen(args[0]), NULL, NULL); } else if (strcmp(cmd, "setapp") == 0 && argc >= 1) { nxApiUpdateAppSettings((uint8_t*)args[0], strlen(args[0]), NULL, NULL); } else if (strcmp(cmd, "reboot") == 0) { esp_restart(); } else { ESP_LOGW(TAG, "Unknown command or missing args: %s", cmd); } } static void onCommandRequest(const uint8_t* content, size_t ctLen, const char** response, size_t* respLen) { nxUpdateStatus(STATUS_OK_WORKING); nxParseCommandString((const char*)content, handleCommand); } static void onWifiConnected(void) { nxUpdateStatus(serviceMode ? STATUS_OK_ALT : STATUS_OK); if (wifiStarted) { return; } wifiStarted = true; systemSettings->deviceName = nxGetWifiDeviceName(); if (!serviceMode) { nxInitSntpClient(SNTP_RETRIES, systemSettings->sntpAddr, systemSettings->tzEnv); strcpy(mqttSettings.brokerAddr, appSettings->mqttHost); strcpy(mqttSettings.apiTopic, appSettings->mqttApiUri); strcpy(mqttSettings.hbTopic, appSettings->mqttHbUri); strcpy(mqttSettings.user, appSettings->mqttUser); strcpy(mqttSettings.password, appSettings->mqttPassword); strcpy(mqttSettings.clientId, appSettings->overrideDevName ? appSettings->customDevName : systemSettings->deviceName); mqttSettings.hbIntervalSec = appSettings->mqttHbIntervalSec; mqttSettings.caCrt = appSettings->mqttUseTls ? appSettings->caCert : NULL; mqttSettings.messageCb = onMqttMessage; mqttSettings.connectedCb = onMqttConnected; mqttSettings.disconnectedCb = onMqttDisconnected; mqttSettings.errorCb = onMqttError; if (!nxStartMqttClient(&mqttSettings)) { onMqttError(); } } nxSetMqTriggerHttpCallbacks(&httpCallbacks); nxStartMqTriggerHttpServer(); } static void onAppSettingsUpdate(void) { ESP_LOGI(TAG, "App settings updated"); nxUpdateStatus(STATUS_OK_WORKING); } static void onMqttMessage(const char* msg) { ESP_LOGI(TAG, "MQTT MESSAGE RECEIVED: %s", msg); nxUpdateStatus(STATUS_OK_WORKING); nxParseCommandString(msg, handleCommand); } static void onWifiError() { ESP_LOGE(TAG, "WiFi ERROR"); nxUpdateStatus(STATUS_SYSTEM_ERROR); } static void onMqttConnected() { ESP_LOGI(TAG, "MQTT CONNECTED"); if (!mqttStarted) { xTaskCreate(&hbTask, "hb_task", 2048, NULL, 1, NULL); mqttStarted = true; } // (re)subscribe if (nxMqttSubscribe(appSettings->mqttApiUri, API_QOS)) { nxUpdateStatus(STATUS_OK); } } static void onMqttDisconnected() { ESP_LOGW(TAG, "MQTT DISCONNECTED"); nxUpdateStatus(STATUS_APP_ERROR); } static void onMqttError() { ESP_LOGE(TAG, "MQTT ERROR"); nxUpdateStatus(STATUS_APP_ERROR); } static void hbTask(void* param) { // UNUSED(param); ESP_LOGI(TAG, "Starting MQTT heartbeat task"); ESP_LOGI(TAG, "hbIntervalSec: %d", appSettings->mqttHbIntervalSec); if (appSettings->mqttHbIntervalSec < 1) { ESP_LOGI(TAG, "Heartbeat interval < 1 sec, skipping"); vTaskDelete(NULL); return; } while (1) { if (nxMqttIsConnected()) { ESP_LOGI(TAG, "Sending MQTT heartbeat"); char timeStr[DT_FORMAT_LEN]; nxGetTimeStr(timeStr); char hbMessage[DT_FORMAT_LEN + strlen(nxGetWifiDeviceName()) + 2]; strcpy(hbMessage, nxGetWifiDeviceName()); strcat(hbMessage, " "); strcat(hbMessage, timeStr); if(nxMqttPublish(appSettings->mqttHbUri, HB_QOS, hbMessage, strlen(hbMessage), 1) < 0) { ESP_LOGE(TAG, "Cannot publish heartbeat message"); } } else { ESP_LOGW(TAG, "Skipping MQTT heartbeat due to the disconnected client"); } vTaskDelay(appSettings->mqttHbIntervalSec*1000 / portTICK_PERIOD_MS); } } static void connWatchdogTask(void* param) { if (appSettings->wdogMaxSec == 0 || serviceMode) { ESP_LOGI(TAG, "Connection watchdog disabled"); vTaskDelete(NULL); return; } ESP_LOGI(TAG, "Starting connection watchdog task"); vTaskDelay(1000 * 20 / portTICK_PERIOD_MS); // wait for initialization uint32_t fails = 0; const uint32_t WD_DELAY_SEC = 10; const uint32_t MAX_FAILS = appSettings->wdogMaxSec/WD_DELAY_SEC; while (1) { if (!nxMqttIsConnected() || !nxIsWifiStaConnected()) { ESP_LOGI(TAG, "Connection watchdog: DEVICE NOT CONNECTED TO WIFI/MQTT BROKER"); fails += 1; if (fails >= MAX_FAILS) { ESP_LOGW(TAG, "MAX CONNECTION FAILED CHECKS REACHED (%i), REBOOTING", MAX_FAILS); esp_restart(); } } else if (fails > 0){ fails = 0; } vTaskDelay(WD_DELAY_SEC * 1000/ portTICK_PERIOD_MS); } } // ----- void nxStartMqtriggerApp(void) { nxInitStatusIndicator(); nxUpdateStatus(STATUS_BOOT); xTaskCreate(&nxStatusIndicatorTask, "status_task", 2048, NULL, 1, NULL); nxInitButtons(buttons, sizeof(buttons)/sizeof(buttons[0])); nxInitStorage(); // uncomment to force default NVS initialization for development // uint8_t zero = 0; // nxStorageWrite("ledinit", &zero, 1); // nxStorageWrite("sysinit", &zero, 1); nxInitSystemSettings(nxStorageWrite, nxStorageRead); nxInitAppSettings(nxStorageWrite, nxStorageRead, onAppSettingsUpdate); systemSettings = nxGetSystemSettings(); appSettings = nxGetAppSettings(); restoreDefaultsOnStartupButtonHold(); nxInitGpioTrigger(TRIGGER_GPIO, sizeof(TRIGGER_GPIO)); // uncomment to force WiFi settings for development // strcpy(systemSettings->wifiSsid, "myssid"); // strcpy(systemSettings->wifiPassword, "password"); // systemSettings->useStaticAddr = false; nxStartRestarter(systemSettings->rsSchedule, systemSettings->tzEnv); xTaskCreate(&connWatchdogTask, "conn_watchdog_task", 2048, NULL, 1, NULL); nxSetWifiConnectedCallback(onWifiConnected); nxSetWifiErrorCallback(onWifiError); startWifi(); }