Browse Source

Refactored MQTT client

master
chodak166 4 years ago
parent
commit
113dc05239
  1. 9
      components/firmware/include/nx/firmware/MqttClient.h
  2. 1
      components/firmware/include/nx/firmware/Wifi.h
  3. 2
      components/firmware/src/HttpHelpers.h
  4. 1
      components/firmware/src/MqTriggerHttpServer.c
  5. 90
      components/firmware/src/MqttClient.c
  6. 4
      components/firmware/src/SntpClient.c
  7. 14
      components/firmware/src/Wifi.c
  8. 6
      components/software/include/nx/software/AppSettings.h
  9. 1
      components/software/include/nx/software/SystemSettings.h
  10. 17
      components/software/src/AppSettings.c
  11. 24
      components/software/src/AppSettingsApi.c
  12. 7
      components/software/src/KeyValueParser.c
  13. 7
      components/software/src/SystemSettingsApi.c
  14. 1
      main/CMakeLists.txt
  15. 53
      main/Main.c
  16. 22
      main/static/app.html
  17. 28
      main/static/index.js
  18. 6
      main/static/min/app.html
  19. 7
      main/static/min/index.js
  20. 3
      main/static/min/sys.html
  21. 6
      main/static/sys.html

9
components/firmware/include/nx/firmware/MqttClient.h

@ -2,10 +2,12 @@
#define COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_MQTTCLIENT_H_ #define COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_MQTTCLIENT_H_
#include <stdint.h> #include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#define MQTT_BROKER_URI_MAX_LEN 128 #define MQTT_BROKER_URI_MAX_LEN 128
#define MQTT_TOPIC_MAX_LEN 128 #define MQTT_TOPIC_MAX_LEN 128
#define MQTT_USER_MAX_LEN 16 #define MQTT_USER_MAX_LEN 32
typedef void (*MqttConnectedCallback)(); typedef void (*MqttConnectedCallback)();
typedef void (*MqttDisconnectedCallback)(); typedef void (*MqttDisconnectedCallback)();
@ -17,6 +19,7 @@ typedef struct MqttSettings
char brokerAddr[MQTT_BROKER_URI_MAX_LEN]; char brokerAddr[MQTT_BROKER_URI_MAX_LEN];
char apiTopic[MQTT_TOPIC_MAX_LEN]; char apiTopic[MQTT_TOPIC_MAX_LEN];
char hbTopic[MQTT_TOPIC_MAX_LEN]; char hbTopic[MQTT_TOPIC_MAX_LEN];
char clientId[MQTT_USER_MAX_LEN];
char user[MQTT_USER_MAX_LEN]; char user[MQTT_USER_MAX_LEN];
char password[MQTT_USER_MAX_LEN]; char password[MQTT_USER_MAX_LEN];
uint16_t hbIntervalSec; uint16_t hbIntervalSec;
@ -30,6 +33,8 @@ typedef struct MqttSettings
void nxStartMqttClient(const MqttSettings* settings); void nxStartMqttClient(const MqttSettings* settings);
void nxMqttHeartbeatTask(void*); bool nxMqttSubscribe(const char* topic, uint8_t qos);
uint32_t nxMqttPublish(const char* topic, uint8_t qos, const char* data, size_t size, uint8_t retain);
#endif /* COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_MQTTCLIENT_H_ */ #endif /* COMPONENTS_FIRMWARE_INCLUDE_NX_FIRMWARE_MQTTCLIENT_H_ */

1
components/firmware/include/nx/firmware/Wifi.h

@ -31,6 +31,7 @@ bool nxInitWifi(WifiSettings* wifiSettings);
void nxSetWifiConnectedCallback(WifiCallback callback); void nxSetWifiConnectedCallback(WifiCallback callback);
void nxSetWifiErrorCallback(WifiCallback callback); void nxSetWifiErrorCallback(WifiCallback callback);
const char* nxGetWifiDeviceName(void);
#endif // COMPONENTS_FIRMWARE_WIFI_H #endif // COMPONENTS_FIRMWARE_WIFI_H

2
components/firmware/src/HttpHelpers.h

@ -6,7 +6,7 @@
typedef void(*RequestCallback)(const uint8_t* content, size_t ctLen, typedef void(*RequestCallback)(const uint8_t* content, size_t ctLen,
const char** response, size_t* respLen); const char** response, size_t* respLen);
#define REQ_CONTENT_MAX_SIZE 1024 #define REQ_CONTENT_MAX_SIZE 2048
// Request handler helper macro // Request handler helper macro
#define ADD_HTTP_HANDLER(_URI, _NAME, _METHOD)\ #define ADD_HTTP_HANDLER(_URI, _NAME, _METHOD)\

1
components/firmware/src/MqTriggerHttpServer.c

@ -82,6 +82,7 @@ bool nxStartMqTriggerHttpServer(void)
config.server_port = 80; config.server_port = 80;
config.max_uri_handlers = 16; config.max_uri_handlers = 16;
config.uri_match_fn = httpd_uri_match_wildcard; config.uri_match_fn = httpd_uri_match_wildcard;
config.stack_size = 8192;
// ... // ...
if (server != NULL) { if (server != NULL) {

90
components/firmware/src/MqttClient.c

@ -17,12 +17,6 @@
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
static const char* TAG = "MQTT"; 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; static const MqttSettings* settings = NULL;
@ -32,8 +26,6 @@ esp_mqtt_client_handle_t clientHandle = NULL;
static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
{ {
esp_mqtt_client_handle_t client = event->client; esp_mqtt_client_handle_t client = event->client;
int msg_id;
// your_context_t *context = event->context;
switch (event->event_id) { switch (event->event_id) {
case MQTT_EVENT_CONNECTED: case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED"); ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
@ -42,15 +34,6 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
if (settings->connectedCb) { if (settings->connectedCb) {
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; break;
case MQTT_EVENT_DISCONNECTED: case MQTT_EVENT_DISCONNECTED:
@ -78,8 +61,6 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event)
settings->messageCb(event->data); 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; break;
case MQTT_EVENT_ERROR: case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR"); ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
@ -117,58 +98,41 @@ void nxStartMqttClient(const MqttSettings* mqttSettings)
esp_mqtt_client_config_t mqtt_cfg = {}; esp_mqtt_client_config_t mqtt_cfg = {};
mqtt_cfg.uri = settings->brokerAddr; mqtt_cfg.uri = settings->brokerAddr;
mqtt_cfg.client_id = settings->clientId;
mqtt_cfg.client_id = CLIENT_ID;
mqtt_cfg.cert_pem = settings->caCrt; mqtt_cfg.cert_pem = settings->caCrt;
mqtt_cfg.username = settings->user; mqtt_cfg.username = settings->user;
mqtt_cfg.password = settings->password; 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_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_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, client);
esp_mqtt_client_start(client); esp_mqtt_client_start(client);
} }
void nxMqttHeartbeatTask(void* param) bool nxMqttSubscribe(const char* topic, uint8_t qos)
{ {
UNUSED(param); if (!clientHandle) {
// ESP_LOGI(TAG, "Starting MQTT heartbeat task"); ESP_LOGE(TAG, "MQTT client not initialized, aborting subscription of topic %s", topic);
// return false;
// 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);
// }
} }
int msg_id = esp_mqtt_client_subscribe(clientHandle, settings->apiTopic, qos);
if (msg_id == -1) {
ESP_LOGE(TAG, "Subscribtion of topic %s failed", topic);
if (settings->errorCb) {
settings->errorCb();
}
return false;
}
return true;
}
uint32_t nxMqttPublish(const char* topic, uint8_t qos, const char* data, size_t size, uint8_t retain)
{
if (!clientHandle) {
ESP_LOGE(TAG, "MQTT client not initialized, aborting data publishing in topic %s", topic);
return -1;
}
return esp_mqtt_client_publish(clientHandle, topic, data, size, qos, retain);
}

4
components/firmware/src/SntpClient.c

@ -8,7 +8,7 @@
static const char* TAG = "SNTP_CLIENT"; static const char* TAG = "SNTP_CLIENT";
static const char* tzString = "CET-1CEST,M3.5.0,M10.5.0/3"; static const char* tzString = NULL;
bool nxInitSntpClient(int retries, const char* serverAddr, const char* timezoneString) bool nxInitSntpClient(int retries, const char* serverAddr, const char* timezoneString)
{ {
@ -35,8 +35,10 @@ void nxGetTimeStr(char buf[DT_FORMAT_LEN])
struct tm timeinfo; struct tm timeinfo;
time(&now); time(&now);
if (tzString) {
setenv("TZ", tzString, 1); setenv("TZ", tzString, 1);
tzset(); tzset();
}
localtime_r(&now, &timeinfo); localtime_r(&now, &timeinfo);
strftime(buf, DT_FORMAT_LEN, "%Y-%m-%d %H:%M:%S", &timeinfo); strftime(buf, DT_FORMAT_LEN, "%Y-%m-%d %H:%M:%S", &timeinfo);
} }

14
components/firmware/src/Wifi.c

@ -43,7 +43,7 @@ static WifiSettings* settings = NULL;
static WifiCallback errorCallback = NULL; static WifiCallback errorCallback = NULL;
static WifiCallback connectedCallback = NULL; static WifiCallback connectedCallback = NULL;
static char deviceName[DEVICE_NAME_MAX_LEN]; static char deviceName[DEVICE_NAME_MAX_LEN] = {0};
static bool initWifiStation(void); static bool initWifiStation(void);
@ -342,3 +342,15 @@ static bool initWifiStation(void)
return waitForStaConnection(); return waitForStaConnection();
} }
const char* nxGetWifiDeviceName(void)
{
if (settings && settings->devicePrefix && strlen(deviceName) == 0) {
generateDeviceName();
return deviceName;
}
else if (strlen(deviceName)) {
return deviceName;
}
return "unknown";
}

6
components/software/include/nx/software/AppSettings.h

@ -8,6 +8,8 @@
#define WIFI_STRINGS_MAX_LEN 32 #define WIFI_STRINGS_MAX_LEN 32
#define URI_MAX_SIZE 64 #define URI_MAX_SIZE 64
#define USER_DATA_MAX_SIZE 16 #define USER_DATA_MAX_SIZE 16
#define CA_CERT_MAX_SIZE 1500
#define DEVICE_NAME_MAX_LEN 64
typedef bool (*StorageReadFn)(const char* key, void* data, size_t size); typedef bool (*StorageReadFn)(const char* key, void* data, size_t size);
@ -23,7 +25,9 @@ typedef struct AppSettings {
char mqttPassword[USER_DATA_MAX_SIZE]; char mqttPassword[USER_DATA_MAX_SIZE];
uint16_t mqttHbIntervalSec; uint16_t mqttHbIntervalSec;
uint8_t mqttUseTls; uint8_t mqttUseTls;
char caCert[CA_CERT_MAX_SIZE];
bool overrideDevName;
char customDevName[DEVICE_NAME_MAX_LEN];
} AppSettings; } AppSettings;

1
components/software/include/nx/software/SystemSettings.h

@ -26,6 +26,7 @@ typedef struct SystemSettings {
char sntpAddr[URI_MAX_LEN]; char sntpAddr[URI_MAX_LEN];
char tzEnv[TZ_ENV_LEN]; char tzEnv[TZ_ENV_LEN];
char rsSchedule[RESTART_SCHEDULE_SIZE]; char rsSchedule[RESTART_SCHEDULE_SIZE];
const char* deviceName; // set in the main component
} SystemSettings; } SystemSettings;

17
components/software/src/AppSettings.c

@ -10,6 +10,9 @@
#define KEY_MQTT_TLS "mqtls" #define KEY_MQTT_TLS "mqtls"
#define KEY_MQTT_USER "mquser" #define KEY_MQTT_USER "mquser"
#define KEY_MQTT_PASS "mqpass" #define KEY_MQTT_PASS "mqpass"
#define KEY_OVR_DEVNAME "ovdn"
#define KEY_CUSTOM_DEVNAME "cdn"
#define KEY_CA_CERT "cacert"
#define STORAGE_READ(_KEY, _NAME)\ #define STORAGE_READ(_KEY, _NAME)\
storageRead(_KEY, &settings._NAME, sizeof(settings._NAME)); storageRead(_KEY, &settings._NAME, sizeof(settings._NAME));
@ -26,6 +29,9 @@ static const uint16_t DEFAULT_MQTT_HB_SEC = 60 * 15;
static const uint8_t DEFAULT_MQTT_TLS = 0; static const uint8_t DEFAULT_MQTT_TLS = 0;
static const char* DEFAULT_MQTT_USER = ""; static const char* DEFAULT_MQTT_USER = "";
static const char* DEFAULT_MQTT_PASS = ""; static const char* DEFAULT_MQTT_PASS = "";
static const bool DEFAULT_OV_DEVNAME = false;
static const char* DEFAULT_CUSTOM_DEVNAME = "esp-dev";
static const char* DEFAULT_CA_CERT = "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----\n";
static StorageWriteFn storageWrite = NULL; static StorageWriteFn storageWrite = NULL;
@ -53,6 +59,9 @@ static void loadSettings(void)
STORAGE_READ(KEY_MQTT_TLS , mqttUseTls ); STORAGE_READ(KEY_MQTT_TLS , mqttUseTls );
STORAGE_READ(KEY_MQTT_USER , mqttUser ); STORAGE_READ(KEY_MQTT_USER , mqttUser );
STORAGE_READ(KEY_MQTT_PASS , mqttPassword ); STORAGE_READ(KEY_MQTT_PASS , mqttPassword );
STORAGE_READ(KEY_OVR_DEVNAME , overrideDevName );
STORAGE_READ(KEY_CUSTOM_DEVNAME , customDevName );
STORAGE_READ(KEY_CA_CERT , caCert );
} }
@ -92,6 +101,10 @@ void nxRestoreAppDefaultSettings(void)
strcpy(settings.mqttHost , DEFAULT_MQTT_HOST ); strcpy(settings.mqttHost , DEFAULT_MQTT_HOST );
strcpy(settings.mqttUser , DEFAULT_MQTT_USER ); strcpy(settings.mqttUser , DEFAULT_MQTT_USER );
strcpy(settings.mqttPassword , DEFAULT_MQTT_PASS ); strcpy(settings.mqttPassword , DEFAULT_MQTT_PASS );
strcpy(settings.caCert , DEFAULT_CA_CERT );
strcpy(settings.customDevName , DEFAULT_CUSTOM_DEVNAME);
settings.overrideDevName = DEFAULT_OV_DEVNAME;
nxWriteAppSettings(); nxWriteAppSettings();
} }
@ -109,7 +122,9 @@ void nxWriteAppSettings(void)
STORAGE_WRITE(KEY_MQTT_TLS , mqttUseTls ); STORAGE_WRITE(KEY_MQTT_TLS , mqttUseTls );
STORAGE_WRITE(KEY_MQTT_USER , mqttUser ); STORAGE_WRITE(KEY_MQTT_USER , mqttUser );
STORAGE_WRITE(KEY_MQTT_PASS , mqttPassword ); STORAGE_WRITE(KEY_MQTT_PASS , mqttPassword );
STORAGE_WRITE(KEY_CA_CERT , caCert );
STORAGE_WRITE(KEY_CUSTOM_DEVNAME , customDevName );
STORAGE_WRITE(KEY_OVR_DEVNAME , overrideDevName );
settingsUpdatedCb(); settingsUpdatedCb();
} }

24
components/software/src/AppSettingsApi.c

@ -15,16 +15,19 @@
#define API_KEY_MQTT_TLS "mqtls" #define API_KEY_MQTT_TLS "mqtls"
#define API_KEY_MQTT_USER "mquser" #define API_KEY_MQTT_USER "mquser"
#define API_KEY_MQTT_PASS "mqpass" #define API_KEY_MQTT_PASS "mqpass"
#define API_KEY_CA_CERT "cacert"
#define API_KEY_OV_DEVNAME "ovdn"
#define API_KEY_CUSTOM_DEVNAME "cdn"
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
#define RESP_BUF_SIZE 1024 #define RESP_BUF_SIZE 2048
char responseBuffer[RESP_BUF_SIZE]; char responseBuffer[RESP_BUF_SIZE];
static void handleKvPair(const char* key, const char* value, const void* userData, bool* done) 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); printf("App settings update; Key: %s, Value: %s\n", key, value);
AppSettings* settings = nxGetAppSettings(); AppSettings* settings = nxGetAppSettings();
if (strcmp(key, API_KEY_RESTORE) == 0) { if (strcmp(key, API_KEY_RESTORE) == 0) {
@ -51,6 +54,15 @@ static void handleKvPair(const char* key, const char* value, const void* userDat
else if (strcmp(key, API_KEY_MQTT_TLS) == 0) { else if (strcmp(key, API_KEY_MQTT_TLS) == 0) {
settings->mqttUseTls = atoi(value); settings->mqttUseTls = atoi(value);
} }
else if (strcmp(key, API_KEY_CA_CERT) == 0) {
strcpy(settings->caCert, value);
}
else if (strcmp(key, API_KEY_CUSTOM_DEVNAME) == 0) {
strcpy(settings->customDevName, value);
}
else if (strcmp(key, API_KEY_OV_DEVNAME) == 0) {
settings->overrideDevName = atoi(value);
}
else { else {
fprintf(stderr, "Unknown key: %s\n", key); fprintf(stderr, "Unknown key: %s\n", key);
} }
@ -72,14 +84,18 @@ void nxApiGetAppSettings(const uint8_t* msg, size_t msgLen,
"\"%s\":\"%s\", " "\"%s\":\"%s\", "
"\"%s\":%i, " // hb interval "\"%s\":%i, " // hb interval
"\"%s\":%i, " "\"%s\":%i, "
"\"%s\":\"%s\" " // mqtt user "\"%s\":\"%s\", " // mqtt user
"\"%s\":%i," // ovdn
"\"%s\":\"%s\"" // cdn
"}", "}",
API_KEY_MQTT_HOST , settings->mqttHost , API_KEY_MQTT_HOST , settings->mqttHost ,
API_KEY_MQTT_API_URI , settings->mqttApiUri , API_KEY_MQTT_API_URI , settings->mqttApiUri ,
API_KEY_MQTT_HB_URI , settings->mqttHbUri , API_KEY_MQTT_HB_URI , settings->mqttHbUri ,
API_KEY_MQTT_HB_SEC , settings->mqttHbIntervalSec, API_KEY_MQTT_HB_SEC , settings->mqttHbIntervalSec,
API_KEY_MQTT_TLS , settings->mqttUseTls , API_KEY_MQTT_TLS , settings->mqttUseTls ,
API_KEY_MQTT_USER , settings->mqttUser API_KEY_MQTT_USER , settings->mqttUser ,
API_KEY_OV_DEVNAME , settings->overrideDevName ,
API_KEY_CUSTOM_DEVNAME, settings->customDevName
); );
*response = responseBuffer; *response = responseBuffer;
*respLen = strlen(responseBuffer); *respLen = strlen(responseBuffer);

7
components/software/src/KeyValueParser.c

@ -1,24 +1,25 @@
#include "KeyValueParser.h" #include "KeyValueParser.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#define MAX_KEY_SIZE 32 #define MAX_KEY_SIZE 32
#define MAX_VALUE_SIZE 32
#define MAX_PAIRS 16 #define MAX_PAIRS 16
void nxParseKeyValueString(const char* string, KeyValueHandler handler, const void* userData) void nxParseKeyValueString(const char* string, KeyValueHandler handler, const void* userData)
{ {
bool done = false; bool done = false;
char key[MAX_KEY_SIZE]; char key[MAX_KEY_SIZE];
char value[MAX_VALUE_SIZE]; char* value = calloc(1, strlen(string));
FILE *stream; FILE *stream;
stream = fmemopen((void*)string, strlen(string), "r"); stream = fmemopen((void*)string, strlen(string), "r");
int maxPairs = MAX_PAIRS; int maxPairs = MAX_PAIRS;
while (fscanf(stream, "%127[^=]=%127[^&]%*c", key, value) == 2 && maxPairs > 0 && !done) { while (fscanf(stream, "%32[^=]=%2048[^&]%*c", key, value) == 2 && maxPairs > 0 && !done) {
handler(key, value, userData, &done); handler(key, value, userData, &done);
maxPairs -= 1; maxPairs -= 1;
} }
fclose(stream); fclose(stream);
free(value);
} }

7
components/software/src/SystemSettingsApi.c

@ -25,6 +25,7 @@
#define API_KEY_SNTP_ADDR "sntp" #define API_KEY_SNTP_ADDR "sntp"
#define API_KEY_TZ_ENV "tz" #define API_KEY_TZ_ENV "tz"
#define API_KEY_RS_SCHEDULE "rsch" #define API_KEY_RS_SCHEDULE "rsch"
#define API_KEY_DEVNAME "dn"
#define API_KEY_RESTORE "restore_default" #define API_KEY_RESTORE "restore_default"
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
@ -111,7 +112,8 @@ void nxApiGetSystemSettings(const uint8_t* msg, size_t msgLen,
"\"%s\":\"%i.%i.%i.%i\"," // dns "\"%s\":\"%i.%i.%i.%i\"," // dns
"\"%s\":\"%s\"," // sntp "\"%s\":\"%s\"," // sntp
"\"%s\":\"%s\"," // tz "\"%s\":\"%s\"," // tz
"\"%s\":\"%s\"" // rsch "\"%s\":\"%s\"," // rsch
"\"%s\":\"%s\"" // dn
"}", "}",
API_KEY_SSID, settings->wifiSsid, API_KEY_SSID, settings->wifiSsid,
API_KEY_WPWSAVE, settings->wifiPowerSave ? "true" : "false", API_KEY_WPWSAVE, settings->wifiPowerSave ? "true" : "false",
@ -122,7 +124,8 @@ void nxApiGetSystemSettings(const uint8_t* msg, size_t msgLen,
API_KEY_DNS_ADDR, settings->dnsAddr[0], settings->dnsAddr[1], settings->dnsAddr[2], settings->dnsAddr[3], API_KEY_DNS_ADDR, settings->dnsAddr[0], settings->dnsAddr[1], settings->dnsAddr[2], settings->dnsAddr[3],
API_KEY_SNTP_ADDR, settings->sntpAddr, API_KEY_SNTP_ADDR, settings->sntpAddr,
API_KEY_TZ_ENV, settings->tzEnv, API_KEY_TZ_ENV, settings->tzEnv,
API_KEY_RS_SCHEDULE, settings->rsSchedule API_KEY_RS_SCHEDULE, settings->rsSchedule,
API_KEY_DEVNAME, settings->deviceName
); );
*response = responseBuffer; *response = responseBuffer;
*respLen = strlen(responseBuffer); *respLen = strlen(responseBuffer);

1
main/CMakeLists.txt

@ -12,4 +12,3 @@ target_add_binary_data(${COMPONENT_TARGET} "static/min/index.css" TEXT)
target_add_binary_data(${COMPONENT_TARGET} "static/min/index.js" TEXT) target_add_binary_data(${COMPONENT_TARGET} "static/min/index.js" TEXT)
target_add_binary_data(${COMPONENT_TARGET} "static/min/sys.html" TEXT) target_add_binary_data(${COMPONENT_TARGET} "static/min/sys.html" TEXT)
target_add_binary_data(${COMPONENT_TARGET} "static/min/app.html" TEXT) target_add_binary_data(${COMPONENT_TARGET} "static/min/app.html" TEXT)
target_add_binary_data(${COMPONENT_TARGET} "static/ca.crt" TEXT)

53
main/Main.c

@ -24,10 +24,10 @@
#define TAG "MAIN" #define TAG "MAIN"
#define SNTP_RETRIES 8 #define SNTP_RETRIES 8
#define API_QOS 2
#define HB_QOS 1
static const char* DEVICE_NAME_PREFIX = "mqtrigger-"; static const char* DEVICE_NAME_PREFIX = "mqt-";
extern const char ca_crt_start[] asm("_binary_ca_crt_start");
static void startWifi(void); static void startWifi(void);
static void onWifiConnected(void); static void onWifiConnected(void);
@ -38,6 +38,8 @@ static void onMqttDisconnected();
static void onMqttError(); static void onMqttError();
static void onMqttMessage(const char* msg); static void onMqttMessage(const char* msg);
static void hbTask(void*);
static SystemSettings* systemSettings = NULL; static SystemSettings* systemSettings = NULL;
static AppSettings* appSettings = NULL; static AppSettings* appSettings = NULL;
static WifiSettings wifiSettings; static WifiSettings wifiSettings;
@ -53,7 +55,7 @@ static const MqTriggerHttpCallbacks httpCallbacks = {
.postSysSet = nxApiUpdateSystemSettings, .postSysSet = nxApiUpdateSystemSettings,
.getAppSet = nxApiGetAppSettings, .getAppSet = nxApiGetAppSettings,
.postAppSet = nxApiUpdateAppSettings .postAppSet = nxApiUpdateAppSettings
// .postCmd = nxApiHandleLedCmd // .postCmd = nxApiHandleCmd
}; };
void app_main(void) void app_main(void)
@ -81,8 +83,6 @@ void app_main(void)
static void startWifi(void) static void startWifi(void)
{ {
strcpy(systemSettings->wifiSsid, "cintra");
strcpy(systemSettings->wifiPassword, "fffefdfcfb");
systemSettings->useStaticAddr = true; systemSettings->useStaticAddr = true;
systemSettings->ip4addr[3] = 91; systemSettings->ip4addr[3] = 91;
@ -106,15 +106,22 @@ static void startWifi(void)
static void onWifiConnected(void) static void onWifiConnected(void)
{ {
systemSettings->deviceName = nxGetWifiDeviceName();
nxInitSntpClient(SNTP_RETRIES, systemSettings->sntpAddr, systemSettings->tzEnv); nxInitSntpClient(SNTP_RETRIES, systemSettings->sntpAddr, systemSettings->tzEnv);
strcpy(mqttSettings.brokerAddr, appSettings->mqttHost); strcpy(mqttSettings.brokerAddr, appSettings->mqttHost);
strcpy(mqttSettings.apiTopic, appSettings->mqttApiUri); strcpy(mqttSettings.apiTopic, appSettings->mqttApiUri);
strcpy(mqttSettings.hbTopic, appSettings->mqttHbUri); strcpy(mqttSettings.hbTopic, appSettings->mqttHbUri);
strcpy(mqttSettings.user, appSettings->mqttUser); strcpy(mqttSettings.user, appSettings->mqttUser);
strcpy(mqttSettings.password, appSettings->mqttPassword); strcpy(mqttSettings.password, appSettings->mqttPassword);
strcpy(mqttSettings.clientId, appSettings->overrideDevName
? appSettings->customDevName
: systemSettings->deviceName);
mqttSettings.hbIntervalSec = appSettings->mqttHbIntervalSec; mqttSettings.hbIntervalSec = appSettings->mqttHbIntervalSec;
mqttSettings.caCrt = appSettings->mqttUseTls ? ca_crt_start : NULL; mqttSettings.caCrt = appSettings->mqttUseTls ? appSettings->caCert : NULL;
mqttSettings.messageCb = onMqttMessage; mqttSettings.messageCb = onMqttMessage;
mqttSettings.connectedCb = onMqttConnected; mqttSettings.connectedCb = onMqttConnected;
mqttSettings.disconnectedCb = onMqttDisconnected; mqttSettings.disconnectedCb = onMqttDisconnected;
@ -147,8 +154,11 @@ static void onWifiError()
static void onMqttConnected() static void onMqttConnected()
{ {
ESP_LOGI(TAG, "MQTT CONNECTED"); ESP_LOGI(TAG, "MQTT CONNECTED");
if (nxMqttSubscribe(appSettings->mqttApiUri, API_QOS)) {
xTaskCreate(&hbTask, "hb_task", 2048, NULL, 1, NULL);
nxUpdateStatus(STATUS_OK); nxUpdateStatus(STATUS_OK);
} }
}
static void onMqttDisconnected() static void onMqttDisconnected()
{ {
@ -161,3 +171,32 @@ static void onMqttError()
ESP_LOGE(TAG, "MQTT ERROR"); ESP_LOGE(TAG, "MQTT ERROR");
nxUpdateStatus(STATUS_APP_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) {
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);
nxMqttPublish(appSettings->mqttHbUri, HB_QOS, hbMessage, strlen(hbMessage), 1);
vTaskDelay(appSettings->mqttHbIntervalSec*1000 / portTICK_PERIOD_MS);
}
}

22
main/static/app.html

@ -48,6 +48,27 @@
</label> </label>
</p> </p>
<p>
<label class="label">Override device name:
<select class="select" name="ovdn">
<option value="0">No</option>
<option value="1">Yes</option>
</select>
</label>
</p>
<p>
<label>Custom device name:
<input type="text" name="cdn">
</label>
</p>
<p>
<label>Certificate data (begin to end, tags included):
<p><textarea name="cacert"></textarea></p>
</label>
</p>
<p> <p>
<button type="button" class="send">SEND</button> <button type="button" class="send">SEND</button>
</p> </p>
@ -61,7 +82,6 @@
</div> </div>
<h3>Restore defaults</h3> <h3>Restore defaults</h3>
<form id="restore-form" action="/app"> <form id="restore-form" action="/app">
<input type="hidden" name="restore_default" value="1" /> <input type="hidden" name="restore_default" value="1" />

28
main/static/index.js

@ -1,3 +1,5 @@
var valuesCache = {};
function ip2int(ip) { function ip2int(ip) {
return ip.split('.').reduce(function(ipInt, octet) { return (ipInt<<8) + parseInt(octet, 10)}, 0) >>> 0; return ip.split('.').reduce(function(ipInt, octet) { return (ipInt<<8) + parseInt(octet, 10)}, 0) >>> 0;
} }
@ -70,16 +72,37 @@ function loadContent(url) {
} }
function loadValues(element) { function loadValues(element) {
const forms = element.querySelectorAll('form'); const forms = element.querySelectorAll('form');
for (i = 0; i < forms.length; ++i) { for (i = 0; i < forms.length; ++i) {
const srcUrl = forms[i].getAttribute('data-values-src'); const srcUrl = forms[i].getAttribute('data-values-src');
if (!srcUrl) { if (!srcUrl) {
continue; continue;
} }
if (valuesCache[srcUrl]) {
console.log("Source values already cached: " + valuesCache[srcUrl]);
//TODO: dry
let obj = JSON.parse(valuesCache[srcUrl]);
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];
if (input.hasAttribute('data-onset')) {
eval(input.getAttribute("data-onset"));
}
}
}
}
continue;
}
console.log("Getting values from " + srcUrl); console.log("Getting values from " + srcUrl);
let http = new XMLHttpRequest(); let http = new XMLHttpRequest();
http.open('GET', srcUrl, true); http.open('GET', srcUrl, false);
//Send the proper header information along with the request //Send the proper header information along with the request
//http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); //http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
@ -87,6 +110,9 @@ function loadValues(element) {
http.onreadystatechange = function () {//Call a function when the state changes. http.onreadystatechange = function () {//Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) { if (http.readyState == 4 && http.status == 200) {
console.log("Values received: " + http.responseText); console.log("Values received: " + http.responseText);
valuesCache[srcUrl] = http.responseText;
//TODO: dry
let obj = JSON.parse(http.responseText); let obj = JSON.parse(http.responseText);
if (obj) { if (obj) {
for (var key of Object.keys(obj)) { for (var key of Object.keys(obj)) {

6
main/static/min/app.html

@ -7,4 +7,8 @@
<input type=password name=mqpass></label><p><label class=label>Use TLS: <input type=password name=mqpass></label><p><label class=label>Use TLS:
<select class=select name=mqtls> <select class=select name=mqtls>
<option value=0>No <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> <option value=1>Yes</select></label><p><label class=label>Override device name:
<select class=select name=ovdn>
<option value=0>No
<option value=1>Yes</select></label><p><label>Custom device name:
<input name=cdn></label><p><label>Certificate data (begin to end, tags included):<p><textarea name=cacert></textarea></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>

7
main/static/min/index.js

@ -1,4 +1,4 @@
function ip2int(ip){return ip.split('.').reduce(function(ipInt,octet){return(ipInt<<8)+parseInt(octet,10)},0)>>>0;} var valuesCache={};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){if(!element.hasAttribute('data-ignore')){params+=params?'&':'';if(element.hasAttribute('data-ip32')){console.log("IPv4 32");params+=element.name+'='+ip2int(element.value);} 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){if(!element.hasAttribute('data-ignore')){params+=params?'&':'';if(element.hasAttribute('data-ip32')){console.log("IPv4 32");params+=element.name+'='+ip2int(element.value);}
else{params+=element.name+'='+element.value;}} else{params+=element.name+'='+element.value;}}
else{console.log("Ignoring "+element.name);}}});console.log("params: "+params);postParams(form.getAttribute('action'),params);console.log("Form sent");} else{console.log("Ignoring "+element.name);}}});console.log("params: "+params);postParams(form.getAttribute('action'),params);console.log("Form sent");}
@ -6,7 +6,10 @@ function initFormSendButtons(form){const btns=form.querySelectorAll('.send');for
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);}} 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();} 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;} 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]) if(valuesCache[srcUrl]){console.log("Source values already cached: "+valuesCache[srcUrl]);let obj=JSON.parse(valuesCache[srcUrl]);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];if(input.hasAttribute('data-onset')){eval(input.getAttribute("data-onset"));}}}}
continue;}
console.log("Getting values from "+srcUrl);let http=new XMLHttpRequest();http.open('GET',srcUrl,false);http.onreadystatechange=function(){if(http.readyState==4&&http.status==200){console.log("Values received: "+http.responseText);valuesCache[srcUrl]=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];if(input.hasAttribute('data-onset')){eval(input.getAttribute("data-onset"));}}}}}} let input=element.querySelector("[name='"+key+"'");if(input){input.value=obj[key];if(input.hasAttribute('data-onset')){eval(input.getAttribute("data-onset"));}}}}}}
http.send();}} 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);}} 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);}}

3
main/static/min/sys.html

@ -1,4 +1,5 @@
<h1>Device system management</h1><form id=net-form action=/sys data-values-src=/sys><h3>Network</h3><p><label>WiFi SSID: <h1>Device system management</h1><form id=net-form action=/sys data-values-src=/sys><p><label>Device name:
<input name=dn readonly data-ignore></label><h3>Network</h3><p><label>WiFi SSID:
<input name=ssid></label><p><label>WiFi password: <input name=ssid></label><p><label>WiFi password:
<input type=password name=wpass></label><p><label class=label>WiFi power saving: <input type=password name=wpass></label><p><label class=label>WiFi power saving:
<select class=select name=wpwsave> <select class=select name=wpwsave>

6
main/static/sys.html

@ -1,6 +1,12 @@
<h1>Device system management</h1> <h1>Device system management</h1>
<form id="net-form" action="/sys" data-values-src="/sys"> <form id="net-form" action="/sys" data-values-src="/sys">
<p>
<label>Device name:
<input type="text" name="dn" readonly="readonly" data-ignore>
</label>
</p>
<h3>Network</h3> <h3>Network</h3>
<p> <p>
<label>WiFi SSID: <label>WiFi SSID:

Loading…
Cancel
Save