#ifdef ESP8266
#include <ESP8266WiFi.h>
// Disable PROGMEM because the ESP8266WiFi library,
// does not support flash strings.
#define THINGSBOARD_ENABLE_PROGMEM 0
#else
#ifdef ESP32
#include <WiFi.h>
#include <WiFiClientSecure.h>
#endif // ESP32
#endif // ESP8266
#include <Arduino_MQTT_Client.h>
#include <ThingsBoard.h>
// Whether the given script is using encryption or not,
// generally recommended as it increases security (communication with the server is not in clear text anymore),
// it does come with an overhead tough as having an encrypted session requires a lot of memory,
// which might not be avaialable on lower end devices.
#define ENCRYPTED false
// PROGMEM can only be added when using the ESP32 WiFiClient,
// will cause a crash if using the ESP8266WiFiSTAClass instead.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char WIFI_SSID[] PROGMEM = "Wokwi-GUEST";
constexpr char WIFI_PASSWORD[] PROGMEM = "";
#else
constexpr char WIFI_SSID[] = "Wokwi-GUEST";
constexpr char WIFI_PASSWORD[] = "";
#endif
// See https://thingsboard.io/docs/getting-started-guides/helloworld/
// to understand how to obtain an access token
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char TOKEN[] PROGMEM = "r49g8d05ghuor6b7bmp6";
#else
constexpr char TOKEN[] = "r49g8d05ghuor6b7bmp6";
#endif
// Thingsboard we want to establish a connection too
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char THINGSBOARD_SERVER[] PROGMEM = "mqtt.thingsboard.cloud";
#else
constexpr char THINGSBOARD_SERVER[] = "mqtt.thingsboard.cloud";
#endif
// 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
#if THINGSBOARD_ENABLE_PROGMEM
constexpr uint16_t THINGSBOARD_PORT PROGMEM = 8883U;
#else
constexpr uint16_t THINGSBOARD_PORT = 8883U;
#endif
#else
#if THINGSBOARD_ENABLE_PROGMEM
constexpr uint16_t THINGSBOARD_PORT PROGMEM = 1883U;
#else
constexpr uint16_t THINGSBOARD_PORT = 1883U;
#endif
#endif
// 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
#if THINGSBOARD_ENABLE_PROGMEM
constexpr uint16_t MAX_MESSAGE_SIZE PROGMEM = 256U;
#else
constexpr uint16_t MAX_MESSAGE_SIZE = 256U;
#endif
// Baud rate for the debugging serial connection.
// If the Serial output is mangled, ensure to change the monitor speed accordingly to this variable
#if THINGSBOARD_ENABLE_PROGMEM
constexpr uint32_t SERIAL_DEBUG_BAUD PROGMEM = 115200U;
#else
constexpr uint32_t SERIAL_DEBUG_BAUD = 115200U;
#endif
#if ENCRYPTED
// See https://comodosslstore.com/resources/what-is-a-root-ca-certificate-and-how-do-i-download-it/
// on how to get the root certificate of the server we want to communicate with,
// this is needed to establish a secure connection and changes depending on the website.
#if THINGSBOARD_ENABLE_PROGMEM
constexpr char ROOT_CERT[] PROGMEM = R"(-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
)";
#else
constexpr char ROOT_CERT[] = R"(-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
)";
#endif
#endif
#if THINGSBOARD_ENABLE_PROGMEM
constexpr const char RPC_TEMPERATURE_METHOD[] PROGMEM = "example_set_temperature";
constexpr const char RPC_TEMPERATURE_GET_METHOD[] PROGMEM = "example_get_temperature";
constexpr const char RPC_SWITCH_METHOD[] PROGMEM = "example_set_switch";
constexpr const char RPC_TEMPERATURE_KEY[] PROGMEM = "temp";
constexpr const char RPC_SWITCH_KEY[] PROGMEM = "switch";
constexpr const char RPC_RESPONSE_KEY[] PROGMEM = "example_response";
#else
constexpr const char RPC_TEMPERATURE_METHOD[] = "example_set_temperature";
constexpr const char RPC_TEMPERATURE_GET_METHOD[] = "example_get_temperature";
constexpr const char RPC_SWITCH_METHOD[] = "example_set_switch";
constexpr const char RPC_TEMPERATURE_KEY[] = "temp";
constexpr const char RPC_SWITCH_KEY[] PROGMEM = "switch";
constexpr const char RPC_RESPONSE_KEY[] = "example_response";
#endif
// Initialize underlying client, used to establish a connection
#if ENCRYPTED
WiFiClientSecure espClient;
#else
WiFiClient espClient;
#endif
// Initalize the Mqtt client instance
Arduino_MQTT_Client mqttClient(espClient);
// Initialize ThingsBoard instance with the maximum needed buffer size
ThingsBoard tb(mqttClient, MAX_MESSAGE_SIZE);
// Statuses for subscribing to rpc
bool subscribed = false;
constexpr char TEMPERATURE_KEY[] = "temperature";
constexpr char HUMIDITY_KEY[] = "humidity";
constexpr uint8_t LED1_PIN = 33;
uint8_t currentTemp = 25;
void init() {
// Initalize serial connection for debugging
Serial.begin(SERIAL_DEBUG_BAUD);
// If analog input pin 0 is unconnected, random analog
// noise will cause the call to randomSeed() to generate
// different seed numbers each time the sketch runs.
// randomSeed() will then shuffle the random function.
randomSeed(analogRead(0));
pinMode(LED1_PIN,OUTPUT) ;
}
void InitWiFi() {
Serial.println("Connecting to AP ...");
// Attempting to establish a connection to the given WiFi network
// Channel 6 for Wokwi simulation
WiFi.begin(WIFI_SSID, WIFI_PASSWORD, 6);
while (WiFi.status() != WL_CONNECTED) {
delay(5000);
Serial.print(".");
}
Serial.println(F("Connected to AP"));
#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() {
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;
}
/// @brief Processes function for RPC call "example_set_temperature"
/// RPC_Data is a JSON variant, that can be queried using operator[]
/// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details
/// @param data Data containing the rpc data that was called and its current value
/// @return Response that should be sent to the cloud. Useful for getMethods
RPC_Response processTemperatureChange(const RPC_Data &data) {
Serial.println("Received the set temperature RPC method");
// Process data
// const float example_temperature = data[RPC_TEMPERATURE_KEY];
const float example_temperature = data;
Serial.print("Example temperature: ");
Serial.println(example_temperature);
// Just an response example
StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc;
doc[TEMPERATURE_KEY] = example_temperature;
return RPC_Response(doc);
}
RPC_Response processTemperatureGet(const RPC_Data &data) {
Serial.println("Received get temperature RPC method");
return RPC_Response(TEMPERATURE_KEY, 77);
}
/// @brief Processes function for RPC call "example_set_switch"
/// RPC_Data is a JSON variant, that can be queried using operator[]
/// See https://arduinojson.org/v5/api/jsonvariant/subscript/ for more details
/// @param data Data containing the rpc data that was called and its current value
/// @return Response that should be sent to the cloud. Useful for getMethods
RPC_Response processSwitchChange(const RPC_Data &data) {
Serial.println("Received the set switch method");
// Process data
const bool switch_state = data[RPC_SWITCH_KEY];
Serial.print("Example switch state: ");
Serial.println(switch_state);
controlLED(switch_state);
// Just an response example
StaticJsonDocument<JSON_OBJECT_SIZE(1)> doc;
doc[RPC_RESPONSE_KEY] = switch_state;
return RPC_Response(doc);
}
void controlLED(bool state) {
digitalWrite(LED1_PIN, state);
}
void setup() {
init();
delay(1000);
InitWiFi();
}
void loop() {
delay(1000);
if (!reconnect()) {
return;
}
if (!tb.connected()) {
// Reconnect to the ThingsBoard server,
// if a connection was disrupted or has not yet been established
Serial.printf("Connecting to: (%s) with token \n", THINGSBOARD_SERVER);
if (!tb.connect(THINGSBOARD_SERVER, TOKEN, THINGSBOARD_PORT)) {
Serial.println("Failed to connect");
return;
}
tb.sendAttributeData("MAC", WiFi.macAddress().c_str());
}
if (!subscribed) {
Serial.println("Subscribing for RPC...");
const std::array<RPC_Callback, 3U> callbacks = {
RPC_Callback{ RPC_TEMPERATURE_METHOD, processTemperatureChange },
RPC_Callback{ RPC_TEMPERATURE_GET_METHOD, processTemperatureGet },
RPC_Callback{ RPC_SWITCH_METHOD, processSwitchChange }
};
// Perform a subscription. All consequent data processing will happen in
// processTemperatureChange() and processSwitchChange() functions,
// as denoted by callbacks array.
if (!tb.RPC_Subscribe(callbacks.cbegin(), callbacks.cend())) {
Serial.println("Failed to subscribe for RPC");
return;
}
Serial.println("Subscribe done");
subscribed = true;
}
sendSensorData();
tb.sendAttributeData("temperature", currentTemp);
tb.loop();
}
void sendSensorData() {
currentTemp = random(10, 31);
uint8_t rndHum = random(50, 90);
tb.sendTelemetryData(TEMPERATURE_KEY, currentTemp);
tb.sendTelemetryData(HUMIDITY_KEY, rndHum);
Serial.printf("Sent Temperature:(%d) Humidity:(%d) \n", currentTemp, rndHum);
}