Tobi_Lxtr
Published © GPL3+

I Let Everyone On The Internet Control My X-Mas Decoration

Control any of the 55 RGB LEDs on the tree, let Santa twerk for you, control a model railroad, or display custom text on an LED dot matrix.

AdvancedShowcase (no instructions)8,454
I Let Everyone On The Internet Control My X-Mas Decoration

Things used in this project

Hardware components

WS2812 RGB LED
Or compatible types. We used P9823's.
×1
Arduino Mega 2560
Arduino Mega 2560
×1
Arduino Mega Proto Shield
Arduino Mega Proto Shield
or similar proto shield.
×1
Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
Level Shifter Board
SparkFun Level Shifter Board
or compatible for shifting 5V Arduino level to 3.3V Pi level
×1
LED dot matrix with MAX7219 control IC
×8
Arduino Nano R3
Arduino Nano R3
×1
Arduino MKR1000
Arduino MKR1000
×1
8 channel relay board
currently we're only using 4 channels
×1
5V, 5A (switching) power supply
×1

Software apps and online services

openweathermap.org
timezonedb.com
TelegramBot Api

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Drill
Jigsaw

Story

Read more

Schematics

Basic connections and data exchange.

Our basic concept of how this whole mess works.

Code

Serial_Data_Read.ino

Arduino
Code for the "main" Arduino Mega. Reads data from pi and controls all LEDs and devices.
#include "FastLED.h"
#include "leds_lookup_table.h"
#include <EEPROM.h>

#define DEBUG 0

#define READY_PIN 28
#define SANTA_PIN 5
#define TRAIN_PIN 39  //relay 3
#define LIGHT_CHAIN_PIN 35  //relay 1
#define SPOT_LIGHT_PIN 37
#define FRONT_LIGHT_PIN 41
#define LAP_COUNT_PIN 6


#define NUM_LEDS 55    //number of total P9823's 
#define DATA_PIN 2
#define FADE_DELAY 50
#define FADE_TIME 2000
#define CHANCE_SANTA_SOLO 20 //chance of a santa solo in percent

#define PiSerial Serial1
#define DebugSerial Serial

//status bytes knnen werte von 1 bis 191 haben (0 = undefined)
#define LED_START 1
#define LED_END 55
#define TEXT_EINGABE 124
#define TWERKING_SANTA 69
#define TRAIN 56
#define LIGHT_CHAIN 57
#define SPARKLING 58
#define ANIMATIONS 59
#define TREE_BLACKOUT 60

//info/data bytes von 192 bis 254
#define OFF 192
#define ON 193
#define FARBEN_START 194
#define FARBEN_END 210

#define DATA_ERROR 255

struct ledStepStruct {
  int rStep, gStep, bStep;
  boolean fade;
};

byte data[2], rndLED, hueTemp;
char text[31];
boolean newText, santaFlag, trainFlag, lightChainFlag, sparklingFlag, sparkleOn, santaSoloFlag, pinActive = true;
const unsigned int fadeSteps = FADE_TIME / FADE_DELAY;
unsigned long currentMillis, lastFadeTime, santaTime, trainTime, lightChainTime, sparklingTime, lastSparkleTime, lastLapCount, currentTime, trainActionTime, lastTrainAction;
unsigned int lapCounter;

CRGB leds[NUM_LEDS], ledsAim[NUM_LEDS], ledBefore;

ledStepStruct ledsStep[NUM_LEDS];

void setup() {
  PiSerial.begin(9600);

  DebugSerial.begin(9600);

  pinMode(READY_PIN, OUTPUT);
  pinMode(SANTA_PIN, OUTPUT);
  pinMode(TRAIN_PIN, OUTPUT);
  pinMode(LIGHT_CHAIN_PIN, OUTPUT);
  pinMode(SPOT_LIGHT_PIN, OUTPUT);
  pinMode(FRONT_LIGHT_PIN, OUTPUT);
  pinMode(LAP_COUNT_PIN, INPUT_PULLUP);


  FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);
  FastLED.setBrightness(70);
  FastLED.clear();
  FastLED.show();

  digitalWrite(READY_PIN, HIGH);
  digitalWrite(SANTA_PIN, LOW);
  digitalWrite(TRAIN_PIN, LOW);
  digitalWrite(LIGHT_CHAIN_PIN, HIGH);
  digitalWrite(SPOT_LIGHT_PIN, LOW);
  digitalWrite(FRONT_LIGHT_PIN, HIGH);

  lightChainFlag = true;

  //lapCounter = 200; //add counts up to now, since eeprom wasnt' implemented yet
  //EEPROM.put(0, lapCounter);
  EEPROM.get(0, lapCounter);

}

void loop() {

  handleSerialCommunication();
  handleData();
  handleColorFade();
  handleControl();
  handleSparkling();
  handleLapCount();

}

void handleLapCount() {
  currentTime = millis();
  if (!digitalRead(LAP_COUNT_PIN) && pinActive && currentTime - trainActionTime > 100) {
    lapCounter++;
    pinActive = false;
    lastLapCount = currentTime;
    PiSerial.print(lapCounter);
    PiSerial.flush();
    EEPROM.put(0, lapCounter);
    //Serial.println(lapCounter);
  }

  if (digitalRead(LAP_COUNT_PIN) && currentTime - lastLapCount > 2000) {
    pinActive = true;
  }
}

void handleSparkling() {
  if (sparklingFlag) {
    if (millis() - sparklingTime > 20000) {
      sparklingFlag = false;
      leds[rndLED] = ledBefore;
      FastLED.show();
      sparkleOn = false;
    }
    else if (!sparkleOn && millis() - lastSparkleTime > 100) {
      rndLED = random8(NUM_LEDS);
      ledBefore = leds[rndLED];
      leds[rndLED] += CRGB::White;
      FastLED.show();
      sparkleOn = true;
      lastSparkleTime = millis();
    }
    else if (sparkleOn && millis() - lastSparkleTime > 50) {
      leds[rndLED] = ledBefore;
      FastLED.show();
      sparkleOn = false;
      lastSparkleTime = millis();
    }
  }
}

void handleControl() {
  if (santaFlag) {
    if (santaTime == 0) {
      digitalWrite(SANTA_PIN, HIGH);
      santaTime = millis();
    }
    else if (millis() - santaTime > 20000) {
      digitalWrite(SANTA_PIN, LOW);
      santaTime = 0;
      santaFlag = false;
    }
  }

  else if (santaSoloFlag) {

    if (trainFlag) {
      trainFlag = false;
      digitalWrite(TRAIN_PIN, LOW);
    }
    if (lightChainFlag) {
      digitalWrite(LIGHT_CHAIN_PIN, LOW);
    }
    FastLED.clear();
    FastLED.show();

    digitalWrite(FRONT_LIGHT_PIN, LOW);
    FastLED.delay(1000);
    digitalWrite(SPOT_LIGHT_PIN, HIGH);
    FastLED.delay(500);
    digitalWrite(SANTA_PIN, HIGH);
    FastLED.delay(50);
    digitalWrite(SANTA_PIN, LOW);

    /*
        for (int i; i < 500; i++) {
          fill_rainbow(leds, NUM_LEDS, hueTemp, 1);
          FastLED.show();
          hueTemp++;
          FastLED.delay(25);
        }
        FastLED.clear();
        FastLED.show();
    */
    for ( int k = 0; k < 20; k++ ) {
      triangle();
    }


    digitalWrite(SPOT_LIGHT_PIN, LOW);

    digitalWrite(SANTA_PIN, HIGH);
    FastLED.delay(50);
    digitalWrite(SANTA_PIN, LOW);

    FastLED.delay(1000);

    digitalWrite(FRONT_LIGHT_PIN, HIGH);
    digitalWrite(LIGHT_CHAIN_PIN, HIGH);
    lightChainFlag = true;

    for (byte i = 0; i < NUM_LEDS; i++) {
      leds[i] = ledsAim[i];
    }
    FastLED.show();
    santaSoloFlag = false;
    digitalWrite(READY_PIN, HIGH);
  }

  if (trainFlag) {
    if (millis() - trainTime > 30000) {
      trainFlag = false;
      digitalWrite(TRAIN_PIN, LOW);
    }
  }

  if (!lightChainFlag) {
    if (millis() - lightChainTime > 60000) {
      lightChainFlag = true;
      digitalWrite(LIGHT_CHAIN_PIN, HIGH);
    }
  }
}

void handleColorFade() {
  currentMillis = millis();
  if (currentMillis - lastFadeTime >= FADE_DELAY) {

    for (byte i = 0; i < NUM_LEDS; i++) {
      if (ledsStep[i].fade) {
        ledsStep[i].fade = false;
        if (leds[i].r != ledsAim[i].r) {
          ledsStep[i].fade = true;
          int rTemp = (int)leds[i].r + (int)ledsStep[i].rStep;
          if ((ledsStep[i].rStep > 0 && rTemp > ledsAim[i].r) || (ledsStep[i].rStep < 0 && rTemp < ledsAim[i].r) || rTemp > 255 || rTemp < 0) leds[i].r = ledsAim[i].r;
          else leds[i].r = rTemp;

          if ((ledsStep[i].rStep > 0 && leds[i].r > ledsAim[i].r) || (ledsStep[i].rStep < 0 && leds[i].r < ledsAim[i].r)) leds[i].r = ledsAim[i].r;
        }
        if (leds[i].g != ledsAim[i].g) {
          ledsStep[i].fade = true;
          int gTemp = (int)leds[i].g + (int)ledsStep[i].gStep;
          if ((ledsStep[i].gStep > 0 && gTemp > ledsAim[i].g) || (ledsStep[i].gStep < 0 && gTemp < ledsAim[i].g) || gTemp > 255 || gTemp < 0) leds[i].g = ledsAim[i].g;
          else leds[i].g = gTemp;
        }
        if (leds[i].b != ledsAim[i].b) {
          ledsStep[i].fade = true;
          int bTemp = (int)leds[i].b + (int)ledsStep[i].bStep;
          if ((ledsStep[i].bStep > 0 && bTemp > ledsAim[i].b) || (ledsStep[i].bStep < 0 && bTemp < ledsAim[i].b) || bTemp > 255 || bTemp < 0) leds[i].b = ledsAim[i].b;
          else leds[i].b = bTemp;
        }

      }
    }
    FastLED.show();
    lastFadeTime = currentMillis;
  }


}

void handleSerialCommunication() {
  if (PiSerial.available() > 0) {   //if there's any serial data
    /*
      if (data[0] == TEXT_EINGABE) {  //if the upcoming data is a text; indicated by first data byte = 124
      //inputString = PiSerial.readString();

      for (byte textIndex = 0; textIndex < (data[1] - 193); textIndex++) {  //194 = textlnge 1, ..., 224 = textlnge 30
        text[textIndex] = PiSerial.read();
      }
      text[data[1] - 193] = '\0';

      newText = true;   //text saved
      data[0] = 0;
      data[1] = 0;

      }
    */
    //else if !!!!!!! wenn oben auskommentiert

    if (PiSerial.peek() >= 1 && PiSerial.peek() <= 191) {    //if first data byte is valid
      PiSerial.readBytes(data, 2);
      //if (data[0] == TEXT_EINGABE) digitalWrite(READY_PIN, LOW);
#if DEBUG
      DebugSerial.println("Empfangen: " + (String)data[0] + ", " + (String)data[1]);
#endif
    }
    else {  //else; first data byte is invalid (0 or greater than 191)
      byte errorMessage = PiSerial.read();
      //PiSerial.print(DATA_ERROR);
      //PiSerial.print(errorMessage);
#if DEBUG
      DebugSerial.print("DATA_ERROR: ");
      DebugSerial.println(errorMessage);
#endif
    }
  }
}

void handleData() {
  if (data[0] >= LED_START && data[0] <= LED_END) {   //if data is LED data
    if (data[1] < OFF || data[1] > FARBEN_END) {   //if second data byte has invalid color
      //invalid color/data byte
      //PiSerial.print(DATA_ERROR);
      //PiSerial.print(data[1]);
#if DEBUG
      DebugSerial.print("DATA_ERROR_INVALID_COLOR: ");
      DebugSerial.println(data[1]);
#endif

    }
    else {  //color value is valid
      //led mit farbe (in data[1]) ansteuern
      byte i = data[0] - 1;

      if (data[1] == OFF) ledsAim[i] = CRGB::Black;
      else if (data[1] == ON) ledsAim[i] = CRGB::White; //hier warmwei einfgen
      else ledsAim[i].setHue((data[1] - 194) * 16);  //set aim color for led to chosen value

      setLEDFade(i);

    }
    data[0] = 0;
    data[1] = 0;
  }

  else if (data[0] == TWERKING_SANTA) {


    if (!santaFlag) {
      digitalWrite(READY_PIN, LOW);

      if (random8(100) > CHANCE_SANTA_SOLO - 1) {
        santaFlag = true;
        digitalWrite(READY_PIN, HIGH);
      }
      else santaSoloFlag = true;
    }

    data[0] = 0;
    data[1] = 0;

  }

  else if ( data[0] == TRAIN) {
    currentTime = millis();
    if (currentTime - lastTrainAction > 1000) {
      if (data[1] == OFF && trainFlag) {
        trainActionTime = currentTime;
        lastTrainAction = trainActionTime;
        digitalWrite(TRAIN_PIN, LOW);
        trainFlag = false;
      }

      else if (data[1] == ON && !trainFlag) {
        trainActionTime = currentTime;
        lastTrainAction = trainActionTime;
        digitalWrite(TRAIN_PIN, HIGH);
        trainFlag = true;
        trainTime = millis();
      }
    }
    data[0] = 0;
    data[1] = 0;

    
  }

  else if (data[0] == LIGHT_CHAIN) {
    if (data[1] == OFF && lightChainFlag) {
      digitalWrite(LIGHT_CHAIN_PIN, LOW);
      lightChainFlag = false;
      lightChainTime = millis();
    }

    else if (data[1] == ON && !lightChainFlag) {
      digitalWrite(LIGHT_CHAIN_PIN, HIGH);
      lightChainFlag = true;
    }
    data[0] = 0;
    data[1] = 0;
  }

  else if (data[0] == SPARKLING) {
    if (data[1] == ON && !sparklingFlag) {
      sparklingFlag = true;
      sparklingTime = millis();
    }

    else if (data[1] == OFF && sparklingFlag) {
      sparklingFlag = false;
    }

    data[0] = 0;
    data[1] = 0;
  }

  else if (data[0] == ANIMATIONS) {
    digitalWrite(READY_PIN, LOW);
    animations();
    digitalWrite(READY_PIN, HIGH);
    data[0] = 0;
    data[1] = 0;
  }

  else if (data[0] == TREE_BLACKOUT) {

    for (byte i = 0; i < NUM_LEDS; i++) {
      ledsAim[i] = CRGB::Black;
      setLEDFade(i);
    }

    data[0] = 0;
    data[1] = 0;
  }


  /*
    if (newText) {    //handle text entry
    //inputString.toCharArray(text, 31);

    #if DEBUG
    DebugSerial.println(text);    //debug
    DebugSerial.flush();
    #endif
    digitalWrite(READY_PIN, HIGH);
    //text anzeigen
    newText = false;
    }
  */
}

void animations() {
  fillSpiralCCW();
  fillSpiralCW();
  fillSpiralCCW();
  fillSpiralCW();

  for (byte i = 0; i < NUM_LEDS; i++) {
    leds[i] = ledsAim[i];
  }
  FastLED.show();
}

void setLEDFade(byte i) {
  if (leds[i] != ledsAim[i]) {
    ledsStep[i].fade = true;

    if (ledsAim[i].r != leds[i].r) {
      ledsStep[i].rStep = ((int)ledsAim[i].r - (int)leds[i].r) / (int)fadeSteps;
      if (ledsStep[i].rStep == 0) {
        if (ledsAim[i].r > leds[i].r) ledsStep[i].rStep = 1;
        else ledsStep[i].rStep = -1;
      }
#if DEBUG
      DebugSerial.println("rStep: " + (String)ledsStep[i].rStep);    //debug
#endif
    }
    if (ledsAim[i].g != leds[i].g) {
      ledsStep[i].gStep = ((int)ledsAim[i].g - (int)leds[i].g) / (int)fadeSteps;
      if (ledsStep[i].gStep == 0) {
        if (ledsAim[i].g > leds[i].g) ledsStep[i].gStep = 1;
        else ledsStep[i].gStep = -1;
      }
#if DEBUG
      DebugSerial.println("gStep: " + (String)ledsStep[i].gStep);    //debug
#endif
    }
    if (ledsAim[i].b != leds[i].b) {
      ledsStep[i].bStep = ((int)ledsAim[i].b - (int)leds[i].b) / (int)fadeSteps;
      if (ledsStep[i].bStep == 0) {
        if (ledsAim[i].b > leds[i].b) ledsStep[i].bStep = 1;
        else ledsStep[i].bStep = -1;
      }
#if DEBUG
      DebugSerial.println("bStep: " + (String)ledsStep[i].bStep);    //debug
#endif
    }

  }
}


void fillSpiralCCW() {

  FastLED.clear();
  FastLED.show();

  for (byte i = 0; i < (sizeof(spiralCCW) / sizeof(byte)); i++) {
    leds[spiralCCW[i]].setHue(hueTemp);
    FastLED.show();
    FastLED.delay(30);
    hueTemp += 2;
  }

  for (byte i = (sizeof(spiralCCW) / sizeof(byte)) - 1; i > 0; i--) {
    leds[spiralCCW[i]] = CRGB::Black;
    FastLED.show();
    FastLED.delay(30);
  }
}

void fillSpiralCW() {

  FastLED.clear();
  FastLED.show();

  for (byte i = 0; i < (sizeof(spiralCW) / sizeof(byte)); i++) {
    leds[spiralCW[i]].setHue(hueTemp);
    FastLED.show();
    FastLED.delay(30);
    hueTemp += 2;
  }

  for (byte i = (sizeof(spiralCW) / sizeof(byte)) - 1; i > 0; i--) {
    leds[spiralCW[i]] = CRGB::Black;
    FastLED.show();
    FastLED.delay(30);
  }
}


void frameMarquee() {
  FastLED.clear();
  FastLED.show();

  for (int j = 0; j < 400; j++) {
    for (byte i = 0; i < (sizeof(frame) / sizeof(byte)); i++) {
      leds[frame[i]].setHue(hueTemp);
      hueTemp += 2;
    }
    hueTemp = 2 * j;
    FastLED.show();
    FastLED.delay(40);
  }
  FastLED.clear();
  FastLED.show();
}

void innerFrame() {
  for (int j = 0; j < 15; j++) {

    hueTemp += 40;

    for (byte i = 0; i < (sizeof(frame) / sizeof(byte)); i++) {
      leds[frame[i]].setHue(hueTemp);
    }
    FastLED.show();
    FastLED.delay(200);

    for (byte i = 0; i < (sizeof(frame) / sizeof(byte)); i++) {
      leds[frame[i]] = CRGB::Black;
    }

    hueTemp += 40;

    for (byte i = 0; i < (sizeof(inner) / sizeof(byte)); i++) {
      leds[inner[i]].setHue(hueTemp);
    }
    FastLED.show();
    FastLED.delay(200);

    for (byte i = 0; i < (sizeof(inner) / sizeof(byte)); i++) {
      leds[inner[i]] = CRGB::Black;
    }
  }
  FastLED.clear();
  FastLED.show();
}

void triangle() {
  hueTemp += 16;
  for (byte i = 0; i < (sizeof(triangle1) / sizeof(byte)); i++) {
    leds[triangle1[i]].setHue(hueTemp);
  }
  FastLED.show();
  FastLED.delay(150);
  for (byte i = 0; i < (sizeof(triangle1) / sizeof(byte)); i++) {
    leds[triangle1[i]] = CRGB::Black;
  }
  hueTemp += 16;
  for (byte i = 0; i < (sizeof(triangle2) / sizeof(byte)); i++) {
    leds[triangle2[i]].setHue(hueTemp);
  }
  FastLED.show();
  FastLED.delay(150);
  for (byte i = 0; i < (sizeof(triangle2) / sizeof(byte)); i++) {
    leds[triangle2[i]] = CRGB::Black;
  }
  hueTemp += 16;
  for (byte i = 0; i < (sizeof(triangle3) / sizeof(byte)); i++) {
    leds[triangle3[i]].setHue(hueTemp);
  }
  FastLED.show();
  FastLED.delay(150);
  for (byte i = 0; i < (sizeof(triangle3) / sizeof(byte)); i++) {
    leds[triangle3[i]] = CRGB::Black;
  }
  hueTemp += 16;
  for (byte i = 0; i < (sizeof(triangle4) / sizeof(byte)); i++) {
    leds[triangle4[i]].setHue(hueTemp);
  }
  FastLED.show();
  FastLED.delay(150);
  for (byte i = 0; i < (sizeof(triangle4) / sizeof(byte)); i++) {
    leds[triangle4[i]] = CRGB::Black;
  }

}

leds_lookup_table.h

Arduino
Look up table for the LEDs on the tree.
byte frame[] = {52, 51, 43, 42, 32, 31, 19, 18, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 25, 26, 38, 39, 47, 48, 54, 53};

byte inner[] = {50, 49, 44, 45, 46, 41, 40, 33, 34, 35, 36, 37, 30, 29, 28, 27, 20, 21, 22, 23, 24, 17, 16, 15, 14, 13, 12, 11, 10};

byte spiralCCW[] = {52, 51, 43, 32, 18, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 38, 47, 48, 54, 53, 50, 44, 42, 33, 31, 19, 17,
                    16, 15, 14, 13, 12, 11, 10, 25, 26, 37, 39, 46, 49, 45, 41, 34, 30, 20, 21, 22, 23, 24,
                    27, 36, 40, 35, 29, 28, 35};
byte spiralCW[] = {52, 53, 54, 48, 47, 38, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 18, 32, 43, 51, 50, 49, 46, 39, 37, 26, 25, 10, 11, 12, 13, 14, 15, 16, 17, 19, 31,
                   33, 42, 44, 45, 40, 36, 27, 24, 23, 22, 21, 20, 30, 34, 41, 35, 28, 29};

byte triangle1[] = {35, 29, 28};

byte triangle2[] = {45, 41, 34, 30, 20, 21, 22, 23, 24, 27, 36, 40};

byte triangle3[] = {53, 50, 44, 42, 33, 31, 19, 17, 16, 15, 14, 13, 12, 11, 10, 25, 26, 37, 39, 46, 49};

byte triangle4[] = {52, 51, 43, 32, 18, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 38, 47, 48, 54};

Telegram_Bot_xmas.ino

Arduino
Code for the Arduino MKR1000.
#define TEXT_SERIAL 1
#define WATCHDOG 1
#define DEBUG 0

#include <Arduino.h>
#include <Time.h>
#include <TimeLib.h>
#include <WiFi101.h>
//#include <WiFiUdp.h>
#include <SPI.h>
#include <TelegramBot.h>
//#include <time.h>
//#include <RTCZero.h>

#if TEXT_SERIAL
#include <wiring_private.h>
#endif

#if WATCHDOG
#include <Adafruit_SleepyDog.h>
#endif

#if TEXT_SERIAL
Uart textSerial (&sercom3, 0, 1, SERCOM_RX_PAD_1, UART_TX_PAD_0);

void SERCOM3_Handler()    // Interrupt handler for SERCOM3
{
  textSerial.IrqHandler();
}
#endif

#define ledPin 6
#define RBUFFSIZE 1000

#define MAX_MESSAGES 16

#define TIME 1
#define TEXT 2
#define WEATHER 3
#define STATS 4

/*
  #define BEER_NAME 0
  #define BEER_ID 1
*/

char responseBuffer[RBUFFSIZE];
int  rbindex = 0;

boolean startCapture;
unsigned long lastTimeSentTime, lastTextSent, currentTime, lastClockSent;

// Initialize Wifi connection

const char ssid[] = "";           // network SSID
const char pass[] = "";           // network key

// Initialize Telegram BOT

const char BotToken[] = "";

char server[] = "api.openweathermap.org";

String eingabe; //Nachrichten[128];
char Nachrichten[MAX_MESSAGES][100];
byte numMessages, messageIndex, sendMessageIndex;

WiFiSSLClient client;
WiFiClient clienthttp;

TelegramBot  bot(BotToken, client);

//RTCZero rtc;

//TelegramKeyboard keyboardStart;
/*
const char* row1Start[] = {"/help", "/wlan"};
const char* row2Start[] = {"/ip", "/signal"};
//const char* row3Start[] = {"/alloff"};
*/

time_t tClock = 0;


void setup() {

  delay(3000);

#if TEXT_SERIAL
  textSerial.begin(9600);           // Begin Uart for sending text to LED matrix
#endif

  Serial.begin(9600);

#if TEXT_SERIAL
  pinPeripheral(0, PIO_SERCOM);   // Assign pins 0 & 1 SERCOM functionality
  pinPeripheral(1, PIO_SERCOM);
#endif

#if WATCHDOG
  Watchdog.enable(10000);
#endif

  // attempt to connect to Wifi network:
#if DEBUG
  Serial.print(F("Verbine mit WLAN: "));
  Serial.println(ssid);
#endif

  while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
    delay(500);
  }

#if DEBUG
  Serial.println(F("Mit WLAN verbunden."));
#endif
/*
  byte counterT = 0;
  while (tClock == 0 && counterT < 6) {
    delay(1000);
    counterT++;

    tClock = WiFi.getTime();

  }
  Serial.println(tClock);
  */
  getCurrentTime();
  
  if (tClock != 0) {
    textSerial.write(TIME);
    textSerial.write(hour(tClock));
    textSerial.write(minute(tClock));
    textSerial.write(second(tClock));
  }
  tClock = 0;
  
  lastClockSent = millis();

  bot.begin();

  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
/*
  keyboardStart.addRow(row1Start, 2);
  keyboardStart.addRow(row2Start, 2);
  //keyboardStart.addRow(row3Start, 1);
  */

  message m = bot.getUpdates();
}

void loop() {
#if WATCHDOG
  Watchdog.reset();
#endif

  if ( WiFi.status() != WL_CONNECTED) {
    //WiFi.disconnect();
    while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
#if DEBUG
      Serial.print(".");
#endif
      delay(1000);
    }
  }
  currentTime = millis();

  if (numMessages > 0 && currentTime - lastTextSent > 20000) {
    textSerial.write(TEXT);
    textSerial.print(String(Nachrichten[sendMessageIndex]));
    lastTextSent = currentTime;
    sendMessageIndex++;
    numMessages--;
    if (sendMessageIndex >= MAX_MESSAGES) sendMessageIndex = 0;
  }

  if (currentTime - lastTextSent > 120000) {
    textSerial.write(TEXT);
    textSerial.print("To send custom text, open/download the 'Telegram' App and add: TannenbaumBot.");
    lastTextSent = currentTime;
  }

  if (currentTime - lastClockSent > 600000) {
    tClock = 0;

    getCurrentTime();
    /*
    byte counterT = 0;
    while (tClock == 0 && counterT < 6) {
      counterT++;
      delay(500);
      tClock = WiFi.getTime();
    }
    */
    if (tClock != 0) {
      textSerial.write(TIME);
      textSerial.write(hour(tClock));
      textSerial.write(minute(tClock));
      textSerial.write(second(tClock));
    }
    

    /*
      textSerial.write(rtc.getHours());
      textSerial.write(rtc.getMinutes());
      textSerial.write(rtc.getSeconds());
    */

    lastClockSent = currentTime;
  }

  message m = bot.getUpdates();
  /*
    if(millis() - lastTimeSentTime > 600000) {
      textSerial.write(TIME);
      textSerial.print(String(rtc.getEpoch()) + "\n");
      lastTimeSentTime = millis();
    }
  */

  if (Serial.available() > 0) {
    eingabe = Serial.readStringUntil('\n');
    if (!eingabe.equalsIgnoreCase("i")) {
      bot.sendMessage("", eingabe);
      eingabe = "";
    }
  }

  else if (eingabe.equalsIgnoreCase("i")) {
    if (WL_CONNECTED) {
      Serial.println(F("Mit WLAN verbunden"));
      IPAddress ip = WiFi.localIP();
      Serial.print(F("IP Address: "));
      Serial.println(ip);

      // print the received signal strength:
      long rssi = WiFi.RSSI();
      Serial.print(F("signal strength (RSSI):"));
      Serial.print(rssi);
      Serial.println(F(" dBm"));
      eingabe = "";
    }
    else Serial.println(F("KEINE Verbindung zum WLAN"));
    eingabe = "";
  }

  if ( m.chat_id != 0 ) {
    if (1) { // if a message is received from unique Chats m.sender.equals("xxxxx")

      //Serial.println("Location: " + m.location.lon + ", " + m.location.lat);
      //Serial.println("Name: " + m.first_name);
      //Serial.println(m.chat_id);
      //Serial.println(m.sender);

#if DEBUG
      time_t t = m.date.toInt();
      t = t + 1 * 60 * 60;

      Serial.print(m.sender);
      Serial.print(F(" hat um "));
      Serial.print(hour(t));
      Serial.print(":");
      Serial.print(minute(t));

      Serial.print(F(" folgendes gesendet: "));
      Serial.println(m.text);
#endif

      if (m.location.lon.length() != 0) {
        bot.sendMessage(m.chat_id, "Requesting weather data...");
#if DEBUG
        Serial.println(F("Verbinde zu api.openweathermap.org"));
#endif

        weatherHttpRequest(m.location.lat, m.location.lon, m.chat_id);

      }

      if (m.text.equalsIgnoreCase("/help")) {
        bot.sendMessage(m.chat_id, "To send custom text, type: /text and your message. For example: '/text Santa rocks!'. To display the current weather in your area, simply send your location.");
     
      
      }
/*
      else if (m.text.equalsIgnoreCase("/watchdog")) {
        while (1);
      }
*/
      else if (m.text.equalsIgnoreCase("/start")) {
        bot.sendMessage(m.chat_id, "Welcome " + m.first_name + "! Send /help for more information.");

      }

      else if (m.text.startsWith("/text") || m.text.startsWith("/Text")) {
        m.text.remove(0, 6);
        m.text += "\n";

        if (m.text.length() <= 2) {
          bot.sendMessage(m.chat_id, "Sorry, your text is too short. Please send at least 2 characters.");
        }
        else if (m.text.length() >= 75) {
          bot.sendMessage(m.chat_id, "Sorry, your text is too long. The maxmimal text length is 74 characters.");
        }
        else if (numMessages == MAX_MESSAGES) {
          bot.sendMessage(m.chat_id, "Sorry, there are currently too many people sending messages to Santa. Try again later.");
        }
        else {
          if (m.first_name.length() >= 21) m.first_name.remove(20);
          eingabe = m.first_name + ": " + m.text;
          eingabe.toCharArray(Nachrichten[messageIndex], 100);
          eingabe = "";
          numMessages++;
          messageIndex++;
          if (messageIndex >= MAX_MESSAGES) messageIndex = 0;
          bot.sendMessage(m.chat_id, "Your text is on position " + String(numMessages) + " in the pipeline. It will be displayed in approximately " + String((numMessages) * 20) + " seconds.");
        }

#if DEBUG
        Serial.println("Der reine Text lautet: " + m.text);

#endif
        
      }
      /*
            else if (m.text.startsWith("/bierID")) {
              m.text.remove(0, 7);
              m.text.trim();
              beerHttpRequest(BEER_ID, m.text, m.chat_id);
            }

            else if (m.text.startsWith("/bier") || m.text.startsWith("/Bier")) {
              m.text.remove(0, 6);
              m.text.trim();
              beerHttpRequest(BEER_NAME, m.text, m.chat_id);
            }
      */
      /*
      else if (m.text.equalsIgnoreCase("/wlan")) {
        bot.sendMessage(m.chat_id, ssid);

      }
      else if (m.text.equalsIgnoreCase("/ip")) {
        IPAddress ip = WiFi.localIP();
        bot.sendMessage(m.chat_id, String(ip[0]) + String(".") + \
                        String(ip[1]) + String(".") + \
                        String(ip[2]) + String(".") + \
                        String(ip[3]));
      }
      else if (m.text.equalsIgnoreCase("/signal")) {
        long rssi = WiFi.RSSI();
        bot.sendMessage(m.chat_id, String(rssi) + " dBm");
      }
      */
    }

    else {
      bot.sendMessage(m.chat_id, "Sie sind nicht befugt diesen Befehl zu verwenden.");

      Serial.println(m.chat_id);
      Serial.println(m.sender);
    }
  }
}

int findText(String needle, String haystack) {
  int foundpos = -1;
  for (int i = 0; i <= haystack.length() - needle.length(); i++) {
    if (haystack.substring(i, needle.length() + i) == needle) {
      foundpos = i;
    }
  }
  return foundpos;
}

void weatherHttpRequest(String lat, String lon, String chatId) {
  if (clienthttp.connect(server, 80)) {
#if DEBUG
    Serial.println(F("Verbunden zum Server"));
#endif

    clienthttp.println("GET /data/2.5/weather?lat=" + lat + "&lon=" + lon + "&APPID=**yourapikey** HTTP/1.1");
    clienthttp.println("Host: api.openweathermap.org");
    clienthttp.println("Connection: close");
    clienthttp.println();

    responseBuffer[0] = '\0';
    rbindex = 0;

    startCapture = false;
  }

  else {
    bot.sendMessage(chatId, "Cannot connect to weather server.");
    return;
  }

  char c;
  unsigned long startTime = millis();
  while (clienthttp.connected() && millis() - startTime < 7000) {
    if (clienthttp.available()) {
      c = clienthttp.read();
      if (c == '{') {
        startCapture = true;
      }
      if (startCapture && rbindex < RBUFFSIZE) {
        responseBuffer[rbindex] = c;
        rbindex++;
      }
    }
  }


  if (clienthttp.connected()) {
    clienthttp.stop();
    clienthttp.flush();
#if DEBUG
    Serial.println(F("Client won't disconnect"));
#endif
    bot.sendMessage(chatId, "Data couldn't be recieved.");
    return;
  }

#if DEBUG
  Serial.println(F("Received bytes."));
  //Serial.print(strlen(responseBuffer));
  Serial.println(F("Disconnecting."));
#endif

  clienthttp.stop();
  clienthttp.flush();

  StaticJsonBuffer<RBUFFSIZE> jsonBuffer;

  JsonObject& root = jsonBuffer.parseObject(responseBuffer);

  if (!root.success()) {
#if DEBUG
    Serial.println(F("parseObject() fehlgeschlagen"));
#endif

    bot.sendMessage(chatId, "Datenverarbeitung fehlgeschlagen.");
  }
  else {
    char bufferw[20];
    char bufferc[30];
    root["weather"][0]["description"].prettyPrintTo(bufferw, sizeof(bufferw));
    root["name"].prettyPrintTo(bufferc, sizeof(bufferc));

#if DEBUG
    Serial.println("Schnee: " + String((float)root["snow"]["3h"]));
#endif
    //bot.sendMessage(m.chat_id, "Aktuelle Wetter fr " + String(bufferc) + ":");
    textSerial.write(TEXT);
    eingabe = "Current weather in " + String(bufferc) + ": " + String(bufferw) + " at " + String(((double)root["main"]["temp"]) - 273.15, 1) + " C";
    textSerial.print(eingabe);
    lastTextSent = millis();
    eingabe = "";
    
    bot.sendMessage(chatId, "The current weather in your region will be displayed soon.");

#if DEBUG
    Serial.print(F("Aktuelle Temperatur: "));
    Serial.print(((double)root["main"]["temp"]) - 273.15);
    Serial.println(F(" C"));

    Serial.print(F("Beschreibung: "));
    Serial.println(bufferw);
#endif
  }
}
void getCurrentTime() {
  if (clienthttp.connect("api.timezonedb.com", 80)) {
    clienthttp.println("GET /v2/get-time-zone?key=**yourapikey**&format=json&by=zone&zone=Europe/London HTTP/1.1");
    clienthttp.println("Host: api.timezonedb.com");
    clienthttp.println("Connection: close");
    clienthttp.println();

    responseBuffer[0] = '\0';
    rbindex = 0;

    startCapture = false;
  }

  else {
    Serial.println("Verbindung zum Zeitserver fehlgeschlagen");
    return;
  }

   char c;
  unsigned long startTime = millis();

  while (clienthttp.connected() && millis() - startTime < 7000) {
    if (clienthttp.available()) {
      c = clienthttp.read();
      if (c == '{') {
        startCapture = true;
      }
      if (startCapture && rbindex < RBUFFSIZE) {
        responseBuffer[rbindex] = c;
        rbindex++;
      }
    }
  }

  if (clienthttp.connected()) {
    clienthttp.stop();
    clienthttp.flush();
  #if DEBUG
    Serial.println(F("Client won't disconnect"));
  #endif
    return;
  }


  #if DEBUG
  Serial.println(F("Received bytes."));
  //Serial.print(strlen(responseBuffer));
  Serial.println(F("Disconnecting."));
  #endif

  clienthttp.stop();
  clienthttp.flush();

  StaticJsonBuffer<RBUFFSIZE> jsonBuffer;

  JsonObject& root = jsonBuffer.parseObject(responseBuffer);

  if (!root.success()) {
  #if DEBUG
    Serial.println(F("parseObject() fehlgeschlagen"));
  #endif

  }
  else {
    tClock = root["timestamp"];
    Serial.println(tClock);
  }
}
//wanted to implement the brewery db but couldn't make it work so fart
/*
  void beerHttpRequest(boolean type, String request, String chatId) {
  if (clienthttp.connect("api.brewerydb.com", 80)) {
  #if DEBUG
    Serial.println(F("Verbunden zum Server"));
  #endif

    if (type == BEER_NAME) clienthttp.println("GET /v2/search?q=" + request + "&type=beer&key=**yourapikey**&format=json HTTP/1.1");
    else clienthttp.println("GET /v2/beer/" + request + "?key=**yourapikey**&format=json HTTP/1.1");
    clienthttp.println("Host: api.brewerydb.com");
    clienthttp.println("Connection: close");
    clienthttp.println();

    responseBuffer[0] = '\0';
    rbindex = 0;

    startCapture = false;

  }

  else {
    bot.sendMessage(chatId, "Verbindung zur Brewery Databse nicht mglich.");
    return;
  }

  char c;
  unsigned long startTime = millis();

  while (clienthttp.connected() && millis() - startTime < 7000) {
    if (clienthttp.available()) {
      c = clienthttp.read();
      if (c == '{') {
        startCapture = true;
      }
      if (startCapture && rbindex < RBUFFSIZE) {
        responseBuffer[rbindex] = c;
        rbindex++;
      }
    }
  }

  if (clienthttp.connected()) {
    clienthttp.stop();
    clienthttp.flush();
  #if DEBUG
    Serial.println(F("Client won't disconnect"));
  #endif
    bot.sendMessage(chatId, "Daten konnten nicht empfangen werden.");
    return;
  }


  #if DEBUG
  Serial.println(F("Received bytes."));
  //Serial.print(strlen(responseBuffer));
  Serial.println(F("Disconnecting."));
  #endif

  clienthttp.stop();
  clienthttp.flush();

  StaticJsonBuffer<RBUFFSIZE> jsonBuffer;

  JsonObject& root = jsonBuffer.parseObject(responseBuffer);

  if (!root.success()) {
  #if DEBUG
    Serial.println(F("parseObject() fehlgeschlagen"));
  #endif

    bot.sendMessage(chatId, "Datenverarbeitung fehlgeschlagen.");
  }
  else {

    if (type == BEER_NAME) {
      char bufferName[10][50]; // char bufferName[10][50];
      char bufferID[10][9];   //char bufferID[10][6];
      int results = root["totalResults"];  // int results = atoi(&root["totalResults"]);
      if (results > 10) results = 10;
      for (byte i = 0; i < results; i++) {

        root["data"][i]["id"].prettyPrintTo(bufferID[i], sizeof(bufferID[i]));
        root["data"][i]["name"].prettyPrintTo(bufferName[i], sizeof(bufferName[i]));
      }

      String beerMessage;

      if (results > 0) {
        beerMessage = "Hier die ersten " + String(results) + " Treffer. Meintest du:";
        for (byte i = 0; i < results; i++) {
          beerMessage += '\n';
          beerMessage += String(bufferName[i]);
          beerMessage += ": /bierID";
          String IDtemp = String(bufferID[i]);
          IDtemp.remove(0, 1);
          IDtemp.remove(6, 1);
          beerMessage += IDtemp;
        }
      }

      else beerMessage = "Zu diesem Suchbegriff gab es leider keine Treffer.";

      bot.sendMessage(chatId, beerMessage);
  #if DEBUG
      Serial.println(F("test"));
  #endif
    }
    else {
      char bufferDescription[1000];
      char bufferStyle[50];

      if (root["data"]["description"] != NULL) {
        root["data"]["description"].prettyPrintTo(bufferDescription, sizeof(bufferDescription));
        bot.sendMessage(chatId, bufferDescription);
  #if DEBUG
        Serial.println(bufferDescription);
  #endif
      }
      if (root["data"]["style"]["description"] != NULL) {
        root["data"]["style"]["description"].prettyPrintTo(bufferDescription, sizeof(bufferDescription));
        root["data"]["style"]["name"].prettyPrintTo(bufferStyle, sizeof(bufferStyle));

        bot.sendMessage(chatId, "Es handelt sich um ein: " + String(bufferStyle) + ".'\n'" + String(bufferDescription));
  #if DEBUG
        Serial.println("Es handelt sich um ein: " + String(bufferStyle) + ".'\n'" + String(bufferDescription));
  #endif
      }
    }
  }
  }
*/

dot_matrix_scrolling_text_test.ino

Arduino
Code for the Arduino Nano to run the dot matrix.
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Time.h>
#include <TimeLib.h>
#include "Parola_Fonts_data.h"

#define MAX_DEVICES 8
#define CLK_PIN   13
#define DATA_PIN  11
#define CS_PIN    10

#define TIME 1
#define TEXT 2
#define WEATHER 3
#define STATS 4

#define NUM_TIMEZONES 8

MD_Parola P = MD_Parola(CS_PIN, MAX_DEVICES);

uint8_t scrollSpeed = 30;    // default frame delay value
textEffect_t scrollEffect = PA_SCROLL_LEFT;
textPosition_t scrollAlign = PA_CENTER;
uint16_t scrollPause = 0; // in milliseconds

#define	BUF_SIZE	100
char curMessage[BUF_SIZE] = "www.steuermeinentannenbaum.de"; //{0x01};
char newMessage[BUF_SIZE];
String newMessageString = "";
bool newMessageAvailable = false, showClock = false;
unsigned long lastNewMessageTime;
byte dataByte = 0, zoneIndex, timeBuffer[3];

struct timeZonesStruct {
  int GMT;
  String name;
};

timeZonesStruct timeZones[NUM_TIMEZONES] = {
  {1, "Berlin"},
  {0, "UK"},
  { -8, "LA"},
  { -6, "Texas"},
  { -5, "NYC"},
  {11, "Sydney"},
  {9, "Tokyo"},
  {3, "Russia"}
};

/*
  void readSerial(void)
  {
  static char *cp = newMessage;

  while (Serial1.available())
  {
     cp = (char)Serial1.read();
    if ((*cp == '\n') || (cp - newMessage >= BUF_SIZE-2)) // end of message character or full buffer
    {
       cp = '\0'; // end the string
      // restart the index for next filling spree and flag we have a message waiting
      cp = newMessage;
      newMessageAvailable = true;
      Serial.println(cp);
      //Serial.flush();
    }
    else  // move char pointer to next position
      cp++;
  }
  }
*/

void readSerial(void) {
  if (Serial.available()) {
    if (dataByte == 0) dataByte = Serial.read();
    else if (dataByte == TEXT) {
      newMessageString = Serial.readStringUntil('\n');
      newMessageString.toCharArray(newMessage, BUF_SIZE);
      newMessageAvailable = true;
      showClock = false;
      lastNewMessageTime = millis();
      dataByte = 0;
    }
    else if (dataByte == TIME) {
      Serial.readBytes(timeBuffer, 3);
      setTime(timeBuffer[0], timeBuffer[1], timeBuffer[2], day(), month(), year());
      dataByte = 0;
    }
  }
}



void setup()
{
  Serial.begin(9600);

  randomSeed(analogRead(0));


  P.begin();
  P.setFont(ExtASCII);
  P.displayClear();
  P.displaySuspend(false);

  //P.setScrollSpacing(1);

  lastNewMessageTime = millis();

  P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);

  //newMessage[0] = '\0';
}

void loop()
{
  readSerial();

  if (millis() - lastNewMessageTime > 30000) showClock = true;

  if (P.displayAnimate())
  {
    if (showClock) {
      P.setTextEffect(PA_SCROLL_DOWN, PA_SCROLL_DOWN);
      P.setPause(5000);

      newMessageString = timeZones[zoneIndex].name + ": ";
      newMessageString += returnLocalTime(zoneIndex);

      zoneIndex++;
      if (zoneIndex >= NUM_TIMEZONES) zoneIndex = 0;

      newMessageString.toCharArray(newMessage, BUF_SIZE);
      strcpy(curMessage, newMessage);
      newMessageString = "";

      P.displayReset();
    }
    else if (newMessageAvailable)
    {
      P.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
      P.setPause(0);

      strcpy(curMessage, newMessage);
      newMessageAvailable = false;
      newMessageString = "";
    }
    P.displayReset();
  }
}

String returnLocalTime(byte index) {
  String temp;
  int hourInt = hour() + timeZones[index].GMT;

  if (hourInt < 0) hourInt = 24 + hourInt;
  else if (hourInt > 23) hourInt -= 24;

  if (hourInt < 10) temp += "0";
  temp += String(hourInt) + ":";
  if (minute() < 10) temp += "0";
  temp += String(minute());

  return temp;
}

Parola_Fonts_data.h

Arduino
Fonts for the dot matrix to allow umlaute etc.
// Data file for UTF-8 example user defined fonts
#ifndef FONTS_DATA_H
#define FONTS_DATA_H

MD_MAX72XX::fontType_t ExtASCII[] PROGMEM =
{
  0,		// 0 - 'Unused'
  0,		// 1 - 'Unused'
  0,		// 2 - 'Unused'
  0,		// 3 - 'Unused'
  0,		// 4 - 'Unused'
  0,		// 5 - 'Unused'
  0,		// 6 - 'Unused'
  0,		// 7 - 'Unused'
  0,		// 8 - 'Unused'
  0,		// 9 - 'Unused'
  0,		// 10 - 'Unused'
  0,		// 11 - 'Unused'
  0,		// 12 - 'Unused'
  0,		// 13 - 'Unused'
  0,		// 14 - 'Unused'
  0,		// 15 - 'Unused'
  0,		// 16 - 'Unused'
  0,		// 17 - 'Unused'
  0,		// 18 - 'Unused'
  0,		// 19 - 'Unused'
  0,		// 20 - 'Unused'
  0,		// 21 - 'Unused'
  0,		// 22 - 'Unused'
  0,		// 23 - 'Unused'
  0,		// 24 - 'Unused'
  0,		// 25 - 'Unused'
  0,		// 26 - 'Unused'
  0,		// 27 - 'Unused'
  0,		// 28 - 'Unused'
  0,		// 29 - 'Unused'
  0,		// 30 - 'Unused'
  0,		// 31 - 'Unused'
  2, 0, 0,		// 32 - 'Space'
  1, 95,		// 33 - '!'
  3, 7, 0, 7,		// 34 - '"'
  5, 20, 127, 20, 127, 20,		// 35 - '#'
  5, 36, 42, 127, 42, 18,		// 36 - '$'
  5, 35, 19, 8, 100, 98,		// 37 - '%'
  5, 54, 73, 86, 32, 80,		// 38 - '&'
  2, 4, 3,		// 39
  3, 28, 34, 65,		// 40 - '('
  3, 65, 34, 28,		// 41 - ')'
  5, 42, 28, 127, 28, 42,		// 42 - '*'
  5, 8, 8, 62, 8, 8,		// 43 - '+'
  2, 128, 96,		// 44 - ','
  5, 8, 8, 8, 8, 8,		// 45 - '-'
  2, 96, 96,		// 46 - '.'
  5, 32, 16, 8, 4, 2,		// 47 - '/'
  5, 62, 81, 73, 69, 62,		// 48 - '0'
  3, 66, 127, 64,		// 49 - '1'
  5, 114, 73, 73, 73, 70,		// 50 - '2'
  5, 33, 65, 73, 77, 51,		// 51 - '3'
  5, 24, 20, 18, 127, 16,		// 52 - '4'
  5, 39, 69, 69, 69, 57,		// 53 - '5'
  5, 60, 74, 73, 73, 49,		// 54 - '6'
  5, 65, 33, 17, 9, 7,		// 55 - '7'
  5, 54, 73, 73, 73, 54,		// 56 - '8'
  5, 70, 73, 73, 41, 30,		// 57 - '9'
  1, 20,		// 58 - ':'
  2, 128, 104,		// 59 - ';'
  4, 8, 20, 34, 65,		// 60 - '<'
  5, 20, 20, 20, 20, 20,		// 61 - '='
  4, 65, 34, 20, 8,		// 62 - '>'
  5, 2, 1, 89, 9, 6,		// 63 - '?'
  5, 62, 65, 93, 89, 78,		// 64 - '@'
  5, 124, 18, 17, 18, 124,		// 65 - 'A'
  5, 127, 73, 73, 73, 54,		// 66 - 'B'
  5, 62, 65, 65, 65, 34,		// 67 - 'C'
  5, 127, 65, 65, 65, 62,		// 68 - 'D'
  5, 127, 73, 73, 73, 65,		// 69 - 'E'
  5, 127, 9, 9, 9, 1,		// 70 - 'F'
  5, 62, 65, 65, 81, 115,		// 71 - 'G'
  5, 127, 8, 8, 8, 127,		// 72 - 'H'
  3, 65, 127, 65,		// 73 - 'I'
  5, 32, 64, 65, 63, 1,		// 74 - 'J'
  5, 127, 8, 20, 34, 65,		// 75 - 'K'
  5, 127, 64, 64, 64, 64,		// 76 - 'L'
  5, 127, 2, 28, 2, 127,		// 77 - 'M'
  5, 127, 4, 8, 16, 127,		// 78 - 'N'
  5, 62, 65, 65, 65, 62,		// 79 - 'O'
  5, 127, 9, 9, 9, 6,		// 80 - 'P'
  5, 62, 65, 81, 33, 94,		// 81 - 'Q'
  5, 127, 9, 25, 41, 70,		// 82 - 'R'
  5, 38, 73, 73, 73, 50,		// 83 - 'S'
  5, 3, 1, 127, 1, 3,		// 84 - 'T'
  5, 63, 64, 64, 64, 63,		// 85 - 'U'
  5, 31, 32, 64, 32, 31,		// 86 - 'V'
  5, 63, 64, 56, 64, 63,		// 87 - 'W'
  5, 99, 20, 8, 20, 99,		// 88 - 'X'
  5, 3, 4, 120, 4, 3,		// 89 - 'Y'
  5, 97, 89, 73, 77, 67,		// 90 - 'Z'
  3, 127, 65, 65,		// 91 - '['
  5, 2, 4, 8, 16, 32,		// 92 - '\'
  3, 65, 65, 127,		// 93 - ']'
  5, 4, 2, 1, 2, 4,		// 94 - '^'
  5, 64, 64, 64, 64, 64,		// 95 - '_'
  2, 3, 4,		// 96 - '`'
  5, 32, 84, 84, 120, 64,		// 97 - 'a'
  5, 127, 40, 68, 68, 56,		// 98 - 'b'
  5, 56, 68, 68, 68, 40,		// 99 - 'c'
  5, 56, 68, 68, 40, 127,		// 100 - 'd'
  5, 56, 84, 84, 84, 24,		// 101 - 'e'
  4, 8, 126, 9, 2,		// 102 - 'f'
  5, 24, 164, 164, 156, 120,		// 103 - 'g'
  5, 127, 8, 4, 4, 120,		// 104 - 'h'
  3, 68, 125, 64,		// 105 - 'i'
  4, 64, 128, 128, 122,		// 106 - 'j'
  4, 127, 16, 40, 68,		// 107 - 'k'
  3, 65, 127, 64,		// 108 - 'l'
  5, 124, 4, 120, 4, 120,		// 109 - 'm'
  5, 124, 8, 4, 4, 120,		// 110 - 'n'
  5, 56, 68, 68, 68, 56,		// 111 - 'o'
  5, 252, 24, 36, 36, 24,		// 112 - 'p'
  5, 24, 36, 36, 24, 252,		// 113 - 'q'
  5, 124, 8, 4, 4, 8,		// 114 - 'r'
  5, 72, 84, 84, 84, 36,		// 115 - 's'
  4, 4, 63, 68, 36,		// 116 - 't'
  5, 60, 64, 64, 32, 124,		// 117 - 'u'
  5, 28, 32, 64, 32, 28,		// 118 - 'v'
  5, 60, 64, 48, 64, 60,		// 119 - 'w'
  5, 68, 40, 16, 40, 68,		// 120 - 'x'
  5, 76, 144, 144, 144, 124,		// 121 - 'y'
  5, 68, 100, 84, 76, 68,		// 122 - 'z'
  3, 8, 54, 65,		// 123 - '{'
  1, 119,		// 124 - '|'
  3, 65, 54, 8,		// 125 - '}'
  5, 2, 1, 2, 4, 2,		// 126 - '~'
  0,		// 127 - 'Unused'
  6, 20, 62, 85, 85, 65, 34,		// 128 - 'Euro sign'
  5, 28, 62, 124, 62, 28,		// 129 - 'Heart' costum char!!!
  2, 128, 96,		// 130 - 'Single low 9 quotation mark'
  5, 192, 136, 126, 9, 3,		// 131 - 'f with hook'
  4, 128, 96, 128, 96,		// 132 - 'Single low 9 quotation mark'
  8, 96, 96, 0, 96, 96, 0, 96, 96,		// 133 - 'Horizontal ellipsis'
  3, 4, 126, 4,		// 134 - 'Dagger'
  3, 20, 126, 20,		// 135 - 'Double dagger'
  4, 2, 1, 1, 2,		// 136 - 'Modifier circumflex'
  7, 35, 19, 104, 100, 2, 97, 96,		// 137 - 'Per mille sign'
  5, 72, 85, 86, 85, 36,		// 138 - 'S with caron'
  3, 8, 20, 34,		// 139 - '< quotation'
  6, 62, 65, 65, 127, 73, 73,		// 140 - 'OE'
  0,		// 141 - 'Not used'
  5, 68, 101, 86, 77, 68,		// 142 - 'z with caron'
  0,		// 143 - 'Not used'
  0,		// 144 - 'Not used'
  2, 3, 4,		// 145 - 'Left single quote mark'
  2, 4, 3,		// 146 - 'Right single quote mark'
  4, 3, 4, 3, 4,		// 147 - 'Left double quote marks'
  4, 4, 3, 4, 3,		// 148 - 'Right double quote marks'
  4, 0, 24, 60, 24,		// 149 - 'Bullet Point'
  3, 8, 8, 8,		// 150 - 'En dash'
  5, 8, 8, 8, 8, 8,		// 151 - 'Em dash'
  4, 2, 1, 2, 1,		// 152 - 'Small ~'
  7, 1, 15, 1, 0, 15, 2, 15,		// 153 - 'TM'
  5, 72, 85, 86, 85, 36,		// 154 - 's with caron'
  3, 34, 20, 8,		// 155 - '> quotation'
  7, 56, 68, 68, 124, 84, 84, 8,		// 156 - 'oe'
  0,		// 157 - 'Not used'
  5, 68, 101, 86, 77, 68,		// 158 - 'z with caron'
  5, 12, 17, 96, 17, 12,		// 159 - 'Y diaresis'
  2, 0, 0,		// 160 - 'Non-breaking space'
  1, 125,		// 161 - 'Inverted !'
  5, 60, 36, 126, 36, 36,		// 162 - 'Cent sign'
  5, 72, 126, 73, 65, 102,		// 163 - 'Pound sign'
  5, 34, 28, 20, 28, 34,		// 164 - 'Currency sign'
  5, 43, 47, 252, 47, 43,		// 165 - 'Yen'
  1, 119,		// 166 - '|'
  4, 102, 137, 149, 106,		// 167 - 'Section sign'
  3, 1, 0, 1,		// 168 - 'Spacing diaresis'
  7, 62, 65, 93, 85, 85, 65, 62,		// 169 - 'Copyright'
  3, 13, 13, 15,		// 170 - 'Feminine Ordinal Ind.'
  5, 8, 20, 42, 20, 34,		// 171 - '<<'
  5, 8, 8, 8, 8, 56,		// 172 - 'Not sign'
  0,		// 173 - 'Soft Hyphen'
  7, 62, 65, 127, 75, 117, 65, 62,		// 174 - 'Registered Trademark'
  5, 1, 1, 1, 1, 1,		// 175 - 'Spacing Macron Overline'
  3, 2, 5, 2,		// 176 - 'Degree'
  5, 68, 68, 95, 68, 68,		// 177 - '+/-'
  3, 25, 21, 19,		// 178 - 'Superscript 2'
  3, 17, 21, 31,		// 179 - 'Superscript 3'
  2, 2, 1,		// 180 - 'Acute accent'
  4, 252, 64, 64, 60,		// 181 - 'micro (mu)'
  5, 6, 9, 127, 1, 127,		// 182 - 'Paragraph Mark'
  2, 24, 24,		// 183 - 'Middle Dot'
  3, 128, 128, 96,		// 184 - 'Spacing sedilla'
  2, 2, 31,		// 185 - 'Superscript 1'
  4, 6, 9, 9, 6,		// 186 - 'Masculine Ordinal Ind.'
  5, 34, 20, 42, 20, 8,		// 187 - '>>'
  6, 64, 47, 16, 40, 52, 250,		// 188 - '1/4'
  6, 64, 47, 16, 200, 172, 186,		// 189 - '1/2'
  6, 85, 53, 31, 40, 52, 250,		// 190 - '3/4'
  5, 48, 72, 77, 64, 32,		// 191 - 'Inverted ?'
  5, 120, 20, 21, 22, 120,		// 192 - 'A grave'
  5, 120, 22, 21, 20, 120,		// 193 - 'A acute'
  5, 122, 21, 21, 21, 122,		// 194 - 'A circumflex'
  5, 120, 22, 21, 22, 121,		// 195 - 'A tilde'
  5, 120, 21, 20, 21, 120,		// 196 - 'A diaresis'
  5, 120, 20, 21, 20, 120,		// 197 - 'A ring above'
  6, 124, 10, 9, 127, 73, 73,		// 198 - 'AE'
  5, 30, 161, 161, 97, 18,		// 199 - 'C sedilla'
  4, 124, 85, 86, 68,		// 200 - 'E grave'
  4, 124, 86, 85, 68,		// 201 - 'E acute'
  4, 126, 85, 85, 70,		// 202 - 'E circumflex'
  4, 124, 85, 84, 69,		// 203 - 'E diaresis'
  3, 68, 125, 70,		// 204 - 'I grave'
  3, 68, 126, 69,		// 205 - 'I acute'
  3, 70, 125, 70,		// 206 - 'I circumplex'
  3, 69, 124, 69,		// 207 - 'I diaresis'
  6, 4, 127, 69, 65, 65, 62,		// 208 - 'Capital Eth'
  5, 124, 10, 17, 34, 125,		// 209 - 'N tilde'
  5, 56, 68, 69, 70, 56,		// 210 - 'O grave'
  5, 56, 70, 69, 68, 56,		// 211 - 'O acute'
  5, 58, 69, 69, 69, 58,		// 212 - 'O circumflex'
  5, 56, 70, 69, 70, 57,		// 213 - 'O tilde'
  5, 56, 69, 68, 69, 56,		// 214 - 'O diaresis'
  5, 34, 20, 8, 20, 34,		// 215 - 'Multiplication sign'
  7, 64, 62, 81, 73, 69, 62, 1,		// 216 - 'O slashed'
  5, 60, 65, 66, 64, 60,		// 217 - 'U grave'
  5, 60, 64, 66, 65, 60,		// 218 - 'U acute'
  5, 58, 65, 65, 65, 58,		// 219 - 'U circumflex'
  5, 60, 65, 64, 65, 60,		// 220 - 'U diaresis'
  5, 12, 16, 98, 17, 12,		// 221 - 'Y acute'
  4, 127, 18, 18, 12,		// 222 - 'Capital thorn'
  4, 254, 37, 37, 26,		// 223 - 'Small letter sharp S'
  5, 32, 84, 85, 122, 64,		// 224 - 'a grave'
  5, 32, 84, 86, 121, 64,		// 225 - 'a acute'
  5, 34, 85, 85, 121, 66,		// 226 - 'a circumflex'
  5, 32, 86, 85, 122, 65,		// 227 - 'a tilde'
  5, 32, 85, 84, 121, 64,		// 228 - 'a diaresis'
  5, 32, 84, 85, 120, 64,		// 229 - 'a ring above'
  7, 32, 84, 84, 124, 84, 84, 8,		// 230 - 'ae'
  5, 24, 36, 164, 228, 40,		// 231 - 'c sedilla'
  5, 56, 84, 85, 86, 88,		// 232 - 'e grave'
  5, 56, 84, 86, 85, 88,		// 233 - 'e acute'
  5, 58, 85, 85, 85, 90,		// 234 - 'e circumflex'
  5, 56, 85, 84, 85, 88,		// 235 - 'e diaresis'
  3, 68, 125, 66,		// 236 - 'i grave'
  3, 68, 126, 65,		// 237 - 'i acute'
  3, 70, 125, 66,		// 238 - 'i circumflex'
  3, 69, 124, 65,		// 239 - 'i diaresis'
  4, 48, 75, 74, 61,		// 240 - 'Small eth'
  4, 122, 9, 10, 113,		// 241 - 'n tilde'
  5, 56, 68, 69, 70, 56,		// 242 - 'o grave'
  5, 56, 70, 69, 68, 56,		// 243 - 'o acute'
  5, 58, 69, 69, 69, 58,		// 244 - 'o circumflex'
  5, 56, 70, 69, 70, 57,		// 245 - 'o tilde'
  5, 56, 69, 68, 69, 56,		// 246 - 'o diaresis'
  5, 8, 8, 42, 8, 8,		// 247 - 'Division sign'
  6, 64, 56, 84, 76, 68, 58,		// 248 - 'o slashed'
  5, 60, 65, 66, 32, 124,		// 249 - 'u grave'
  5, 60, 64, 66, 33, 124,		// 250 - 'u acute'
  5, 58, 65, 65, 33, 122,		// 251 - 'u circumflex'
  5, 60, 65, 64, 33, 124,		// 252 - 'u diaresis'
  4, 156, 162, 161, 124,		// 253 - 'y acute'
  4, 252, 72, 72, 48,		// 254 - 'small thorn'
  4, 157, 160, 160, 125,		// 255 - 'y diaresis'
};

#endif

TelegramBot.cpp

Arduino
I made some changes to the TelegramBot library to get the first name and location of the user. And also a function to convert utf16 to extended ascii for use with the dot matrix.
#include "TelegramBot.h"

TelegramBot::TelegramBot(const char* token, Client &client)	{
	this->client = &client;
	this->token=token;
}


void TelegramBot::begin()	{
	if(!client->connected()){
		client->connect(HOST, SSL_PORT);
	}
}

/************************************************************************************
 * GetUpdates - function to receive messages from telegram as a Json and parse them *
 ************************************************************************************/
message TelegramBot::getUpdates()  {
		begin();

		//Send your request to api.telegram.org
		String getRequest = "GET /bot"+String(token)+"/getUpdates?limit=1&offset="+String(last_message_recived)+" HTTP/1.1";
		client->println(getRequest);
		client->println("User-Agent: curl/7.37.1");
		client->println("Host: api.telegram.org");
		client->println("Accept: */*");
		client->println();

		String payload = readPayload();
	    if (payload != "") {
			message m;
			StaticJsonBuffer<JSON_BUFF_SIZE> jsonBuffer;
			JsonObject & root = jsonBuffer.parseObject(payload);



			if(root.success()){

				int update_id = root["result"][0]["update_id"];
				update_id = update_id+1;

				if(last_message_recived != update_id ){
					String sender = root["result"][0]["message"]["from"]["username"];
					String text = root["result"][0]["message"]["text"];
					String chat_id = root["result"][0]["message"]["chat"]["id"];
					String date = root["result"][0]["message"]["date"];
                    String first_name = root["result"][0]["message"]["from"]["first_name"];
                    String lon = root["result"][0]["message"]["location"]["longitude"];
                    String lat = root["result"][0]["message"]["location"]["latitude"];

					m.sender = sender;
					m.text = text;
					m.chat_id = chat_id;
					m.date = date;
                    m.first_name = first_name;
                    m.location.lon = lon;
                    m.location.lat = lat;
                    
					last_message_recived=update_id;
					return m;
				}else{
					m.chat_id = "";
					return m;
				}
			}
			else{
				Serial.println("");
				Serial.println("Message too long, skipped.");
				Serial.println("");
				int update_id_first_digit=0;
				int update_id_last_digit=0;
				for(int a =0; a<3; a++){
					update_id_first_digit= payload.indexOf(':',update_id_first_digit+1);
				}
				for(int a =0; a<2; a++){
					update_id_last_digit= payload.indexOf(',',update_id_last_digit+1);
				}
			last_message_recived = payload.substring(update_id_first_digit+1,update_id_last_digit).toInt() +1;
			}
		}
	}

// send message function
// send a simple text message to a telegram char
String TelegramBot::sendMessage(String chat_id, String text)  {
	if(chat_id!="0" && chat_id!=""){
		StaticJsonBuffer<JSON_BUFF_SIZE> jsonBuffer;
		JsonObject& buff = jsonBuffer.createObject();
		buff["chat_id"] = chat_id;
		buff["text"] = text;

		String msg;
		buff.printTo(msg);
		return postMessage(msg);
	} else {
		Serial.println("Chat_id not defined");
	}
}

// send a message to a telegram chat with a reply markup
String TelegramBot::sendMessage(String chat_id, String text, TelegramKeyboard &keyboard_markup, bool one_time_keyboard, bool resize_keyboard)  {
		StaticJsonBuffer<JSON_BUFF_SIZE> jsonBuffer;
		JsonObject& buff = jsonBuffer.createObject();
		buff["chat_id"] = chat_id;
		buff["text"] = text;

		JsonObject& reply_markup = buff.createNestedObject("reply_markup");
		JsonArray& keyboard = reply_markup.createNestedArray("keyboard");

		for (int a = 1 ; a <= keyboard_markup.length() ; a++){
			JsonArray& row = keyboard.createNestedArray();
				for( int b = 1; b <= keyboard_markup.rowSize(a) ; b++){
					row.add(keyboard_markup.getButton(a,b));
				}
		}

		reply_markup.set<bool>("one_time_keyboard", one_time_keyboard);
		reply_markup.set<bool>("resize_keyboard", resize_keyboard);
		reply_markup.set<bool>("selective", false);

		String msg;
		buff.printTo(msg);
		// Serial.println(msg);
		return postMessage(msg);
}

// gets the telegram json string
// posts the message to telegram
// returns the payload
String TelegramBot::postMessage(String msg) {
		begin();

		client->println("POST /bot"+String(token)+"/sendMessage"+" HTTP/1.1");
		client->println("Host: api.telegram.org");
	    client->println("Content-Type: application/json");
	    client->println("Connection: close");
	    client->print("Content-Length: ");
	    client->println(msg.length());
	    client->println();
	    client->println(msg);

		return readPayload();
}

// reads the payload coming from telegram server
// returns the payload string
String TelegramBot::readPayload(){
	char c;
	String payload="";
		//Read the answer and save it in String payload
		while (client->connected()) {
		payload = client->readStringUntil('\n');
		if (payload == "\r") {
			break;
		 }
	  }
	payload = client->readStringUntil('\r');
	// Serial.println(payload);
	return convertText(payload);
}

String TelegramBot::convertText(String unicodeStr){ //converts UTF-16 Unicode to extended Ascii. Emojis not supported
  String out = "";
  int len = unicodeStr.length();
  char iChar;
  char* error;
  for (int i = 0; i < len; i++){
     iChar = unicodeStr[i];
     if(iChar == '\\'){ // got escape char
       iChar = unicodeStr[++i];
       if(iChar == 'u'){ // got unicode hex
         char unicode[6];
         unicode[0] = '0';
         unicode[1] = 'x';
         for (int j = 0; j < 4; j++){
           iChar = unicodeStr[++i];
           unicode[j + 2] = iChar;
         }
         long unicodeVal = strtol(unicode, &error, 16); //convert the string
         if (unicodeVal == 0x20AC) unicodeVal = 0x80; // euro sign
         //Serial.print("strtol: ");
         //Serial.println(unicodeVal, HEX);
         out += (char)unicodeVal;
       } else if(iChar == '/'){
         out += iChar;
       } else if(iChar == 'n'){
         out += '\n';
       }
     } else if (iChar == '<' && unicodeStr[i + 1] == '3') { //convert "<3" to costum set 'heart' char, ascii: 0x81
         long charTemp = 0x81;
         out += (char)charTemp;
         i++;
     } else {
       out += iChar;
     }
  }
  return out;
}

TelegramBot.h

Arduino
Adapt to changed *.cpp
// Copyright Casa Jasmina 2016
// LGPL License
//
// TelegramBot library
// https://github.com/CasaJasmina/TelegramBot-Library

#ifndef TelegramBot_h
#define TelegramBot_h

#include <Arduino.h>
#include <ArduinoJson.h>
#include <Client.h>
#include <TelegramKeyboard.h>

#define HOST "api.telegram.org"
#define SSL_PORT 443
#define JSON_BUFF_SIZE 10000

struct locationStruct{
    String lon;
    String lat;
};

struct message{
  String text;
  String chat_id;
  String sender;
  String first_name;
  String date;
  locationStruct location;
};


class TelegramBot
{
  public:
    TelegramBot(const char* token, Client &client);
  	void begin();
    String sendMessage(String chat_id, String text);
    String sendMessage(String chat_id, String text, TelegramKeyboard &keyboard_markup, bool one_time_keyboard = true, bool resize_keyboard = true);
    String postMessage(String msg);
    message getUpdates();
    

  private:
      String readPayload();
      String convertText(String unicodeStr);
      const char* token;
      
      int last_message_recived;

      Client *client;
};

#endif

Credits

Tobi_Lxtr

Tobi_Lxtr

3 projects • 14 followers
Electrical engineer and tinkerer from Germany.

Comments