Tobi_Lxtrwotanzero
Published © GPL3+

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

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.

AdvancedShowcase (no instructions)2,984
I Let Everyone on the Internet Control My X-Mas Decoration 2

Things used in this project

Story

Read more

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.

Code

Main Arduino Mega 2560 code

Arduino
#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 animations

Arduino
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 code

Arduino
#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 code

Python
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()

Credits

Tobi_Lxtr
3 projects • 14 followers
Electrical engineer and tinkerer from Germany.
Contact
wotanzero
1 project • 0 followers
Contact

Comments

Please log in or sign up to comment.