From 3fdf508a5cb078641ede0a30fb638c4b35b67816 Mon Sep 17 00:00:00 2001 From: Julien Lazarewicz Date: Sun, 29 Mar 2026 23:46:22 +0200 Subject: [PATCH] no message --- include/config.h | 7 +- platformio.ini | 6 +- src/main.cpp | 509 ++++++++++++++++++++++------------------------- 3 files changed, 239 insertions(+), 283 deletions(-) diff --git a/include/config.h b/include/config.h index 95c70b1..3f6f7c7 100644 --- a/include/config.h +++ b/include/config.h @@ -39,23 +39,22 @@ constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U; // Enable Thingsboard library debug #define THINGSBOARD_ENABLE_DEBUG false -// WIFI parameters constexpr char WIFI_SSID[] = "thingsboard"; constexpr char WIFI_PASSWORD[] = "thingsboard"; // Module actionneur Thingsboard token access // Doit être modifié suivant binome -constexpr char TOKEN[] = "1voazulw2mqr9avkdonw"; +constexpr char device_id[] = "LAZ"; // Thingsboard server IP address -constexpr char THINGSBOARD_SERVER[] = "10.42.0.1"; +constexpr char MQTT_SERVER[] = "10.42.0.2"; // MQTT port used to communicate with the server, 1883 is the default unencrypted MQTT port, // whereas 8883 would be the default encrypted SSL MQTT port #if ENCRYPTED constexpr uint16_t THINGSBOARD_PORT = 8883U; #else -constexpr uint16_t THINGSBOARD_PORT = 1883U; +constexpr uint16_t THINGSBOARD_PORT = 1884U; #endif #if ENCRYPTED diff --git a/platformio.ini b/platformio.ini index 3f56db2..f4626b8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,7 +14,5 @@ board = adafruit_feather_esp32_v2 framework = arduino lib_deps = adafruit/Adafruit NeoPixel@^1.12.5 - thingsboard/ThingsBoard@^0.15.0 - -; Need to be updated according to your OS and hardware configuration -upload_port = /dev/cu.usbserial-59100221861 + bblanchon/ArduinoJson@^7.2.2 + knolleary/PubSubClient@^2.8 diff --git a/src/main.cpp b/src/main.cpp index 4ce8db4..38569b2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,334 +1,293 @@ #include "config.h" #include "version.h" +#include #include -#include -#include - -#include -#include -#include - -// Initialize underlying client, used to establish a connection #if ENCRYPTED -WiFiClientSecure espClient; +#include #else -WiFiClient espClient; +#include #endif +#include +#include -// Initalize the Mqtt client instance -Arduino_MQTT_Client mqttClient(espClient); +// ----------------------------------------------------------------------------- +// Notes: +// - device_id from config.h is used as the device_id. Nothing is hardcoded. +// - The actuator publishes its real state on: +// imt_test/actuator//state +// - The actuator listens for commands on: +// imt_test/actuator//set +// ----------------------------------------------------------------------------- -// Actuator status global variables -bool VMC_STATUS; // Status VMC -bool LIGHT_STATUS; // Status light -bool HEATER_STATUS; // Status heater -bool AC_STATUS; // Status A/C - -/// @brief Initalizes WiFi connection, -// will endlessly delay until a connection has been successfully established -void InitWiFi(); - -/// @brief Reconnects the WiFi uses InitWiFi if the connection has been removed -/// @return Returns true as soon as a connection has been established again -bool reconnect(); - -/// @brief Update callback that will be called as soon as one of the provided shared attributes -/// changes value, if none are provided we subscribe to any shared attribute change instead -/// @param data Data containing the shared attributes that were changed and their current value -void processSharedAttributeUpdate(const JsonObjectConst& data); - -/// @brief Process Light change RPC -void processSwitchLightChange(const JsonVariantConst& data, JsonDocument& response); - -/// @brief Process VMC change RPC -void processSwitchVmcChange(const JsonVariantConst& data, JsonDocument& response); - -/// @brief Process heater change RPC -void processSwitchHeaterChange(const JsonVariantConst& data, JsonDocument& response); - -/// @brief Process AC change RPC -void processSwitchACChange(const JsonVariantConst& data, JsonDocument& response); - -/// @brief Process light status inquiry RPC -void getSwitchLight(const JsonVariantConst& data, JsonDocument& response); - -/// @brief Process VMC status inquiry RPC -void getSwitchVmc(const JsonVariantConst& data, JsonDocument& response); - -/// @brief Process heater status inquiry RPC -void getSwitchHeater(const JsonVariantConst& data, JsonDocument& response); - -/// @brief Process AC status inquiry RPC -void getSwitchAC(const JsonVariantConst& data, JsonDocument& response); - -/// @brief Set light pin value and publish it to Thingsboard server -/// @return Returns true if pin is HIGH, false if LOW -bool setLight(bool status); - -/// @brief Set VMC pin value and publish it to Thingsboard server -/// @return Returns true if pin is HIGH, false if LOW -bool setVMC(bool status); - -/// @brief Set heater pin value and publish it to Thingsboard server -/// @return Returns true if pin is HIGH, false if LOW -bool setHeater(bool status); - -/// @brief Set AC pin value and publish it to Thingsboard server -/// @return Returns true if pin is HIGH, false if LOW -bool setAC(bool status); - -constexpr const char CONNECTING_MSG[] = "Connecting to: (%s) with token (%s)\n"; -constexpr const char LIGHT_RELAY_KEY[] = "LIGHT_RELAY"; -constexpr const char VMC_RELAY_KEY[] = "VMC_RELAY"; -constexpr const char HEATER_RELAY_KEY[] = "HEATER_RELAY"; -constexpr const char AC_RELAY_KEY[] = "AC_RELAY"; -constexpr const char VERSION_KEY[] = "VERSION"; - -constexpr const char RPC_JSON_METHOD[] = "example_json"; -constexpr const char RPC_GET_LIGHT_SWITCH_METHOD[] = "get_light_switch"; -constexpr const char RPC_SET_LIGHT_SWITCH_METHOD[] = "set_light_switch"; -constexpr const char RPC_LIGHT_SWITCH_KEY[] = "LIGHT_RELAY"; - -constexpr const char RPC_GET_VMC_SWITCH_METHOD[] = "get_vmc_switch"; -constexpr const char RPC_SET_VMC_SWITCH_METHOD[] = "set_vmc_switch"; -constexpr const char RPC_VMC_SWITCH_KEY[] = "VMC_RELAY"; - -constexpr const char RPC_GET_HEATER_SWITCH_METHOD[] = "get_heater_switch"; -constexpr const char RPC_SET_HEATER_SWITCH_METHOD[] = "set_heater_switch"; -constexpr const char RPC_HEATER_SWITCH_KEY[] = "HEATER_RELAY"; - -constexpr const char RPC_GET_AC_SWITCH_METHOD[] = "get_ac_switch"; -constexpr const char RPC_SET_AC_SWITCH_METHOD[] = "set_ac_switch"; -constexpr const char RPC_AC_SWITCH_KEY[] = "AC_RELAY"; - -// Maximum size packets will ever be sent or received by the underlying MQTT client, -// if the size is to small messages might not be sent or received messages will be discarded -constexpr uint16_t MAX_MESSAGE_SEND_SIZE = 128U; -constexpr uint16_t MAX_MESSAGE_RECEIVE_SIZE = 128U; -constexpr uint8_t MAX_RPC_SUBSCRIPTIONS = 8U; -constexpr uint8_t MAX_RPC_RESPONSE = 16U; -constexpr uint8_t MAX_RPC_REQUEST = 10U; -constexpr uint64_t REQUEST_TIMEOUT_MICROSECONDS = 5000U * 1000U; - -// Maximum amount of attributs we can request or subscribe, has to be set both in the ThingsBoard -// template list and Attribute_Request_Callback template list and should be the same as the amount -// of variables in the passed array. If it is less not all variables will be requested or subscribed -constexpr size_t MAX_ATTRIBUTES = 3U; - -// Initialize used apis -Server_Side_RPC server_rpc; -const std::array apis - = { &server_rpc /*, &client_rpc , &shared_update */ }; - -// Initialize ThingsBoard instance with the maximum needed buffer size -ThingsBoard tb( - mqttClient, MAX_MESSAGE_RECEIVE_SIZE, MAX_MESSAGE_SEND_SIZE, Default_Max_Stack_Size, apis); - -// Statuses for subscribing to shared attributes -bool RPC_subscribed = false; - -// Initial client attributes sent -bool init_att_published = false; - -void setup() +namespace { -// Initalize serial connection for debugging -#if SERIAL_DEBUG - Serial.begin(SERIAL_DEBUG_BAUD); - delay(200); -#endif - // Set VMC, LIGHT, HEATER & AC pin mode as OUTPUT +constexpr uint32_t WIFI_CONNECT_TIMEOUT_MS = 15000U; +constexpr uint32_t MQTT_CONNECT_TIMEOUT_MS = 10000U; +constexpr uint32_t MQTT_RECONNECT_DELAY_MS = 1000U; + +bool g_vmcState = false; +bool g_lightState = false; +bool g_heaterState = false; +bool g_acState = false; + +String stateTopic; +String setTopic; + +#if ENCRYPTED +WiFiClientSecure netClient; +#else +WiFiClient netClient; +#endif +PubSubClient mqttClient(netClient); + +void initOutputs() +{ pinMode(VMC_PIN, OUTPUT); pinMode(LIGHT_PIN, OUTPUT); pinMode(HEATER_PIN, OUTPUT); pinMode(AC_PIN, OUTPUT); - // Set initial value LOW for VMC, LIGHT, HEATER & AC pin digitalWrite(VMC_PIN, LOW); digitalWrite(LIGHT_PIN, LOW); digitalWrite(HEATER_PIN, LOW); digitalWrite(AC_PIN, LOW); - // Set global variables actuator status as FALSE - - // Init Wifi connexion - InitWiFi(); + g_vmcState = false; + g_lightState = false; + g_heaterState = false; + g_acState = false; } -void loop() +bool setVmc(const bool status) { - if (!reconnect()) + digitalWrite(VMC_PIN, status ? HIGH : LOW); + g_vmcState = status; + return g_vmcState; +} + +bool setLight(const bool status) +{ + digitalWrite(LIGHT_PIN, status ? HIGH : LOW); + g_lightState = status; + return g_lightState; +} + +bool setHeater(const bool status) +{ + digitalWrite(HEATER_PIN, status ? HIGH : LOW); + g_heaterState = status; + return g_heaterState; +} + +bool setAc(const bool status) +{ + digitalWrite(AC_PIN, status ? HIGH : LOW); + g_acState = status; + return g_acState; +} + +void buildTopics() +{ + stateTopic = String("imt_test/actuator/") + device_id + "/state"; + setTopic = String("imt_test/actuator/") + device_id + "/set"; +} + +bool publishState() +{ + StaticJsonDocument<256> doc; + doc["device_id"] = device_id; + doc["vmc"] = g_vmcState; + doc["heating"] = g_heaterState; + doc["lighting"] = g_lightState; + doc["cooling"] = g_acState; + doc["version"] = VERSION; + + char payload[256]; + const size_t len = serializeJson(doc, payload, sizeof(payload)); + +#if SERIAL_DEBUG + Serial.print("Publish state topic: "); + Serial.println(stateTopic); + Serial.print("Payload: "); + Serial.println(payload); +#endif + + return mqttClient.publish( + stateTopic.c_str(), reinterpret_cast(payload), len, false); +} + +void applyCommandJson(const JsonDocument& doc) +{ + if (doc.containsKey("vmc")) { + setVmc(doc["vmc"].as()); + } + if (doc.containsKey("heating")) + { + setHeater(doc["heating"].as()); + } + if (doc.containsKey("lighting")) + { + setLight(doc["lighting"].as()); + } + if (doc.containsKey("cooling")) + { + setAc(doc["cooling"].as()); + } +} + +void mqttCallback(char* topic, byte* payload, unsigned int length) +{ +#if SERIAL_DEBUG + Serial.print("MQTT rx topic: "); + Serial.println(topic); +#endif + + if (String(topic) != setTopic) + { +#if SERIAL_DEBUG + Serial.println("Ignoring message on unexpected topic"); +#endif return; } - // Check Thingsboard connection - if (!tb.connected()) + StaticJsonDocument<256> doc; + const auto err = deserializeJson(doc, payload, length); + if (err) { - // Reconnect to the ThingsBoard server, - // if a connection was disrupted or has not yet been established #if SERIAL_DEBUG - Serial.printf(CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); + Serial.print("JSON parse error: "); + Serial.println(err.c_str()); #endif - if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) - { -#if SERIAL_DEBUG - Serial.println("Failed to connect"); -#endif - return; - } + return; } - // Send initial values - if (!init_att_published) - { - Serial.println("Sending device type attribute..."); - tb.sendAttributeData(VERSION_KEY, VERSION); - tb.sendAttributeData(LIGHT_RELAY_KEY, LIGHT_STATUS); - // NEED TO COMPLETED - init_att_published = true; - } - - if (!RPC_subscribed) - { - Serial.println("Requesting RPC...."); - - Serial.println("Subscribing for RPC..."); - const RPC_Callback callbacks[MAX_RPC_SUBSCRIPTIONS] = { - { RPC_SET_LIGHT_SWITCH_METHOD, processSwitchLightChange }, - { RPC_SET_VMC_SWITCH_METHOD, processSwitchVmcChange } - // NEED TO BE COMPLETED - }; - - if (!server_rpc.RPC_Subscribe(callbacks + 0U, callbacks + MAX_RPC_SUBSCRIPTIONS)) - { - Serial.println("Failed to subscribe for RPC"); - return; - } - - Serial.println("Subscribe done"); - RPC_subscribed = true; - } - - tb.loop(); + applyCommandJson(doc); + publishState(); } -/// @brief Initalizes WiFi connection, -// will endlessly delay until a connection has been successfully established -void InitWiFi() +bool connectWiFi() { -#if SERIAL_DEBUG - Serial.println("Connecting to AP ..."); -#endif - // Attempting to establish a connection to the given WiFi network - WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - while (WiFi.status() != WL_CONNECTED) - { - // Delay 500ms until a connection has been successfully established - delay(500); -#if SERIAL_DEBUG - Serial.print("."); -#endif - } -#if SERIAL_DEBUG - Serial.printf("\nConnected to AP : %s\n", WIFI_SSID); -#endif -#if ENCRYPTED - espClient.setCACert(ROOT_CERT); -#endif -} - -/// @brief Reconnects the WiFi uses InitWiFi if the connection has been removed -/// @return Returns true as soon as a connection has been established again -bool reconnect() -{ - // Check to ensure we aren't connected yet - const wl_status_t status = WiFi.status(); - if (status == WL_CONNECTED) + if (WiFi.status() == WL_CONNECTED) { return true; } - // If we aren't establish a new connection to the given WiFi network - InitWiFi(); +#if SERIAL_DEBUG + Serial.print("Connecting to WiFi SSID: "); + Serial.println(WIFI_SSID); +#endif + + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + + const uint32_t start = millis(); + while (WiFi.status() != WL_CONNECTED) + { + delay(250); +#if SERIAL_DEBUG + Serial.print('.'); +#endif + if ((millis() - start) > WIFI_CONNECT_TIMEOUT_MS) + { +#if SERIAL_DEBUG + Serial.println(); + Serial.println("WiFi connection timeout"); +#endif + return false; + } + } + +#if SERIAL_DEBUG + Serial.println(); + Serial.print("WiFi connected, IP: "); + Serial.println(WiFi.localIP()); +#endif return true; } -/// @brief Process VMC change RPC -void processSwitchVmcChange(const JsonVariantConst& data, JsonDocument& response) +bool connectMqtt() { - bool rcvSwitchStatus; - -#if SERIAL_DEBUG - Serial.println("Received the set vmc switch method"); -#endif - - const int switch_state = data["enabled"]; - - if (switch_state == 0) + if (mqttClient.connected()) { - rcvSwitchStatus = false; - } - if (switch_state == 1) - { - rcvSwitchStatus = true; + return true; } -#if SERIAL_DEBUG - Serial.print("VMC switch received state: "); - Serial.println(switch_state); +#if ENCRYPTED + netClient.setCACert(ROOT_CERT); #endif - response.set(rcvSwitchStatus); - setVMC(rcvSwitchStatus); -} + mqttClient.setServer(MQTT_SERVER, THINGSBOARD_PORT); + mqttClient.setCallback(mqttCallback); + + const String clientId = String("actuator-") + device_id; + const uint32_t start = millis(); -/// @brief Process light status inquiry RPC -void getSwitchLight(const JsonVariantConst& data, JsonDocument& response) -{ #if SERIAL_DEBUG - Serial.println("Received the json RPC method"); + Serial.print("Connecting to MQTT broker "); + Serial.print(MQTT_SERVER); + Serial.print(":"); + Serial.println(THINGSBOARD_PORT); #endif - // Size of the response document needs to be configured to the size of the innerDoc + 1. - StaticJsonDocument<16> doc; - StaticJsonDocument innerDoc; - if (LIGHT_STATUS) + while (!mqttClient.connected()) { - innerDoc = true; - } - else - { - innerDoc = false; - } - response[LIGHT_RELAY_KEY] = innerDoc; -} - -/// @brief Set heater pin value and publish it to Thingsboard server -/// @return Returns true if pin is HIGH, false if LOW -bool setHeater(bool status) -{ -#if SERIAL_DEBUG - Serial.printf("Changing heater status to : %s\n", status ? "true" : "false"); -#endif - digitalWrite(HEATER_PIN, status); - HEATER_STATUS = status; - if (!tb.connected()) - { -// Reconnect to the ThingsBoard server, -// if a connection was disrupted or has not yet been established -#if SERIAL_DEBUG - Serial.printf(CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN); -#endif - if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) + if (mqttClient.connect(clientId.c_str())) { #if SERIAL_DEBUG - Serial.println("Failed to connect"); + Serial.println("MQTT connected"); #endif + if (!mqttClient.subscribe(setTopic.c_str())) + { +#if SERIAL_DEBUG + Serial.println("MQTT subscribe failed"); +#endif + return false; + } +#if SERIAL_DEBUG + Serial.print("Subscribed to: "); + Serial.println(setTopic); +#endif + publishState(); + return true; } + + if ((millis() - start) > MQTT_CONNECT_TIMEOUT_MS) + { +#if SERIAL_DEBUG + Serial.print("MQTT connection timeout, rc="); + Serial.println(mqttClient.state()); +#endif + return false; + } + + delay(500); +#if SERIAL_DEBUG + Serial.print('.'); +#endif } - tb.sendAttributeData(HEATER_RELAY_KEY, HEATER_STATUS); - return status; -} \ No newline at end of file + + return true; +} + +} // namespace + +void setup() +{ +#if SERIAL_DEBUG + Serial.begin(SERIAL_DEBUG_BAUD); + delay(300); + Serial.println(); + Serial.println("Actuator firmware startup"); +#endif + + initOutputs(); + buildTopics(); + + connectWiFi(); + connectMqtt(); +} + +void loop() {}