diff --git a/platformio.ini b/platformio.ini index 3947eb8..0f4a756 100644 --- a/platformio.ini +++ b/platformio.ini @@ -20,7 +20,6 @@ board_build.filesystem = littlefs extra_scripts = post:scripts/build_interface.py lib_deps = - adafruit/Adafruit NeoPixel@^1.11.0 ottowinter/AsyncMqttClient-esphome@^0.8.6 ottowinter/ESPAsyncWebServer-esphome@^3.1.0 bblanchon/ArduinoJson@^6.21.3 diff --git a/src/app/routes.cpp b/src/app/routes.cpp index 102ba32..19ed6ef 100644 --- a/src/app/routes.cpp +++ b/src/app/routes.cpp @@ -1,10 +1,13 @@ #include "app/routes.h" #include "infra/eth.h" #include "infra/httpServer.h" +#include "infra/mqtt.h" #include "infra/relay.h" #include "infra/fs.h" #include "config/config.h" #include "utils/print.h" +#include "utils/settings.h" +#include "utils/utils.h" void handleDoorOpen(AsyncWebServerRequest *request) { relayTurnOn(); @@ -31,7 +34,7 @@ void getFeatures(AsyncWebServerRequest *request) { } void intercomStatus(AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_AP_STATUS_SIZE); + AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_INTERCOM_STATUS_SIZE); JsonObject root = response->getRoot(); root["status"] = 0; @@ -46,25 +49,8 @@ void intercomStatus(AsyncWebServerRequest* request) { void networkStatus(AsyncWebServerRequest* request) { AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_NETWORK_STATUS_SIZE); JsonObject root = response->getRoot(); - wl_status_t status = WL_CONNECTED; - root["status"] = (uint8_t)status; - if (status == WL_CONNECTED) { - root["local_ip"] = ETH.localIP().toString(); - IPv6Address localIPv6 = ETH.localIPv6(); - if (!(localIPv6 == IPv6Address())) - root["local_ip_v6"] = ETH.localIPv6().toString(); - root["mac_address"] = ETH.macAddress(); - root["full_duplex"] = ETH.fullDuplex(); - root["link_speed"] = ETH.linkSpeed(); - root["link_up"] = ETH.linkUp(); - root["network_id"] = ETH.networkID(); - root["subnet_mask"] = ETH.subnetMask().toString(); - root["gateway_ip"] = ETH.gatewayIP().toString(); - IPAddress dnsIP = ETH.dnsIP(); - if (dnsIP) - root["dns_ip"] = dnsIP.toString(); - } + getEthStatus(root); response->setLength(); request->send(response); @@ -79,20 +65,14 @@ void scanNetworks(AsyncWebServerRequest* request) { } static void networkSettingsRead(AsyncWebServerRequest* request) { - AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_NETWORK_STATUS_SIZE); + AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_NETWORK_SETTINGS_SIZE); DynamicJsonDocument doc(1024); bool success = readJsonVariantFromFile(NETWORK_SETTINGS_PATH, doc); JsonVariant root = doc.as(); - if (!success) { - root["static_ip_config"] = false; - root["local_ip"] = ETH.localIP().toString(); - root["gateway_ip"] = ETH.gatewayIP().toString(); - root["subnet_mask"] = ETH.subnetMask().toString(); - root["dns_ip"] = ETH.dnsIP().toString(); - root["dns_ip_2"] = ETH.dnsIP(1).toString(); - } + if (!success) + getDefaultEthConf(root); response->setLength(); @@ -101,24 +81,21 @@ static void networkSettingsRead(AsyncWebServerRequest* request) { request->send(200, "application/json", jsonString.c_str()); } -static void networkSettingsUpdate(AsyncWebServerRequest* request, JsonVariant &json) { - if (!json.is()) { +static void networkSettingsUpdate(AsyncWebServerRequest *request, +uint8_t *data, size_t len, size_t index, size_t total) { + DynamicJsonDocument jsonDoc(MAX_NETWORK_SETTINGS_SIZE); + String jsonStr = requestDataToStr(data, len); + DeserializationError error = deserializeJson(jsonDoc, jsonStr); + + if (!jsonDoc.is()) { request->send(400); return; } - JsonVariant root = json.as(); + JsonVariant root = jsonDoc.as(); bool success = writeJsonVariantToFile(NETWORK_SETTINGS_PATH, root); - setEthConfig( - root["static_ip_config"].as(), - root["local_ip"].as(), - root["gateway_ip"].as(), - root["subnet_mask"].as(), - root["dns_ip"].as() - ); - if (success) { String jsonString; serializeJson(root, jsonString); @@ -126,6 +103,106 @@ static void networkSettingsUpdate(AsyncWebServerRequest* request, JsonVariant &j } else request->send(500, "text/plain", "Network settings not updated"); + + configureEth( + root["static_ip_config"].as(), + root["local_ip"].as(), + root["gateway_ip"].as(), + root["subnet_mask"].as(), + root["dns_ip"].as() + ); +} + +static void mqttStatus(AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_MQTT_STATUS_SIZE); + JsonObject root = response->getRoot(); + + root["enabled"] = getMqttEnabled(); + root["connected"] = getMqttClient().connected(); + root["client_id"] = getMqttClient().getClientId(); + root["disconnect_reason"] = (uint8_t)getMqttDisconnectReason(); + + response->setLength(); + request->send(response); +} + +static void mqttSettingsRead(AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_MQTT_SETTINGS_SIZE); + DynamicJsonDocument doc(1024); + + bool success = readJsonVariantFromFile(MQTT_SETTINGS_PATH, doc); + JsonVariant root = doc.as(); + + if (!success) + getDefaultMqttConf(root); + + response->setLength(); + + String jsonString; + serializeJson(root, jsonString); + request->send(200, "application/json", jsonString.c_str()); +} + +static void mqttSettingsUpdate(AsyncWebServerRequest *request, +uint8_t *data, size_t len, size_t index, size_t total) { + DynamicJsonDocument jsonDoc(MAX_MQTT_SETTINGS_SIZE); + String jsonStr = requestDataToStr(data, len); + DeserializationError error = deserializeJson(jsonDoc, jsonStr); + + if (!jsonDoc.is() || error) { + request->send(400); + return; + } + + JsonVariant root = jsonDoc.as(); + + bool fileLoaded = writeJsonVariantToFile(MQTT_SETTINGS_PATH, root); + bool enabled = root["enabled"].as(); + + bool mqttConfigured = configureMqtt( + root["enabled"].as(), + root["host"].as(), + root["port"].as(), + root["username"].as(), + root["password"].as(), + root["client_id"].as(), + root["keep_alive"].as(), + root["clean_session"].as(), + root["max_topic_length"].as() + ); + + if (!fileLoaded) { + request->send(500, "text/plain", "MQTT settings not updated"); + } else if (!mqttConfigured && enabled) { + request->send(404, "text/plain", "MQTT settings not configured"); + } else { + String jsonString; + serializeJson(root, jsonString); + request->send(200, "application/json", jsonString.c_str()); + } +} + +void systemStatus(AsyncWebServerRequest* request) { + AsyncJsonResponse* response = new AsyncJsonResponse(false, MAX_ESP_STATUS_SIZE); + JsonObject root = response->getRoot(); + + root["esp_platform"] = "esp32"; + root["max_alloc_heap"] = ESP.getMaxAllocHeap(); + root["psram_size"] = ESP.getPsramSize(); + root["free_psram"] = ESP.getFreePsram(); + root["cpu_freq_mhz"] = ESP.getCpuFreqMHz(); + root["free_heap"] = ESP.getFreeHeap(); + root["sketch_size"] = ESP.getSketchSize(); + root["free_sketch_space"] = ESP.getFreeSketchSpace(); + root["sdk_version"] = ESP.getSdkVersion(); + root["flash_chip_size"] = ESP.getFlashChipSize(); + root["flash_chip_speed"] = ESP.getFlashChipSpeed(); + + root["fs_total"] = LittleFS.totalBytes(); + root["fs_used"] = LittleFS.usedBytes(); + + response->setLength(); + request->send(response); } void handleDoorStatus(AsyncWebServerRequest *request) { @@ -133,14 +210,25 @@ void handleDoorStatus(AsyncWebServerRequest *request) { request->send(200, "text/plain", "OK"); } -void handleBodyRequest(AsyncWebServer& server, const char* uri, WebRequestMethodComposite method, \ + +void handleBodyRequest(AsyncWebServer& server, const char* uri, WebRequestMethodComposite method, ArJsonRequestHandlerFunction onRequest) { AsyncCallbackJsonWebHandler* handler = \ - new AsyncCallbackJsonWebHandler(uri, networkSettingsUpdate); + new AsyncCallbackJsonWebHandler(uri, onRequest); handler->setMethod(method); server.addHandler(handler); } +void restartNow(AsyncWebServerRequest *request) { + request->send(200, "text/plain", "OK"); + ESP.restart(); +} + +void factoryReset(AsyncWebServerRequest *request) { + deleteFilesInDir(FS_CONFIG_DIRECTORY); + restartNow(request); +} + void initRoutes() { AsyncWebServer& server = getServer(); server.on("/api/v1/door/open", handleDoorOpen); @@ -150,7 +238,15 @@ void initRoutes() { server.on("/api/v1/networkStatus", networkStatus); server.on("/api/v1/networkSettings", HTTP_GET, networkSettingsRead); - handleBodyRequest(server, "/api/v1/networkSettings", HTTP_POST, networkSettingsUpdate); + server.on("/api/v1/networkSettings", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL, networkSettingsUpdate); server.on("/api/v1/intercomStatus", intercomStatus); + + server.on("/api/v1/mqttStatus", mqttStatus); + server.on("/api/v1/mqttSettings", HTTP_GET, mqttSettingsRead); + server.on("/api/v1/mqttSettings", HTTP_POST, [](AsyncWebServerRequest *request) {}, NULL, mqttSettingsUpdate); + + server.on("/api/v1/systemStatus", systemStatus); + server.on("/api/v1/restart", restartNow); + server.on("/api/v1/factoryReset", factoryReset); } \ No newline at end of file diff --git a/src/app/routes.h b/src/app/routes.h index 92d5138..2f72a75 100644 --- a/src/app/routes.h +++ b/src/app/routes.h @@ -1,4 +1,5 @@ #include #include +#include void initRoutes(); \ No newline at end of file diff --git a/src/config/config.h b/src/config/config.h index 964b972..661b8b8 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -1,11 +1,18 @@ #pragma once -#define DEFAULT_STATIC_LOCAL_IP IPAddress(192, 168, 1, 150) -#define DEFAULT_STATIC_GATEWAY IPAddress(192, 168, 1, 1) -#define DEFAULT_STATIC_SUBNET IPAddress(255, 255, 255, 0) +#define FACTORY_STATIC_LOCAL_IP IPAddress(192, 168, 1, 150) +#define FACTORY_STATIC_GATEWAY IPAddress(192, 168, 1, 1) +#define FACTORY_STATIC_SUBNET IPAddress(255, 255, 255, 0) -#define MQTT_HOST IPAddress(192, 168, 1, 173) -#define MQTT_PORT 1883 +#define FACTORY_MQTT_ENABLED false +#define FACTORY_MQTT_HOST "" +#define FACTORY_MQTT_PORT 1883 +#define FACTORY_MQTT_USERNAME "" +#define FACTORY_MQTT_PASSWORD "" +#define FACTORY_MQTT_CLIENT_ID "#{platform}-#{unique_id}" +#define FACTORY_MQTT_KEEP_ALIVE 16 +#define FACTORY_MQTT_CLEAN_SESSION true +#define FACTORY_MQTT_MAX_TOPIC_LENGTH 128 #define SERIAL_NUMBER "4823" @@ -21,13 +28,18 @@ #define DOOR_SENS_PIN 114 #define DATA_PIN 12 -#define DATA_PERIOD 240 // microseconds +#define DATA_PERIOD 120 // microseconds -#define PRINT_RAW_SIGNAL_FLAG 0 +#define PRINT_RAW_SIGNAL_FLAG 1 #define MAX_FEATURES_SIZE 256 -#define MAX_AP_STATUS_SIZE 1024 #define MAX_NETWORK_STATUS_SIZE 1024 #define MAX_NETWORK_SETTINGS_SIZE 1024 +#define MAX_INTERCOM_STATUS_SIZE 1024 +#define MAX_MQTT_STATUS_SIZE 1024 +#define MAX_MQTT_SETTINGS_SIZE 1024 +#define MAX_ESP_STATUS_SIZE 1024 -#define NETWORK_SETTINGS_PATH "/config/networkSettings.json" \ No newline at end of file +#define FS_CONFIG_DIRECTORY "/config" +#define NETWORK_SETTINGS_PATH "/config/networkSettings.json" +#define MQTT_SETTINGS_PATH "/config/mqttSettings.json" \ No newline at end of file diff --git a/src/domain/stateMachine.cpp b/src/domain/stateMachine.cpp index 07b9748..781fe77 100644 --- a/src/domain/stateMachine.cpp +++ b/src/domain/stateMachine.cpp @@ -38,7 +38,7 @@ void receiveData(int data) { previousData = data; } -void changeState(State state, bool resetCountersFlag=true) { +void changeState(State state, bool resetCountersFlag) { currentState = state; switch (state) { @@ -139,4 +139,8 @@ void updateStateMachine(int data) { break; } } +} + +void initStateMachine() { + changeState(CONNECTED); } \ No newline at end of file diff --git a/src/domain/stateMachine.h b/src/domain/stateMachine.h index 515af33..7876d8e 100644 --- a/src/domain/stateMachine.h +++ b/src/domain/stateMachine.h @@ -19,5 +19,7 @@ enum State { void resetCounters(); void updateStateMachine(int data); +void initStateMachine(); +void changeState(State state, bool resetCountersFlag=true); #endif // STATE_MACHINE_H \ No newline at end of file diff --git a/src/infra/eth.cpp b/src/infra/eth.cpp index e28f21d..f563d1f 100644 --- a/src/infra/eth.cpp +++ b/src/infra/eth.cpp @@ -1,12 +1,11 @@ -#include #include "infra/eth.h" #include "infra/fs.h" #include "infra/mqtt.h" #include "config/config.h" #include "utils/print.h" -extern bool eth_connected; TimerHandle_t ethReconnectTimer; +wl_status_t status; void WiFiEvent(WiFiEvent_t event) { @@ -19,6 +18,7 @@ void WiFiEvent(WiFiEvent_t event) break; case ARDUINO_EVENT_ETH_CONNECTED: Serial.println("ETH Connected"); + status = WL_CONNECTED; break; case ARDUINO_EVENT_ETH_GOT_IP: Serial.print("ETH MAC: "); @@ -31,17 +31,14 @@ void WiFiEvent(WiFiEvent_t event) Serial.print(", "); Serial.print(ETH.linkSpeed()); Serial.println("Mbps"); - eth_connected = true; delay(2000); - connectToMqtt(); break; case ARDUINO_EVENT_ETH_DISCONNECTED: Serial.println("ETH Disconnected"); - eth_connected = false; + status = WL_DISCONNECTED; break; case ARDUINO_EVENT_ETH_STOP: Serial.println("ETH Stopped"); - eth_connected = false; break; default: break; @@ -67,15 +64,12 @@ void WiFiEvent(WiFiEvent_t event) Serial.print(", "); Serial.print(ETH.linkSpeed()); Serial.println("Mbps"); - eth_connected = true; break; case SYSTEM_EVENT_ETH_DISCONNECTED: Serial.println("ETH Disconnected"); - eth_connected = false; break; case SYSTEM_EVENT_ETH_STOP: Serial.println("ETH Stopped"); - eth_connected = false; break; default: break; @@ -83,6 +77,75 @@ void WiFiEvent(WiFiEvent_t event) #endif } +void configureEth(bool isStatic, String localIp, String gateway, +String subnet, String dns1, String dns2) { + IPAddress newLocalIp = IPAddress(); + IPAddress newGateway = IPAddress(); + IPAddress newSubnet = IPAddress(); + IPAddress newDns1 = IPAddress(); + IPAddress newDns2 = IPAddress(); + + newLocalIp.fromString(localIp); + newGateway.fromString(gateway); + newSubnet.fromString(subnet); + newDns1.fromString(dns1); + newDns2.fromString(dns2); + + if (isStatic) + ETH.config( + newLocalIp, + newGateway, + newSubnet, + newDns1, + newDns2 + ); + else { + //ESP.restart(); + ETH.config((uint32_t)0, (uint32_t)0, (uint32_t)0); + } +} + +void configureEth(JsonVariant& root) { + configureEth( + root["static_ip_config"].as(), + root["local_ip"].as(), + root["gateway_ip"].as(), + root["subnet_mask"].as(), + root["dns_ip"].as() + ); +} + +void getDefaultEthConf(JsonVariant& root) { + root["static_ip_config"] = true; + root["local_ip"] = ETH.localIP().toString(); + root["gateway_ip"] = ETH.gatewayIP().toString(); + root["subnet_mask"] = ETH.subnetMask().toString(); + root["dns_ip"] = ETH.dnsIP().toString(); + root["dns_ip_2"] = ETH.dnsIP(1).toString(); +} + +void getEthStatus(JsonObject& root) { + root["status"] = (uint8_t)status; + + if (status == WL_CONNECTED) { + root["local_ip"] = ETH.localIP().toString(); + IPv6Address localIPv6 = ETH.localIPv6(); + if (!(localIPv6 == IPv6Address())) + root["local_ip_v6"] = ETH.localIPv6().toString(); + root["mac_address"] = ETH.macAddress(); + root["full_duplex"] = ETH.fullDuplex(); + root["link_speed"] = ETH.linkSpeed(); + root["link_up"] = ETH.linkUp(); + root["network_id"] = ETH.networkID(); + root["subnet_mask"] = ETH.subnetMask().toString(); + root["gateway_ip"] = ETH.gatewayIP().toString(); + + IPAddress dnsIP = ETH.dnsIP(); + if (dnsIP) + root["dns_ip"] = dnsIP.toString(); + } +} + bool loadEthConfig() { DynamicJsonDocument doc(1024); @@ -93,13 +156,7 @@ bool loadEthConfig() { JsonVariant root = doc.as(); - setEthConfig( - root["static_ip_config"].as(), - root["local_ip"].as(), - root["gateway_ip"].as(), - root["subnet_mask"].as(), - root["dns_ip"].as() - ); + configureEth(root); return true; } @@ -122,17 +179,16 @@ void initEth() { ETH_TYPE, ETH_CLK_MODE); - if (!loadEthConfig() && DEFAULT_STATIC_LOCAL_IP) { + if (!loadEthConfig() && FACTORY_STATIC_LOCAL_IP) { ETH.config( - DEFAULT_STATIC_LOCAL_IP, - DEFAULT_STATIC_GATEWAY, - DEFAULT_STATIC_SUBNET + FACTORY_STATIC_LOCAL_IP, + FACTORY_STATIC_GATEWAY, + FACTORY_STATIC_SUBNET ); } } -void testClient(const char * host, uint16_t port) -{ +void testClient(const char * host, uint16_t port) { Serial.print("\nconnecting to "); Serial.println(host); @@ -151,29 +207,13 @@ void testClient(const char * host, uint16_t port) client.stop(); } -void setEthConfig(bool isStatic, String localIp, String gateway, -String subnet, String dns1, String dns2) { - IPAddress newLocalIp = IPAddress(); - IPAddress newGateway = IPAddress(); - IPAddress newSubnet = IPAddress(); - IPAddress newDns1 = IPAddress(); - IPAddress newDns2 = IPAddress(); +bool checkHost(const char* host, uint16_t port) { + WiFiClient client; - newLocalIp.fromString(localIp); - newGateway.fromString(gateway); - newSubnet.fromString(subnet); - newDns1.fromString(dns1); - newDns2.fromString(dns2); - - if (isStatic) - ETH.config( - newLocalIp, - newGateway, - newSubnet, - newDns1, - newDns2 - ); - else { - ETH.config((uint32_t)0, (uint32_t)0, (uint32_t)0); - } + if (client.connect(host, port)) { + client.stop(); + return true; + } else { + return false; + } } \ No newline at end of file diff --git a/src/infra/eth.h b/src/infra/eth.h index 38613db..5edd2f4 100644 --- a/src/infra/eth.h +++ b/src/infra/eth.h @@ -1,6 +1,7 @@ #pragma once #include +#include /* * ETH_CLOCK_GPIO0_IN - default: external clock from crystal oscillator @@ -30,11 +31,15 @@ void WiFiEvent(WiFiEvent_t event); void initEth(); void testClient(const char * host, uint16_t port); -void setEthConfig( +bool checkHost(const char* host, uint16_t port); +void getEthStatus(JsonObject& root); +void configureEth( bool isStatic, String localIp, String gateway, String subnet, String dns1="", String dns2="" -); \ No newline at end of file +); +void configureEth(JsonVariant& root); +void getDefaultEthConf(JsonVariant& root); \ No newline at end of file diff --git a/src/infra/fs.cpp b/src/infra/fs.cpp index a065060..9724e46 100644 --- a/src/infra/fs.cpp +++ b/src/infra/fs.cpp @@ -24,7 +24,7 @@ bool readJsonVariantFromFile(const char* file_path, DynamicJsonDocument& jsonDoc } String fileContent = file.readString(); - println("Reading config: ", fileContent); + println("Reading config: ", file_path, ", content:", fileContent); DeserializationError error = deserializeJson(jsonDoc, fileContent); @@ -62,10 +62,32 @@ bool writeJsonVariantToFile(const char* file_path, JsonVariant& jsonVariant) { String jsonString; serializeJson(jsonVariant, jsonString); - println("Writing config: ", jsonString); + println("Writing config: ", file_path, ", content:", jsonString); file.print(jsonString); file.close(); return true; -} \ No newline at end of file +} + +void deleteFilesInDir(const char* path) { + File dir = LittleFS.open(path); + if (!dir.isDirectory()) { + Serial.println("Not a directory"); + return; + } + + File file = dir.openNextFile(); + while (file) { + String fullPath = String(path) + '/' + String(file.name()); + if (file.isDirectory()) { + deleteFilesInDir(fullPath.c_str()); + } else { + file.close(); + LittleFS.remove(fullPath); + } + + file = dir.openNextFile(); + } + dir.close(); +} diff --git a/src/infra/fs.h b/src/infra/fs.h index 27f2cc6..0ed55f6 100644 --- a/src/infra/fs.h +++ b/src/infra/fs.h @@ -4,4 +4,5 @@ void initFileSystem(); bool readJsonVariantFromFile(const char* filename, DynamicJsonDocument& jsonDoc); -bool writeJsonVariantToFile(const char* filename, JsonVariant& jsonObj); \ No newline at end of file +bool writeJsonVariantToFile(const char* filename, JsonVariant& jsonObj); +void deleteFilesInDir(const char* path); \ No newline at end of file diff --git a/src/infra/intercom.cpp b/src/infra/intercom.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/intercom.h b/src/infra/intercom.h new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/mqtt.cpp b/src/infra/mqtt.cpp index acd2f65..2ed038b 100644 --- a/src/infra/mqtt.cpp +++ b/src/infra/mqtt.cpp @@ -1,16 +1,23 @@ -#include -#include - #include "config/config.h" #include "infra/mqtt.h" +#include "infra/eth.h" +#include "infra/fs.h" +#include "utils/settings.h" AsyncMqttClient mqttClient; TimerHandle_t mqttReconnectTimer; +AsyncMqttClientDisconnectReason mqttDisconnectReason; -void connectToMqtt() { - Serial.println("Connecting to MQTT..."); - mqttClient.connect(); -} +DynamicJsonDocument mqttConf(1024); +bool mqttConnected = false; +bool mqttEnabled = true; + +// Pointers to hold retained copies of the mqtt client connection strings. +// This is required as AsyncMqttClient holds refrences to the supplied connection strings. +char* retainedHost; +char* retainedClientId; +char* retainedUsername; +char* retainedPassword; void publishToMQTT(const char* topic, const char* message) { mqttClient.publish(topic, 2, true, message); @@ -34,24 +41,25 @@ void publishToMQTT(const char* topic, bool message) { } void onMqttConnect(bool sessionPresent) { - Serial.println("Connected to MQTT."); - Serial.print("Session present: "); - Serial.println(sessionPresent); + Serial.println("Connected to MQTT."); + Serial.print("Session present: "); + Serial.println(sessionPresent); - publishToMQTT(MAC_ADDRESS_MQTT_TOPIC, ETH.macAddress().c_str()); - publishToMQTT(IP_ADDRESS_MQTT_TOPIC, ETH.localIP().toString().c_str()); - publishToMQTT(SERIAL_NUMBER_MQTT_TOPIC, SERIAL_NUMBER); + mqttConnected = true; - //uint16_t packetIdSub = mqttClient.subscribe("esp32/led", 0); - //Serial.print("Subscribing at QoS 0, packetId: "); - //Serial.println(packetIdSub); + publishToMQTT(MAC_ADDRESS_MQTT_TOPIC, ETH.macAddress().c_str()); + publishToMQTT(IP_ADDRESS_MQTT_TOPIC, ETH.localIP().toString().c_str()); + publishToMQTT(SERIAL_NUMBER_MQTT_TOPIC, SERIAL_NUMBER); + + //uint16_t packetIdSub = mqttClient.subscribe("esp32/led", 0); + //Serial.print("Subscribing at QoS 0, packetId: "); + //Serial.println(packetIdSub); } void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) { Serial.println("Disconnected from MQTT."); - if (WiFi.isConnected()) { - xTimerStart(mqttReconnectTimer, 0); - } + mqttConnected = false; + mqttDisconnectReason = reason; } void onMqttSubscribe(uint16_t packetId, uint8_t qos) { @@ -74,14 +82,157 @@ void onMqttPublish(uint16_t packetId) { Serial.println(packetId); } -void initMQTT() { - mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast(connectToMqtt)); +AsyncMqttClient& getMqttClient() { + return mqttClient; +} +AsyncMqttClientDisconnectReason& getMqttDisconnectReason() { + return mqttDisconnectReason; +} + +bool getMqttConnected() { + return mqttConnected; +} + +bool getMqttEnabled() { + return mqttEnabled; +} + +/** + * Retains a copy of the cstr provided in the pointer provided using dynamic allocation. + * + * Frees the pointer before allocation and leaves it as nullptr if cstr == nullptr. + */ +static char* retainCstr(const char* cstr, char** ptr) { + // free up previously retained value if exists + free(*ptr); + *ptr = nullptr; + + // dynamically allocate and copy cstr (if non null) + if (cstr != nullptr) { + *ptr = (char*)malloc(strlen(cstr) + 1); + strcpy(*ptr, cstr); + } + + // return reference to pointer for convenience + return *ptr; +} + +void setMqttServer(String host, uint16_t port) { + Serial.print("MQTT server: "); + Serial.print(host); + Serial.print(":"); + Serial.println(port); + mqttClient.setServer(retainCstr(host.c_str(), &retainedHost), port); +} + +bool checkMqttHost(String host, uint16_t port) { + if (!checkHost(host.c_str(), port)) { + Serial.print("MQTT host is not responding: "); + Serial.print(host); + Serial.print(":"); + Serial.println(port); + return false; + } + return true; +} + +bool configureMqtt(bool enabled, String host, uint16_t port, String username, String password, +String clientId, uint16_t keepAlive, bool cleanSession, uint16_t maxTopicLength) { + if (!mqttEnabled) { + mqttEnabled = enabled; + return checkMqttHost(host, port); + } + + mqttEnabled = enabled; + mqttClient.disconnect(); + + if (!checkMqttHost(host, port)) + return false; + + if (enabled) { + Serial.println(F("Reconnecting to MQTT...")); + setMqttServer(host, port); + if (username.length() > 0) { + mqttClient.setCredentials( + retainCstr(username.c_str(), &retainedUsername), + retainCstr(password.length() > 0 ? password.c_str() : nullptr, &retainedPassword)); + } else { + mqttClient.setCredentials(retainCstr(nullptr, &retainedUsername), retainCstr(nullptr, &retainedPassword)); + } + mqttClient.setClientId(retainCstr(clientId.c_str(), &retainedClientId)); + mqttClient.setKeepAlive(keepAlive); + mqttClient.setCleanSession(cleanSession); + mqttClient.setMaxTopicLength(maxTopicLength); + mqttClient.connect(); + return true; + } + + return false; +} + +bool configureMqtt(JsonVariant& root) { + configureMqtt( + root["enabled"].as(), + root["host"].as(), + root["port"].as(), + root["username"].as(), + root["password"].as(), + root["client_id"].as(), + root["keep_alive"].as(), + root["clean_session"].as(), + root["max_topic_length"].as() + ); +} + +void getDefaultMqttConf(JsonVariant& root) { + root["enabled"] = FACTORY_MQTT_ENABLED; + root["host"] = FACTORY_MQTT_HOST; + root["port"] = FACTORY_MQTT_PORT; + root["username"] = FACTORY_MQTT_USERNAME; + root["password"] = FACTORY_MQTT_PASSWORD; + root["client_id"] = formatSetting(FACTORY_MQTT_CLIENT_ID); + root["keep_alive"] = FACTORY_MQTT_KEEP_ALIVE; + root["clean_session"] = FACTORY_MQTT_CLEAN_SESSION; + root["max_topic_length"] = FACTORY_MQTT_MAX_TOPIC_LENGTH; +} + +bool loadMqttConfig() { + bool success = readJsonVariantFromFile(MQTT_SETTINGS_PATH, mqttConf); + + if (!success) + return false; +} + +void reconnectMQTTIfNeeded() { + if (!mqttClient.connected()) { + if (mqttConf.isNull()) + loadMqttConfig(); + + JsonVariant root = mqttConf.as(); + + if (!mqttConf.is()) + return; + + bool mqttConfigured = configureMqtt( + root["enabled"].as(), + root["host"].as(), + root["port"].as(), + root["username"].as(), + root["password"].as(), + root["client_id"].as(), + root["keep_alive"].as(), + root["clean_session"].as(), + root["max_topic_length"].as() + ); + } +} + +void initMQTT() { mqttClient.onConnect(onMqttConnect); mqttClient.onDisconnect(onMqttDisconnect); mqttClient.onSubscribe(onMqttSubscribe); mqttClient.onUnsubscribe(onMqttUnsubscribe); //mqttClient.onMessage(onMqttMessage); //mqttClient.onPublish(onMqttPublish); - mqttClient.setServer(MQTT_HOST, MQTT_PORT); -} \ No newline at end of file +} diff --git a/src/infra/mqtt.h b/src/infra/mqtt.h index 8cb84eb..bad56b6 100644 --- a/src/infra/mqtt.h +++ b/src/infra/mqtt.h @@ -1,17 +1,37 @@ #pragma once +#include +#include +#include #include #include -void connectToMqtt(); void onMqttConnect(bool sessionPresent); void onMqttDisconnect(AsyncMqttClientDisconnectReason reason); void onMqttSubscribe(uint16_t packetId, uint8_t qos); void onMqttUnsubscribe(uint16_t packetId); void onMqttPublish(uint16_t packetId); -void sendDataToMQTT(int flatNumber); void publishToMQTT(const char* topic, const char* message); void publishToMQTT(const char* topic, int message); void publishToMQTT(const char* topic, float message); void publishToMQTT(const char* topic, bool message); -void initMQTT(); \ No newline at end of file +void initMQTT(); + +AsyncMqttClient& getMqttClient(); +AsyncMqttClientDisconnectReason& getMqttDisconnectReason(); +bool getMqttConnected(); +bool getMqttEnabled(); +bool configureMqtt( + bool enabled, + String host, + uint16_t port, + String username, + String password, + String clientId, + uint16_t keepAlive, + bool cleanSession, + uint16_t maxTopicLength +); +void getDefaultMqttConf(JsonVariant& root); +bool configureMqtt(JsonVariant& root); +void reconnectMQTTIfNeeded(); \ No newline at end of file diff --git a/src/infra/system.cpp b/src/infra/system.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/infra/system.h b/src/infra/system.h new file mode 100644 index 0000000..e69de29 diff --git a/src/main.cpp b/src/main.cpp index 4efa08d..bc4fd5a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,10 +22,10 @@ uint32_t lastMillis; uint64_t lastMicros; -bool eth_connected = false; - int data = 0; +bool flag = false; + void setup() { Serial.begin(115200); @@ -44,6 +44,7 @@ void setup() { relayTurnOn(); delay(2000); + //initStateMachine(); initFileSystem(); initEth(); initMQTT(); @@ -59,11 +60,21 @@ void loop() { if (PRINT_RAW_SIGNAL_FLAG) printf("{}", data); - lastMillis = micros() + DATA_PERIOD; + lastMicros = micros() + DATA_PERIOD; } - if (eth_connected) { - if (lastMillis < millis()) {; + if (true) { + if (lastMillis < millis()) { + if (getMqttEnabled() && !getMqttConnected()) { + reconnectMQTTIfNeeded(); + } + /*if (flag) { + changeState(RECEIVING_DATA); + flag = false; + } else { + changeState(CONNECTED); + flag = true; + }*/ lastMillis = millis() + 3000; } } diff --git a/src/utils/settings.cpp b/src/utils/settings.cpp new file mode 100644 index 0000000..f55f4b1 --- /dev/null +++ b/src/utils/settings.cpp @@ -0,0 +1,51 @@ +#include "utils/settings.h" + +#ifdef ESP32 +const String PLATFORM = "esp32"; +#elif defined(ESP8266) +const String PLATFORM = "esp8266"; +#endif + +/** + * Returns a new string after replacing each instance of the pattern with a value generated by calling the provided + * callback. + */ +String replaceEach(String value, String pattern, String (*generateReplacement)()) { + while (true) { + int index = value.indexOf(pattern); + if (index == -1) { + break; + } + value = value.substring(0, index) + generateReplacement() + value.substring(index + pattern.length()); + } + return value; +} + +/** + * Generates a random number, encoded as a hex string. + */ +String getRandom() { + return String(random(2147483647), HEX); +} + +/** + * Uses the station's MAC address to create a unique id for each device. + */ +String getUniqueId() { + uint8_t mac[6]; +#ifdef ESP32 + esp_read_mac(mac, ESP_MAC_WIFI_STA); +#elif defined(ESP8266) + wifi_get_macaddr(STATION_IF, mac); +#endif + char macStr[13] = {0}; + sprintf(macStr, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return String(macStr); +} + +String formatSetting(String value) { + value = replaceEach(value, "#{random}", getRandom); + value.replace("#{unique_id}", getUniqueId()); + value.replace("#{platform}", PLATFORM); + return value; +} \ No newline at end of file diff --git a/src/utils/settings.h b/src/utils/settings.h new file mode 100644 index 0000000..74c5baa --- /dev/null +++ b/src/utils/settings.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +String formatSetting(String value); \ No newline at end of file diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp new file mode 100644 index 0000000..507aaa3 --- /dev/null +++ b/src/utils/utils.cpp @@ -0,0 +1,11 @@ +#include "utils/utils.h" + +String requestDataToStr(uint8_t *data, size_t len) { + String str = ""; + + for (size_t i = 0; i < len; i++) { + str += (char)data[i]; + } + + return str; +} \ No newline at end of file diff --git a/src/utils/utils.h b/src/utils/utils.h new file mode 100644 index 0000000..0d24f03 --- /dev/null +++ b/src/utils/utils.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +String requestDataToStr(uint8_t *data, size_t len); \ No newline at end of file