6 changed files with 380 additions and 369 deletions
@ -0,0 +1,368 @@ |
|||||||
|
#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 <freertos/FreeRTOS.h> |
||||||
|
#include <freertos/task.h> |
||||||
|
#include <esp_system.h> |
||||||
|
#include <esp_log.h> |
||||||
|
|
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#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 1500 |
||||||
|
|
||||||
|
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(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); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// -----
|
||||||
|
|
||||||
|
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(); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,6 @@ |
|||||||
|
#ifndef MAIN_MQTRIGGERAPP_H_ |
||||||
|
#define MAIN_MQTRIGGERAPP_H_ |
||||||
|
|
||||||
|
void nxStartMqtriggerApp(void); |
||||||
|
|
||||||
|
#endif /* MAIN_MQTRIGGERAPP_H_ */ |
||||||
@ -1 +1 @@ |
|||||||
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon type=image/png href=data:image/png,%89PNG%0D%0A%1A%0A><link rel=stylesheet href=index.css><script src=index.js></script><div class=.float-left>MqTrigger v1.1</div><header class="sticky row"><div class="container nav-bar"><ul><li><a id=app-btn href=# role=button class=nav-btn data-dst=/app/form>Application</a><li><a href=# role=button class=nav-btn data-dst=/sys/form>System</a></ul></div></header><div class=container><div class=col-md-offset-1 id=content><h1>Loading</h1><p>Loading content... Please make sure that JavaScript is enabled.</div></div><hr><footer><div class=container><p>Copyright© nixlab.in 2022</div></footer><script>initNavButtons();document.getElementById('app-btn').click();</script> |
<meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon type=image/png href=data:image/png,%89PNG%0D%0A%1A%0A><link rel=stylesheet href=index.css><script src=index.js></script><title>MqTrigger web panel</title><header class="sticky row"><div class="container nav-bar"><ul><li><a id=app-btn href=# role=button class=nav-btn data-dst=/app/form>Application</a><li><a href=# role=button class=nav-btn data-dst=/sys/form>System</a></ul></div></header><div class=container><div class=col-md-offset-1 id=content><h1>Loading</h1><p>Loading content... Please make sure that JavaScript is enabled.</div></div><hr><footer><div class=container><p>MqTrigger 2022</div></footer><script>initNavButtons();document.getElementById('app-btn').click();</script> |
||||||
Loading…
Reference in new issue