no message

This commit is contained in:
Julien Lazarewicz
2026-03-29 23:46:22 +02:00
parent 8b93e08e15
commit 3fdf508a5c
3 changed files with 239 additions and 283 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -1,334 +1,293 @@
#include "config.h"
#include "version.h"
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <Adafruit_NeoPixel.h>
#include <Arduino_MQTT_Client.h>
#include <Server_Side_RPC.h>
#include <ThingsBoard.h>
// Initialize underlying client, used to establish a connection
#if ENCRYPTED
WiFiClientSecure espClient;
#include <WiFiClientSecure.h>
#else
WiFiClient espClient;
#include <WiFiClient.h>
#endif
#include <PubSubClient.h>
#include <ArduinoJson.h>
// 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/<device_id>/state
// - The actuator listens for commands on:
// imt_test/actuator/<device_id>/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<MAX_RPC_SUBSCRIPTIONS, MAX_RPC_RESPONSE> server_rpc;
const std::array<IAPI_Implementation*, 1U> 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())
{
return;
digitalWrite(VMC_PIN, status ? HIGH : LOW);
g_vmcState = status;
return g_vmcState;
}
// Check Thingsboard connection
if (!tb.connected())
bool setLight(const bool status)
{
// Reconnect to the ThingsBoard server,
// if a connection was disrupted or has not yet been established
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.printf(CONNECTING_MSG, THINGSBOARD_SERVER, TOKEN);
Serial.print("Publish state topic: ");
Serial.println(stateTopic);
Serial.print("Payload: ");
Serial.println(payload);
#endif
if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT))
return mqttClient.publish(
stateTopic.c_str(), reinterpret_cast<const uint8_t*>(payload), len, false);
}
void applyCommandJson(const JsonDocument& doc)
{
if (doc.containsKey("vmc"))
{
setVmc(doc["vmc"].as<bool>());
}
if (doc.containsKey("heating"))
{
setHeater(doc["heating"].as<bool>());
}
if (doc.containsKey("lighting"))
{
setLight(doc["lighting"].as<bool>());
}
if (doc.containsKey("cooling"))
{
setAc(doc["cooling"].as<bool>());
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length)
{
#if SERIAL_DEBUG
Serial.println("Failed to connect");
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;
}
}
// Send initial values
if (!init_att_published)
StaticJsonDocument<256> doc;
const auto err = deserializeJson(doc, payload, length);
if (err)
{
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");
#if SERIAL_DEBUG
Serial.print("JSON parse error: ");
Serial.println(err.c_str());
#endif
return;
}
Serial.println("Subscribe done");
RPC_subscribed = true;
applyCommandJson(doc);
publishState();
}
tb.loop();
}
/// @brief Initalizes WiFi connection,
// will endlessly delay until a connection has been successfully established
void InitWiFi()
bool connectWiFi()
{
if (WiFi.status() == WL_CONNECTED)
{
return true;
}
#if SERIAL_DEBUG
Serial.println("Connecting to AP ...");
Serial.print("Connecting to WiFi SSID: ");
Serial.println(WIFI_SSID);
#endif
// Attempting to establish a connection to the given WiFi network
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
const uint32_t start = millis();
while (WiFi.status() != WL_CONNECTED)
{
// Delay 500ms until a connection has been successfully established
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;
}
bool connectMqtt()
{
if (mqttClient.connected())
{
return true;
}
#if ENCRYPTED
netClient.setCACert(ROOT_CERT);
#endif
mqttClient.setServer(MQTT_SERVER, THINGSBOARD_PORT);
mqttClient.setCallback(mqttCallback);
const String clientId = String("actuator-") + device_id;
const uint32_t start = millis();
#if SERIAL_DEBUG
Serial.print("Connecting to MQTT broker ");
Serial.print(MQTT_SERVER);
Serial.print(":");
Serial.println(THINGSBOARD_PORT);
#endif
while (!mqttClient.connected())
{
if (mqttClient.connect(clientId.c_str()))
{
#if SERIAL_DEBUG
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
}
#if SERIAL_DEBUG
Serial.printf("\nConnected to AP : %s\n", WIFI_SSID);
#endif
#if ENCRYPTED
espClient.setCACert(ROOT_CERT);
Serial.print('.');
#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)
{
return true;
}
// If we aren't establish a new connection to the given WiFi network
InitWiFi();
return true;
}
} // namespace
/// @brief Process VMC change RPC
void processSwitchVmcChange(const JsonVariantConst& data, JsonDocument& response)
{
bool rcvSwitchStatus;
#if SERIAL_DEBUG
Serial.println("Received the set vmc switch method");
#endif
const int switch_state = data["enabled"];
if (switch_state == 0)
{
rcvSwitchStatus = false;
}
if (switch_state == 1)
{
rcvSwitchStatus = true;
}
#if SERIAL_DEBUG
Serial.print("VMC switch received state: ");
Serial.println(switch_state);
#endif
response.set(rcvSwitchStatus);
setVMC(rcvSwitchStatus);
}
/// @brief Process light status inquiry RPC
void getSwitchLight(const JsonVariantConst& data, JsonDocument& response)
void setup()
{
#if SERIAL_DEBUG
Serial.println("Received the json RPC method");
Serial.begin(SERIAL_DEBUG_BAUD);
delay(300);
Serial.println();
Serial.println("Actuator firmware startup");
#endif
// Size of the response document needs to be configured to the size of the innerDoc + 1.
StaticJsonDocument<16> doc;
StaticJsonDocument<JSON_OBJECT_SIZE(1)> innerDoc;
if (LIGHT_STATUS)
{
innerDoc = true;
}
else
{
innerDoc = false;
}
response[LIGHT_RELAY_KEY] = innerDoc;
initOutputs();
buildTopics();
connectWiFi();
connectMqtt();
}
/// @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 SERIAL_DEBUG
Serial.println("Failed to connect");
#endif
}
}
tb.sendAttributeData(HEATER_RELAY_KEY, HEATER_STATUS);
return status;
}
void loop() {}