From 570321be6c96142c758d1802c3d92743763344bb Mon Sep 17 00:00:00 2001 From: Marcel Walter Date: Wed, 7 Jan 2026 02:07:21 +0100 Subject: [PATCH] add MQTT code untested --- homeassistant_configuration.yaml | 24 +++ include/secrets.h_template | 4 + platformio.ini | 5 +- src/Telegram.cpp | 4 +- src/main.cpp | 311 +++++++++++++++++++++++-------- 5 files changed, 263 insertions(+), 85 deletions(-) create mode 100644 homeassistant_configuration.yaml diff --git a/homeassistant_configuration.yaml b/homeassistant_configuration.yaml new file mode 100644 index 0000000..f458513 --- /dev/null +++ b/homeassistant_configuration.yaml @@ -0,0 +1,24 @@ +mqtt: + - text: + unique_id: FlipDotDisplayText + name: "FlipDotDisplay Text" + availability: + - topic: "flipdotdisplay/available" + mode: "text" + command_topic: "flipdotdisplay/text" + state_topic: "flipdotdisplay/text/state" + qos: 0 + retain: false + + - switch: + unique_id: FlipDotDisplaySwitch + name: "FlipDotDisplay Switch" + availability: + - topic: "flipdotdisplay/available" + command_topic: "flipdotdisplay/switch" + state_topic: "flipdotdisplay/switch/state" + payload_on: "open" + payload_off: "close" + qos: 0 + retain: false + diff --git a/include/secrets.h_template b/include/secrets.h_template index 69ab21d..a6e730c 100644 --- a/include/secrets.h_template +++ b/include/secrets.h_template @@ -5,3 +5,7 @@ #define WIFI_SSID "Wifi_SSID" #define WIFI_PASSWORD "secretPassword" + +#define MQTT_SERVER "mqttserver.fritz.box" +#define MQTT_USER "user" +#define MQTT_PASSWORD "password" diff --git a/platformio.ini b/platformio.ini index f1d7cd8..ad99ccb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,8 +14,9 @@ board = esp32dev framework = arduino monitor_speed = 115200 lib_deps = - witnessmenow/UniversalTelegramBot @ ^1.3.0 - adafruit/Adafruit GFX Library @ 1.12.3 + witnessmenow/UniversalTelegramBot @ ~1.3.0 + adafruit/Adafruit GFX Library @ ~1.12.3 + knolleary/PubSubClient @ ~2.8 build_flags=-DTELEGRAM_DEBUG ; OTA update path: hostname is flipdottelegram.fritz.box upload_protocol = espota diff --git a/src/Telegram.cpp b/src/Telegram.cpp index 770cf10..4c21b7c 100644 --- a/src/Telegram.cpp +++ b/src/Telegram.cpp @@ -56,9 +56,9 @@ void Telegram::handleNewMessages(int numNewMessages) } } - if (text == "/display_start") + if ((text == "/start") || (text == "/help")) { - String welcome = "Willkommen beim Tür-Display-Bot," + from_name + ".\n"; + String welcome = "Willkommen beim Tür-Display-Bot, " + from_name + ".\n\n"; welcome += "Hier kommen die möglichen Kommandos, die ich verstehe:\n"; for (uint32_t i = 0; i < cmdListCount; i++) diff --git a/src/main.cpp b/src/main.cpp index 24cb0dc..b75490a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,54 +3,79 @@ #include #include #include +#include +#include #include "Telegram.h" #include "FlipDotDrv.h" #include "secrets.h" +String Text = ""; + +#define HOSTNAME "flipdottelegram" + +/* ------------ MQTT variables -------------- */ +WiFiClient espClient; +PubSubClient mqtt(espClient); +#define FLIPDOT_AVAILABILITY_TOPIC "flipdotdisplay/available" +#define FLIPDOT_SWITCH_TOPIC "flipdotdisplay/switch" +#define FLIPDOT_TEXT_TOPIC "flipdotdisplay/text" +void mqttRefresh(); +void mqttCallback(char *topic, unsigned char *message, unsigned int length); + +void publishOpen() +{ + mqtt.publish(FLIPDOT_SWITCH_TOPIC "/state", "open"); + + JsonDocument doc; + doc["value"] = Text; + char output[256]; + serializeJson(doc, output); + mqtt.publish(FLIPDOT_TEXT_TOPIC "/state", output); +} + +void publishClose() +{ + mqtt.publish(FLIPDOT_SWITCH_TOPIC "/state", "close"); + + JsonDocument doc; + doc["value"] = Text; + char output[256]; + serializeJson(doc, output); + mqtt.publish(FLIPDOT_TEXT_TOPIC "/state", output); +} + +/* ------------ MQTT variables -------------- */ bool OTAStarted; -void onOTAStart() { +void onOTAStart() +{ OTAStarted = true; } -// BearSSL::WiFiClientSecure secured_client; -WiFiClientSecure secured_client; -bool Start = false; - +/* ------------ FlipDot variables -------------- */ #define WIDTH 96 #define HEIGHT 16 #define ROTATION 0 // 0 or 2 for 0 degrees or 180 degrees -#define MAX_CHAR_PER_LINE WIDTH/6 +#define MAX_CHAR_PER_LINE WIDTH / 6 FlipDotDrv flip(WIDTH, HEIGHT, 0); GFXcanvas1 canvas(WIDTH, HEIGHT); -void displayText(const char* text, bool background, int16_t x = -1, int16_t y = -1); +void displayText(const char *text, bool background, int16_t x = -1, int16_t y = -1); -String Text = ""; +/* ------------ Telegram variables -------------- */ +// BearSSL::WiFiClientSecure secured_client; +WiFiClientSecure secured_client; -void printOpen(String Args) -{ - Text = "offen"; -} +//TelegramCommand callbacks must fulfill a function pointer: void (*) (const String arguments) +void printOpen(String Args); +void printClose(String Args); +void printText(String Args); -void printClose(String Args) -{ - Text = "leider zu"; -} - -void printText(String Args) -{ - Serial.print("Display would show '"); - Serial.print(Args); - Serial.println("'"); - Text = Args; -} - -TelegramCommand myCmdList[] { - {"/display_open", printOpen}, - {"/display_close", printClose}, - {"/display_text", printText}, +TelegramCommand myCmdList[]{ + {"/open", printOpen}, + {"/close", printClose}, + {"/text", printText}, }; uint32_t myCmdListCnt = sizeof(myCmdList) / sizeof(myCmdList[0]); @@ -60,14 +85,14 @@ Telegram tele(secured_client, myCmdList, myCmdListCnt); void setup() { - uint8_t rawBuff[192] = {}; - Serial.begin(115200); Serial.println(); - //rotate the Canvas so it is displayed in the correct orientation on the FlipDotDisplay + // rotate the Canvas so it is displayed in the correct orientation on the FlipDotDisplay canvas.setRotation(ROTATION); + /* initialize the display with several checker template */ + uint8_t rawBuff[192] = {}; Serial.println("Initializing Display"); flip.sendRaw(rawBuff, sizeof(rawBuff)); @@ -105,10 +130,10 @@ void setup() } flip.sendRaw(rawBuff, sizeof(rawBuff)); - // attempt to connect to Wifi network: + /* attempt to connect to Wifi network */ Serial.print("Connecting to Wifi SSID "); Serial.print(WIFI_SSID); - WiFi.setHostname("flipdottelegram"); + WiFi.setHostname(HOSTNAME); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { @@ -120,6 +145,7 @@ void setup() Serial.print("\nWiFi connected. IP address: "); Serial.println(WiFi.localIP()); + /* get internet time */ Serial.print("Retrieving time: "); configTime(0, 0, "pool.ntp.org"); // get UTC time via NTP configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"); @@ -132,13 +158,19 @@ void setup() } Serial.println(now); - - ArduinoOTA.setHostname("flipdottelegram"); + /* setup ArduinoOTA */ + ArduinoOTA.setHostname(HOSTNAME); ArduinoOTA.begin(); ArduinoOTA.onStart(onOTAStart); + /* initialize and connect telegram bot */ tele.init(); + /* prepare MQTT client */ + mqtt.setServer(MQTT_SERVER, 1883); + mqtt.setCallback(mqttCallback); + + /* initialy print closed at the display */ printClose(""); } @@ -148,29 +180,38 @@ void loop() static unsigned long lastDisplayRefresh = now; static unsigned long lastTelegramRefresh = now; - ArduinoOTA.handle(); + /* OTA handling */ + if (WiFi.status() == WL_CONNECTED) + { + ArduinoOTA.handle(); + } + + /* MQTT handling */ + mqttRefresh(); yield(); + /* Telegram handling */ if ((now - lastTelegramRefresh) > 5000) { lastTelegramRefresh = now; tele.cyclic(now); } + /* FlipDot Handling */ if ((now - lastDisplayRefresh) > 2000) { lastDisplayRefresh = now; - //fill canvas with black pixels + // fill canvas with black pixels canvas.fillScreen(0); - //if textlength is larger than possible on the display we need to scroll + // if textlength is larger than possible on the display we need to scroll uint32_t TextLength = Text.length(); if (TextLength > (MAX_CHAR_PER_LINE - 6)) { static uint32_t offset = 0; - //get the substring ffor this iteration + // get the substring ffor this iteration String SubText = Text.substring(offset, offset + MAX_CHAR_PER_LINE); if (SubText.length() < MAX_CHAR_PER_LINE) @@ -180,71 +221,179 @@ void loop() } else { - //increase the offset and reset it at the end of the scrol + // increase the offset and reset it at the end of the scrol offset += 4; } - //write substring to canvas + // write substring to canvas displayText(SubText.c_str(), true, 0, 4); } else { - //display current time on the left + // display current time on the left tm timeinfo; - char buffer [6] {0}; - if(getLocalTime(&timeinfo)) + char buffer[6]{0}; + if (getLocalTime(&timeinfo)) { strftime(buffer, 6, "%R", &timeinfo); } displayText(buffer, true, 0, 4); - //display text on the right centered + // display text on the right centered displayText(Text.c_str(), true, (6 + (MAX_CHAR_PER_LINE - 6 - TextLength) / 2) * 6, 4); } - //send canvas to display + // send canvas to display flip.sendCanvas(&canvas); } } -void displayText(const char* text, bool background, int16_t x, int16_t y) +/* function to write text onto the canvas + * + * @param text - C string to be printed + * @param background - boolean switch if the background should be transparent (false) or black (true) + * @param x - position in X direction to write the Text, -1 centers the text + * @param y - position in Y direction to write the Text, -1 centers the text + * + * */ +void displayText(const char *text, bool background, int16_t x, int16_t y) { - // canvas.fillScreen(0); - canvas.setFont(NULL); - canvas.setTextSize(1); - canvas.setTextColor(1); + // canvas.fillScreen(0); + canvas.setFont(NULL); + canvas.setTextSize(1); + canvas.setTextColor(1); - int16_t cursor_x, cursor_y; - int16_t bounds_x, bounds_y; - uint16_t bounds_w, bounds_h; - canvas.getTextBounds(text, 0, 0, &bounds_x, &bounds_y, &bounds_w, &bounds_h); + int16_t cursor_x, cursor_y; + int16_t bounds_x, bounds_y; + uint16_t bounds_w, bounds_h; + canvas.getTextBounds(text, 0, 0, &bounds_x, &bounds_y, &bounds_w, &bounds_h); - if (x == -1) - { - // Zentriert - cursor_x = (WIDTH - bounds_w) / 2; - } - else - { - cursor_x = x; - } - if (y == -1) - { - // Zentriert - cursor_y = (HEIGHT - bounds_h) / 2; - } - else - { - cursor_y = y; - } + if (x == -1) + { + // centered + cursor_x = (WIDTH - bounds_w) / 2; + } + else + { + cursor_x = x; + } + if (y == -1) + { + // centered + cursor_y = (HEIGHT - bounds_h) / 2; + } + else + { + cursor_y = y; + } - if (background) - { - canvas.fillRect(cursor_x - 1, cursor_y - 1, bounds_w + 1, bounds_h + 1, 0); - } - - canvas.setCursor(cursor_x, cursor_y); - canvas.print(text); + /* draw a black rectangle in the background of the text if it should not be transparent */ + if (background) + { + canvas.fillRect(cursor_x - 1, cursor_y - 1, bounds_w + 1, bounds_h + 1, 0); + } + + /* print the text to the destination on canvas */ + canvas.setCursor(cursor_x, cursor_y); + canvas.print(text); } +void printOpen(String Args) +{ + Text = "offen"; + publishOpen(); +} + +void printClose(String Args) +{ + Text = "leider zu"; + publishClose(); +} + +void printText(String Args) +{ + Serial.print("Display would show '"); + Serial.print(Args); + Serial.println("'"); + Text = Args; + publishClose(); +} + +void mqttRefresh() +{ + if (WiFi.status() == WL_CONNECTED) + { + if (!mqtt.loop()) + { + Serial.println("MQTT not connected"); + + if (mqtt.connect(HOSTNAME, MQTT_USER, MQTT_PASSWORD, FLIPDOT_AVAILABILITY_TOPIC, 2, true, "offline")) + { + mqtt.subscribe(FLIPDOT_SWITCH_TOPIC); + mqtt.subscribe(FLIPDOT_TEXT_TOPIC); + mqtt.publish(FLIPDOT_AVAILABILITY_TOPIC, "available"); + } + } + } + else + { + mqtt.disconnect(); + Serial.println("* disconnected MQTT"); + } +} + +void mqttCallback(char *topic, unsigned char *message, unsigned int length) +{ + String MessageString; + + /* Convert all char* to Strings for simpler handling */ + for (int i = 0; i < length; i++) + { + MessageString += message[i]; + } + String ToppicString(topic); + + /* if topic is the switch topic check for on, off, open, close */ + if (ToppicString == FLIPDOT_SWITCH_TOPIC) + { + Serial.println("Switch topic received: " + MessageString); + + if ((MessageString == "open") || + (MessageString == "on")) + { + printOpen(MessageString); + } + else if ((MessageString == "close") || + (MessageString == "off")) + { + printClose(MessageString); + } + } + + /* if topic is the text topic try deserializing the json text*/ + if (ToppicString == FLIPDOT_TEXT_TOPIC) + { + Serial.println("Text topic received: " + MessageString); + JsonDocument doc; + if (deserializeJson(doc, (const char*)message, length) == DeserializationError::Ok) + { + MessageString = doc["value"] | String("none"); //the pipe operand tells the default value, if "value" is not existing + + if ((MessageString != "none")) + { + printText(MessageString); + } + else + { + Serial.print(FLIPDOT_TEXT_TOPIC); + Serial.println(" has no [\"value\"]!"); + } + } + else + { + Serial.print(FLIPDOT_TEXT_TOPIC); + Serial.println(" is no json!"); + } + } +}