Project showcase
I Let Everyone on the Internet Control My X-Mas Decoration 2

I Let Everyone on the Internet Control My X-Mas Decoration 2 © GPL3+

Control 55 RGB LEDs on the tree, let Santa twerk, control a model railroad, bus and balloon, or send your personal message to the display.

  • 11 views
  • 0 comments
  • 0 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

About this project

"ControlMyXMasTree.com" is finally back - and we added some new features!

After last years awesome feedback to our little web controllable Christmas decoration and because of the many people who told us that we brought a little joy to them and their family, we definitely wanted to bring it back this year.

So here we are. We proudly present: "www.ControlMyXMasTree.com 2.0"

If you haven't seen last year's post in which I explain the fundamental functionality of the system, you may want to read that first, since I will only discuss all the updates and new features we added here. ;)

New Devices

To accompany Santa so he doesn't have to twerk on his own, we already added the dancing X-mas tree last year, which naturally is back this year and connects through a MOSFET to the main controller, an Arduino Mega 2560.

But we also threw in two completely new devices: An hot-air-balloon and a little remote controlled bus with its dedicated charging station.

Bus and Charging Station

The bus is a little toy "line follow" car I bought online. We initially wanted to draw a line onto the board so the bus would follow along, but that was way too jerkily and the radii it could take were quite big. So my friend had the idea to 3D print a little pin which we would put to the bottom of the bus and mill a groove into the board which would guide the pin and therefore the bus. Perfect!

So I opened the bus, removed the batteries and electronics, except from the motors and gear boxes, and put in a little LiPo, a 433MHz receiver and a voltage regulator.

To control the bus and charge it automatically I designed a little bus stop in CAD and 3D printed it, so we could put in an IR distance sensor to count the laps and more importantly position the bus correctly to be charged by the gripper arm above it. Therefore I added two little wires to the outside of the bus so they look like side mirrors, which then are connected to the battery in the inside.

All that is controlled by an Arduino Nano which sits behind the bus station and connects to a 433 MHz transmitter to power on or off the bus and send it to charging every 20 laps. The charging process itself is handled by an TP4056 5V LiPo charging module which outputs are soldered to small copper foil pieces on the gripper.

Charging process in detail

The Arduino Nano is then connected to the main Arduino Mega via two GPIO pins, to tell it whether the bus is currently charging and to receive signals on when it should start and stop.

Hot Air Balloon

We also found and ordered the balloon model online. It's connected to a nylon thread which is guided threw two spools on the ceiling until it's winded to a third, 3D printed spool, which is connected to the shaft of a stepper motor. The stepper motor is controlled by an Arduino Uno via a A4988 stepper driver.

That Arduino Uno is also connected to the Arduino Mega to receive the 'start flag'.

Telegram Bot Update

Last year we already created a Telegram Bot allowing people to send individual messages to the LED dot matrix display. It ran on an Arduino MKR1000. Unfortunately it wasn't quite reliable, when many messages were sent at once.

So this year we upgraded it and ported (more or less rewrote) the entire code to Python 3, so it would run on a Raspberry Pi. The Pi sends the messages, the current time and the weather information (if you send your location) via its serial port to an Arduino Nano which controls the display.

This gives us also the possibility to log all messages, so, in the worst case, we can block user, who send discriminating or insulting messages. Fortunately we really just had a few last year. We really believe that there are many nice people out there on the internet and not just trolls hiding behind their anonymity.

For the Finishing Touch...

... We built a hill out of styrofoam and plaster dressing for the train to drive through it and to make it look nicer we added some color, little trees and street signs.

If you want to know more about it or have questions, just leave a comment below. I'll be glad to answer them. :)

But for now, I really hope that we could bring some delight to you, your family and friends and that you will give Santa a little twerk!

Tobi

P.S. And as always, please excuse my bad english and supposably many mistakes. Kind regards from Germany. ;)

Code

Main Arduino Mega 2560 codeArduino
#include "FastLED.h"
#include "leds_lookup_table.h"
#include <EEPROM.h>

#define DEBUG 0
#define SEND_RESPONSE 0

#define READY_PIN 28
#define SANTA_PIN 8
#define DANCING_TREE_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 BUS_GO_PIN 3
#define BALLOON_PIN 9
#define BUS_CHARGING_PIN 11


#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 189 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
#define DANCING_TREE 61
#define BALLOON 62
#define BUS 63

#define LAP_COUNTER 190

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

// response data bytes to PI in case of conflicts or errors
#define OK 249
#define BEREITS_AN 250
#define BEREITS_AUS 251
#define SPAM_SCHUTZ 252 //lnger warten zwischen befehlen
#define CHARGING 253
#define NOT_CHARGING 248

#define DATA_ERROR_FIRST_BYTE 254
#define DATA_ERROR_COLOR 255

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

byte data[2], responseData[2], rndLED, hueTemp;
char text[31];
boolean newText, santaFlag, trainFlag, busFlag, lightChainFlag, sparklingFlag, sparkleOn, santaSoloFlag, pinActive = true, dancingTreeFlag, balloonFlag, balloonPinReseted = true, busCharging;
const unsigned int fadeSteps = FADE_TIME / FADE_DELAY;
unsigned long currentMillis, lastStatiUpdate, lastFadeTime, santaTime, trainTime, busTime, balloonTime, lightChainTime, sparklingTime, lastSparkleTime, lastLapCount, currentTime, trainActionTime, lastTrainAction, busActionTime, lastBusAction, dancingTreeTime;
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);
  pinMode(DANCING_TREE_PIN, OUTPUT);
  pinMode(BUS_GO_PIN, OUTPUT);
  pinMode(BALLOON_PIN, OUTPUT);
  pinMode(BUS_CHARGING_PIN, INPUT);


  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);
  digitalWrite(DANCING_TREE_PIN, LOW);
  digitalWrite(BUS_GO_PIN, LOW);
  digitalWrite(BALLOON_PIN, LOW);

  lightChainFlag = true;
  //!!!!!!!!!!!!!!!!!!!!!!!! UNCOMMENT AFTER START OF PROJECT !!!!!!!!!!!!!!!!!!!!!!
  lapCounter = 0;
  EEPROM.put(0, lapCounter);
  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  EEPROM.get(0, lapCounter);
  Serial.println(lapCounter);
}

void loop() {

  handleSerialCommunication();
  handleData();
  handleColorFade();
  handleControl();
  handleSparkling();
  handleLapCount();
  handleBusCharging();
  updateStati();
}

void updateStati() {
  if (millis() - lastStatiUpdate > 600000) {
    lastStatiUpdate = millis();
    if (trainFlag) {
      responseData[0] = TRAIN;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    else {
      responseData[0] = TRAIN;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }

    if (balloonFlag) {
      responseData[0] = BALLOON;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    else {
      responseData[0] = BALLOON;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }

    if (busFlag) {
      responseData[0] = BUS;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    else {
      responseData[0] = BUS;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }

    if (charging) {
      responseData[0] = BUS;
      responseData[1] = CHARGING;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    else {
      responseData[0] = BUS;
      responseData[1] = NOT_CHARGING;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }

    if (santaFlag) {
      responseData[0] = TWERKING_SANTA;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    else {
      responseData[0] = TWERKING_SANTA;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }

    if (dancingTreeFlag) {
      responseData[0] = DANCING_TREE;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    else {
      responseData[0] = DANCING_TREE;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }

    if (lightChainFlag) {
      responseData[0] = LIGHT_CHAIN;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    else {
      responseData[0] = LIGHT_CHAIN;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
  }
}

void handleBusCharging() {
  //busCharging = false;

  if (!digitalRead(BUS_CHARGING_PIN) && !busCharging) {
    busCharging = true;
    responseData[0] = BUS;
    responseData[1] = CHARGING;
    PiSerial.write(responseData, 2);
    PiSerial.flush();
  }
  else if (digitalRead(BUS_CHARGING_PIN) && busCharging) {
    busCharging = false;
    responseData[0] = BUS;
    responseData[1] = NOT_CHARGING;
    PiSerial.write(responseData, 2);
    PiSerial.flush();
  }
}

void handleLapCount() {
  currentTime = millis();
  if (!digitalRead(LAP_COUNT_PIN) && pinActive && currentTime - trainActionTime > 100) {
    lapCounter++;
    pinActive = false;
    lastLapCount = currentTime;
    PiSerial.print("LAP_COUNTER," + String(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 (dancingTreeFlag) {
    if (dancingTreeTime == 0) {
      //Serial.println("Set dancing tree pin high");
      responseData[0] = DANCING_TREE;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
      digitalWrite(DANCING_TREE_PIN, HIGH);
      dancingTreeTime = millis();
    }
    else if (millis() - dancingTreeTime > 18000) {
      digitalWrite(DANCING_TREE_PIN, LOW);
      dancingTreeTime = 0;
      dancingTreeFlag = false;
      responseData[0] = DANCING_TREE;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
  }

  if (balloonFlag) {
    if (balloonTime == 0) {
      responseData[0] = BALLOON;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
      digitalWrite(BALLOON_PIN, HIGH);
      balloonPinReseted = false;
      balloonTime = millis();
    }
    else if (millis() - balloonTime > 50000) {
      balloonTime = 0;
      balloonFlag = false;
      responseData[0] = BALLOON;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    else if (millis() - balloonTime > 1000 && !balloonPinReseted) {
      balloonPinReseted = true;
      digitalWrite(BALLOON_PIN, LOW);
    }
  }

  if (santaFlag) {
    if (santaTime == 0) {
      responseData[0] = TWERKING_SANTA;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
      digitalWrite(SANTA_PIN, HIGH);
      santaTime = millis();
    }
    else if (millis() - santaTime > 20000) {
      digitalWrite(SANTA_PIN, LOW);
      santaTime = 0;
      santaFlag = false;
      responseData[0] = TWERKING_SANTA;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
  }

  else if (santaSoloFlag) {

    if (trainFlag) {
      trainFlag = false;
      digitalWrite(TRAIN_PIN, LOW);
      responseData[0] = TRAIN;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();

    }
    if (lightChainFlag) {
      lightChainFlag = false;
      digitalWrite(LIGHT_CHAIN_PIN, LOW);
    }
    if (busFlag) {
      busFlag = false;
      digitalWrite(BUS_GO_PIN, LOW);
      responseData[0] = BUS;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
    if (dancingTreeFlag) {
      dancingTreeFlag = false;
      digitalWrite(DANCING_TREE_PIN, LOW);
    }
    responseData[0] = DANCING_TREE;
    responseData[1] = OFF;
    PiSerial.write(responseData, 2);
    PiSerial.flush();

    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);

    dancingTreeTime = 0;

    digitalWrite(SANTA_PIN, HIGH);
    FastLED.delay(50);
    digitalWrite(SANTA_PIN, LOW);
    santaFlag = false;
    santaTime = 0;

    responseData[0] = TWERKING_SANTA;
    responseData[1] = OFF;
    PiSerial.write(responseData, 2);
    PiSerial.flush();

    FastLED.delay(1000); //1000

    digitalWrite(FRONT_LIGHT_PIN, HIGH);
    digitalWrite(LIGHT_CHAIN_PIN, HIGH);
    lightChainFlag = true;
    responseData[0] = LIGHT_CHAIN;
    responseData[1] = ON;
    PiSerial.write(responseData, 2);
    PiSerial.flush();


    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);
      responseData[0] = TRAIN;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
  }

  if (busFlag) {
    if (millis() - busTime > 30000) {
      busFlag = false;
      digitalWrite(BUS_GO_PIN, LOW);
      responseData[0] = BUS;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
  }

  if (!lightChainFlag) {
    if (millis() - lightChainTime > 60000) {
      lightChainFlag = true;
      digitalWrite(LIGHT_CHAIN_PIN, HIGH);
      responseData[0] = LIGHT_CHAIN;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
  }


}

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 (PiSerial.peek() >= 1 && PiSerial.peek() <= 191) {    //if first data byte is valid
      PiSerial.readBytes(data, 2);
#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();

#if SEND_RESPONSE
      responseData[0] = DATA_ERROR_FIRST_BYTE;
      responseData[1] = errorMessage;
      PiSerial.write(responseData, 2);
#endif


#if DEBUG
      DebugSerial.print("DATA_ERROR_FIRST_BYTE: ");
      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

#if SEND_RESPONSE
      responseData[0] = DATA_ERROR_COLOR;
      responseData[1] = data[1];
      PiSerial.write(responseData, 2);
#endif

#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;
    }
#if SEND_RESPONSE
    else {
      responseData[0] = TWERKING_SANTA;
      responseData[1] = BEREITS_AN;
      PiSerial.write(responseData, 2);
    }
#endif

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

  }

  else if (data[0] == BALLOON) {
    if (!balloonFlag) {
      balloonFlag = true;
    }
    data[0] = 0;
    data[1] = 0;
  }

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

    if (!dancingTreeFlag) {
      dancingTreeFlag = true;
    }
#if SEND_RESPONSE
    else {
      responseData[0] = DANCING_TREE;
      responseData[1] = BEREITS_AN;
      PiSerial.write(responseData, 2);
    }
#endif

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

  }

  else if ( data[0] == BUS) {
    currentTime = millis();
    if (currentTime - lastBusAction > 1000) {
      if (data[1] == OFF && busFlag && digitalRead(BUS_CHARGING_PIN)) {
        busActionTime = currentTime;
        lastBusAction = busActionTime;
        digitalWrite(BUS_GO_PIN, LOW);
        busFlag = false;
        responseData[0] = BUS;
        responseData[1] = OFF;
        PiSerial.write(responseData, 2);
        PiSerial.flush();
      }

      else if (data[1] == ON && !busFlag && digitalRead(BUS_CHARGING_PIN)) {
        busActionTime = currentTime;
        lastBusAction = busActionTime;
        digitalWrite(BUS_GO_PIN, HIGH);
        busFlag = true;
        busTime = millis();
        responseData[0] = BUS;
        responseData[1] = ON;
        PiSerial.write(responseData, 2);
        PiSerial.flush();
      }
    }
    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;
        responseData[0] = TRAIN;
        responseData[1] = OFF;
        PiSerial.write(responseData, 2);
        PiSerial.flush();
      }

      else if (data[1] == ON && !trainFlag) {
        trainActionTime = currentTime;
        lastTrainAction = trainActionTime;
        digitalWrite(TRAIN_PIN, HIGH);
        trainFlag = true;
        trainTime = millis();
        responseData[0] = TRAIN;
        responseData[1] = ON;
        PiSerial.write(responseData, 2);
        PiSerial.flush();
      }
#if SEND_RESPONSE
      else if (data[1] == ON && trainFlag) {
        responseData[0] = TRAIN;
        responseData[1] = BEREITS_AN;
        PiSerial.write(responseData, 2);
      }
      else if (data[1] == OFF && !trainFlag) {
        responseData[0] = TRAIN;
        responseData[1] = BEREITS_AUS;
        PiSerial.write(responseData, 2);
      }
#endif
    }
#if SEND_RESPONSE
    else {
      responseData[0] = TRAIN;
      responseData[1] = SPAM_SCHUTZ;
      PiSerial.write(responseData, 2);
    }
#endif
    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();
      responseData[0] = LIGHT_CHAIN;
      responseData[1] = OFF;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }

    else if (data[1] == ON && !lightChainFlag) {
      digitalWrite(LIGHT_CHAIN_PIN, HIGH);
      lightChainFlag = true;
      responseData[0] = LIGHT_CHAIN;
      responseData[1] = ON;
      PiSerial.write(responseData, 2);
      PiSerial.flush();
    }
#if SEND_RESPONSE
    else if (data[1] == ON && lightChainFlag) {
      responseData[0] = LIGHT_CHAIN;
      responseData[1] = BEREITS_AN;
      PiSerial.write(responseData, 2);
    }
    else if (data[1] == OFF && !lightChainFlag) {
      responseData[0] = LIGHT_CHAIN;
      responseData[1] = BEREITS_AUS;
      PiSerial.write(responseData, 2);
    }
#endif

    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;
    }
#if SEND_RESPONSE
    else if (data[1] == ON && sparklingFlag) {
      responseData[0] = SPARKLING;
      responseData[1] = BEREITS_AN;
      PiSerial.write(responseData, 2);
    }
    else if (data[1] == OFF && !sparklingFlag) {
      responseData[0] = SPARKLING;
      responseData[1] = BEREITS_AUS;
      PiSerial.write(responseData, 2);
    }
#endif

    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;
  }
}

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 for animationsArduino
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};
Charging station codeArduino
#include <RCSwitch.h>
#include <Servo.h>
#include <EEPROM.h>

#define SERVO_PIN 6
#define PHOTO_SENSOR_PIN 7
#define RC_PIN 10

#define CONTROL_PIN 2
#define CHARGING_PIN 3

#define ON_COMMAND 6247872
#define OFF_COMMAND 6247728

#define OPEN_POS 100
#define CLOSED_POS 155

#define SERVO_DELAY 250
#define BUS_DELAY 200
#define CHARGING_TIME 60000
#define LAPS_UNTIL_CHARGE 10

boolean charging, driving, lapCountPinSet, busInFrontOfStation;
unsigned long lastActionTime, driveToChargeStart, lapCountPinTime, lastLapCount;
unsigned int counter, chargingCounter, lastChargingLaps;

RCSwitch rcSwitch = RCSwitch();
Servo myServo;

void setup() {
  Serial.begin(9600);
  rcSwitch.enableTransmit(RC_PIN);

  pinMode(PHOTO_SENSOR_PIN, INPUT);
  pinMode(CONTROL_PIN, INPUT);
  pinMode(CHARGING_PIN, OUTPUT);

  digitalWrite(CHARGING_PIN, HIGH);

  myServo.attach(SERVO_PIN);
  myServo.write(OPEN_POS);
  delay(SERVO_DELAY);
  myServo.detach();

  //!!!!!!!!!!!!!!!!!!!!!!!! UNCOMMENT AFTER START OF PROJECT !!!!!!!!!!!!!!!!!!!!!!
  //EEPROM.put(0, counter);
  //EEPROM.put(sizeof(int), chargingCounter);
  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  EEPROM.get(0, counter);
  Serial.println("Laps: " + String(counter));

  EEPROM.get(sizeof(int), chargingCounter);
  Serial.println("Charging cycles: " + String(chargingCounter));

  lastChargingLaps = counter;
  Serial.println(digitalRead(PHOTO_SENSOR_PIN));
}

void loop() {

  handleLapCount();
  handleCharging();
  handleControl();

}

void handleControl() {
  if (digitalRead(CONTROL_PIN) && !driving) {
    delay(20);
    if (digitalRead(CONTROL_PIN)) {
      Serial.println("ON COMMAND!");
      rcSwitch.send(ON_COMMAND, 24);
      //delay(100); //COMMENT!!!!
      driving = true;
      lastActionTime = millis();
    }
  }
  else if (!digitalRead(CONTROL_PIN) && driving) {
    delay(20);
    if (!digitalRead(CONTROL_PIN)) {
      Serial.println("OFF COMMAND!");
      rcSwitch.send(OFF_COMMAND, 24);
      //delay(100); //COMMENT
      driving = false;
      lastActionTime = millis();
    }
  }
}

void handleCharging() {
  if (counter - lastChargingLaps >= LAPS_UNTIL_CHARGE && millis() - lastActionTime > 5000 && !driving) {
    charging = true;
    Serial.println("DRIVE TO CHARGING!");
    digitalWrite(CHARGING_PIN, LOW);
    rcSwitch.send(ON_COMMAND, 24);
    driving = true;
    while (!digitalRead(PHOTO_SENSOR_PIN));
    driveToChargeStart = millis();
    while (digitalRead(PHOTO_SENSOR_PIN) && millis() - driveToChargeStart < 30000);
    delay(80);
    rcSwitch.send(OFF_COMMAND, 24);
    if (!digitalRead(PHOTO_SENSOR_PIN)) {
      delay(BUS_DELAY);

      myServo.attach(SERVO_PIN);
      myServo.write(CLOSED_POS);

      delay(CHARGING_TIME);

      myServo.write(OPEN_POS);
      delay(SERVO_DELAY);
      myServo.detach();

      rcSwitch.send(ON_COMMAND, 24);
      delay(2000);
      rcSwitch.send(OFF_COMMAND, 24);

      chargingCounter++;
      lastChargingLaps = counter;
      EEPROM.put(sizeof(int), chargingCounter);
      Serial.println("CHARGING CYCLES: " + String(chargingCounter));
    }

    charging = false;
    digitalWrite(CHARGING_PIN, HIGH);
    driving = false;
  }
}

void handleLapCount() {
  if (busInFrontOfStation && digitalRead(PHOTO_SENSOR_PIN)) busInFrontOfStation = false;

  if (!digitalRead(PHOTO_SENSOR_PIN) && !charging && (millis() - lastLapCount) > 2000 && !busInFrontOfStation) {
    busInFrontOfStation = true;
    //Serial.println("LAP COUNT DELTA: " + String(millis() - lastLapCount));
    lastLapCount = millis();
    counter++;
    //digitalWrite(CHARGING_PIN, LOW);
    lapCountPinTime = millis();
    lapCountPinSet = true;

    EEPROM.put(0, counter);
    Serial.println("LAPS: " + String(counter));
  }
  if (lapCountPinSet && millis() - lapCountPinTime > 100) {
    //digitalWrite(CHARGING_PIN, HIGH);
    lapCountPinSet = false;
  }
}
Telegram Bot Pyhton codePython
import time
import telepot
from telepot.loop import MessageLoop
import requests
import datetime
import pickle
import os
import ftplib
import serial
import struct
import emoji


TOKEN = "###ENTER_YOUR_BOT_TOKEN_HERE###"


t = datetime.datetime.now()

ser = serial.Serial ("/dev/serial0")    #Open named port on pi 3 you need to activate serial and disable bluetooth
ser.baudrate = 9600

#### Load Files ####

if os.path.isfile("logs/" + str(t.day) + "_" + str(t.month) + "_" + str(t.year) + ".log"):
    text_log = pickle.load(open("logs/" + str(t.day) + "_" + str(t.month) + "_" + str(t.year) + ".log", "rb"))
else:
    text_log = []

####

if os.path.isfile("logs/stats.log"):
    stats = pickle.load(open("logs/stats.log", "rb"))
    #print(stats)
else:
    stats = {"users": 0, "messages": 0, "locations": 0}

####

if os.path.isfile("logs/chat_ids.log"):
    chat_ids = pickle.load(open("logs/chat_ids.log", "rb"))
else:
    chat_ids = []

####

if os.path.isfile("logs/block_list.log"):
    block_list = pickle.load(open("logs/block_list.log", "rb"))
else:
    block_list = []

####

messages_buffer = ["www.ControlMyXMasTree.com\n"]
last_text_sent = 0
last_clock_sent = -600

admins = [] ######ENTER ALL ADMIN CHAT_IDs HERE

stream_delay = 5

def remove_emoji(text):
    return emoji.get_emoji_regexp().sub(u'', text)

def log_to_txt(date):
    if os.path.isfile("logs/" + str(date) + ".log"):
        temp_log = pickle.load(open("logs/" + str(date) + ".log", "rb"))
        txt_file = open("**logs-location-locally**" + str(date) + ".txt", "w")
        for line in temp_log:
            txt_file.write(str(line) + "\n")
        txt_file.close()
        return "SUCCESS"

def handle_telegram(msg):
    global messages_buffer
    global stream_delay
    global text_log
    global stats
    global chat_ids
    global admins
    global block_list
    global t

    t = datetime.datetime.now()
    content_type, chat_type, chat_id = telepot.glance(msg)
    #print(content_type, chat_type, chat_id)

    if content_type == 'text':
        #bot.sendMessage(chat_id, msg['text'])

        ############ START ############
        if msg['text'] == "/start":
            bot.sendMessage(chat_id, " Hey there, " + msg["chat"]["first_name"] + ".\nSend /help for information"
                                                                                        " on how to use this bot.")
            if chat_id not in chat_ids:
                chat_ids.append(chat_id)
                pickle.dump(chat_ids, open("logs/chat_ids.log", "wb"))
                stats["users"] += 1
                pickle.dump(stats, open("logs/stats.log", "wb"))

        ############ HELP ############
        elif msg['text'] == "/help":
            bot.sendMessage(chat_id, "\nTo send text to the display, "
                                     "use: /text followed by your Message (for example: /text Santa rocks!).\n"
                                     "To display the current weather in your area, simply send me your location "
                                     "(Only on mobile, click paper-clip icon on bottom left).")

        ############ TEXT ############
        elif (msg['text'].startswith("/text") or msg['text'].startswith("/Text")) and chat_id not in block_list:
            msg['text'] = msg['text'][6:]

            msg['text'] = remove_emoji(msg['text'])

            if len(msg['text']) >= 2 and len(msg['text']) <= 75:
                display_name = msg["chat"]["first_name"]
                if len(display_name) > 20:
                    display_name = display_name[:20] + "."

                display_text = display_name + ": " + msg['text']
                message_time = datetime.datetime.fromtimestamp(msg['date'])
                time = message_time.strftime("%H:%M:%S")
                #print(time)
                print(display_text)
                messages_buffer.append(display_text + "\n")

                bot.sendMessage(chat_id, " Your message will be displayed in approx. "
                                + str((len(messages_buffer) - 1) * 20 + stream_delay) + " seconds.",
                                reply_to_message_id=msg["message_id"])

                try:
                    username = msg["chat"]["username"]
                except:
                    username = ""

                try:
                    last_name = msg["chat"]["last_name"]
                except:
                    last_name = ""

                if not os.path.isfile("logs/" + str(t.day) + "_" + str(t.month) + "_" + str(t.year) + ".log"):
                    text_log = []

                text_log.append({"time": time, "chat_id": chat_id, "username": username,
                                 "first_name": msg["chat"]["first_name"], "last_name": last_name,
                                 "text": msg['text']})
                pickle.dump(text_log, open("logs/" + str(t.day) + "_" + str(t.month) + "_" + str(t.year)+ ".log", "wb"))

                stats["messages"] += 1
                pickle.dump(stats, open("logs/stats.log", "wb"))

            elif len(msg['text']) < 2:
                bot.sendMessage(chat_id, "Ooops.. Your message is too short. Please send at least two characters.")

            elif len(msg['text']) > 75:
                bot.sendMessage(chat_id, "Ooops.. Your message is too long. Please send a maximum of 75 characters.")


        ############ ADMIN COMMANDS ############
        elif chat_id in admins:

            ############ LOG ############
            if msg['text'].startswith("/log"):
                msg['text'] = msg['text'][5:]
                if not log_to_txt(msg["text"]) == "SUCCESS":
                    bot.sendMessage(chat_id, "No log file for this day available.")
                    return
                ftp = ftplib.FTP('***ftp-server***', '***user-name***', '***password***')
                try:
                    ftp.cwd("**directory**")
                except:
                    bot.sendMessage(chat_id, "Can't reach ftp directory.")
                    return

                f = open('***logs-location-locally***' + msg["text"] + ".txt", 'rb')
                ftp.storbinary('STOR ' + msg["text"] + ".txt", f)
                f.close()
                ftp.quit()
                bot.sendMessage(chat_id, "***logs-location***" + msg['text'] + ".txt")

            ############ BULK ############
            elif msg['text'].startswith("/bulk"):
                msg['text'] = msg['text'][6:]
                for n in chat_ids:
                    bot.sendMessage(n, msg['text'])

            ############ STATS ############
            elif msg['text'] == "/stats":
                bot.sendMessage(chat_id, stats)

            ############ MESSAGE TO SPCECIFIC CHAT_ID ############
            elif msg['text'].startswith("/message"):
                msg['text'] = msg['text'][9:]
                temp_chat_id, msg['text'] = msg['text'].split('#')
                try:
                    bot.sendMessage(temp_chat_id, msg['text'])
                except:
                    bot.sendMessage(chat_id, "Unable to send message.")
                    return

                bot.sendMessage(chat_id, "Message send.")

            ############ BLOCK LIST ############
            elif msg['text'] == "/block_list":
                bot.sendMessage(chat_id, block_list)

            ############ BLOCK ############
            elif msg['text'].startswith("/block"):
                msg['text'] = msg['text'][7:]

                try:
                    bot.sendMessage(int(msg['text']), " You've temporarily been blocked by an admin from sending "
                                                      "messages.")
                except:
                    bot.sendMessage(chat_id, msg['text'] + " wrong chat_id!!!")
                    return

                block_list.append(int(msg['text']))
                pickle.dump(block_list, open("logs/block_list.log", "wb"))
                bot.sendMessage(chat_id, msg['text'] + " has been blocked!")

            ############ UNBLOCK ############
            elif msg['text'].startswith("/unblock"):
                msg['text'] = msg['text'][9:]
                try:
                    block_list.remove(int(msg['text']))
                except:
                    bot.sendMessage(chat_id, "Chat ID not in block list.")
                    return

                pickle.dump(block_list, open("logs/block_list.log", "wb"))
                bot.sendMessage(chat_id, msg['text'] + " has been UN-blocked!")

        ############ NO PERMISSION / INVALID COMMAND ############
        else:
            bot.sendMessage(chat_id, " Not a valid command or you don't have permission to use it.")

    ############ LOCATION / WEATHER ############
    elif content_type == "location":
        #print("Location send.")
        location_data = {'lat': msg['location']['latitude'], 'lon': msg['location']['longitude'],
                         'APPID': 'ENTER_YOUR_APP_ID'}
        server = "http://api.openweathermap.org/data/2.5/weather"
        r = requests.get(server, params=location_data).json()
        #print(r)
        celsius = r["main"]["temp"] - 273.2

        weather_string = "Current weather in " + r["name"] + ": " + r["weather"][0]["description"].capitalize() + " at %.1f C" % (celsius)
        messages_buffer.append(weather_string + "\n")

        bot.sendPhoto(chat_id,
                      "http://www.controlmyxmastree.com/ui/weather_conditions/{}.png".format(r["weather"][0]["icon"]),
                      r["weather"][0]["description"].capitalize() + " in " + r["name"] + " at %.1f C" % (celsius))
        bot.sendMessage(chat_id, "This is the current weather at your location. It will also be displayed in the "
                                 "live stream in aprox. " + str((len(messages_buffer)) * 20 + stream_delay) +
                        " seconds.")

        stats["locations"] += 1
        pickle.dump(stats, open("logs/stats.log", "wb"))

def handle_serial():
    while(1):
        global messages_buffer
        global last_text_sent
        global last_clock_sent
        current_time = time.time()

        if len(messages_buffer) > 0 and (current_time - last_text_sent > 20): #change to 20
            ser.write(struct.pack('>B', 2))
            ser.write(str.encode(messages_buffer[0]))
            last_text_sent = current_time
            messages_buffer.pop(0)

        if current_time - last_text_sent > 60: # change to 60
            ser.write(struct.pack('>B', 2))
            ser.write(str.encode("To send custom text, open/download the 'Telegram' App and add: "
                                 "@CMXT_Bot.\n"))
            last_text_sent = current_time

        if current_time - last_clock_sent > 600:  ##change to 600
            global t
            t = datetime.datetime.now()
            ser.write(struct.pack('>B', 1))
            ser.write(struct.pack('>B', t.hour))
            ser.write(struct.pack('>B', t.minute))
            ser.write(struct.pack('>B', t.second))
            last_clock_sent = current_time


bot = telepot.Bot(TOKEN)
MessageLoop(bot, handle_telegram).run_as_thread()
print('Listening ...')

# Keep the program running.
handle_serial()

Custom parts and enclosures

Bus stop base
The base of the busstop with the compartment for the ir distance sensor.
The roof of the bus stop
The arm for the gripper
The arm to hold the gripper in the right position to charge the bus

Schematics

Basic connections and data exchange.
Our basic concept of how this whole mess works.
Svggraph (5) qdn4mnuc5z 2dmkmcj5dd

Comments

Similar projects you might like

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

Project showcase by Tobi_Lxtr

  • 5,714 views
  • 7 comments
  • 41 respects

Christmas Crib Controlling via Internet

Project showcase by tomek10861

  • 1,163 views
  • 0 comments
  • 4 respects

Servo Control with TV Remote Control

Project showcase by eldo85

  • 6,459 views
  • 5 comments
  • 21 respects

Blinds (Or Any AC Power Motor) Control

Project tutorial by gomecin

  • 12,131 views
  • 23 comments
  • 54 respects

Control an LED with the Remote Control

Project showcase by Nicholas_N

  • 3,900 views
  • 2 comments
  • 11 respects
Add projectSign up / Login