38 changed files with 5390 additions and 2046 deletions
@ -0,0 +1,35 @@ |
|||||||
|
#ifndef COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_MQTTCLIENT_H_ |
||||||
|
#define COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_MQTTCLIENT_H_ |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#define MQTT_BROKER_URI_MAX_LEN 128 |
||||||
|
#define MQTT_TOPIC_MAX_LEN 128 |
||||||
|
#define MQTT_USER_MAX_LEN 16 |
||||||
|
|
||||||
|
typedef void (*MqttConnectedCallback)(); |
||||||
|
typedef void (*MqttDisconnectedCallback)(); |
||||||
|
typedef void (*MqttErrorCallback)(); |
||||||
|
typedef void (*MqttMessageCallback)(const char* msg); |
||||||
|
|
||||||
|
typedef struct MqttSettings |
||||||
|
{ |
||||||
|
char brokerAddr[MQTT_BROKER_URI_MAX_LEN]; |
||||||
|
char apiTopic[MQTT_TOPIC_MAX_LEN]; |
||||||
|
char hbTopic[MQTT_TOPIC_MAX_LEN]; |
||||||
|
char user[MQTT_USER_MAX_LEN]; |
||||||
|
char password[MQTT_USER_MAX_LEN]; |
||||||
|
uint16_t hbIntervalSec; |
||||||
|
const char* caCrt; |
||||||
|
|
||||||
|
MqttMessageCallback messageCb; |
||||||
|
MqttConnectedCallback connectedCb; |
||||||
|
MqttDisconnectedCallback disconnectedCb; |
||||||
|
MqttErrorCallback errorCb; |
||||||
|
} MqttSettings; |
||||||
|
|
||||||
|
void nxStartMqttClient(const MqttSettings* settings); |
||||||
|
|
||||||
|
void nxMqttHeartbeatTask(void*); |
||||||
|
|
||||||
|
#endif /* COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_MQTTCLIENT_H_ */ |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
#ifndef COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_SNTPCLIENT_H_ |
||||||
|
#define COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_SNTPCLIENT_H_ |
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
|
||||||
|
#define DT_FORMAT_LEN 20 |
||||||
|
|
||||||
|
bool nxInitSntpClient(int retries, const char* serverAddr, const char* timezoneString); |
||||||
|
void nxGetTimeStr(char buf[DT_FORMAT_LEN]); |
||||||
|
|
||||||
|
#endif /* COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_SNTPCLIENT_H_ */ |
||||||
@ -0,0 +1,174 @@ |
|||||||
|
#include "nx/firmware/MqttClient.h" |
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h" |
||||||
|
#include "freertos/task.h" |
||||||
|
#include "freertos/semphr.h" |
||||||
|
#include "freertos/queue.h" |
||||||
|
|
||||||
|
#include "lwip/sockets.h" |
||||||
|
#include "lwip/dns.h" |
||||||
|
#include "lwip/netdb.h" |
||||||
|
|
||||||
|
#include "esp_log.h" |
||||||
|
#include "mqtt_client.h" |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#define UNUSED(x) (void)(x) |
||||||
|
|
||||||
|
static const char* TAG = "MQTT"; |
||||||
|
static const int CMD_QOS = 2; |
||||||
|
//static const int HB_QOS = 1;
|
||||||
|
|
||||||
|
//static const long int MINIMAL_HB_INTERVAL_MS = 1000 * 60;
|
||||||
|
|
||||||
|
static const char* CLIENT_ID = "p10-dev"; |
||||||
|
|
||||||
|
static const MqttSettings* settings = NULL; |
||||||
|
|
||||||
|
esp_mqtt_client_handle_t clientHandle = NULL; |
||||||
|
|
||||||
|
|
||||||
|
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) |
||||||
|
{ |
||||||
|
esp_mqtt_client_handle_t client = event->client; |
||||||
|
int msg_id; |
||||||
|
// your_context_t *context = event->context;
|
||||||
|
switch (event->event_id) { |
||||||
|
case MQTT_EVENT_CONNECTED: |
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); |
||||||
|
clientHandle = client; |
||||||
|
|
||||||
|
if (settings->connectedCb) { |
||||||
|
settings->connectedCb(); |
||||||
|
} |
||||||
|
|
||||||
|
msg_id = esp_mqtt_client_subscribe(client, settings->apiTopic, CMD_QOS); |
||||||
|
if (msg_id == -1) { |
||||||
|
ESP_LOGE(TAG, "Subscribtion failed"); |
||||||
|
if (settings->errorCb) { |
||||||
|
settings->errorCb(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
break; |
||||||
|
|
||||||
|
case MQTT_EVENT_DISCONNECTED: |
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED"); |
||||||
|
clientHandle = NULL; |
||||||
|
if (settings->disconnectedCb) { |
||||||
|
settings->disconnectedCb(); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
case MQTT_EVENT_SUBSCRIBED: |
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id); |
||||||
|
break; |
||||||
|
case MQTT_EVENT_UNSUBSCRIBED: |
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id); |
||||||
|
break; |
||||||
|
case MQTT_EVENT_PUBLISHED: |
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id); |
||||||
|
break; |
||||||
|
case MQTT_EVENT_DATA: |
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_DATA"); |
||||||
|
|
||||||
|
if (settings->messageCb) { |
||||||
|
event->data[event->data_len] = '\0'; |
||||||
|
settings->messageCb(event->data); |
||||||
|
} |
||||||
|
|
||||||
|
printf("TOPIC=%.*s\r\n", event->topic_len, event->topic); |
||||||
|
printf("DATA=%.*s\r\n", event->data_len, event->data); |
||||||
|
break; |
||||||
|
case MQTT_EVENT_ERROR: |
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); |
||||||
|
if (settings->errorCb) { |
||||||
|
settings->errorCb(); |
||||||
|
} |
||||||
|
break; |
||||||
|
default: |
||||||
|
ESP_LOGI(TAG, "Other event id:%d", event->event_id); |
||||||
|
break; |
||||||
|
} |
||||||
|
return ESP_OK; |
||||||
|
} |
||||||
|
|
||||||
|
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) { |
||||||
|
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%d", base, event_id); |
||||||
|
mqtt_event_handler_cb(event_data); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// --------- Public API --------- //
|
||||||
|
|
||||||
|
|
||||||
|
void nxStartMqttClient(const MqttSettings* mqttSettings) |
||||||
|
{ |
||||||
|
ESP_LOGI(TAG, "Initializing MQTT"); |
||||||
|
|
||||||
|
if (strlen(mqttSettings->brokerAddr) == 0) { |
||||||
|
ESP_LOGW(TAG, "Empty broker address, skipping MQTT initialization"); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
settings = mqttSettings; |
||||||
|
|
||||||
|
esp_mqtt_client_config_t mqtt_cfg = {}; |
||||||
|
|
||||||
|
mqtt_cfg.uri = settings->brokerAddr; |
||||||
|
|
||||||
|
mqtt_cfg.client_id = CLIENT_ID; |
||||||
|
mqtt_cfg.cert_pem = settings->caCrt; |
||||||
|
mqtt_cfg.username = settings->user; |
||||||
|
mqtt_cfg.password = settings->password; |
||||||
|
|
||||||
|
if (mqtt_cfg.host) { |
||||||
|
ESP_LOGI(TAG, "host: %s", mqtt_cfg.host); |
||||||
|
} |
||||||
|
if (mqtt_cfg.uri) { |
||||||
|
ESP_LOGI(TAG, "uri: %s", mqtt_cfg.uri); |
||||||
|
} |
||||||
|
ESP_LOGI(TAG, "client: %s", mqtt_cfg.client_id); |
||||||
|
ESP_LOGI(TAG, "user: %s", mqtt_cfg.username); |
||||||
|
ESP_LOGI(TAG, "pass: %s", mqtt_cfg.password); |
||||||
|
ESP_LOGI(TAG, "cert: %s", mqtt_cfg.cert_pem); |
||||||
|
|
||||||
|
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); |
||||||
|
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client); |
||||||
|
esp_mqtt_client_start(client); |
||||||
|
} |
||||||
|
|
||||||
|
void nxMqttHeartbeatTask(void* param) |
||||||
|
{ |
||||||
|
UNUSED(param); |
||||||
|
// ESP_LOGI(TAG, "Starting MQTT heartbeat task");
|
||||||
|
//
|
||||||
|
// ESP_LOGI(TAG, "hbIntervalMin: %d", settings->hbIntervalMin);
|
||||||
|
//
|
||||||
|
// long int hbIntervalMs = 1000 * 60 * settings->hbIntervalMin;
|
||||||
|
//
|
||||||
|
// ESP_LOGI(TAG, "hbIntervalMs: %li", hbIntervalMs);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// if (hbIntervalMs < MINIMAL_HB_INTERVAL_MS) {
|
||||||
|
// hbIntervalMs = MINIMAL_HB_INTERVAL_MS;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// while (1) {
|
||||||
|
// if (clientHandle) {
|
||||||
|
// ESP_LOGI(TAG, "Sending MQTT heartbeat");
|
||||||
|
//
|
||||||
|
// char timeStr[DT_FORMAT_LEN];
|
||||||
|
// getTimeStr(timeStr);
|
||||||
|
// char hbMessage[DT_FORMAT_LEN + strlen(DEVICE_ID) + 2];
|
||||||
|
// strcpy(hbMessage, DEVICE_ID);
|
||||||
|
// strcat(hbMessage, " ");
|
||||||
|
// strcat(hbMessage, timeStr);
|
||||||
|
//
|
||||||
|
// esp_mqtt_client_publish(clientHandle, HEARTBEAT_TOPIC,
|
||||||
|
// hbMessage, strlen(hbMessage), HB_QOS, 1);
|
||||||
|
// }
|
||||||
|
// vTaskDelay(hbIntervalMs / portTICK_PERIOD_MS);
|
||||||
|
// }
|
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
#include "nx/firmware/SntpClient.h" |
||||||
|
#include <time.h> |
||||||
|
#include <sys/time.h> |
||||||
|
#include "esp_sntp.h" |
||||||
|
#include "esp_log.h" |
||||||
|
|
||||||
|
#define RETRY_DELAY_MS 2000 |
||||||
|
|
||||||
|
static const char* TAG = "SNTP_CLIENT"; |
||||||
|
|
||||||
|
static const char* tzString = "CET-1CEST,M3.5.0,M10.5.0/3"; |
||||||
|
|
||||||
|
bool nxInitSntpClient(int retries, const char* serverAddr, const char* timezoneString) |
||||||
|
{ |
||||||
|
tzString = timezoneString; |
||||||
|
sntp_setoperatingmode(SNTP_OPMODE_POLL); |
||||||
|
sntp_setservername(0, serverAddr); |
||||||
|
sntp_init(); |
||||||
|
int retry = 0; |
||||||
|
|
||||||
|
while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retries) { |
||||||
|
ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retries); |
||||||
|
vTaskDelay(RETRY_DELAY_MS / portTICK_PERIOD_MS); |
||||||
|
} |
||||||
|
|
||||||
|
char buf[DT_FORMAT_LEN]; |
||||||
|
nxGetTimeStr(buf); |
||||||
|
ESP_LOGI(TAG, "Initial system time: %s", buf); |
||||||
|
return retry < retries; |
||||||
|
} |
||||||
|
|
||||||
|
void nxGetTimeStr(char buf[DT_FORMAT_LEN]) |
||||||
|
{ |
||||||
|
time_t now; |
||||||
|
struct tm timeinfo; |
||||||
|
time(&now); |
||||||
|
|
||||||
|
setenv("TZ", tzString, 1); |
||||||
|
tzset(); |
||||||
|
localtime_r(&now, &timeinfo); |
||||||
|
strftime(buf, DT_FORMAT_LEN, "%Y-%m-%d %H:%M:%S", &timeinfo); |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
#ifndef NX_SOFTWARE_APPSETTINGS_H_ |
||||||
|
#define NX_SOFTWARE_APPSETTINGS_H_ |
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
#include <stddef.h> |
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#define WIFI_STRINGS_MAX_LEN 32 |
||||||
|
#define URI_MAX_SIZE 64 |
||||||
|
#define USER_DATA_MAX_SIZE 16 |
||||||
|
|
||||||
|
typedef bool (*StorageReadFn)(const char* key, void* data, size_t size); |
||||||
|
|
||||||
|
typedef void (*StorageWriteFn)(const char* key, const void* data, size_t size); |
||||||
|
|
||||||
|
typedef void (*SettingsUpdatedCb)(void); |
||||||
|
|
||||||
|
typedef struct AppSettings { |
||||||
|
char mqttHost[URI_MAX_SIZE]; |
||||||
|
char mqttApiUri[URI_MAX_SIZE]; |
||||||
|
char mqttHbUri[URI_MAX_SIZE]; |
||||||
|
char mqttUser[USER_DATA_MAX_SIZE]; |
||||||
|
char mqttPassword[USER_DATA_MAX_SIZE]; |
||||||
|
uint16_t mqttHbIntervalSec; |
||||||
|
uint8_t mqttUseTls; |
||||||
|
|
||||||
|
} AppSettings; |
||||||
|
|
||||||
|
|
||||||
|
void nxInitAppSettings(StorageWriteFn writeFn, StorageReadFn readFn, SettingsUpdatedCb updateCb); |
||||||
|
|
||||||
|
void nxUpdateAppSettings(AppSettings* newSettings); |
||||||
|
|
||||||
|
void nxRestoreAppDefaultSettings(void); |
||||||
|
|
||||||
|
void nxWriteAppSettings(void); |
||||||
|
|
||||||
|
AppSettings* nxGetAppSettings(void); |
||||||
|
|
||||||
|
#endif /* NX_SOFTWARE_APPSETTINGS_H_ */ |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
#ifndef NX_SOFTWARE_APPSETTINGSAPI_H_ |
||||||
|
#define NX_SOFTWARE_APPSETTINGSAPI_H_ |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <stddef.h> |
||||||
|
|
||||||
|
void nxApiUpdateAppSettings(const uint8_t* msg, size_t msgLen, |
||||||
|
const char** response, size_t* respLen); |
||||||
|
|
||||||
|
void nxApiGetAppSettings(const uint8_t* msg, size_t msgLen, |
||||||
|
const char** response, size_t* respLen); |
||||||
|
|
||||||
|
#endif /* NX_SOFTWARE_APPSETTINGSAPI_H_ */ |
||||||
@ -0,0 +1,119 @@ |
|||||||
|
#include "nx/software/AppSettings.h" |
||||||
|
#include <stdio.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#define KEY_INIT_FLAG "appinit" |
||||||
|
#define KEY_MQTT_HOST "mqhost" |
||||||
|
#define KEY_MQTT_API_URI "mqapi" |
||||||
|
#define KEY_MQTT_HB_URI "mqhb" |
||||||
|
#define KEY_MQTT_HB_SEC "mqhbs" |
||||||
|
#define KEY_MQTT_TLS "mqtls" |
||||||
|
#define KEY_MQTT_USER "mquser" |
||||||
|
#define KEY_MQTT_PASS "mqpass" |
||||||
|
|
||||||
|
#define STORAGE_READ(_KEY, _NAME)\ |
||||||
|
storageRead(_KEY, &settings._NAME, sizeof(settings._NAME)); |
||||||
|
|
||||||
|
#define STORAGE_WRITE(_KEY, _NAME)\ |
||||||
|
storageWrite(_KEY, &settings._NAME, sizeof(settings._NAME)); |
||||||
|
|
||||||
|
static const uint8_t INIT_FLAG_VALUE = 1; |
||||||
|
|
||||||
|
static const char* DEFAULT_MQTT_HOST = ""; |
||||||
|
static const char* DEFAULT_MQTT_API_URI = "dev/api"; |
||||||
|
static const char* DEFAULT_MQTT_HB_URI = "dev/hb"; |
||||||
|
static const uint16_t DEFAULT_MQTT_HB_SEC = 0; |
||||||
|
static const uint8_t DEFAULT_MQTT_TLS = 0; |
||||||
|
static const char* DEFAULT_MQTT_USER = ""; |
||||||
|
static const char* DEFAULT_MQTT_PASS = ""; |
||||||
|
|
||||||
|
|
||||||
|
static StorageWriteFn storageWrite = NULL; |
||||||
|
static StorageReadFn storageRead = NULL; |
||||||
|
static SettingsUpdatedCb settingsUpdatedCb = NULL; |
||||||
|
|
||||||
|
static AppSettings settings; |
||||||
|
|
||||||
|
static bool firstRun(void) |
||||||
|
{ |
||||||
|
uint8_t initFlag = 0; |
||||||
|
if (!storageRead(KEY_INIT_FLAG, &initFlag, sizeof(initFlag))) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
return initFlag != INIT_FLAG_VALUE; |
||||||
|
} |
||||||
|
|
||||||
|
static void loadSettings(void) |
||||||
|
{ |
||||||
|
printf("Loading application settings\n"); |
||||||
|
STORAGE_READ(KEY_MQTT_HOST , mqttHost ); |
||||||
|
STORAGE_READ(KEY_MQTT_API_URI, mqttApiUri ); |
||||||
|
STORAGE_READ(KEY_MQTT_HB_URI , mqttHbUri ); |
||||||
|
STORAGE_READ(KEY_MQTT_HB_SEC , mqttHbIntervalSec); |
||||||
|
STORAGE_READ(KEY_MQTT_TLS , mqttUseTls ); |
||||||
|
STORAGE_READ(KEY_MQTT_USER , mqttUser ); |
||||||
|
STORAGE_READ(KEY_MQTT_PASS , mqttPassword ); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// --------- Public API --------- //
|
||||||
|
|
||||||
|
|
||||||
|
void nxInitAppSettings(StorageWriteFn writeFn, |
||||||
|
StorageReadFn readFn, SettingsUpdatedCb updateCb) |
||||||
|
{ |
||||||
|
storageWrite = writeFn; |
||||||
|
storageRead = readFn; |
||||||
|
settingsUpdatedCb = updateCb; |
||||||
|
|
||||||
|
if (firstRun()) { |
||||||
|
nxRestoreAppDefaultSettings(); |
||||||
|
} |
||||||
|
else { |
||||||
|
loadSettings(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void nxUpdateAppSettings(AppSettings* newSettings) |
||||||
|
{ |
||||||
|
memcpy(&settings, newSettings, sizeof(AppSettings)); |
||||||
|
nxWriteAppSettings(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void nxRestoreAppDefaultSettings(void) |
||||||
|
{ |
||||||
|
printf("Restoring DEFAULT application settings\n"); |
||||||
|
settings.mqttHbIntervalSec = DEFAULT_MQTT_HB_SEC; |
||||||
|
settings.mqttUseTls = DEFAULT_MQTT_TLS; |
||||||
|
|
||||||
|
strcpy(settings.mqttHbUri , DEFAULT_MQTT_HB_URI ); |
||||||
|
strcpy(settings.mqttApiUri , DEFAULT_MQTT_API_URI); |
||||||
|
strcpy(settings.mqttHost , DEFAULT_MQTT_HOST ); |
||||||
|
strcpy(settings.mqttUser , DEFAULT_MQTT_USER ); |
||||||
|
strcpy(settings.mqttPassword, DEFAULT_MQTT_PASS ); |
||||||
|
|
||||||
|
nxWriteAppSettings(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void nxWriteAppSettings(void) |
||||||
|
{ |
||||||
|
printf("WRITING APPLICATION SETTINGS\n"); |
||||||
|
storageWrite(KEY_INIT_FLAG , &INIT_FLAG_VALUE , 1); |
||||||
|
|
||||||
|
STORAGE_WRITE(KEY_MQTT_HOST , mqttHost ); |
||||||
|
STORAGE_WRITE(KEY_MQTT_API_URI, mqttApiUri ); |
||||||
|
STORAGE_WRITE(KEY_MQTT_HB_URI , mqttHbUri ); |
||||||
|
STORAGE_WRITE(KEY_MQTT_HB_SEC , mqttHbIntervalSec); |
||||||
|
STORAGE_WRITE(KEY_MQTT_TLS , mqttUseTls ); |
||||||
|
STORAGE_WRITE(KEY_MQTT_USER , mqttUser ); |
||||||
|
STORAGE_WRITE(KEY_MQTT_PASS , mqttPassword ); |
||||||
|
|
||||||
|
settingsUpdatedCb(); |
||||||
|
} |
||||||
|
|
||||||
|
AppSettings* nxGetAppSettings(void) |
||||||
|
{ |
||||||
|
return &settings; |
||||||
|
} |
||||||
@ -0,0 +1,95 @@ |
|||||||
|
#include "nx/software/AppSettingsApi.h" |
||||||
|
#include "nx/software/AppSettings.h" |
||||||
|
#include "KeyValueParser.h" |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <inttypes.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#define API_KEY_RESTORE "restore_default" |
||||||
|
#define API_KEY_MQTT_HOST "mqhost" |
||||||
|
#define API_KEY_MQTT_API_URI "mqapi" |
||||||
|
#define API_KEY_MQTT_HB_URI "mqhb" |
||||||
|
#define API_KEY_MQTT_HB_SEC "mqhbs" |
||||||
|
#define API_KEY_MQTT_TLS "mqtls" |
||||||
|
#define API_KEY_MQTT_USER "mquser" |
||||||
|
#define API_KEY_MQTT_PASS "mqpass" |
||||||
|
|
||||||
|
#define UNUSED(x) (void)(x) |
||||||
|
|
||||||
|
#define RESP_BUF_SIZE 1024 |
||||||
|
|
||||||
|
char responseBuffer[RESP_BUF_SIZE]; |
||||||
|
|
||||||
|
static void handleKvPair(const char* key, const char* value, const void* userData, bool* done) |
||||||
|
{ |
||||||
|
printf("LED display settings update; Key: %s, Value: %s\n", key, value); |
||||||
|
|
||||||
|
AppSettings* settings = nxGetAppSettings(); |
||||||
|
if (strcmp(key, API_KEY_RESTORE) == 0) { |
||||||
|
nxRestoreAppDefaultSettings(); |
||||||
|
} |
||||||
|
else if (strcmp(key, API_KEY_MQTT_HOST) == 0) { |
||||||
|
strcpy(settings->mqttHost, value); |
||||||
|
} |
||||||
|
else if (strcmp(key, API_KEY_MQTT_API_URI) == 0) { |
||||||
|
strcpy(settings->mqttApiUri, value); |
||||||
|
} |
||||||
|
else if (strcmp(key, API_KEY_MQTT_HB_URI) == 0) { |
||||||
|
strcpy(settings->mqttHbUri, value); |
||||||
|
} |
||||||
|
else if (strcmp(key, API_KEY_MQTT_USER) == 0) { |
||||||
|
strcpy(settings->mqttUser, value); |
||||||
|
} |
||||||
|
else if (strcmp(key, API_KEY_MQTT_PASS) == 0) { |
||||||
|
strcpy(settings->mqttPassword, value); |
||||||
|
} |
||||||
|
else if (strcmp(key, API_KEY_MQTT_HB_SEC) == 0) { |
||||||
|
settings->mqttHbIntervalSec = atoi(value); |
||||||
|
} |
||||||
|
else if (strcmp(key, API_KEY_MQTT_TLS) == 0) { |
||||||
|
settings->mqttUseTls = atoi(value); |
||||||
|
} |
||||||
|
else { |
||||||
|
fprintf(stderr, "Unknown key: %s\n", key); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// --------- Public API --------- //
|
||||||
|
|
||||||
|
void nxApiGetAppSettings(const uint8_t* msg, size_t msgLen, |
||||||
|
const char** response, size_t* respLen) |
||||||
|
{ |
||||||
|
UNUSED(msg); UNUSED(msgLen); |
||||||
|
memset(responseBuffer, 0, RESP_BUF_SIZE); |
||||||
|
AppSettings* settings = nxGetAppSettings(); |
||||||
|
|
||||||
|
sprintf(responseBuffer, |
||||||
|
"{" |
||||||
|
"\"%s\":\"%s\", " // mqtt host
|
||||||
|
"\"%s\":\"%s\", " |
||||||
|
"\"%s\":\"%s\", " |
||||||
|
"\"%s\":%i, " // hb interval
|
||||||
|
"\"%s\":%i, " |
||||||
|
"\"%s\":\"%s\" " // mqtt user
|
||||||
|
"}", |
||||||
|
API_KEY_MQTT_HOST , settings->mqttHost , |
||||||
|
API_KEY_MQTT_API_URI, settings->mqttApiUri , |
||||||
|
API_KEY_MQTT_HB_URI , settings->mqttHbUri , |
||||||
|
API_KEY_MQTT_HB_SEC , settings->mqttHbIntervalSec, |
||||||
|
API_KEY_MQTT_TLS , settings->mqttUseTls , |
||||||
|
API_KEY_MQTT_USER , settings->mqttUser |
||||||
|
); |
||||||
|
*response = responseBuffer; |
||||||
|
*respLen = strlen(responseBuffer); |
||||||
|
} |
||||||
|
|
||||||
|
void nxApiUpdateAppSettings(const uint8_t* msg, size_t msgLen, |
||||||
|
const char** response, size_t* respLen) |
||||||
|
{ |
||||||
|
memset(responseBuffer, 0, RESP_BUF_SIZE); |
||||||
|
nxParseKeyValueString((const char*)msg, handleKvPair, NULL); |
||||||
|
nxWriteAppSettings(); |
||||||
|
} |
||||||
|
|
||||||
@ -0,0 +1,24 @@ |
|||||||
|
#include "KeyValueParser.h" |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#define MAX_KEY_SIZE 32 |
||||||
|
#define MAX_VALUE_SIZE 32 |
||||||
|
#define MAX_PAIRS 16 |
||||||
|
|
||||||
|
void nxParseKeyValueString(const char* string, KeyValueHandler handler, const void* userData) |
||||||
|
{ |
||||||
|
bool done = false; |
||||||
|
char key[MAX_KEY_SIZE]; |
||||||
|
char value[MAX_VALUE_SIZE]; |
||||||
|
FILE *stream; |
||||||
|
stream = fmemopen((void*)string, strlen(string), "r"); |
||||||
|
int maxPairs = MAX_PAIRS; |
||||||
|
while (fscanf(stream, "%127[^=]=%127[^&]%*c", key, value) == 2 && maxPairs > 0 && !done) { |
||||||
|
handler(key, value, userData, &done); |
||||||
|
maxPairs -= 1; |
||||||
|
} |
||||||
|
|
||||||
|
fclose(stream); |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
#ifndef COMPONENTS_SOFTWARE_KEYVALUEPARSER_H_ |
||||||
|
#define COMPONENTS_SOFTWARE_KEYVALUEPARSER_H_ |
||||||
|
|
||||||
|
#include <stdbool.h> |
||||||
|
|
||||||
|
typedef void (*KeyValueHandler)(const char* key, const char* value, const void* userData, bool* done); |
||||||
|
|
||||||
|
void nxParseKeyValueString(const char* string, KeyValueHandler handler, const void* userData); |
||||||
|
|
||||||
|
|
||||||
|
#endif /* COMPONENTS_SOFTWARE_KEYVALUEPARSER_H_ */ |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
#ifndef MAIN_HTTPHANDLERS_H_ |
||||||
|
#define MAIN_HTTPHANDLERS_H_ |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <stddef.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
extern const char index_html_start[] asm("_binary_index_html_start"); |
||||||
|
extern const char index_css_start[] asm("_binary_index_css_start"); |
||||||
|
extern const char index_js_start[] asm("_binary_index_js_start"); |
||||||
|
extern const char sys_html_start[] asm("_binary_sys_html_start"); |
||||||
|
extern const char app_html_start[] asm("_binary_app_html_start"); |
||||||
|
|
||||||
|
static void rootHandler(const uint8_t* content, size_t ctLen, |
||||||
|
const char** response, size_t* respLen) |
||||||
|
{ |
||||||
|
*response = index_html_start; |
||||||
|
*respLen = strlen(index_html_start); |
||||||
|
} |
||||||
|
|
||||||
|
static void jsHandler(const uint8_t* content, size_t ctLen, |
||||||
|
const char** response, size_t* respLen) |
||||||
|
{ |
||||||
|
*response = index_js_start; |
||||||
|
*respLen = strlen(index_js_start); |
||||||
|
} |
||||||
|
|
||||||
|
static void cssHandler(const uint8_t* content, size_t ctLen, |
||||||
|
const char** response, size_t* respLen) |
||||||
|
{ |
||||||
|
*response = index_css_start; |
||||||
|
*respLen = strlen(index_css_start); |
||||||
|
} |
||||||
|
|
||||||
|
static void sysFormHandler(const uint8_t* content, size_t ctLen, |
||||||
|
const char** response, size_t* respLen) |
||||||
|
{ |
||||||
|
*response = sys_html_start; |
||||||
|
*respLen = strlen(sys_html_start); |
||||||
|
} |
||||||
|
|
||||||
|
static void appFormHandler(const uint8_t* content, size_t ctLen, |
||||||
|
const char** response, size_t* respLen) |
||||||
|
{ |
||||||
|
*response = app_html_start; |
||||||
|
*respLen = strlen(app_html_start); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#endif /* MAIN_HTTPHANDLERS_H_ */ |
||||||
@ -0,0 +1,118 @@ |
|||||||
|
#include "nx/software/SystemSettings.h" |
||||||
|
#include "nx/software/SystemSettingsApi.h" |
||||||
|
#include "nx/software/AppSettings.h" |
||||||
|
#include "nx/software/AppSettingsApi.h" |
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.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 "HttpHandlers.h" |
||||||
|
|
||||||
|
#include "esp_log.h" |
||||||
|
|
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#define TAG "MAIN" |
||||||
|
|
||||||
|
#define SNTP_RETRIES 5 |
||||||
|
|
||||||
|
static const char* DEVICE_NAME_PREFIX = "mqtrigger-"; |
||||||
|
|
||||||
|
extern const char ca_crt_start[] asm("_binary_ca_crt_start"); |
||||||
|
|
||||||
|
static void startWifi(void); |
||||||
|
static void onWifiConnected(void); |
||||||
|
static void onAppSettingsUpdate(void); |
||||||
|
static void onMqttMessage(const char* msg); |
||||||
|
|
||||||
|
static SystemSettings* systemSettings = NULL; |
||||||
|
static AppSettings* appSettings = NULL; |
||||||
|
static WifiSettings wifiSettings; |
||||||
|
static MqttSettings mqttSettings; |
||||||
|
|
||||||
|
static const MqTriggerHttpCallbacks httpCallbacks = { |
||||||
|
.getRoot = rootHandler, |
||||||
|
.getJs = jsHandler, |
||||||
|
.getCss = cssHandler, |
||||||
|
.getSysSetForm = sysFormHandler, |
||||||
|
.getAppSetForm = appFormHandler, |
||||||
|
.getSysSet = nxApiGetSystemSettings, |
||||||
|
.postSysSet = nxApiUpdateSystemSettings, |
||||||
|
.getAppSet = nxApiGetAppSettings, |
||||||
|
.postAppSet = nxApiUpdateAppSettings |
||||||
|
// .postCmd = nxApiHandleLedCmd
|
||||||
|
}; |
||||||
|
|
||||||
|
void app_main(void) |
||||||
|
{ |
||||||
|
nxInitStorage(); |
||||||
|
|
||||||
|
nxInitSystemSettings(nxStorageWrite, nxStorageRead); |
||||||
|
nxInitAppSettings(nxStorageWrite, nxStorageRead, onAppSettingsUpdate); |
||||||
|
|
||||||
|
systemSettings = nxGetSystemSettings(); |
||||||
|
appSettings = nxGetAppSettings(); |
||||||
|
|
||||||
|
nxSetWifiConnectedCallback(onWifiConnected); |
||||||
|
startWifi(); |
||||||
|
} |
||||||
|
|
||||||
|
// -----
|
||||||
|
|
||||||
|
static void startWifi(void) |
||||||
|
{ |
||||||
|
strcpy(systemSettings->wifiSsid, "cintra"); |
||||||
|
strcpy(systemSettings->wifiPassword, "fffefdfcfb"); |
||||||
|
systemSettings->useStaticAddr = true; |
||||||
|
systemSettings->ip4addr[3] = 91; |
||||||
|
|
||||||
|
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 |
||||||
|
}; |
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Initializing WiFi"); |
||||||
|
nxInitWifi(&wifiSettings); |
||||||
|
} |
||||||
|
|
||||||
|
static void onWifiConnected(void) |
||||||
|
{ |
||||||
|
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); |
||||||
|
mqttSettings.hbIntervalSec = appSettings->mqttHbIntervalSec; |
||||||
|
mqttSettings.caCrt = appSettings->mqttUseTls ? ca_crt_start : NULL; |
||||||
|
mqttSettings.messageCb = onMqttMessage; |
||||||
|
|
||||||
|
nxStartMqttClient(&mqttSettings); |
||||||
|
|
||||||
|
nxSetMqTriggerHttpCallbacks(&httpCallbacks); |
||||||
|
nxStartMqTriggerHttpServer(); |
||||||
|
} |
||||||
|
|
||||||
|
static void onAppSettingsUpdate(void) |
||||||
|
{ |
||||||
|
ESP_LOGI(TAG, "App settings updated"); |
||||||
|
} |
||||||
|
|
||||||
|
static void onMqttMessage(const char* msg) |
||||||
|
{ |
||||||
|
ESP_LOGI(TAG, "MQTT MESSAGE RECEIVED: %s", msg); |
||||||
|
} |
||||||
@ -1,48 +0,0 @@ |
|||||||
#include "freertos/FreeRTOS.h" |
|
||||||
#include "esp_system.h" |
|
||||||
|
|
||||||
#include "nx/firmware/Storage.h" |
|
||||||
#include "nx/firmware/Wifi.h" |
|
||||||
#include "nx/firmware/MqTriggerHttpServer.h" |
|
||||||
#include "nx/software/nxSystemSettings.h" |
|
||||||
|
|
||||||
#include "esp_log.h" |
|
||||||
|
|
||||||
#include <string.h> |
|
||||||
|
|
||||||
#define TAG "MAIN" |
|
||||||
|
|
||||||
static const char* DEVICE_NAME_PREFIX = "mqtrigger-"; |
|
||||||
|
|
||||||
static SystemSettings* systemSettings = NULL; |
|
||||||
static WifiSettings wifiSettings; |
|
||||||
|
|
||||||
void app_main(void) |
|
||||||
{ |
|
||||||
nxInitStorage(); |
|
||||||
|
|
||||||
nxInitSystemSettings(nxStorageWrite, nxStorageRead, esp_restart); |
|
||||||
ESP_LOGI(TAG, "System settings initialized"); |
|
||||||
systemSettings = nxGetSystemSettings(); |
|
||||||
|
|
||||||
strcpy(systemSettings->wifiSsid, "cintra"); |
|
||||||
strcpy(systemSettings->wifiPassword, "fffefdfcfb"); |
|
||||||
systemSettings->useStaticAddr = true; |
|
||||||
systemSettings->ip4addr[3] = 91; |
|
||||||
|
|
||||||
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 |
|
||||||
}; |
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Initializing WiFi"); |
|
||||||
nxInitWifi(&wifiSettings); |
|
||||||
nxStartMqTriggerHttpServer(); |
|
||||||
} |
|
||||||
|
|
||||||
@ -0,0 +1,83 @@ |
|||||||
|
<h1>Application settings</h1> |
||||||
|
|
||||||
|
<h3>MQTT settings</h3> |
||||||
|
<form id="app-settings" action="/app" data-values-src="/app"> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>MQTT host address: |
||||||
|
<input type="text" name="mqhost"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>API topic: |
||||||
|
<input type="text" name="mqapi"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>Heartbeat topic [TODO]: |
||||||
|
<input type="text" name="mqhb"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>Heartbeat interval (sec) [TODO] |
||||||
|
<input type="number" name="mqhbs"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>User name: |
||||||
|
<input type="text" name="mquser"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>Password: |
||||||
|
<input type="password" name="mqpass"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label class="label">Use TLS: |
||||||
|
<select class="select" name="mqtls"> |
||||||
|
<option value="0">No</option> |
||||||
|
<option value="1">Yes</option> |
||||||
|
</select> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<button type="button" class="send">SEND</button> |
||||||
|
</p> |
||||||
|
|
||||||
|
NOTE: device reboot is required |
||||||
|
|
||||||
|
</form> |
||||||
|
|
||||||
|
<div class="col-md-offset-1"> |
||||||
|
<hr /> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h3>Restore defaults</h3> |
||||||
|
<form id="restore-form" action="/app"> |
||||||
|
<input type="hidden" name="restore_default" value="1" /> |
||||||
|
<p> |
||||||
|
<button type="button" class="send">RESTORE DEFAULT SETTINGS</button> |
||||||
|
</p> |
||||||
|
</form> |
||||||
|
|
||||||
|
<div class="col-md-offset-1"> |
||||||
|
<hr /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<h3>Reboot</h3> |
||||||
|
<form id="reboot-form" action="/sys/reboot"> |
||||||
|
<input type="hidden" name="reboot" value="1" /> |
||||||
|
<p> |
||||||
|
<button type="button" class="send">REBOOT</button> |
||||||
|
</p> |
||||||
|
</form> |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
-----BEGIN CERTIFICATE----- |
||||||
|
MIIDlzCCAn+gAwIBAgIUWURIxc42eG2pExKTzmZbH61aNK0wDQYJKoZIhvcNAQEL |
||||||
|
BQAwWzELMAkGA1UEBhMCUEwxFDASBgNVBAgMC01hem93aWVja2llMREwDwYDVQQH |
||||||
|
DAhXYXJzemF3YTEPMA0GA1UECgwGbml4bGFiMRIwEAYDVQQDDAluaXhsYWIuaW4w |
||||||
|
HhcNMjIwNTE4MDkxNTM4WhcNNDIwNTEzMDkxNTM4WjBbMQswCQYDVQQGEwJQTDEU |
||||||
|
MBIGA1UECAwLTWF6b3dpZWNraWUxETAPBgNVBAcMCFdhcnN6YXdhMQ8wDQYDVQQK |
||||||
|
DAZuaXhsYWIxEjAQBgNVBAMMCW5peGxhYi5pbjCCASIwDQYJKoZIhvcNAQEBBQAD |
||||||
|
ggEPADCCAQoCggEBAM1PrRlwzAi5Z6627dhCIIxrRf3f6m8QYRnfNfme1HRYyCYP |
||||||
|
cZFDQDRIRkBjvBCeIOpU4caFtzJok/k8C523/pqMUzpBtFSCu4ASxRxy22xUR2pW |
||||||
|
KVd76NTrm6PbkuKL6mtk5hOKqqAZSbZhwflmOKDhqb2GMDCvCFv/klSBe6IuX6Pk |
||||||
|
oHFIwPntYRX58nwFMoTc65DGn9XKo5jik1F+g8pH9aNDRLW0mBRw77mAQuqvMLvG |
||||||
|
8DNkKEREBvmuGJzU0uy91hN1EpKpGZ5Mn3OuuCMC6wucmyykCSDA12efBXJfKWGM |
||||||
|
EHiG7SyJVJeLl6mHTk3aaIVloNk1yuvtzTfUwBsCAwEAAaNTMFEwHQYDVR0OBBYE |
||||||
|
FGnHRrCf0gUfF5X5uFjKppU0sDkzMB8GA1UdIwQYMBaAFGnHRrCf0gUfF5X5uFjK |
||||||
|
ppU0sDkzMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADnQEVM2 |
||||||
|
ovaGc3BEI2tnIurUSzEiG86gebS6OsK8w/uPhEHr/PCUpIDNEq0jFvenYi0WyuTG |
||||||
|
pdQjdHe8cynqcprfxA+/gqzSQN17Ok8xKJSDcJ0ZV0awIMWRAoG18+HYcACQvHFN |
||||||
|
56Ovkkvc0YVY8x6tLYTTTL2re7C73gT7Nsc7Kdvpmjj763DuoYlxLLaf1RCfEdtc |
||||||
|
s+d9zKENZWOgj4xekPETZUFfxwPS3p39x1zUrJtld1L1cIhcBz8HawphrPyAl9Wl |
||||||
|
K77J3blOxKmsxKJo95kpEiDss0Git066gqHFZjFSriLCTRds7oPK1URm0/5nbLG8 |
||||||
|
l1xpAT+JDQIiU90= |
||||||
|
-----END CERTIFICATE----- |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
<html> |
||||||
|
|
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||||
|
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo="> |
||||||
|
<link rel="stylesheet" href="index.css" /> |
||||||
|
<script type="text/javascript" src="index.js"></script> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
|
||||||
|
<header class="sticky row"> |
||||||
|
<div class="col-sm col-md-10 col-md-offset-1"> |
||||||
|
<a id="app-btn" href="#" role="button" class="nav-btn" data-dst="/app/form">App</a> |
||||||
|
<a href="#" role="button" class="nav-btn" data-dst="/sys/form">System</a> |
||||||
|
<!-- <a href="#" role="button" class="nav-btn" data-dst="/test-form.html">TEST FORM</a> --> |
||||||
|
</div> |
||||||
|
</header> |
||||||
|
|
||||||
|
<div class="container"> |
||||||
|
<div class="row cols-sm-12 cols-md-10"> |
||||||
|
|
||||||
|
<div class="col-md-offset-1" id="content"> |
||||||
|
<h1>Loading</h1> |
||||||
|
<p>Loading content... Please make sure that JavaScript is enabled.</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="col-md-offset-1"> <hr/> </div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<footer> |
||||||
|
<div class="col-sm col-md-10 col-md-offset-1"> |
||||||
|
<p> |
||||||
|
Copyright © Łukasz Chodyła 2022 |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
</footer> |
||||||
|
|
||||||
|
<script> |
||||||
|
initNavButtons(); |
||||||
|
document.getElementById('app-btn').click(); |
||||||
|
</script> |
||||||
|
|
||||||
|
</body> |
||||||
|
|
||||||
|
</html> |
||||||
@ -0,0 +1,126 @@ |
|||||||
|
function ip2int(ip) { |
||||||
|
return ip.split('.').reduce(function(ipInt, octet) { return (ipInt<<8) + parseInt(octet, 10)}, 0) >>> 0; |
||||||
|
} |
||||||
|
|
||||||
|
function sendClickHandler(event) { |
||||||
|
console.log("Sending form..."); |
||||||
|
//console.debug(event);
|
||||||
|
let form = event.srcElement.form; |
||||||
|
console.debug(form); |
||||||
|
|
||||||
|
let params = ''; |
||||||
|
Array.from(form.elements).forEach(element => { |
||||||
|
console.log(element); |
||||||
|
console.log(element.name); |
||||||
|
console.log(element.value); |
||||||
|
|
||||||
|
if (element.value) { //TODO: checkbox/radio
|
||||||
|
params += params ? '&' : ''; |
||||||
|
|
||||||
|
if (element.hasAttribute('data-ip32')) { |
||||||
|
console.log("IPv4 32"); |
||||||
|
params += element.name + '=' + ip2int(element.value); |
||||||
|
} |
||||||
|
else { |
||||||
|
params += element.name + '=' + element.value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
console.log("params: " + params); |
||||||
|
|
||||||
|
postParams(form.getAttribute('action'), params); |
||||||
|
console.log("Form sent"); |
||||||
|
} |
||||||
|
|
||||||
|
function initFormSendButtons(form) { |
||||||
|
const btns = form.querySelectorAll('.send'); |
||||||
|
for (i = 0; i < btns.length; ++i) { |
||||||
|
btns[i].addEventListener('click', sendClickHandler); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function loadContent(url) { |
||||||
|
let http = new XMLHttpRequest(); |
||||||
|
http.open('GET', url, true); |
||||||
|
|
||||||
|
let element = document.getElementById('content'); |
||||||
|
element.innerHTML = "LOADING..."; //TODO: add spinner
|
||||||
|
|
||||||
|
//Send the proper header information along with the request
|
||||||
|
//http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
http.onreadystatechange = function () {//Call a function when the state changes.
|
||||||
|
if (http.readyState == 4 && http.status == 200) { |
||||||
|
console.log("Content received"); |
||||||
|
element.innerHTML = http.responseText; |
||||||
|
loadValues(element); |
||||||
|
initFormSendButtons(element); |
||||||
|
} |
||||||
|
} |
||||||
|
http.send(); |
||||||
|
} |
||||||
|
|
||||||
|
function loadValues(element) { |
||||||
|
const forms = element.querySelectorAll('form'); |
||||||
|
for (i = 0; i < forms.length; ++i) { |
||||||
|
const srcUrl = forms[i].getAttribute('data-values-src'); |
||||||
|
if (!srcUrl) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
console.log("Getting values from " + srcUrl); |
||||||
|
|
||||||
|
let http = new XMLHttpRequest(); |
||||||
|
http.open('GET', srcUrl, true); |
||||||
|
|
||||||
|
//Send the proper header information along with the request
|
||||||
|
//http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
http.onreadystatechange = function () {//Call a function when the state changes.
|
||||||
|
if (http.readyState == 4 && http.status == 200) { |
||||||
|
console.log("Values received: " + http.responseText); |
||||||
|
let obj = JSON.parse(http.responseText); |
||||||
|
if (obj) { |
||||||
|
for (var key of Object.keys(obj)) { |
||||||
|
console.log(key + " -> " + obj[key]) |
||||||
|
let input = element.querySelector("[name='" + key + "'"); |
||||||
|
if (input) { //TODO: checkbox/radio
|
||||||
|
input.value = obj[key]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
http.send(); |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function postParams(url, params) { |
||||||
|
var http = new XMLHttpRequest(); |
||||||
|
http.open('POST', url, true); |
||||||
|
//Send the proper header information along with the request
|
||||||
|
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); |
||||||
|
|
||||||
|
http.onreadystatechange = function () {//Call a function when the state changes.
|
||||||
|
if (http.readyState == 4 && http.status == 200) { |
||||||
|
alert(http.responseText); |
||||||
|
} |
||||||
|
} |
||||||
|
http.send(params); |
||||||
|
} |
||||||
|
|
||||||
|
function navClickHandler(event) { |
||||||
|
console.log('Button Clicked'); |
||||||
|
console.log('Destination: ' + this.getAttribute('data-dst')); |
||||||
|
loadContent(this.getAttribute('data-dst')); |
||||||
|
} |
||||||
|
|
||||||
|
function initNavButtons() { |
||||||
|
const btns = document.querySelectorAll('.nav-btn'); |
||||||
|
for (i = 0; i < btns.length; ++i) { |
||||||
|
btns[i].addEventListener('click', navClickHandler); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
<h1>Application settings</h1><h3>MQTT settings</h3><form id=app-settings action=/app data-values-src=/app><p><label>MQTT host address: |
||||||
|
<input name=mqhost></label><p><label>API topic: |
||||||
|
<input name=mqapi></label><p><label>Heartbeat topic [TODO]: |
||||||
|
<input name=mqhb></label><p><label>Heartbeat interval (sec) [TODO] |
||||||
|
<input type=number name=mqhbs></label><p><label>User name: |
||||||
|
<input name=mquser></label><p><label>Password: |
||||||
|
<input type=password name=mqpass></label><p><label class=label>Use TLS: |
||||||
|
<select class=select name=mqtls> |
||||||
|
<option value=0>No |
||||||
|
<option value=1>Yes</select></label><p><button type=button class=send>SEND</button></p>NOTE: device reboot is required</form><div class=col-md-offset-1><hr></div><h3>Restore defaults</h3><form id=restore-form action=/app><input type=hidden name=restore_default value=1><p><button type=button class=send>RESTORE DEFAULT SETTINGS</button></form><div class=col-md-offset-1><hr></div><h3>Reboot</h3><form id=reboot-form action=/sys/reboot><input type=hidden name=reboot value=1><p><button type=button class=send>REBOOT</button></form> |
||||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,2 @@ |
|||||||
|
<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><header class="sticky row"><div class="col-sm col-md-10 col-md-offset-1"><a id=app-btn href=# role=button class=nav-btn data-dst=/app/form>App</a> |
||||||
|
<a href=# role=button class=nav-btn data-dst=/sys/form>System</a></div></header><div class=container><div class="row cols-sm-12 cols-md-10"><div class=col-md-offset-1 id=content><h1>Loading</h1><p>Loading content... Please make sure that JavaScript is enabled.</div><div class=col-md-offset-1><hr></div></div></div><footer><div class="col-sm col-md-10 col-md-offset-1"><p>Copyright © Łukasz Chodyła 2022</div></footer><script>initNavButtons();document.getElementById('app-btn').click();</script> |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
function ip2int(ip){return ip.split('.').reduce(function(ipInt,octet){return(ipInt<<8)+parseInt(octet,10)},0)>>>0;} |
||||||
|
function sendClickHandler(event){console.log("Sending form...");let form=event.srcElement.form;console.debug(form);let params='';Array.from(form.elements).forEach(element=>{console.log(element);console.log(element.name);console.log(element.value);if(element.value){params+=params?'&':'';if(element.hasAttribute('data-ip32')){console.log("IPv4 32");params+=element.name+'='+ip2int(element.value);} |
||||||
|
else{params+=element.name+'='+element.value;}}});console.log("params: "+params);postParams(form.getAttribute('action'),params);console.log("Form sent");} |
||||||
|
function initFormSendButtons(form){const btns=form.querySelectorAll('.send');for(i=0;i<btns.length;++i){btns[i].addEventListener('click',sendClickHandler);}} |
||||||
|
function loadContent(url){let http=new XMLHttpRequest();http.open('GET',url,true);let element=document.getElementById('content');element.innerHTML="LOADING...";http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){console.log("Content received");element.innerHTML=http.responseText;loadValues(element);initFormSendButtons(element);}} |
||||||
|
http.send();} |
||||||
|
function loadValues(element){const forms=element.querySelectorAll('form');for(i=0;i<forms.length;++i){const srcUrl=forms[i].getAttribute('data-values-src');if(!srcUrl){continue;} |
||||||
|
console.log("Getting values from "+srcUrl);let http=new XMLHttpRequest();http.open('GET',srcUrl,true);http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){console.log("Values received: "+http.responseText);let obj=JSON.parse(http.responseText);if(obj){for(var key of Object.keys(obj)){console.log(key+" -> "+obj[key]) |
||||||
|
let input=element.querySelector("[name='"+key+"'");if(input){input.value=obj[key];}}}}} |
||||||
|
http.send();}} |
||||||
|
function postParams(url,params){var http=new XMLHttpRequest();http.open('POST',url,true);http.setRequestHeader('Content-type','application/x-www-form-urlencoded');http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){alert(http.responseText);}} |
||||||
|
http.send(params);} |
||||||
|
function navClickHandler(event){console.log('Button Clicked');console.log('Destination: '+this.getAttribute('data-dst'));loadContent(this.getAttribute('data-dst'));} |
||||||
|
function initNavButtons(){const btns=document.querySelectorAll('.nav-btn');for(i=0;i<btns.length;++i){btns[i].addEventListener('click',navClickHandler);}} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
<h1>Device system management</h1><form id=sys-form action=/sys data-values-src=/sys><p><label>WiFi SSID: |
||||||
|
<input name=ssid></label><p><label>WiFi password: |
||||||
|
<input type=password name=wpass></label><p><label class=label>WiFi power saving: |
||||||
|
<select class=select name=wpwsave> |
||||||
|
<option value=0>No |
||||||
|
<option value=1>Yes</select></label><p><label class=label>Use static IPv4 address: |
||||||
|
<select class=select name=wstatic> |
||||||
|
<option value=0>No |
||||||
|
<option value=1>Yes</select></label><p><label>Address (x.x.x.x): |
||||||
|
<input name=ip data-ip32></label><p><label>Gateway (x.x.x.x): |
||||||
|
<input name=gw data-ip32></label><p><label>Mask (x.x.x.x): |
||||||
|
<input name=mask data-ip32></label><p><label>DNS server (x.x.x.x): |
||||||
|
<input name=dns data-ip32></label><p><label>SNTP server: |
||||||
|
<input name=sntp></label><p><label>Time zone (GNU TZ variable format) : |
||||||
|
<input name=tz></label></p><button type=button class=send>SEND</button></form><div class=col-md-offset-1><hr></div><h3>Restore defaults</h3><form id=restore-form action=/sys><input type=hidden name=restore_default value=1><p><button type=button class=send>RESTORE DEFAULT SETTINGS</button></form><div class=col-md-offset-1><hr></div><h3>Reboot</h3><form id=reboot-form action=/sys/reboot><input type=hidden name=reboot value=1><p><button type=button class=send>REBOOT</button></form> |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
minify index.html > min/index.html |
||||||
|
minify index.js > min/index.js |
||||||
|
minify index.css > min/index.css |
||||||
|
minify sys.html > min/sys.html |
||||||
|
minify app.html > min/app.html |
||||||
@ -0,0 +1,91 @@ |
|||||||
|
<h1>Device system management</h1> |
||||||
|
|
||||||
|
<form id="sys-form" action="/sys" data-values-src="/sys"> |
||||||
|
<p> |
||||||
|
<label>WiFi SSID: |
||||||
|
<input type="text" name="ssid"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
<label>WiFi password: |
||||||
|
<input type="password" name="wpass"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label class="label">WiFi power saving: |
||||||
|
<select class="select" name="wpwsave"> |
||||||
|
<option value="0">No</option> |
||||||
|
<option value="1">Yes</option> |
||||||
|
</select> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label class="label">Use static IPv4 address: |
||||||
|
<select class="select" name="wstatic"> |
||||||
|
<option value="0">No</option> |
||||||
|
<option value="1">Yes</option> |
||||||
|
</select> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>Address (x.x.x.x): |
||||||
|
<input type="text" name="ip" data-ip32> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
<label>Gateway (x.x.x.x): |
||||||
|
<input type="text" name="gw" data-ip32> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
<label>Mask (x.x.x.x): |
||||||
|
<input type="text" name="mask" data-ip32> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
<p> |
||||||
|
<label>DNS server (x.x.x.x): |
||||||
|
<input type="text" name="dns" data-ip32> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>SNTP server: |
||||||
|
<input type="text" name="sntp"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<p> |
||||||
|
<label>Time zone (GNU TZ variable format) : |
||||||
|
<input type="text" name="tz"> |
||||||
|
</label> |
||||||
|
</p> |
||||||
|
|
||||||
|
<button type="button" class="send">SEND</button> |
||||||
|
</form> |
||||||
|
|
||||||
|
<div class="col-md-offset-1"> |
||||||
|
<hr /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<h3>Restore defaults</h3> |
||||||
|
<form id="restore-form" action="/sys"> |
||||||
|
<input type="hidden" name="restore_default" value="1" /> |
||||||
|
<p> |
||||||
|
<button type="button" class="send">RESTORE DEFAULT SETTINGS</button> |
||||||
|
</p> |
||||||
|
</form> |
||||||
|
|
||||||
|
<div class="col-md-offset-1"> |
||||||
|
<hr /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<h3>Reboot</h3> |
||||||
|
<form id="reboot-form" action="/sys/reboot"> |
||||||
|
<input type="hidden" name="reboot" value="1" /> |
||||||
|
<p> |
||||||
|
<button type="button" class="send">REBOOT</button> |
||||||
|
</p> |
||||||
|
</form> |
||||||
Loading…
Reference in new issue