#include #include #include #include #include #include #include #include "Telegram.h" #include "FlipDotDrv.h" #include "secrets.h" /* most important global declaratioen ;-) */ String Text = ""; const char *const 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 mqttCallback(char *topic, unsigned char *message, unsigned int length); void publishOpen() { mqtt.publish(FLIPDOT_SWITCH_TOPIC "/state", "open"); mqtt.publish(FLIPDOT_TEXT_TOPIC "/state", Text.c_str()); } void publishClose() { mqtt.publish(FLIPDOT_SWITCH_TOPIC "/state", "close"); mqtt.publish(FLIPDOT_TEXT_TOPIC "/state", Text.c_str()); } void publishHomeAssistantDiscovery() { auto mac = WiFi.macAddress(); mac.replace(":", "_"); JsonDocument doc; JsonObject device = doc["dev"].to(); device["ids"] = mac; device["name"] = "FlipDotDisplay"; device["mf"] = "-mf-"; device["mdl"] = "-mdl-"; device["sw"] = "-sw-"; device["sn"] = "-sn-"; device["hw"] = "-hw-"; JsonObject origin = doc["o"].to(); origin["name"] = "Hobbyhimmel"; origin["sw"] = "-sw-"; origin["url"] = "https://hobbyhimmel.de"; JsonObject components = doc["cmps"].to(); JsonObject sw = components["FlipDotDisplaySwitch"].to(); sw["unique_id"] = mac + "FlipDotDisplaySwitch"; sw["p"] = "switch"; sw["command_topic"] = FLIPDOT_SWITCH_TOPIC; sw["state_topic"] = FLIPDOT_SWITCH_TOPIC "/state"; sw["payload_on"] = "open"; sw["payload_off"] = "close"; sw["qos"] = 0; sw["retain"] = false; JsonObject txt = components["FlipDotDisplayText"].to(); sw["unique_id"] = mac + "FlipDotDisplayText"; sw["p"] = "text"; sw["command_topic"] = FLIPDOT_TEXT_TOPIC; sw["state_topic"] = FLIPDOT_TEXT_TOPIC "/state"; sw["qos"] = 0; sw["retain"] = false; doc["availability_topic "] = FLIPDOT_AVAILABILITY_TOPIC; String jsonString; serializeJson(doc, jsonString); String discoveryTopic = "homeassistant/device/"; discoveryTopic += mac + "/config"; mqtt.publish(discoveryTopic.c_str(), jsonString.c_str()); Serial.print("Homeassistant Discovery was published: "); Serial.println(jsonString); } /* ------------ OTA section and variables -------------- */ bool OTAStarted; void onOTAStart() { OTAStarted = true; } /* ------------ FlipDot variables -------------- */ #define WIDTH 96 #define HEIGHT 16 #define FLIPDOT_BUFFER_SIZE (WIDTH * HEIGHT / 8) #define ROTATION 0 // 0 or 2 for 0 degrees or 180 degrees #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); /* ------------ Telegram variables -------------- */ // BearSSL::WiFiClientSecure secured_client; WiFiClientSecure secured_client; // TelegramCommand callbacks must fulfill a function pointer: String (*) (const String arguments) String printOpen(String Args); String printClose(String Args); String printText(String Args); TelegramCommand myCmdList[]{ {"/open", printOpen}, {"/close", printClose}, {"/text", printText}, }; uint32_t myCmdListCnt = sizeof(myCmdList) / sizeof(myCmdList[0]); Telegram tele(secured_client, myCmdList, myCmdListCnt); void setup() { Serial.begin(115200); Serial.println(); // 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[FLIPDOT_BUFFER_SIZE] = {}; Serial.println("Initializing Display"); flip.sendRaw(rawBuff, sizeof(rawBuff)); delay(1000); Serial.println("draw checker"); memset(rawBuff, 0xAA, FLIPDOT_BUFFER_SIZE); for (int i = 0; i < FLIPDOT_BUFFER_SIZE; ++i) { if ((i % 4) < 2) rawBuff[i] = 0xAA; else rawBuff[i] = 0x55; } flip.sendRaw(rawBuff, sizeof(rawBuff)); delay(1000); Serial.println("bigger checker"); for (int i = 0; i < FLIPDOT_BUFFER_SIZE; ++i) { if ((i % 8) < 4) rawBuff[i] = 0x33; else rawBuff[i] = 0xCC; } flip.sendRaw(rawBuff, sizeof(rawBuff)); delay(1000); Serial.println("largest checker"); for (int i = 0; i < FLIPDOT_BUFFER_SIZE; ++i) { if ((i % 16) < 8) rawBuff[i] = 0x0F; else rawBuff[i] = 0xF0; } flip.sendRaw(rawBuff, sizeof(rawBuff)); /* attempt to connect to Wifi network */ WiFi.setHostname(HOSTNAME); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); /* get internet time */ Serial.print("setup 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"); /* setup ArduinoOTA */ ArduinoOTA.setHostname(HOSTNAME); ArduinoOTA.onStart(onOTAStart); /* prepare MQTT client */ mqtt.setServer(MQTT_SERVER, 1883); mqtt.setCallback(mqttCallback); } void loop() { static wl_status_t oldWifiStatus = (wl_status_t)254; unsigned long now = millis(); static unsigned long lastDisplayRefresh = now; static unsigned long lastTelegramRefresh = now; if (WiFi.status() == WL_CONNECTED) { // if the Wifi is connected do all the internet stuff if (WiFi.status() != oldWifiStatus) { /* we freshly reconnected so tell everybody that we are online */ Serial.print("\nWiFi connected. IP address: "); Serial.println(WiFi.localIP()); 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"); time_t now = time(nullptr); while (now < 24 * 3600) { Serial.print("."); delay(100); now = time(nullptr); } Serial.println(now); ArduinoOTA.begin(); tele.init(); printClose(""); oldWifiStatus = WiFi.status(); } /* handle the OTA update */ ArduinoOTA.handle(); /* handle Telegram messages */ if ((now - lastTelegramRefresh) > 5000) { lastTelegramRefresh = now; tele.cyclic(now); } /* handle MQTT transmissions */ if (!mqtt.loop()) { Serial.println("MQTT not connected"); // try to connect to MQTT broker again if (mqtt.connect(HOSTNAME, MQTT_USER, MQTT_PASSWORD, FLIPDOT_AVAILABILITY_TOPIC, 2, true, "offline")) { // publish availability message mqtt.subscribe(FLIPDOT_SWITCH_TOPIC); mqtt.subscribe(FLIPDOT_TEXT_TOPIC); mqtt.publish(FLIPDOT_AVAILABILITY_TOPIC, "available"); publishHomeAssistantDiscovery(); } } } else { // if wifi is not connected wait for connection if (WiFi.status() != oldWifiStatus) { /* we freshly disconnected so tell everybody that we are offline */ Text = "Wifi \""; Text += WIFI_SSID; Text += "\" not connected"; ArduinoOTA.end(); mqtt.disconnect(); oldWifiStatus = WiFi.status(); } } /* FlipDot Handling */ if ((now - lastDisplayRefresh) > 2000) { lastDisplayRefresh = now; // fill canvas with black pixels canvas.fillScreen(0); // 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 String SubText = Text.substring(offset, offset + MAX_CHAR_PER_LINE); if (SubText.length() < MAX_CHAR_PER_LINE) { // if the text is the last line start over offset = 0; } else { // increase the offset and reset it at the end of the scrol offset += 4; } // write substring to canvas displayText(SubText.c_str(), true, 0, 4); } else { // display current time on the left tm timeinfo; char buffer[6]{0}; if (getLocalTime(&timeinfo)) { strftime(buffer, 6, "%R", &timeinfo); } displayText(buffer, true, 0, 4); // 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 flip.sendCanvas(&canvas); } /* wait here if loop execution was faster then 200ms but yield minimum once */ do { yield(); } while ((millis() - now) < 200); } /* 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); 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) { // 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; } /* 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); } String printOpen(String Args) { Text = "offen"; publishOpen(); return Text; } String printClose(String Args) { Text = "leider zu"; publishClose(); return Text; } String printText(String Args) { Serial.print("Display would show '"); Serial.print(Args); Serial.println("'"); Text = Args; publishClose(); return Text; } 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!"); } } }