Skip to content

Commit

Permalink
Merge pull request #19 from matthias-bs/20241010-feat-secure-mqtt
Browse files Browse the repository at this point in the history
Added secure MQTT
  • Loading branch information
matthias-bs authored Oct 11, 2024
2 parents 248c2d8 + 12499bb commit bc3bf38
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 101 deletions.
58 changes: 41 additions & 17 deletions examples/Waveshare_7_5_T7_Sensors/MqttInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
// History:
//
// 20241010 Extracted from Waveshare_7_5_T7_Sensors.ino
// 20241011 Fixed sensor status flags, added secure MQTT
//
// ToDo:
// -
Expand All @@ -49,26 +50,45 @@ extern RTC_DATA_ATTR time_t LocalHistTStamp;

static bool mqttMessageReceived = false; //!< Flag: MQTT message has been received

#if defined(USE_SECUREWIFI) && defined(CHECK_CA_ROOT)
static const char digicert[] PROGMEM = DIGICERT;
#endif

#ifdef SIMULATE_MQTT
static const char *MqttBuf = "{\"end_device_ids\":{\"device_id\":\"eui-9876b6000011c87b\",\"application_ids\":{\"application_id\":\"flora-lora\"},\"dev_eui\":\"9876B6000011C87B\",\"join_eui\":\"0000000000000000\",\"dev_addr\":\"260BFFCA\"},\"correlation_ids\":[\"as:up:01GH0PHSCTGKZ51EB8XCBBGHQD\",\"gs:conn:01GFQX269DVXYK9W6XF8NNZWDD\",\"gs:up:host:01GFQX26AXQM4QHEAPW48E8EWH\",\"gs:uplink:01GH0PHS6A65GBAPZB92XNGYAP\",\"ns:uplink:01GH0PHS6BEPXS9Y7DMDRNK84Y\",\"rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01GH0PHS6BY76SY2VPRSHNDDRH\",\"rpc:/ttn.lorawan.v3.NsAs/HandleUplink:01GH0PHSCS7D3V8ERSKF0DTJ8H\"],\"received_at\":\"2022-11-04T06:51:44.409936969Z\",\"uplink_message\":{\"session_key_id\":\"AYRBaM/qASfqUi+BQK75Gg==\",\"f_port\":1,\"frm_payload\":\"PwOOWAgACAAIBwAAYEKAC28LAw0D4U0DwAoAAAAAwMxMP8DMTD/AzEw/AAAAAAAAAAAA\",\"decoded_payload\":{\"bytes\":{\"air_temp_c\":\"9.1\",\"battery_v\":2927,\"humidity\":88,\"indoor_humidity\":77,\"indoor_temp_c\":\"9.9\",\"rain_day\":\"0.8\",\"rain_hr\":\"0.0\",\"rain_mm\":\"56.0\",\"rain_mon\":\"0.8\",\"rain_week\":\"0.8\",\"soil_moisture\":10,\"soil_temp_c\":\"9.6\",\"status\":{\"ble_ok\":true,\"res\":false,\"rtc_sync_req\":false,\"runtime_expired\":true,\"s1_batt_ok\":true,\"s1_dec_ok\":true,\"ws_batt_ok\":true,\"ws_dec_ok\":true},\"supply_v\":2944,\"water_temp_c\":\"7.8\",\"wind_avg_meter_sec\":\"0.8\",\"wind_direction_deg\":\"180.0\",\"wind_gust_meter_sec\":\"0.8\"}},\"rx_metadata\":[{\"gateway_ids\":{\"gateway_id\":\"lora-db0fc\",\"eui\":\"3135323538002400\"},\"time\":\"2022-11-04T06:51:44.027496Z\",\"timestamp\":1403655780,\"rssi\":-104,\"channel_rssi\":-104,\"snr\":8.25,\"location\":{\"latitude\":52.27640735,\"longitude\":10.54058183,\"altitude\":65,\"source\":\"SOURCE_REGISTRY\"},\"uplink_token\":\"ChgKFgoKbG9yYS1kYjBmYxIIMTUyNTgAJAAQ5KyonQUaCwiA7ZKbBhCw6tpgIKDtnYPt67cC\",\"channel_index\":4,\"received_at\":\"2022-11-04T06:51:44.182146570Z\"}],\"settings\":{\"data_rate\":{\"lora\":{\"bandwidth\":125000,\"spreading_factor\":8,\"coding_rate\":\"4/5\"}},\"frequency\":\"867300000\",\"timestamp\":1403655780,\"time\":\"2022-11-04T06:51:44.027496Z\"},\"received_at\":\"2022-11-04T06:51:44.203702153Z\",\"confirmed\":true,\"consumed_airtime\":\"0.215552s\",\"locations\":{\"user\":{\"latitude\":52.24619,\"longitude\":10.50106,\"source\":\"SOURCE_REGISTRY\"}},\"network_ids\":{\"net_id\":\"000013\",\"tenant_id\":\"ttn\",\"cluster_id\":\"eu1\",\"cluster_address\":\"eu1.cloud.thethings.network\"}}}";
static const char *MqttBuf = "{\"end_device_ids\":{\"device_id\":\"eui-9876b6000011c87b\",\"application_ids\":{\"application_id\":\"flora-lora\"},\"dev_eui\":\"9876B6000011C87B\",\"join_eui\":\"0000000000000000\",\"dev_addr\":\"260BFFCA\"},\"correlation_ids\":[\"as:up:01GH0PHSCTGKZ51EB8XCBBGHQD\",\"gs:conn:01GFQX269DVXYK9W6XF8NNZWDD\",\"gs:up:host:01GFQX26AXQM4QHEAPW48E8EWH\",\"gs:uplink:01GH0PHS6A65GBAPZB92XNGYAP\",\"ns:uplink:01GH0PHS6BEPXS9Y7DMDRNK84Y\",\"rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01GH0PHS6BY76SY2VPRSHNDDRH\",\"rpc:/ttn.lorawan.v3.NsAs/HandleUplink:01GH0PHSCS7D3V8ERSKF0DTJ8H\"],\"received_at\":\"2022-11-04T06:51:44.409936969Z\",\"uplink_message\":{\"session_key_id\":\"AYRBaM/qASfqUi+BQK75Gg==\",\"f_port\":1,\"frm_payload\":\"PwOOWAgACAAIBwAAYEKAC28LAw0D4U0DwAoAAAAAwMxMP8DMTD/AzEw/AAAAAAAAAAAA\",\"decoded_payload\":{\"bytes\":{\"air_temp_c\":\"9.1\",\"battery_v\":2927,\"humidity\":88,\"indoor_humidity\":77,\"indoor_temp_c\":\"9.9\",\"rain_day\":\"0.8\",\"rain_hr\":\"0.0\",\"rain_mm\":\"56.0\",\"rain_mon\":\"0.8\",\"rain_week\":\"0.8\",\"soil_moisture\":10,\"soil_temp_c\":\"9.6\",\"status\":{\"ble_ok\":true,\"res\":false,\"rtc_sync_req\":false,\"runtime_expired\":true,\"s1_batt_ok\":true,\"s1_dec_ok\":true,\"ws_batt_ok\":true,\"ws_dec_ok\":true},\"supply_v\":2944,\"water_temp_c\":\"7.8\",\"wind_avg_meter_sec\":\"0.8\",\"wind_direction_deg\":\"180.0\",\"wind_gust_meter_sec\":\"0.8\"}},\"rx_metadata\":[{\"gateway_ids\":{\"gateway_id\":\"lora-db0fc\",\"eui\":\"3135323538002400\"},\"time\":\"2022-11-04T06:51:44.027496Z\",\"timestamp\":1403655780,\"rssi\":-104,\"channel_rssi\":-104,\"snr\":8.25,\"location\":{\"latitude\":52.27640735,\"longitude\":10.54058183,\"altitude\":65,\"source\":\"SOURCE_REGISTRY\"},\"uplink_token\":\"ChgKFgoKbG9yYS1kYjBmYxIIMTUyNTgAJAAQ5KyonQUaCwiA7ZKbBhCw6tpgIKDtnYPt67cC\",\"channel_index\":4,\"received_at\":\"2022-11-04T06:51:44.182146570Z\"}],\"settings\":{\"data_rate\":{\"lora\":{\"bandwidth\":125000,\"spreading_factor\":8,\"coding_rate\":\"4/5\"}},\"frequency\":\"867300000\",\"timestamp\":1403655780,\"time\":\"2022-11-04T06:51:44.027496Z\"},\"received_at\":\"2022-11-04T06:51:44.203702153Z\",\"confirmed\":true,\"consumed_airtime\":\"0.215552s\",\"locations\":{\"user\":{\"latitude\":52.24619,\"longitude\":10.50106,\"source\":\"SOURCE_REGISTRY\"}},\"network_ids\":{\"net_id\":\"000013\",\"tenant_id\":\"ttn\",\"cluster_id\":\"eu1\",\"cluster_address\":\"eu1.cloud.thethings.network\"}}}";
#else
static char MqttBuf[MQTT_PAYLOAD_SIZE + 1]; //!< MQTT Payload Buffer
static char MqttBuf[MQTT_PAYLOAD_SIZE + 1]; //!< MQTT Payload Buffer
#endif

/**
* \brief MQTT message reception callback function
*
* Sets the flag <code>mqttMessageReceived</code> and copies the received message to
* <code>MqttBuf</code>.
*/
// MQTT message reception callback function
static void mqttMessageCb(String &topic, String &payload)
{
mqttMessageReceived = true;
log_d("Payload size: %d", payload.length());
mqttMessageReceived = true;
log_d("Payload size: %d", payload.length());
#ifndef SIMULATE_MQTT
strncpy(MqttBuf, payload.c_str(), payload.length());
strncpy(MqttBuf, payload.c_str(), payload.length());
#endif
}

// Constructor
MqttInterface::MqttInterface(MQTTClient &_MqttClient)
{
#if defined(USE_SECUREWIFI)
#ifdef CHECK_CA_ROOT
net.setCACert(digicert);
#endif
#ifdef CHECK_PUB_KEY
error "CHECK_PUB_KEY: not implemented"
#endif
#ifdef CHECK_FINGERPRINT
net.setFingerprint(fp);
#endif
#if (!defined(CHECK_PUB_KEY) and !defined(CHECK_CA_ROOT) and !defined(CHECK_FINGERPRINT))
// do not verify tls certificate
net.setInsecure();
#endif
#endif
MqttClient = _MqttClient;
}

// Connect to MQTT broker
Expand Down Expand Up @@ -212,19 +232,19 @@ void MqttInterface::getMqttData(mqtt_sensors_t &MqttSensors)
MqttSensors.soil_moisture = payload[SOIL1_MOISTURE].isNull() ? INV_UINT8 : payload[SOIL1_MOISTURE];
MqttSensors.soil_temp_c = payload[SOIL1_TEMP_C].isNull() ? INV_TEMP : payload[SOIL1_TEMP_C];
MqttSensors.water_temp_c = payload[OW0_TEMP_C].isNull() ? INV_TEMP : payload[OW0_TEMP_C];
MqttSensors.wind_avg_meter_sec = payload[WS_WIND_AVG_MS].isNull() ? INV_FLOAT : payload[WS_WIND_AVG_MS];
MqttSensors.wind_avg_meter_sec = payload[WS_WIND_AVG_MS].isNull() ? INV_UINT16 : payload[WS_WIND_AVG_MS];
MqttSensors.wind_direction_deg = payload[WS_WIND_DIR_DEG].isNull() ? INV_UINT16 : payload[WS_WIND_DIR_DEG];
MqttSensors.wind_gust_meter_sec = payload[WS_WIND_GUST_MS].isNull() ? INV_FLOAT : payload[WS_WIND_GUST_MS];
MqttSensors.wind_gust_meter_sec = payload[WS_WIND_GUST_MS].isNull() ? INV_UINT16 : payload[WS_WIND_GUST_MS];

// FIXME: This is a workaround for the time being
JsonObject status = payload["status"];
bool ble_ok = MqttSensors.indoor_temp_c != INV_TEMP && MqttSensors.indoor_humidity != INV_UINT8;
// MqttSensors.status.ble_ok = status["ble_ok"] | ble_ok;
MqttSensors.status.ble_ok = ble_ok;
MqttSensors.status.ble_batt_ok = 1; // No MQTT signal available
bool s1_dec_ok = MqttSensors.soil_temp_c != INV_TEMP && MqttSensors.soil_moisture != INV_UINT8;
// MqttSensors.status.s1_dec_ok = status["s1_dec_ok"] | s1_dec_ok;
MqttSensors.status.s1_dec_ok = s1_dec_ok;
bool ws_dec_ok = MqttSensors.air_temp_c != INV_TEMP && MqttSensors.rain_mm != INV_FLOAT;
bool ws_dec_ok = MqttSensors.air_temp_c != INV_TEMP && MqttSensors.humidity != INV_UINT8 && MqttSensors.rain_mm != INV_FLOAT;
// MqttSensors.status.ws_dec_ok = status["ws_dec_ok"] | ws_dec_ok;
MqttSensors.status.ws_dec_ok = ws_dec_ok;

Expand All @@ -249,11 +269,15 @@ void MqttInterface::getMqttData(mqtt_sensors_t &MqttSensors)
MqttSensors.rain_day = 0;
}

log_d("ws_dec_ok: %d", MqttSensors.status.ws_dec_ok);
log_d("s1_dec_ok: %d", MqttSensors.status.s1_dec_ok);
log_d("ble_ok: %d", MqttSensors.status.ble_ok);
log_d("ws_batt_ok: %d", MqttSensors.status.ws_batt_ok);
log_d("s1_batt_ok: %d", MqttSensors.status.s1_batt_ok);
log_i("MQTT data updated: %d", MqttSensors.valid ? 1 : 0);
}


bool MqttInterface::mqttUplink(WiFiClient &net, MQTTClient &MqttClient, local_sensors_t &data)
bool MqttInterface::mqttUplink(MQTTClient &MqttClient, local_sensors_t &data)
{
char payload[21];
char topic[41];
Expand Down
39 changes: 23 additions & 16 deletions examples/Waveshare_7_5_T7_Sensors/MqttInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
// History:
//
// 20241010 Extracted from Waveshare_7_5_T7_Sensors.ino
// 20241011 Fixed sensor status flags, added secure MQTT
//
// ToDo:
// -
Expand All @@ -43,10 +44,16 @@
#define _MQTT_INTERFACE
#include <Arduino.h>
#include <string>
#include "config.h"

#include <WiFi.h>
#if defined(USE_SECUREWIFI)
#include <WiFiClientSecure.h>
#endif

#include <MQTT.h> // https://github.com/256dpi/arduino-mqtt
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson needs version v6 or above
#include "config.h"

#include "secrets.h"
#include "LocalInterface.h"

Expand All @@ -57,11 +64,12 @@ struct MqttS
char received_at[32]; //!< MQTT message received date/time
struct
{
unsigned int ws_batt_ok : 1; //!< weather sensor battery o.k.
unsigned int ws_dec_ok : 1; //!< weather sensor decoding o.k.
unsigned int s1_batt_ok : 1; //!< soil moisture sensor battery o.k.
unsigned int s1_dec_ok : 1; //!< soil moisture sensor dencoding o.k.
unsigned int ble_ok : 1; //!< BLE T-/H-sensor data o.k.
unsigned int ws_batt_ok : 1; //!< weather sensor battery o.k.
unsigned int ws_dec_ok : 1; //!< weather sensor decoding o.k.
unsigned int s1_batt_ok : 1; //!< soil moisture sensor battery o.k.
unsigned int s1_dec_ok : 1; //!< soil moisture sensor dencoding o.k.
unsigned int ble_batt_ok : 1; //!< BLE sensor battery o.k.
unsigned int ble_ok : 1; //!< BLE T-/H-sensor data o.k.

} status;
float air_temp_c; //!< temperature in degC
Expand Down Expand Up @@ -89,9 +97,9 @@ struct MqttS
typedef struct MqttS mqtt_sensors_t; //!< Shortcut for struct Sensor
struct MqttHistQData
{
float temperature; //!< temperature in degC
uint8_t humidity; //!< humidity in %
bool valid; //!< data valid
float temperature; //!< temperature in degC
uint8_t humidity; //!< humidity in %
bool valid; //!< data valid
};

typedef struct MqttHistQData mqtt_hist_t; //!< Shortcut for struct MqttHistQData
Expand Down Expand Up @@ -147,18 +155,18 @@ typedef struct MqttHistQData mqtt_hist_t; //!< Shortcut for struct MqttHistQData
class MqttInterface
{
private:
#if defined(USE_SECUREWIFI)
NetworkClientSecure net;
#else
WiFiClient net;
#endif
MQTTClient MqttClient;

public:
/*!
* \brief Constructor
*/
MqttInterface(WiFiClient &_net, MQTTClient &_MqttClient)
{
net = _net;
MqttClient = _MqttClient;
};
MqttInterface(MQTTClient &_MqttClient);

/**
* \brief Connect to MQTT broker
Expand All @@ -170,11 +178,10 @@ class MqttInterface
/**
* \brief Get MQTT data from broker
*
* \param net network connection
* \param MqttClient MQTT client object
*/
void getMqttData(mqtt_sensors_t &MqttSensors);

bool mqttUplink(WiFiClient &net, MQTTClient &MqttClient, local_sensors_t &data);
bool mqttUplink(MQTTClient &MqttClient, local_sensors_t &data);
};
#endif
69 changes: 40 additions & 29 deletions examples/Waveshare_7_5_T7_Sensors/Waveshare_7_5_T7_Sensors.ino
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,16 @@
*
* 20241008 Fixed B/W display (small artefacts remaining)
* 20241009 Fixed B/W display of main screens
* 20241011 Added secure MQTT
*
*/

#include "config.h"
#include "owm_credentials.h" // See 'owm_credentials' tab and enter your OWM API key and set the Wifi SSID and PASSWORD
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson needs version v6 or above
#include <WiFi.h> // Built-in
// #include <WiFiClientSecure.h>
#include <time.h> // Built-in
#include <SPI.h> // Built-in
//#include <vector> // Built-in

#include <time.h> // Built-in
#include <SPI.h> // Built-in
#include <string> // Built-in
#include <MQTT.h> // https://github.com/256dpi/arduino-mqtt
#include <cQueue.h> // https://github.com/SMFSW/cQueue
Expand Down Expand Up @@ -167,7 +167,6 @@ RTC_DATA_ATTR bool touchPrevTrig = false; //!< Flag: Left touch sensor has bee
RTC_DATA_ATTR bool touchNextTrig = false; //!< Flag: Right touch sensor has been triggered
RTC_DATA_ATTR bool touchMidTrig = false; //!< Flag: Middle touch sensor has been triggered


// ################ PROGRAM VARIABLES and OBJECTS ################

#define autoscale_on true
Expand All @@ -181,11 +180,11 @@ const String Locations[] = LOCATIONS_TXT; //!< /Screen Titles
#define max_readings 24
RTC_DATA_ATTR Forecast_record_type WxConditions[1]; //!< OWM Weather Conditions
Forecast_record_type WxForecast[max_readings]; //!< OWM Weather Forecast
float pressure_readings[max_readings] = {0}; //!< OWM pressure readings
float temperature_readings[max_readings] = {0}; //!< OWM temperature readings
float humidity_readings[max_readings] = {0}; //!< OWM humidity readings
float rain_readings[max_readings] = {0}; //!< OWM rain readings
float snow_readings[max_readings] = {0}; //!< OWM snow readings
float pressure_readings[max_readings] = {0}; //!< OWM pressure readings
float temperature_readings[max_readings] = {0}; //!< OWM temperature readings
float humidity_readings[max_readings] = {0}; //!< OWM humidity readings
float rain_readings[max_readings] = {0}; //!< OWM rain readings
float snow_readings[max_readings] = {0}; //!< OWM snow readings

#include "common.h"

Expand Down Expand Up @@ -238,8 +237,6 @@ void ARDUINO_ISR_ATTR touch_mid_isr()
*/
void setup()
{
WiFiClient net; //!< network object

StartTime = millis();
Serial.begin(115200);
Serial.setDebugOutput(true);
Expand Down Expand Up @@ -362,11 +359,13 @@ void setup()
bool RxWeather = false, RxForecast = false;

while ((RxWeather == false || RxForecast == false) && Attempts <= 2)
{ // Try up-to 2 time for Weather and Forecast data
if (RxWeather == false)
RxWeather = obtain_wx_data(net, "weather");
if (RxForecast == false)
RxForecast = obtain_wx_data(net, "forecast");
{ // Try up-to 2 times for Weather and Forecast data
if (RxWeather == false) {
RxWeather = obtain_wx_data("weather");
}
if (RxForecast == false) {
RxForecast = obtain_wx_data("forecast");
}
Attempts++;
}
if (RxWeather && RxForecast)
Expand Down Expand Up @@ -472,7 +471,9 @@ void setup()

// Fetch MQTT data
MQTTClient MqttClient(MQTT_PAYLOAD_SIZE);
MqttInterface mqttInterface(net, MqttClient);
//NetworkClientSecure net;
//MqttInterface mqttInterface(net, MqttClient);
MqttInterface mqttInterface(MqttClient);
if (mqttInterface.mqttConnect())
{
// Show download icon
Expand Down Expand Up @@ -510,12 +511,23 @@ void setup()

if (display.epd2.hasFastPartialUpdate)
{
display.setPartialWindow(x, y, 48, 48);
display.firstPage();
do
{
display.fillRect(x, y, 48, 48, GxEPD_WHITE);
} while (display.nextPage());
log_d("hasFastPartialUpdate");
if (ScreenNo == ScreenMQTT) {
log_d("DisplayMQTTWeather(NULL)");
display.setFullWindow();
DisplayMQTTWeather(NULL);
} else {
display.setPartialWindow(x, y, 48, 48);
#if defined(DISPLAY_3C)
display.firstPage();
do
{
#endif
display.fillRect(x, y, 48, 48, GxEPD_WHITE);
#if defined(DISPLAY_3C)
} while (display.nextPage());
#endif
}
}
else
{
Expand Down Expand Up @@ -551,7 +563,7 @@ void setup()
}
SaveRainDayData();

mqttInterface.mqttUplink(net, MqttClient, LocalSensors);
mqttInterface.mqttUplink(MqttClient, LocalSensors);
StopWiFi();
BeginSleep();
}
Expand Down Expand Up @@ -622,7 +634,6 @@ inline bool TouchTriggered(void)
return (touchPrevTrig || touchMidTrig || touchNextTrig);
}


/**
* \brief Display Open Weather Map (OWM) data
*
Expand Down Expand Up @@ -851,9 +862,9 @@ void DisplayMQTTWeather(const unsigned char *status_bitmap)
drawString(524, 130, String(MqttSensors.indoor_humidity) + "%", CENTER);

#ifdef FORCE_LOW_BATTERY
MqttSensors.status.ws_batt_ok = false;
MqttSensors.status.ble_batt_ok = false;
#endif
if (!MqttSensors.status.ws_batt_ok)
if (!MqttSensors.status.ble_batt_ok)
{
display.drawBitmap(577, 115, epd_bitmap_battery_alert, 24, 24, GxEPD_BLACK);
}
Expand Down
Loading

0 comments on commit bc3bf38

Please sign in to comment.