Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
| × | 1 | ||||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
![]() |
| |||||
Hand tools and fabrication machines | ||||||
![]() |
|
"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 DevicesTo 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.
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 UpdateLast 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. ;)
#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;
}
}
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};
#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;
}
}
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()
Comments
Please log in or sign up to comment.