From dcc11ba97186f40bf85b25223cc126e71e351916 Mon Sep 17 00:00:00 2001 From: Matthias Prinke Date: Thu, 10 Oct 2024 16:42:42 +0200 Subject: [PATCH] Refactoring --- .../MqttInterface.cpp | 347 +++++++ .../Waveshare_7_5_T7_Sensors/MqttInterface.h | 172 ++++ .../Waveshare_7_5_T7_Sensors.ino | 939 +++++++----------- examples/Waveshare_7_5_T7_Sensors/config.h | 163 +++ .../owm_credentials.h | 149 +-- examples/Waveshare_7_5_T7_Sensors/secrets.h | 16 + examples/Waveshare_7_5_T7_Sensors/utils.cpp | 141 +++ examples/Waveshare_7_5_T7_Sensors/utils.h | 93 ++ 8 files changed, 1360 insertions(+), 660 deletions(-) create mode 100644 examples/Waveshare_7_5_T7_Sensors/MqttInterface.cpp create mode 100644 examples/Waveshare_7_5_T7_Sensors/MqttInterface.h create mode 100644 examples/Waveshare_7_5_T7_Sensors/config.h create mode 100644 examples/Waveshare_7_5_T7_Sensors/secrets.h create mode 100644 examples/Waveshare_7_5_T7_Sensors/utils.cpp create mode 100644 examples/Waveshare_7_5_T7_Sensors/utils.h diff --git a/examples/Waveshare_7_5_T7_Sensors/MqttInterface.cpp b/examples/Waveshare_7_5_T7_Sensors/MqttInterface.cpp new file mode 100644 index 0000000..d91f165 --- /dev/null +++ b/examples/Waveshare_7_5_T7_Sensors/MqttInterface.cpp @@ -0,0 +1,347 @@ +/////////////////////////////////////////////////////////////////////////////// +// MqttInterface.cpp +// +// MQTT Interface for ESP32-e-Paper-Weather-Display +// +// +// created: 10/2024 +// +// +// MIT License +// +// Copyright (c) 2024 Matthias Prinke +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// History: +// +// 20241010 Extracted from Waveshare_7_5_T7_Sensors.ino +// +// ToDo: +// - +// +/////////////////////////////////////////////////////////////////////////////// + +#include "MqttInterface.h" + +extern uint8_t StartWiFi(); +extern bool HistoryUpdateDue(); +extern void SaveLocalData(); +extern bool TouchTriggered(); +extern RTC_DATA_ATTR time_t LocalHistTStamp; + + +static bool mqttMessageReceived = false; //!< Flag: MQTT message has been received + +#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\"}}}"; +#else + static char MqttBuf[MQTT_PAYLOAD_SIZE + 1]; //!< MQTT Payload Buffer +#endif + +/** + * \brief MQTT message reception callback function + * + * Sets the flag mqttMessageReceived and copies the received message to + * MqttBuf. + */ +static void mqttMessageCb(String &topic, String &payload) +{ + mqttMessageReceived = true; + log_d("Payload size: %d", payload.length()); +#ifndef SIMULATE_MQTT + strncpy(MqttBuf, payload.c_str(), payload.length()); +#endif +} + +// Connect to MQTT broker +bool MqttInterface::mqttConnect() +{ + log_d("Checking wifi..."); + if (StartWiFi() != WL_CONNECTED) + { + return false; + } + + log_i("MQTT connecting..."); + unsigned long start = millis(); + + MqttClient.begin(MQTT_HOST, MQTT_PORT, net); + MqttClient.setOptions(MQTT_KEEPALIVE /* keepAlive [s] */, MQTT_CLEAN_SESSION /* cleanSession */, MQTT_TIMEOUT * 1000 /* timeout [ms] */); + + while (!MqttClient.connect(Hostname, MQTT_USER, MQTT_PASS)) + { + log_d("."); + if (millis() > start + MQTT_CONNECT_TIMEOUT * 1000) + { + log_i("Connect timeout!"); + return false; + } + delay(1000); + } + log_i("Connected!"); + + MqttClient.onMessage(mqttMessageCb); + + if (!MqttClient.subscribe(MQTT_SUB_IN)) + { + log_i("MQTT subscription failed!"); + return false; + } + return true; +} + +// Get data from MQTT broker +void MqttInterface::getMqttData() +{ + MqttSensors.valid = false; + + log_i("Waiting for MQTT message..."); + + // allocate the JsonDocument + JsonDocument doc; + + // LoRaWAN fPort + unsigned char f_port; + + do + { +#ifndef SIMULATE_MQTT + unsigned long start = millis(); + int count = 0; + while (!mqttMessageReceived) + { + MqttClient.loop(); + delay(10); + if (count++ == 1000) + { + log_d("."); + count = 0; + } + if (mqttMessageReceived) + break; + if (!MqttClient.connected()) + { + mqttConnect(); + } + if (TouchTriggered()) + { + log_i("Touch interrupt!"); + return; + } + if (millis() > start + MQTT_DATA_TIMEOUT * 1000) + { + log_i("Timeout!"); + MqttClient.disconnect(); + return; + } + // During this time-consuming loop, updating local history could be due + if (HistoryUpdateDue()) + { + time_t now = time(NULL); + if (now - LocalHistTStamp >= (HIST_UPDATE_RATE - HIST_UPDATE_TOL) * 60) + { + LocalHistTStamp = now; + SaveLocalData(); + } + } + } +#else + log_i("(Simulated MQTT incoming message)"); + MqttSensors.valid = true; +#endif + + log_i("done!"); + log_d("%s", MqttBuf); + + log_d("Creating JSON object..."); + + // Deserialize the JSON document + DeserializationError error = deserializeJson(doc, MqttBuf, MQTT_PAYLOAD_SIZE); + + // Test if parsing succeeds. + if (error) + { + log_i("deserializeJson() failed: %s", error.c_str()); + return; + } + else + { + log_d("Done!"); + } + + MqttClient.disconnect(); + MqttSensors.valid = true; + + const char *received_at = doc["received_at"]; + if (received_at) + { + strncpy(MqttSensors.received_at, received_at, 30); + } + f_port = doc["uplink_message"]["f_port"]; + } while (f_port != 1); + JsonVariant payload = doc["uplink_message"]["decoded_payload"]["bytes"]; + + MqttSensors.air_temp_c = payload[WS_TEMP_C].isNull() ? INV_TEMP : payload[WS_TEMP_C]; + MqttSensors.humidity = payload[WS_HUMIDITY].isNull() ? INV_UINT8 : payload[WS_HUMIDITY]; + MqttSensors.indoor_temp_c = payload[TH1_TEMP_C].isNull() ? INV_TEMP : payload[TH1_TEMP_C]; + MqttSensors.indoor_humidity = payload[TH1_HUMIDITY].isNull() ? INV_UINT8 : payload[TH1_HUMIDITY]; + MqttSensors.battery_v = payload[A0_VOLTAGE_MV].isNull() ? INV_UINT16 : payload[A0_VOLTAGE_MV]; + MqttSensors.rain_day = payload[WS_RAIN_DAILY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_DAILY_MM]; + MqttSensors.rain_hr = payload[WS_RAIN_HOURLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_HOURLY_MM]; + MqttSensors.rain_mm = payload[WS_RAIN_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_MM]; + MqttSensors.rain_month = payload[WS_RAIN_MONTHLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_MONTHLY_MM]; + MqttSensors.rain_week = payload[WS_RAIN_WEEKLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_WEEKLY_MM]; + 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_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]; + + // 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; + 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; + // MqttSensors.status.ws_dec_ok = status["ws_dec_ok"] | ws_dec_ok; + MqttSensors.status.ws_dec_ok = ws_dec_ok; + + MqttSensors.status.s1_batt_ok = status["s1_batt_ok"]; + MqttSensors.status.ws_batt_ok = status["ws_batt_ok"]; + + // Sanity checks + if (MqttSensors.humidity == 0) + { + MqttSensors.status.ws_dec_ok = false; + } + MqttSensors.rain_hr_valid = (MqttSensors.rain_hr >= 0) && (MqttSensors.rain_hr < 300); + MqttSensors.rain_day_valid = (MqttSensors.rain_day >= 0) && (MqttSensors.rain_day < 1800); + + // If not valid, set value to zero to avoid any problems with auto-scale etc. + if (!MqttSensors.rain_hr_valid) + { + MqttSensors.rain_hr = 0; + } + if (!MqttSensors.rain_day_valid) + { + MqttSensors.rain_day = 0; + } + + log_i("MQTT data updated: %d", MqttSensors.valid ? 1 : 0); +} + + +bool MqttInterface::mqttUplink(WiFiClient &net, MQTTClient &MqttClient, local_sensors_t &data) +{ + char payload[21]; + char topic[41]; + + log_d("Checking wifi..."); + if (StartWiFi() != WL_CONNECTED) + { + return false; + } + + log_i("MQTT (publishing) connecting..."); + unsigned long start = millis(); + + MqttClient.begin(MQTT_HOST_P, MQTT_PORT_P, net); + MqttClient.setOptions(MQTT_KEEPALIVE /* keepAlive [s] */, MQTT_CLEAN_SESSION /* cleanSession */, MQTT_TIMEOUT * 1000 /* timeout [ms] */); + + while (!MqttClient.connect(Hostname, MQTT_USER_P, MQTT_PASS_P)) + { + log_d("."); + if (millis() > start + MQTT_CONNECT_TIMEOUT * 1000) + { + log_i("Connect timeout!"); + return false; + } + delay(1000); + } + log_i("Connected!"); + + log_d("Publishing..."); +#if defined(SCD4X_EN) + if (data.i2c_co2sensor.valid) + { + snprintf(payload, 20, "%u", data.i2c_co2sensor.co2); + snprintf(topic, 40, "%s/sdc4x/co2", Hostname); + MqttClient.publish(topic, payload); + + snprintf(payload, 20, "%3.1f", data.i2c_co2sensor.temperature); + snprintf(topic, 40, "%s/sdc4x/temperature", Hostname); + MqttClient.publish(topic, payload); + + snprintf(payload, 20, "%3.0f", data.i2c_co2sensor.humidity); + snprintf(topic, 40, "%s/sdc4x/humidity", Hostname); + MqttClient.publish(topic, payload); + } +#endif + +#if defined(BME280_EN) + if (data.i2c_thpsensor[0].valid) + { + snprintf(payload, 20, "%3.1f", data.i2c_thpsensor[0].temperature); + snprintf(topic, 40, "%s/bme280/temperature", Hostname); + MqttClient.publish(topic, payload); + + snprintf(payload, 20, "%3.0f", data.i2c_thpsensor[0].humidity); + snprintf(topic, 40, "%s/bme280/humidity", Hostname); + MqttClient.publish(topic, payload); + + snprintf(payload, 20, "%4.0f", data.i2c_thpsensor[0].pressure); + snprintf(topic, 40, "%s/bme280/pressure", Hostname); + MqttClient.publish(topic, payload); + } +#endif + +#if defined(THEENGSDECODER_EN) || defined(THEENGSDECODER_EN) + if (data.ble_thsensor[0].valid) + { + snprintf(payload, 20, "%3.1f", data.ble_thsensor[0].temperature); + snprintf(topic, 40, "%s/ble/temperature", Hostname); + MqttClient.publish(topic, payload); + + snprintf(payload, 20, "%3.0f", data.ble_thsensor[0].humidity); + snprintf(topic, 40, "%s/ble/humidity", Hostname); + MqttClient.publish(topic, payload); + + snprintf(payload, 20, "%u", data.ble_thsensor[0].batt_level); + snprintf(topic, 40, "%s/ble/batt_level", Hostname); + MqttClient.publish(topic, payload); + } +#endif + + for (int i = 0; i < 10; i++) + { + MqttClient.loop(); + delay(500); + } + + log_i("MQTT (publishing) disconnect."); + MqttClient.disconnect(); + + return true; +} diff --git a/examples/Waveshare_7_5_T7_Sensors/MqttInterface.h b/examples/Waveshare_7_5_T7_Sensors/MqttInterface.h new file mode 100644 index 0000000..adbe25e --- /dev/null +++ b/examples/Waveshare_7_5_T7_Sensors/MqttInterface.h @@ -0,0 +1,172 @@ +/////////////////////////////////////////////////////////////////////////////// +// MqttInterface.h +// +// MQTT Interface for ESP32-e-Paper-Weather-Display +// +// +// created: 10/2024 +// +// +// MIT License +// +// Copyright (c) 2024 Matthias Prinke +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// History: +// +// 20241010 Extracted from Waveshare_7_5_T7_Sensors.ino +// +// ToDo: +// - +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _MQTT_INTERFACE +#define _MQTT_INTERFACE +#include +#include +#include +#include // https://github.com/256dpi/arduino-mqtt +#include // https://github.com/bblanchon/ArduinoJson needs version v6 or above +#include "config.h" +#include "secrets.h" + +// MQTT Sensor Data +struct MqttS +{ + bool valid; //!< + 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. + + } status; + float air_temp_c; //!< temperature in degC + uint8_t humidity; //!< humidity in % + float wind_direction_deg; //!< wind direction in deg + float wind_gust_meter_sec; //!< wind speed (gusts) in m/s + float wind_avg_meter_sec; //!< wind speed (avg) in m/s + float rain_mm; //!< rain gauge level in mm + uint16_t supply_v; //!< supply voltage in mV + uint16_t battery_v; //!< battery voltage in mV + float water_temp_c; //!< water temperature in degC + float indoor_temp_c; //!< indoor temperature in degC + uint8_t indoor_humidity; //!< indoor humidity in % + float soil_temp_c; //!< soil temperature in degC + uint8_t soil_moisture; //!< soil moisture in % + float rain_hr; //!< hourly precipitation in mm + bool rain_hr_valid; //!< hourly precipitation valid + float rain_day; //!< daily precipitation in mm + bool rain_day_valid; //!< daily precipitation valid + float rain_day_prev; //!< daily precipitation in mm, previous value + float rain_week; //!< weekly precipitation in mm + float rain_month; //!< monthly precipitatiion in mm +}; + +typedef struct MqttS mqtt_sensors_t; //!< Shortcut for struct Sensor + +// TOPICS_NEW: BresserWeatherSensorLW +#define TOPICS_NEW + +#ifdef TOPICS_NEW +#define WS_TEMP_C "ws_temp_c" +#define WS_HUMIDITY "ws_humidity" +#define TH1_TEMP_C "th1_temp_c" +#define TH1_HUMIDITY "th1_humidity" +#define A0_VOLTAGE_MV "a0_voltage_mv" +#define WS_RAIN_DAILY_MM "ws_rain_daily_mm" +#define WS_RAIN_HOURLY_MM "ws_rain_hourly_mm" +#define WS_RAIN_MM "ws_rain_mm" +#define WS_RAIN_MONTHLY_MM "ws_rain_monthly_mm" +#define WS_RAIN_WEEKLY_MM "ws_rain_weekly_mm" +#define SOIL1_MOISTURE "soil1_moisture" +#define SOIL1_TEMP_C "soil1_temp_c" +#define OW0_TEMP_C "ow0_temp_c" +#define WS_WIND_AVG_MS "ws_wind_avg_ms" +#define WS_WIND_DIR_DEG "ws_wind_dir_deg" +#define WS_WIND_GUST_MS "ws_wind_gust_ms" +#else +#define WS_TEMP_C "air_temp_c" +#define WS_HUMIDITY "humidity" +#define TH1_TEMP_C "indoor_temp_c" +#define TH1_HUMIDITY "indoor_humidity" +#define A0_VOLTAGE_MV "battery_v" +#define WS_RAIN_DAILY_MM "rain_day" +#define WS_RAIN_HOURLY_MM "rain_hr" +#define WS_RAIN_MM "rain_mm" +#define WS_RAIN_MONTHLY_MM "rain_mon" +#define WS_RAIN_WEEKLY_MM "rain_week" +#define SOIL1_MOISTURE "soil_moisture" +#define SOIL1_TEMP_C "soil_temp_c" +#define OW0_TEMP_C "water_temp_c" +#define WS_WIND_AVG_MS "wind_avg_meter_sec" +#define WS_WIND_DIR_DEG "wind_direction_deg" +#define WS_WIND_GUST_MS "wind_gust_meter_sec" +#endif + +// Encoding of invalid values +// for floating point, see +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN +#define INV_FLOAT NAN +#define INV_UINT32 0xFFFFFFFF +#define INV_UINT16 0xFFFF +#define INV_UINT8 0xFF +#define INV_TEMP 327.67 + +class MqttInterface +{ +private: + WiFiClient net; + MQTTClient MqttClient; + mqtt_sensors_t MqttSensors; + +public: + /*! + * \brief Constructor + */ + MqttInterface(WiFiClient &_net, MQTTClient &_MqttClient, mqtt_sensors_t &_MqttSensors) + { + net = _net; + MqttClient = _MqttClient; + }; + + /** + * \brief Connect to MQTT broker + * + * \return true if connection was succesful, otherwise false + */ + bool mqttConnect(); + + /** + * \brief Get MQTT data from broker + * + * \param net network connection + * \param MqttClient MQTT client object + */ + void getMqttData(); + + bool mqttUplink(WiFiClient &net, MQTTClient &MqttClient, local_sensors_t &data); +}; +#endif \ No newline at end of file diff --git a/examples/Waveshare_7_5_T7_Sensors/Waveshare_7_5_T7_Sensors.ino b/examples/Waveshare_7_5_T7_Sensors/Waveshare_7_5_T7_Sensors.ino index 8d38020..68e7488 100644 --- a/examples/Waveshare_7_5_T7_Sensors/Waveshare_7_5_T7_Sensors.ino +++ b/examples/Waveshare_7_5_T7_Sensors/Waveshare_7_5_T7_Sensors.ino @@ -70,37 +70,16 @@ #include // Built-in #include // https://github.com/256dpi/arduino-mqtt #include // https://github.com/SMFSW/cQueue +#include "MqttInterface.h" // MQTT sensor data interface #include "WeatherSymbols.h" // Functions for drawing weather symbols at runtime #include "bitmap_icons.h" // Icon bitmaps #include "bitmap_weather_report.h" // Picture shown on ScreenStart +#include "bitmap_local.h" // Picture shown on ScreenLocal - replace by your own +#include "bitmap_remote.h" // Picture shown on ScreenMQTT - replace by your own +#include "utils.h" +#include "secrets.h" + -// #define SIMULATE_MQTT -// #define FORCE_LOW_BATTERY -// #define FORCE_NO_SIGNAL - -#define MQTT_PAYLOAD_SIZE 4096 -#define MQTT_CONNECT_TIMEOUT 30 -#define MQTT_DATA_TIMEOUT 600 -#define MQTT_KEEPALIVE 60 -#define MQTT_TIMEOUT 1800 -#define MQTT_CLEAN_SESSION false - -#define MQTT_HIST_SIZE 144 -#define RAIN_HR_HIST_SIZE 24 -#define RAIN_DAY_HIST_SIZE 29 -#define LOCAL_HIST_SIZE 144 -#define HIST_UPDATE_RATE 30 -#define HIST_UPDATE_TOL 5 - -// #define MITHERMOMETER_EN //!< Enable MiThermometer (BLE sensors) -#define THEENGSDECODER_EN //!< Enable Theengs Decoder (BLE sensors) -#define BME280_EN //!< Enable BME280 T/H/p-sensor (I2C) -#define SCD4X_EN //!< Enable SCD4x CO2-sensor (I2C) -// #define WATERTEMP_EN //!< Enable Wather Temperature Display (MQTT) -#define MITHERMOMETER_BATTALERT 6 //!< Low battery alert threshold [%] -#define WATER_TEMP_INVALID -30.0 //!< Water temperature invalid marker [°C] -#define I2C_SDA 21 //!< I2C Serial Data Pin -#define I2C_SCL 22 //!< I2C Serial Clock Pin #define ENABLE_GxEPD2_display 0 #include //!< https://github.com/ZinggJM/GxEPD2 @@ -115,14 +94,6 @@ // #include "src/lang_nl.h" // Localization (Dutch) // #include "src/lang_pl.h" // Localisation (Polish) -// Encoding of invalid values -// for floating point, see -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN -#define INV_FLOAT NAN -#define INV_UINT32 0xFFFFFFFF -#define INV_UINT16 0xFFFF -#define INV_UINT8 0xFF -#define INV_TEMP 327.67 #ifdef MITHERMOMETER_EN // BLE Temperature/Humidity Sensor @@ -142,10 +113,7 @@ #include // https://github.com/Sensirion/arduino-i2c-scd4x #endif -// #define DISPLAY_3C -#define DISPLAY_BW -#define SCREEN_WIDTH 800 //!< EPD screen width -#define SCREEN_HEIGHT 480 //!< EPD screen height + long SleepDuration = 30; //!< Sleep time in minutes, aligned to the nearest minute boundary, so if 30 will always update at 00 or 30 past the hour (+ SleepOffset) long SleepOffset = -120; //!< Offset in seconds from SleepDuration; -120 will trigger wakeup 2 minutes earlier @@ -181,11 +149,11 @@ static const uint8_t TOUCH_NEXT = 32; //!< Touch sensor right (next) static const uint8_t TOUCH_PREV = 33; //!< Touch sensor left (previous) static const uint8_t TOUCH_MID = 35; //!< Touch sensor middle -#ifdef SIMULATE_MQTT -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 -char MqttBuf[MQTT_PAYLOAD_SIZE + 1]; //!< MQTT Payload Buffer -#endif +// #ifdef SIMULATE_MQTT +// 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 +// char MqttBuf[MQTT_PAYLOAD_SIZE + 1]; //!< MQTT Payload Buffer +// #endif #if defined(MITHERMOMETER_EN) || defined(THEENGSDECODER_EN) const int bleScanTime = 31; //!< BLE scan time in seconds @@ -236,7 +204,7 @@ RTC_DATA_ATTR bool touchMidTrig = false; //!< Flag: Middle touch sensor has bee NimBLEScan *pBLEScan; #endif -bool mqttMessageReceived = false; //!< Flag: MQTT message has been received +//bool mqttMessageReceived = false; //!< Flag: MQTT message has been received // ################ PROGRAM VARIABLES and OBJECTS ################ @@ -261,43 +229,43 @@ 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 -// MQTT Sensor Data -struct MqttS -{ - bool valid; //!< - 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. - - } status; - float air_temp_c; //!< temperature in degC - uint8_t humidity; //!< humidity in % - float wind_direction_deg; //!< wind direction in deg - float wind_gust_meter_sec; //!< wind speed (gusts) in m/s - float wind_avg_meter_sec; //!< wind speed (avg) in m/s - float rain_mm; //!< rain gauge level in mm - uint16_t supply_v; //!< supply voltage in mV - uint16_t battery_v; //!< battery voltage in mV - float water_temp_c; //!< water temperature in degC - float indoor_temp_c; //!< indoor temperature in degC - uint8_t indoor_humidity; //!< indoor humidity in % - float soil_temp_c; //!< soil temperature in degC - uint8_t soil_moisture; //!< soil moisture in % - float rain_hr; //!< hourly precipitation in mm - bool rain_hr_valid; //!< hourly precipitation valid - float rain_day; //!< daily precipitation in mm - bool rain_day_valid; //!< daily precipitation valid - float rain_day_prev; //!< daily precipitation in mm, previous value - float rain_week; //!< weekly precipitation in mm - float rain_month; //!< monthly precipitatiion in mm -}; - -typedef struct MqttS mqtt_sensors_t; //!< Shortcut for struct Sensor +// // MQTT Sensor Data +// struct MqttS +// { +// bool valid; //!< +// 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. + +// } status; +// float air_temp_c; //!< temperature in degC +// uint8_t humidity; //!< humidity in % +// float wind_direction_deg; //!< wind direction in deg +// float wind_gust_meter_sec; //!< wind speed (gusts) in m/s +// float wind_avg_meter_sec; //!< wind speed (avg) in m/s +// float rain_mm; //!< rain gauge level in mm +// uint16_t supply_v; //!< supply voltage in mV +// uint16_t battery_v; //!< battery voltage in mV +// float water_temp_c; //!< water temperature in degC +// float indoor_temp_c; //!< indoor temperature in degC +// uint8_t indoor_humidity; //!< indoor humidity in % +// float soil_temp_c; //!< soil temperature in degC +// uint8_t soil_moisture; //!< soil moisture in % +// float rain_hr; //!< hourly precipitation in mm +// bool rain_hr_valid; //!< hourly precipitation valid +// float rain_day; //!< daily precipitation in mm +// bool rain_day_valid; //!< daily precipitation valid +// float rain_day_prev; //!< daily precipitation in mm, previous value +// float rain_week; //!< weekly precipitation in mm +// float rain_month; //!< monthly precipitatiion in mm +// }; + +// typedef struct MqttS mqtt_sensors_t; //!< Shortcut for struct Sensor RTC_DATA_ATTR mqtt_sensors_t MqttSensors; //!< MQTT sensor data RTC_DATA_ATTR Queue_t MqttHistQCtrl; //!< MQTT Sensor Data History FIFO Control @@ -342,34 +310,34 @@ typedef struct RainDayHistQData rain_day_hist_t; //!< Shortcut for struct RainDa RTC_DATA_ATTR rain_hr_hist_t RainDayHist[RAIN_DAY_HIST_SIZE]; //HIST_UPDATE_RATE synchronized - * to the past full hour with a tolerance of HIST_UPDATE_TOL. - * - * \return true if update is due, otherwise false - */ -bool HistoryUpdateDue(void) -{ - int mins = (CurrentHour * 60 + CurrentMin) % HIST_UPDATE_RATE; - bool rv = (mins <= HIST_UPDATE_TOL) || (mins >= (HIST_UPDATE_RATE - HIST_UPDATE_TOL)); - return rv; -} -/** - * \brief MQTT message reception callback function - * - * Sets the flag mqttMessageReceived and copies the received message to - * MqttBuf. - */ -void mqttMessageCb(String &topic, String &payload) -{ - mqttMessageReceived = true; - log_d("Payload size: %d", payload.length()); -#ifndef SIMULATE_MQTT - strncpy(MqttBuf, payload.c_str(), payload.length()); -#endif -} + +// /** +// * \brief MQTT message reception callback function +// * +// * Sets the flag mqttMessageReceived and copies the received message to +// * MqttBuf. +// */ +// void mqttMessageCb(String &topic, String &payload) +// { +// mqttMessageReceived = true; +// log_d("Payload size: %d", payload.length()); +// #ifndef SIMULATE_MQTT +// strncpy(MqttBuf, payload.c_str(), payload.length()); +// #endif +// } /** * \brief Display Open Weather Map (OWM) data @@ -993,142 +947,142 @@ void DisplayOWMWeather(const unsigned char *status_bitmap) #endif } -/** - * \brief Connect to MQTT broker - * - * \param net network connection - * \param MqttClient MQTT client object - * - * \return true if connection was succesful, otherwise false - */ -bool MqttConnect(WiFiClient &net, MQTTClient &MqttClient) -{ - log_d("Checking wifi..."); - if (StartWiFi() != WL_CONNECTED) - { - return false; - } - - log_i("MQTT connecting..."); - unsigned long start = millis(); - - MqttClient.begin(MQTT_HOST, MQTT_PORT, net); - MqttClient.setOptions(MQTT_KEEPALIVE /* keepAlive [s] */, MQTT_CLEAN_SESSION /* cleanSession */, MQTT_TIMEOUT * 1000 /* timeout [ms] */); - - while (!MqttClient.connect(Hostname, MQTT_USER, MQTT_PASS)) - { - log_d("."); - if (millis() > start + MQTT_CONNECT_TIMEOUT * 1000) - { - log_i("Connect timeout!"); - return false; - } - delay(1000); - } - log_i("Connected!"); - - MqttClient.onMessage(mqttMessageCb); - - if (!MqttClient.subscribe(MQTT_SUB_IN)) - { - log_i("MQTT subscription failed!"); - return false; - } - return true; -} - -bool MqttUplink(WiFiClient &net, MQTTClient &MqttClient, local_sensors_t &data) -{ - char payload[21]; - char topic[41]; - - log_d("Checking wifi..."); - if (StartWiFi() != WL_CONNECTED) - { - return false; - } - - log_i("MQTT (publishing) connecting..."); - unsigned long start = millis(); - - MqttClient.begin(MQTT_HOST_P, MQTT_PORT_P, net); - MqttClient.setOptions(MQTT_KEEPALIVE /* keepAlive [s] */, MQTT_CLEAN_SESSION /* cleanSession */, MQTT_TIMEOUT * 1000 /* timeout [ms] */); - - while (!MqttClient.connect(Hostname, MQTT_USER_P, MQTT_PASS_P)) - { - log_d("."); - if (millis() > start + MQTT_CONNECT_TIMEOUT * 1000) - { - log_i("Connect timeout!"); - return false; - } - delay(1000); - } - log_i("Connected!"); - - log_d("Publishing..."); -#if defined(SCD4X_EN) - if (data.i2c_co2sensor.valid) - { - snprintf(payload, 20, "%u", data.i2c_co2sensor.co2); - snprintf(topic, 40, "%s/sdc4x/co2", Hostname); - MqttClient.publish(topic, payload); - - snprintf(payload, 20, "%3.1f", data.i2c_co2sensor.temperature); - snprintf(topic, 40, "%s/sdc4x/temperature", Hostname); - MqttClient.publish(topic, payload); - - snprintf(payload, 20, "%3.0f", data.i2c_co2sensor.humidity); - snprintf(topic, 40, "%s/sdc4x/humidity", Hostname); - MqttClient.publish(topic, payload); - } -#endif - -#if defined(BME280_EN) - if (data.i2c_thpsensor[0].valid) - { - snprintf(payload, 20, "%3.1f", data.i2c_thpsensor[0].temperature); - snprintf(topic, 40, "%s/bme280/temperature", Hostname); - MqttClient.publish(topic, payload); - - snprintf(payload, 20, "%3.0f", data.i2c_thpsensor[0].humidity); - snprintf(topic, 40, "%s/bme280/humidity", Hostname); - MqttClient.publish(topic, payload); - - snprintf(payload, 20, "%4.0f", data.i2c_thpsensor[0].pressure); - snprintf(topic, 40, "%s/bme280/pressure", Hostname); - MqttClient.publish(topic, payload); - } -#endif - -#if defined(THEENGSDECODER_EN) || defined(THEENGSDECODER_EN) - if (data.ble_thsensor[0].valid) - { - snprintf(payload, 20, "%3.1f", data.ble_thsensor[0].temperature); - snprintf(topic, 40, "%s/ble/temperature", Hostname); - MqttClient.publish(topic, payload); - - snprintf(payload, 20, "%3.0f", data.ble_thsensor[0].humidity); - snprintf(topic, 40, "%s/ble/humidity", Hostname); - MqttClient.publish(topic, payload); - - snprintf(payload, 20, "%u", data.ble_thsensor[0].batt_level); - snprintf(topic, 40, "%s/ble/batt_level", Hostname); - MqttClient.publish(topic, payload); - } -#endif - - for (int i = 0; i < 10; i++) - { - MqttClient.loop(); - delay(500); - } - - log_i("MQTT (publishing) disconnect."); - MqttClient.disconnect(); - - return true; -} +// /** +// * \brief Connect to MQTT broker +// * +// * \param net network connection +// * \param MqttClient MQTT client object +// * +// * \return true if connection was succesful, otherwise false +// */ +// bool MqttConnect(WiFiClient &net, MQTTClient &MqttClient) +// { +// log_d("Checking wifi..."); +// if (StartWiFi() != WL_CONNECTED) +// { +// return false; +// } + +// log_i("MQTT connecting..."); +// unsigned long start = millis(); + +// MqttClient.begin(MQTT_HOST, MQTT_PORT, net); +// MqttClient.setOptions(MQTT_KEEPALIVE /* keepAlive [s] */, MQTT_CLEAN_SESSION /* cleanSession */, MQTT_TIMEOUT * 1000 /* timeout [ms] */); + +// while (!MqttClient.connect(Hostname, MQTT_USER, MQTT_PASS)) +// { +// log_d("."); +// if (millis() > start + MQTT_CONNECT_TIMEOUT * 1000) +// { +// log_i("Connect timeout!"); +// return false; +// } +// delay(1000); +// } +// log_i("Connected!"); + +// MqttClient.onMessage(mqttMessageCb); + +// if (!MqttClient.subscribe(MQTT_SUB_IN)) +// { +// log_i("MQTT subscription failed!"); +// return false; +// } +// return true; +// } + +// bool MqttUplink(WiFiClient &net, MQTTClient &MqttClient, local_sensors_t &data) +// { +// char payload[21]; +// char topic[41]; + +// log_d("Checking wifi..."); +// if (StartWiFi() != WL_CONNECTED) +// { +// return false; +// } + +// log_i("MQTT (publishing) connecting..."); +// unsigned long start = millis(); + +// MqttClient.begin(MQTT_HOST_P, MQTT_PORT_P, net); +// MqttClient.setOptions(MQTT_KEEPALIVE /* keepAlive [s] */, MQTT_CLEAN_SESSION /* cleanSession */, MQTT_TIMEOUT * 1000 /* timeout [ms] */); + +// while (!MqttClient.connect(Hostname, MQTT_USER_P, MQTT_PASS_P)) +// { +// log_d("."); +// if (millis() > start + MQTT_CONNECT_TIMEOUT * 1000) +// { +// log_i("Connect timeout!"); +// return false; +// } +// delay(1000); +// } +// log_i("Connected!"); + +// log_d("Publishing..."); +// #if defined(SCD4X_EN) +// if (data.i2c_co2sensor.valid) +// { +// snprintf(payload, 20, "%u", data.i2c_co2sensor.co2); +// snprintf(topic, 40, "%s/sdc4x/co2", Hostname); +// MqttClient.publish(topic, payload); + +// snprintf(payload, 20, "%3.1f", data.i2c_co2sensor.temperature); +// snprintf(topic, 40, "%s/sdc4x/temperature", Hostname); +// MqttClient.publish(topic, payload); + +// snprintf(payload, 20, "%3.0f", data.i2c_co2sensor.humidity); +// snprintf(topic, 40, "%s/sdc4x/humidity", Hostname); +// MqttClient.publish(topic, payload); +// } +// #endif + +// #if defined(BME280_EN) +// if (data.i2c_thpsensor[0].valid) +// { +// snprintf(payload, 20, "%3.1f", data.i2c_thpsensor[0].temperature); +// snprintf(topic, 40, "%s/bme280/temperature", Hostname); +// MqttClient.publish(topic, payload); + +// snprintf(payload, 20, "%3.0f", data.i2c_thpsensor[0].humidity); +// snprintf(topic, 40, "%s/bme280/humidity", Hostname); +// MqttClient.publish(topic, payload); + +// snprintf(payload, 20, "%4.0f", data.i2c_thpsensor[0].pressure); +// snprintf(topic, 40, "%s/bme280/pressure", Hostname); +// MqttClient.publish(topic, payload); +// } +// #endif + +// #if defined(THEENGSDECODER_EN) || defined(THEENGSDECODER_EN) +// if (data.ble_thsensor[0].valid) +// { +// snprintf(payload, 20, "%3.1f", data.ble_thsensor[0].temperature); +// snprintf(topic, 40, "%s/ble/temperature", Hostname); +// MqttClient.publish(topic, payload); + +// snprintf(payload, 20, "%3.0f", data.ble_thsensor[0].humidity); +// snprintf(topic, 40, "%s/ble/humidity", Hostname); +// MqttClient.publish(topic, payload); + +// snprintf(payload, 20, "%u", data.ble_thsensor[0].batt_level); +// snprintf(topic, 40, "%s/ble/batt_level", Hostname); +// MqttClient.publish(topic, payload); +// } +// #endif + +// for (int i = 0; i < 10; i++) +// { +// MqttClient.loop(); +// delay(500); +// } + +// log_i("MQTT (publishing) disconnect."); +// MqttClient.disconnect(); + +// return true; +// } /** * \brief Find min/max temperature in MQTT history data @@ -1182,174 +1136,155 @@ void findMqttMinMaxTemp(float *t_min, float *t_max) *t_max = outdoorTMax; } -void convertUtcTimestamp(String time_str_utc, struct tm *ti_local, int tz_offset) -{ - struct tm received_at_utc; - memset(&received_at_utc, 0, sizeof(struct tm)); - memset(ti_local, 0, sizeof(struct tm)); - - // Read time string - strptime(time_str_utc.c_str(), "%Y-%m-%dT%H:%M:%S%z", &received_at_utc); - log_d("UTC: %d-%02d-%02d %02d:%02d DST: %d\n", received_at_utc.tm_year + 1900, received_at_utc.tm_mon + 1, received_at_utc.tm_mday, received_at_utc.tm_hour, received_at_utc.tm_min, received_at_utc.tm_isdst); - - // Convert time struct and calculate offset - time_t received_at = mktime(&received_at_utc) - tz_offset; - - // Convert time value to time struct (local time; with consideration of DST) - localtime_r(&received_at, ti_local); - char tbuf[26]; - strftime(tbuf, 25, "%Y-%m-%d %H:%M", ti_local); - log_d("Message received at: %s local time, DST: %d\n", tbuf, ti_local->tm_isdst); -} - -/** - * \brief Get MQTT data from broker - * - * \param net network connection - * \param MqttClient MQTT client object - */ -void GetMqttData(WiFiClient &net, MQTTClient &MqttClient) -{ - MqttSensors.valid = false; - - log_i("Waiting for MQTT message..."); - - // allocate the JsonDocument - JsonDocument doc; - - // LoRaWAN fPort - unsigned char f_port; - - do - { -#ifndef SIMULATE_MQTT - unsigned long start = millis(); - int count = 0; - while (!mqttMessageReceived) - { - MqttClient.loop(); - delay(10); - if (count++ == 1000) - { - log_d("."); - count = 0; - } - if (mqttMessageReceived) - break; - if (!MqttClient.connected()) - { - MqttConnect(net, MqttClient); - } - if (TouchTriggered()) - { - log_i("Touch interrupt!"); - return; - } - if (millis() > start + MQTT_DATA_TIMEOUT * 1000) - { - log_i("Timeout!"); - MqttClient.disconnect(); - return; - } - // During this time-consuming loop, updating local history could be due - if (HistoryUpdateDue()) - { - time_t now = time(NULL); - if (now - LocalHistTStamp >= (HIST_UPDATE_RATE - HIST_UPDATE_TOL) * 60) - { - LocalHistTStamp = now; - SaveLocalData(); - } - } - } -#else - log_i("(Simulated MQTT incoming message)"); - MqttSensors.valid = true; -#endif - - log_i("done!"); - log_d("%s", MqttBuf); - - log_d("Creating JSON object..."); - - // Deserialize the JSON document - DeserializationError error = deserializeJson(doc, MqttBuf, MQTT_PAYLOAD_SIZE); - - // Test if parsing succeeds. - if (error) - { - log_i("deserializeJson() failed: %s", error.c_str()); - return; - } - else - { - log_d("Done!"); - } - - MqttClient.disconnect(); - MqttSensors.valid = true; - - const char *received_at = doc["received_at"]; - if (received_at) - { - strncpy(MqttSensors.received_at, received_at, 30); - } - f_port = doc["uplink_message"]["f_port"]; - } while (f_port != 1); - JsonVariant payload = doc["uplink_message"]["decoded_payload"]["bytes"]; - - MqttSensors.air_temp_c = payload[WS_TEMP_C].isNull() ? INV_TEMP : payload[WS_TEMP_C]; - MqttSensors.humidity = payload[WS_HUMIDITY].isNull() ? INV_UINT8 : payload[WS_HUMIDITY]; - MqttSensors.indoor_temp_c = payload[TH1_TEMP_C].isNull() ? INV_TEMP : payload[TH1_TEMP_C]; - MqttSensors.indoor_humidity = payload[TH1_HUMIDITY].isNull() ? INV_UINT8 : payload[TH1_HUMIDITY]; - MqttSensors.battery_v = payload[A0_VOLTAGE_MV].isNull() ? INV_UINT16 : payload[A0_VOLTAGE_MV]; - MqttSensors.rain_day = payload[WS_RAIN_DAILY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_DAILY_MM]; - MqttSensors.rain_hr = payload[WS_RAIN_HOURLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_HOURLY_MM]; - MqttSensors.rain_mm = payload[WS_RAIN_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_MM]; - MqttSensors.rain_month = payload[WS_RAIN_MONTHLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_MONTHLY_MM]; - MqttSensors.rain_week = payload[WS_RAIN_WEEKLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_WEEKLY_MM]; - 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_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]; - - // 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; - 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; - // MqttSensors.status.ws_dec_ok = status["ws_dec_ok"] | ws_dec_ok; - MqttSensors.status.ws_dec_ok = ws_dec_ok; - - MqttSensors.status.s1_batt_ok = status["s1_batt_ok"]; - MqttSensors.status.ws_batt_ok = status["ws_batt_ok"]; - - // Sanity checks - if (MqttSensors.humidity == 0) - { - MqttSensors.status.ws_dec_ok = false; - } - MqttSensors.rain_hr_valid = (MqttSensors.rain_hr >= 0) && (MqttSensors.rain_hr < 300); - MqttSensors.rain_day_valid = (MqttSensors.rain_day >= 0) && (MqttSensors.rain_day < 1800); - - // If not valid, set value to zero to avoid any problems with auto-scale etc. - if (!MqttSensors.rain_hr_valid) - { - MqttSensors.rain_hr = 0; - } - if (!MqttSensors.rain_day_valid) - { - MqttSensors.rain_day = 0; - } - log_i("MQTT data updated: %d", MqttSensors.valid ? 1 : 0); -} +// /** +// * \brief Get MQTT data from broker +// * +// * \param net network connection +// * \param MqttClient MQTT client object +// */ +// void GetMqttData(WiFiClient &net, MQTTClient &MqttClient) +// { +// MqttSensors.valid = false; + +// log_i("Waiting for MQTT message..."); + +// // allocate the JsonDocument +// JsonDocument doc; + +// // LoRaWAN fPort +// unsigned char f_port; + +// do +// { +// #ifndef SIMULATE_MQTT +// unsigned long start = millis(); +// int count = 0; +// while (!mqttMessageReceived) +// { +// MqttClient.loop(); +// delay(10); +// if (count++ == 1000) +// { +// log_d("."); +// count = 0; +// } +// if (mqttMessageReceived) +// break; +// if (!MqttClient.connected()) +// { +// MqttConnect(net, MqttClient); +// } +// if (TouchTriggered()) +// { +// log_i("Touch interrupt!"); +// return; +// } +// if (millis() > start + MQTT_DATA_TIMEOUT * 1000) +// { +// log_i("Timeout!"); +// MqttClient.disconnect(); +// return; +// } +// // During this time-consuming loop, updating local history could be due +// if (HistoryUpdateDue()) +// { +// time_t now = time(NULL); +// if (now - LocalHistTStamp >= (HIST_UPDATE_RATE - HIST_UPDATE_TOL) * 60) +// { +// LocalHistTStamp = now; +// SaveLocalData(); +// } +// } +// } +// #else +// log_i("(Simulated MQTT incoming message)"); +// MqttSensors.valid = true; +// #endif + +// log_i("done!"); +// log_d("%s", MqttBuf); + +// log_d("Creating JSON object..."); + +// // Deserialize the JSON document +// DeserializationError error = deserializeJson(doc, MqttBuf, MQTT_PAYLOAD_SIZE); + +// // Test if parsing succeeds. +// if (error) +// { +// log_i("deserializeJson() failed: %s", error.c_str()); +// return; +// } +// else +// { +// log_d("Done!"); +// } + +// MqttClient.disconnect(); +// MqttSensors.valid = true; + +// const char *received_at = doc["received_at"]; +// if (received_at) +// { +// strncpy(MqttSensors.received_at, received_at, 30); +// } +// f_port = doc["uplink_message"]["f_port"]; +// } while (f_port != 1); +// JsonVariant payload = doc["uplink_message"]["decoded_payload"]["bytes"]; + +// MqttSensors.air_temp_c = payload[WS_TEMP_C].isNull() ? INV_TEMP : payload[WS_TEMP_C]; +// MqttSensors.humidity = payload[WS_HUMIDITY].isNull() ? INV_UINT8 : payload[WS_HUMIDITY]; +// MqttSensors.indoor_temp_c = payload[TH1_TEMP_C].isNull() ? INV_TEMP : payload[TH1_TEMP_C]; +// MqttSensors.indoor_humidity = payload[TH1_HUMIDITY].isNull() ? INV_UINT8 : payload[TH1_HUMIDITY]; +// MqttSensors.battery_v = payload[A0_VOLTAGE_MV].isNull() ? INV_UINT16 : payload[A0_VOLTAGE_MV]; +// MqttSensors.rain_day = payload[WS_RAIN_DAILY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_DAILY_MM]; +// MqttSensors.rain_hr = payload[WS_RAIN_HOURLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_HOURLY_MM]; +// MqttSensors.rain_mm = payload[WS_RAIN_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_MM]; +// MqttSensors.rain_month = payload[WS_RAIN_MONTHLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_MONTHLY_MM]; +// MqttSensors.rain_week = payload[WS_RAIN_WEEKLY_MM].isNull() ? INV_FLOAT : payload[WS_RAIN_WEEKLY_MM]; +// 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_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]; + +// // 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; +// 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; +// // MqttSensors.status.ws_dec_ok = status["ws_dec_ok"] | ws_dec_ok; +// MqttSensors.status.ws_dec_ok = ws_dec_ok; + +// MqttSensors.status.s1_batt_ok = status["s1_batt_ok"]; +// MqttSensors.status.ws_batt_ok = status["ws_batt_ok"]; + +// // Sanity checks +// if (MqttSensors.humidity == 0) +// { +// MqttSensors.status.ws_dec_ok = false; +// } +// MqttSensors.rain_hr_valid = (MqttSensors.rain_hr >= 0) && (MqttSensors.rain_hr < 300); +// MqttSensors.rain_day_valid = (MqttSensors.rain_day >= 0) && (MqttSensors.rain_day < 1800); + +// // If not valid, set value to zero to avoid any problems with auto-scale etc. +// if (!MqttSensors.rain_hr_valid) +// { +// MqttSensors.rain_hr = 0; +// } +// if (!MqttSensors.rain_day_valid) +// { +// MqttSensors.rain_day = 0; +// } + +// log_i("MQTT data updated: %d", MqttSensors.valid ? 1 : 0); +// } /** * \brief Save MQTT data to history FIFO @@ -2042,7 +1977,7 @@ void DisplayMQTTDateTime(int x, int y) return; convertUtcTimestamp(MqttSensors.received_at, &timeinfo, _timezone); - printTime(timeinfo, mqtt_date, mqtt_time, 32); + printTime(timeinfo, mqtt_date, mqtt_time, 32, TXT_UPDATED); u8g2Fonts.setFont(u8g2_font_helvB14_tf); drawString(x, y, mqtt_date, CENTER); @@ -2955,89 +2890,6 @@ void DrawRSSI(int x, int y, int rssi) } } -/** - * \brief Get time from NTP server and initialize/update RTC - * - * The global constants gmtOffset_sec, daylightOffset_sec and ntpServer are used. - * - * \return true if RTC initialization was successfully, false otherwise - */ -boolean SetupTime() -{ - configTime(gmtOffset_sec, daylightOffset_sec, ntpServer, "pool.ntp.org"); //(gmtOffset_sec, daylightOffset_sec, ntpServer) - setenv("TZ", Timezone, 1); // setenv()adds the "TZ" variable to the environment with a value TimeZone, only used if set to 1, 0 means no change - tzset(); // Set the TZ environment variable - delay(100); - bool TimeStatus = UpdateLocalTime(); - return TimeStatus; -} - -/** - * \brief Get local time (from RTC) and update global variables - * - * The global variables CurrentHour, CurrentMin, CurrentSec, CurrentDay - * and day_output/time_output are updated. - * date_output contains the localized date string, - * time_output contains the localized text TXT_UPDATED with the time string. - * - * \return true if RTC time was valid, false otherwise - */ -boolean UpdateLocalTime() -{ - struct tm timeinfo; - char time_output[32], date_output[32]; - while (!getLocalTime(&timeinfo, 10000)) - { // Wait for 10-sec for time to synchronise - log_w("Failed to obtain time"); - return false; - } - CurrentHour = timeinfo.tm_hour; - CurrentMin = timeinfo.tm_min; - CurrentSec = timeinfo.tm_sec; - CurrentDay = timeinfo.tm_mday; - printTime(timeinfo, time_output, date_output, 32); - - Date_str = date_output; - Time_str = time_output; - return true; -} - -/** - * \brief Print localized time to variables - * - * \param timeinfo date and time data structure - * \param date_output test output buffer for localized date - * \param time_output text output buffer for localized time - * \param max_size maximum text buffer size - * - * - */ -void printTime(struct tm &timeinfo, char *date_output, char *time_output, int max_size) -{ - char update_time[30]; - - // See http://www.cplusplus.com/reference/ctime/strftime/ - // Serial.println(&timeinfo, "%a %b %d %Y %H:%M:%S"); // Displays: Saturday, June 24 2017 14:05:49 - if (Units == "M") - { - if ((Language == "CZ") || (Language == "DE") || (Language == "PL") || (Language == "NL")) - { - sprintf(date_output, "%s, %02u. %s %04u", weekday_D[timeinfo.tm_wday], timeinfo.tm_mday, month_M[timeinfo.tm_mon], (timeinfo.tm_year) + 1900); // day_output >> So., 23. Juni 2019 << - } - else - { - sprintf(date_output, "%s %02u-%s-%04u", weekday_D[timeinfo.tm_wday], timeinfo.tm_mday, month_M[timeinfo.tm_mon], (timeinfo.tm_year) + 1900); - } - strftime(update_time, max_size, "%H:%M:%S", &timeinfo); // Creates: '14:05:49' - sprintf(time_output, "%s %s", TXT_UPDATED, update_time); - } - else - { - strftime(date_output, max_size, "%a %b-%d-%Y", &timeinfo); // Creates 'Sat May-31-2019' - strftime(update_time, sizeof(update_time), "%r", &timeinfo); // Creates: '02:05:49pm' - sprintf(time_output, "%s %s", TXT_UPDATED, update_time); - } -} #if 0 /** @@ -3223,30 +3075,7 @@ void InitialiseDisplay() display.setFullWindow(); } // ######################################################################################### -/*String Translate_EN_DE(String text) { - if (text == "clear") return "klar"; - if (text == "sunny") return "sonnig"; - if (text == "mist") return "Nebel"; - if (text == "fog") return "Nebel"; - if (text == "rain") return "Regen"; - if (text == "shower") return "Regenschauer"; - if (text == "cloudy") return "wolkig"; - if (text == "clouds") return "Wolken"; - if (text == "drizzle") return "Nieselregen"; - if (text == "snow") return "Schnee"; - if (text == "thunderstorm") return "Gewitter"; - if (text == "light") return "leichter"; - if (text == "heavy") return "schwer"; - if (text == "mostly cloudy") return "größtenteils bewölkt"; - if (text == "overcast clouds") return "überwiegend bewölkt"; - if (text == "scattered clouds") return "aufgelockerte Bewölkung"; - if (text == "few clouds") return "ein paar Wolken"; - if (text == "clear sky") return "klarer Himmel"; - if (text == "broken clouds") return "aufgerissene Bewölkung"; - if (text == "light rain") return "leichter Regen"; - return text; - } -*/ + /* Version 16.11 diff --git a/examples/Waveshare_7_5_T7_Sensors/config.h b/examples/Waveshare_7_5_T7_Sensors/config.h new file mode 100644 index 0000000..190a502 --- /dev/null +++ b/examples/Waveshare_7_5_T7_Sensors/config.h @@ -0,0 +1,163 @@ +/////////////////////////////////////////////////////////////////////////////// +// config.h +// +// General configuration +// +// Note: Provide secrets in secrets.h! +// +// created: 10/2024 +// +// +// MIT License +// +// Copyright (c) 2024 Matthias Prinke +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// History: +// +// 20241010 Extracted from owm_credentials.h +// +// ToDo: +// - +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef CONFIG_H +#define CONFIG_H + +// Screen definitions +#define ScreenOWM 0 +#define ScreenLocal 1 +#define ScreenMQTT 2 +#define ScreenStart 3 + +#define TXT_START "Your Weather Station" +#define START_SCREEN ScreenStart +#define LAST_SCREEN ScreenMQTT + +// Locations / Screen Titles +#define LOCATIONS_TXT {"Forecast", "Local", "Remote", "Start"} + +// #define DISPLAY_3C +#define DISPLAY_BW +#define SCREEN_WIDTH 800 //!< EPD screen width +#define SCREEN_HEIGHT 480 //!< EPD screen height + +// #define MITHERMOMETER_EN //!< Enable MiThermometer (BLE sensors) +#define THEENGSDECODER_EN //!< Enable Theengs Decoder (BLE sensors) +#define BME280_EN //!< Enable BME280 T/H/p-sensor (I2C) +#define SCD4X_EN //!< Enable SCD4x CO2-sensor (I2C) +// #define WATERTEMP_EN //!< Enable Wather Temperature Display (MQTT) +#define MITHERMOMETER_BATTALERT 6 //!< Low battery alert threshold [%] +#define WATER_TEMP_INVALID -30.0 //!< Water temperature invalid marker [°C] +#define I2C_SDA 21 //!< I2C Serial Data Pin +#define I2C_SCL 22 //!< I2C Serial Clock Pin + +// Domain Name Server - separate bytes by comma! +#define MY_DNS 192,168,0,1 + +// // Weather display's hostname +#define Hostname "your_hostname" + +// TODO: Move to config.h +#define TIMEZONE "GMT0BST,M3.5.0/01,M10.5.0/02" // Choose your time zone from: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv + // See below for examples +#define NTPSERVER "0.uk.pool.ntp.org" // Or, choose a time server close to you, but in most cases it's best to use pool.ntp.org to find an NTP server + // then the NTP system decides e.g. 0.pool.ntp.org, 1.pool.ntp.org as the NTP syem tries to find the closest available servers + // EU "0.europe.pool.ntp.org" + // US "0.north-america.pool.ntp.org" + // See: https://www.ntppool.org/en/ +#define GMT_OFFSET_SEC 0 // UK normal time is GMT, so GMT Offset is 0, for US (-5Hrs) is typically -18000, AU is typically (+8hrs) 28800 +#define DAYLIGHT_OFFSET_SEC 3600 // In the UK DST is +1hr or 3600-secs, other countries may use 2hrs 7200 or 30-mins 1800 or 5.5hrs 19800 Ahead of GMT use + offset behind - offset + +// Example time zones +// "MET-1METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe +// "CET-1CEST,M3.5.0,M10.5.0/3"; // Central Europe +// "EST-2METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe +// "EST5EDT,M3.2.0,M11.1.0"; // EST USA +// "CST6CDT,M3.2.0,M11.1.0"; // CST USA +// "MST7MDT,M4.1.0,M10.5.0"; // MST USA +// "NZST-12NZDT,M9.5.0,M4.1.0/3"; // Auckland +// "EET-2EEST,M3.5.5/0,M10.5.5/0"; // Asia +// "ACST-9:30ACDT,M10.1.0,M4.1.0/3": // Australia + +//!< List of known BLE sensors' MAC addresses (separate multiple entries by comma) + //#define KNOWN_BLE_ADDRESSES {"a4:c1:38:b8:1f:7f"} +#define KNOWN_BLE_ADDRESSES {"49:22:05:17:0c:1f"} + +// #define SIMULATE_MQTT +// #define FORCE_LOW_BATTERY +// #define FORCE_NO_SIGNAL + +// MQTT connection for subscribing (remote sensor data) +#define MQTT_PORT 1883 +#define MQTT_HOST "your_broker" +#define MQTT_SUB_IN "your/subscribe/topic" + +// MQTT connection for publishing (local sensor data) +#define MQTT_PORT_P 1883 +#define MQTT_HOST_P "your_broker_pub" + +#define MQTT_PAYLOAD_SIZE 4096 +#define MQTT_CONNECT_TIMEOUT 30 +#define MQTT_DATA_TIMEOUT 600 +#define MQTT_KEEPALIVE 60 +#define MQTT_TIMEOUT 1800 +#define MQTT_CLEAN_SESSION false + +#define MQTT_HIST_SIZE 144 +#define RAIN_HR_HIST_SIZE 24 +#define RAIN_DAY_HIST_SIZE 29 +#define LOCAL_HIST_SIZE 144 +#define HIST_UPDATE_RATE 30 +#define HIST_UPDATE_TOL 5 + +// TODO: Move to LocalSensors class +// Local Sensor Data +struct LocalS +{ + struct + { + bool valid; //!< data valid + float temperature; //!< temperature in degC + float humidity; //!< humidity in % + uint8_t batt_level; //!< battery level in % + int rssi; //!< RSSI in dBm + } ble_thsensor[1]; + struct + { + bool valid; //!< data valid + float temperature; //!< temperature in degC + float humidity; //!< humidity in % + float pressure; //!< pressure in hPa + } i2c_thpsensor[1]; + struct + { + bool valid; //!< data valid + float temperature; //!< temperature in degC + float humidity; //!< humidity in % + uint16_t co2; //!< CO2 in ppm + } i2c_co2sensor; +}; + +typedef struct LocalS local_sensors_t; //!< Shortcut for struct LocalS + +#endif diff --git a/examples/Waveshare_7_5_T7_Sensors/owm_credentials.h b/examples/Waveshare_7_5_T7_Sensors/owm_credentials.h index 16a0c90..6f98b4d 100644 --- a/examples/Waveshare_7_5_T7_Sensors/owm_credentials.h +++ b/examples/Waveshare_7_5_T7_Sensors/owm_credentials.h @@ -1,74 +1,48 @@ -// Change to your WiFi credentials -const char* ssid0 = "your_ssid0"; -const char* password0 = "your_password0"; -const char* ssid1 = "your_ssid1"; -const char* password1 = "your_password1"; -const char* ssid2 = "your_ssid2"; -const char* password2 = "your_password2"; +/////////////////////////////////////////////////////////////////////////////// +// MqttInterface.h +// +// MQTT Interface for ESP32-e-Paper-Weather-Display +// +// +// created: 10/2024 +// +// +// MIT License +// +// Copyright (c) 2024 Matthias Prinke +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// History: +// +// 20241010 Removed parts not related to OWM +// +// ToDo: +// - +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _OWM_CREDENTIALS_H +#define _OWM_CREDENTIALS_H -// Domain Name Server - separate bytes by comma! -#define MY_DNS 192,168,0,1 - -// Weather display's hostname -const char *Hostname = "your_hostname"; - -//!< List of known BLE sensors' MAC addresses (separate multiple entries by comma) -//#define KNOWN_BLE_ADDRESSES {"a4:c1:38:b8:1f:7f"} -#define KNOWN_BLE_ADDRESSES {"49:22:05:17:0c:1f"} - -// MQTT connection for subscribing (remote sensor data) -const int MQTT_PORT = 1883; -const char *MQTT_HOST = "your_broker"; -const char *MQTT_USER = "your_user"; // leave blank if no credentials used -const char *MQTT_PASS = "your_passwd"; // leave blank if no credentials used -const char *MQTT_SUB_IN = "your/subscribe/topic"; - -// TOPICS_NEW: BresserWeatherSensorLW -#define TOPICS_NEW - -#ifdef TOPICS_NEW - #define WS_TEMP_C "ws_temp_c" - #define WS_HUMIDITY "ws_humidity" - #define TH1_TEMP_C "th1_temp_c" - #define TH1_HUMIDITY "th1_humidity" - #define A0_VOLTAGE_MV "a0_voltage_mv" - #define WS_RAIN_DAILY_MM "ws_rain_daily_mm" - #define WS_RAIN_HOURLY_MM "ws_rain_hourly_mm" - #define WS_RAIN_MM "ws_rain_mm" - #define WS_RAIN_MONTHLY_MM "ws_rain_monthly_mm" - #define WS_RAIN_WEEKLY_MM "ws_rain_weekly_mm" - #define SOIL1_MOISTURE "soil1_moisture" - #define SOIL1_TEMP_C "soil1_temp_c" - #define OW0_TEMP_C "ow0_temp_c" - #define WS_WIND_AVG_MS "ws_wind_avg_ms" - #define WS_WIND_DIR_DEG "ws_wind_dir_deg" - #define WS_WIND_GUST_MS "ws_wind_gust_ms" -#else - #define WS_TEMP_C "air_temp_c" - #define WS_HUMIDITY "humidity" - #define TH1_TEMP_C "indoor_temp_c" - #define TH1_HUMIDITY "indoor_humidity" - #define A0_VOLTAGE_MV "battery_v" - #define WS_RAIN_DAILY_MM "rain_day" - #define WS_RAIN_HOURLY_MM "rain_hr" - #define WS_RAIN_MM "rain_mm" - #define WS_RAIN_MONTHLY_MM "rain_mon" - #define WS_RAIN_WEEKLY_MM "rain_week" - #define SOIL1_MOISTURE "soil_moisture" - #define SOIL1_TEMP_C "soil_temp_c" - #define OW0_TEMP_C "water_temp_c" - #define WS_WIND_AVG_MS "wind_avg_meter_sec" - #define WS_WIND_DIR_DEG "wind_direction_deg" - #define WS_WIND_GUST_MS "wind_gust_meter_sec" -#endif - -// MQTT connection for publishing (local sensor data) -const int MQTT_PORT_P = 1883; -const char *MQTT_HOST_P = "your_broker_pub"; -const char *MQTT_USER_P = "your_user_pub"; -const char *MQTT_PASS_P = "your passwd_pub"; - // Use your own API key by signing up for a free developer account at https://openweathermap.org/ String apikey = "your_API_key"; // See: https://openweathermap.org/ // It's free to get an API key, but don't take more than 60 readings/minute! const char server[] = "api.openweathermap.org"; @@ -84,41 +58,6 @@ String Language = "EN"; // NOTE: Only the wea // Korean (KR) Latvian (LA) Lithuanian (LT) Macedonian (MK) Slovak (SK) Slovenian (SL) Vietnamese (VI) String Hemisphere = "north"; // or "south" String Units = "M"; // Use 'M' for Metric or I for Imperial -const char* Timezone = "GMT0BST,M3.5.0/01,M10.5.0/02"; // Choose your time zone from: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv - // See below for examples -const char* ntpServer = "0.uk.pool.ntp.org"; // Or, choose a time server close to you, but in most cases it's best to use pool.ntp.org to find an NTP server - // then the NTP system decides e.g. 0.pool.ntp.org, 1.pool.ntp.org as the NTP syem tries to find the closest available servers - // EU "0.europe.pool.ntp.org" - // US "0.north-america.pool.ntp.org" - // See: https://www.ntppool.org/en/ -int gmtOffset_sec = 0; // UK normal time is GMT, so GMT Offset is 0, for US (-5Hrs) is typically -18000, AU is typically (+8hrs) 28800 -int daylightOffset_sec = 3600; // In the UK DST is +1hr or 3600-secs, other countries may use 2hrs 7200 or 30-mins 1800 or 5.5hrs 19800 Ahead of GMT use + offset behind - offset - -// Example time zones -//const char* Timezone = "MET-1METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe -//const char* Timezone = "CET-1CEST,M3.5.0,M10.5.0/3"; // Central Europe -//const char* Timezone = "EST-2METDST,M3.5.0/01,M10.5.0/02"; // Most of Europe -//const char* Timezone = "EST5EDT,M3.2.0,M11.1.0"; // EST USA -//const char* Timezone = "CST6CDT,M3.2.0,M11.1.0"; // CST USA -//const char* Timezone = "MST7MDT,M4.1.0,M10.5.0"; // MST USA -//const char* Timezone = "NZST-12NZDT,M9.5.0,M4.1.0/3"; // Auckland -//const char* Timezone = "EET-2EEST,M3.5.5/0,M10.5.5/0"; // Asia -//const char* Timezone = "ACST-9:30ACDT,M10.1.0,M4.1.0/3": // Australia - -// Personalization Options - -// Screen definitions -#define ScreenOWM 0 -#define ScreenLocal 1 -#define ScreenMQTT 2 -#define ScreenStart 3 - -#define TXT_START "Your Weather Station" -#define START_SCREEN ScreenStart -#define LAST_SCREEN ScreenMQTT -// Locations / Screen Titles -#define LOCATIONS_TXT {"Forecast", "Local", "Remote", "Start"} -#include "bitmap_local.h" // Picture shown on ScreenLocal - replace by your own -#include "bitmap_remote.h" // Picture shown on ScreenMQTT - replace by your own +#endif \ No newline at end of file diff --git a/examples/Waveshare_7_5_T7_Sensors/secrets.h b/examples/Waveshare_7_5_T7_Sensors/secrets.h new file mode 100644 index 0000000..971a0b4 --- /dev/null +++ b/examples/Waveshare_7_5_T7_Sensors/secrets.h @@ -0,0 +1,16 @@ +// Change to your WiFi credentials +#define ssid0 "your_ssid0" +#define password0 "your_password0" +#define ssid1 "your_ssid1" +#define password1 "your_password1" +#define ssid2 "your_ssid2" +#define password2 "your_password2" + +// MQTT broker for subscribing +// leave blank if no credentials used +#define MQTT_USER "your_user" +#define MQTT_PASS "your_passwd" + +// MQTT broker for publishing +#define MQTT_USER_P "your_user_pub" +#define MQTT_PASS_P "your passwd_pub" \ No newline at end of file diff --git a/examples/Waveshare_7_5_T7_Sensors/utils.cpp b/examples/Waveshare_7_5_T7_Sensors/utils.cpp new file mode 100644 index 0000000..28688eb --- /dev/null +++ b/examples/Waveshare_7_5_T7_Sensors/utils.cpp @@ -0,0 +1,141 @@ +/////////////////////////////////////////////////////////////////////////////// +// utils.cpp +// +// Utility functions for ESP32-e-Paper-Weather-Display +// +// +// created: 10/2024 +// +// +// MIT License +// +// Copyright (c) 2024 Matthias Prinke +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// History: +// +// 20241010 Extracted from Waveshare_7_5_T7_Sensors.ino +// +// ToDo: +// - +// +/////////////////////////////////////////////////////////////////////////////// + +#include "utils.h" + +extern const char *weekday_D[]; +extern const char *month_M[]; +extern const String Language; +extern const String Units; +extern const String TXT_UPDATED; +extern String Time_str; +extern String Date_str; +extern int CurrentDay; +extern int CurrentHour; +extern int CurrentMin; +extern int CurrentSec; + +// Check if history update is due +bool HistoryUpdateDue(void) +{ + int mins = (CurrentHour * 60 + CurrentMin) % HIST_UPDATE_RATE; + bool rv = (mins <= HIST_UPDATE_TOL) || (mins >= (HIST_UPDATE_RATE - HIST_UPDATE_TOL)); + return rv; +} + +void convertUtcTimestamp(String time_str_utc, struct tm *ti_local, int tz_offset) +{ + struct tm received_at_utc; + memset(&received_at_utc, 0, sizeof(struct tm)); + memset(ti_local, 0, sizeof(struct tm)); + + // Read time string + strptime(time_str_utc.c_str(), "%Y-%m-%dT%H:%M:%S%z", &received_at_utc); + log_d("UTC: %d-%02d-%02d %02d:%02d DST: %d\n", received_at_utc.tm_year + 1900, received_at_utc.tm_mon + 1, received_at_utc.tm_mday, received_at_utc.tm_hour, received_at_utc.tm_min, received_at_utc.tm_isdst); + + // Convert time struct and calculate offset + time_t received_at = mktime(&received_at_utc) - tz_offset; + + // Convert time value to time struct (local time; with consideration of DST) + localtime_r(&received_at, ti_local); + char tbuf[26]; + strftime(tbuf, 25, "%Y-%m-%d %H:%M", ti_local); + log_d("Message received at: %s local time, DST: %d\n", tbuf, ti_local->tm_isdst); +} + +// Get time from NTP server and initialize/update RTC +boolean SetupTime() +{ + configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTPSERVER, "pool.ntp.org"); // (gmtOffset_sec, daylightOffset_sec, ntpServer) + setenv("TZ", TIMEZONE, 1); // setenv() adds the "TZ" variable to the environment with a value TimeZone, only used if set to 1, 0 means no change + tzset(); // Set the TZ environment variable + delay(100); + return UpdateLocalTime(); +} + +// Get local time (from RTC) and update global variables +boolean UpdateLocalTime() +{ + struct tm timeinfo; + char time_output[32], date_output[32]; + while (!getLocalTime(&timeinfo, 10000)) + { // Wait for 10-sec for time to synchronise + log_w("Failed to obtain time"); + return false; + } + CurrentHour = timeinfo.tm_hour; + CurrentMin = timeinfo.tm_min; + CurrentSec = timeinfo.tm_sec; + CurrentDay = timeinfo.tm_mday; + printTime(timeinfo, time_output, date_output, 32, TXT_UPDATED); + + Date_str = date_output; + Time_str = time_output; + return true; +} + +// Print localized time to variables +void printTime(struct tm &timeinfo, char *date_output, char *time_output, int max_size, const String label) +{ + char update_time[30]; + + // See http://www.cplusplus.com/reference/ctime/strftime/ + // Serial.println(&timeinfo, "%a %b %d %Y %H:%M:%S"); // Displays: Saturday, June 24 2017 14:05:49 + if (Units == "M") + { + if ((Language == "CZ") || (Language == "DE") || (Language == "PL") || (Language == "NL")) + { + sprintf(date_output, "%s, %02u. %s %04u", weekday_D[timeinfo.tm_wday], timeinfo.tm_mday, month_M[timeinfo.tm_mon], (timeinfo.tm_year) + 1900); // day_output >> So., 23. Juni 2019 << + } + else + { + sprintf(date_output, "%s %02u-%s-%04u", weekday_D[timeinfo.tm_wday], timeinfo.tm_mday, month_M[timeinfo.tm_mon], (timeinfo.tm_year) + 1900); + } + strftime(update_time, max_size, "%H:%M:%S", &timeinfo); // Creates: '14:05:49' + } + else + { + strftime(date_output, max_size, "%a %b-%d-%Y", &timeinfo); // Creates 'Sat May-31-2019' + strftime(update_time, sizeof(update_time), "%r", &timeinfo); // Creates: '02:05:49pm' + + } + sprintf(time_output, "%s %s", label, update_time); +} \ No newline at end of file diff --git a/examples/Waveshare_7_5_T7_Sensors/utils.h b/examples/Waveshare_7_5_T7_Sensors/utils.h new file mode 100644 index 0000000..9dc57f6 --- /dev/null +++ b/examples/Waveshare_7_5_T7_Sensors/utils.h @@ -0,0 +1,93 @@ +/////////////////////////////////////////////////////////////////////////////// +// utils.h +// +// Utility functions for ESP32-e-Paper-Weather-Display +// +// +// created: 10/2024 +// +// +// MIT License +// +// Copyright (c) 2024 Matthias Prinke +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// History: +// +// 20241010 Extracted from Waveshare_7_5_T7_Sensors.ino +// +// ToDo: +// - +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _UTILS_H +#define _UTILS_H +#include +#include +#include "config.h" + +/** + * \brief Check if history update is due + * + * History is updated at an interval of HIST_UPDATE_RATE synchronized + * to the past full hour with a tolerance of HIST_UPDATE_TOL. + * + * \return true if update is due, otherwise false + */ +bool HistoryUpdateDue(void); + +void convertUtcTimestamp(String time_str_utc, struct tm *ti_local, int tz_offset); + +/** + * \brief Get time from NTP server and initialize/update RTC + * + * The global constants gmtOffset_sec, daylightOffset_sec and ntpServer are used. + * + * \return true if RTC initialization was successfully, false otherwise + */ +boolean SetupTime(); + +/** + * \brief Get local time (from RTC) and update global variables + * + * The global variables CurrentHour, CurrentMin, CurrentSec, CurrentDay + * and day_output/time_output are updated. + * date_output contains the localized date string, + * time_output contains the localized text TXT_UPDATED with the time string. + * + * \return true if RTC time was valid, false otherwise + */ +boolean UpdateLocalTime(); + +/** + * \brief Print localized time to variables + * + * \param timeinfo date and time data structure + * \param date_output test output buffer for localized date + * \param time_output text output buffer for localized time + * \param max_size maximum text buffer size + * + * + */ +void printTime(struct tm &timeinfo, char *date_output, char *time_output, int max_size, const String label); + +#endif \ No newline at end of file