You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
367 lines
10 KiB
367 lines
10 KiB
#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 "freertos/FreeRTOS.h" |
|
#include "freertos/task.h" |
|
#include "esp_system.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 "HttpHandlers.h" |
|
|
|
#include "esp_log.h" |
|
|
|
#include <string.h> |
|
|
|
#define TAG "MAIN" |
|
|
|
#define SNTP_RETRIES 8 |
|
#define API_QOS 2 |
|
#define HB_QOS 1 |
|
|
|
#define BUTTON_SERVICE_ID 1 |
|
#define BUTTON_SERVICE_GPIO_NUM 15 |
|
#define SERVICE_BUTTON_DEFAULT_RESET_HOLD_SEC 1500 |
|
|
|
static const char* DEVICE_NAME_PREFIX = "mqt-"; |
|
|
|
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 const uint8_t TRIGGER_GPIO[] = {27, 26, 25, 33}; |
|
|
|
void app_main(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, "cintra"); |
|
strcpy(systemSettings->wifiPassword, "fffefdfcfb"); |
|
systemSettings->useStaticAddr = false; |
|
|
|
nxStartRestarter(systemSettings->rsSchedule, systemSettings->tzEnv); |
|
xTaskCreate(&connWatchdogTask, "conn_watchdog_task", 2048, NULL, 1, NULL); |
|
|
|
nxSetWifiConnectedCallback(onWifiConnected); |
|
nxSetWifiErrorCallback(onWifiError); |
|
startWifi(); |
|
|
|
} |
|
|
|
// ----- |
|
|
|
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(STATUS_OK); |
|
|
|
if (wifiStarted) { |
|
return; |
|
} |
|
|
|
wifiStarted = true; |
|
systemSettings->deviceName = nxGetWifiDeviceName(); |
|
|
|
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) { |
|
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, REBOOTING"); |
|
esp_restart(); |
|
} |
|
} |
|
else if (fails > 0){ |
|
fails = 0; |
|
} |
|
vTaskDelay(1000 / portTICK_PERIOD_MS); |
|
} |
|
} |
|
|
|
|