Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 8 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
| ||||||
|
Have you always wanted anyone in the world to be able to control your X-mas decoration in your living room? - No? - We neither, but we built it anyway.
We proudly present: www.controlmyxmastree.com
I had the idea quite a while ago, since I always loved projects where you could control somebody's X-mas lighting, a robot arm or generally anything over the web and watching it live on stream. But I was never really sure, if there's anyone else in the world who would dig it and I had no idea how to get requests from anyone on the internet to my Arduino.
Then one day - a few weeks ago - I told a friend what I had in mind and surprisingly he was immediately into it. He's a computer scientist and said he could program an api on node.js, which would run on a raspberry pi. The concept was born.
DiagramI made a quick, professionell and definitely not shitty looking diagram to describe our basic setup:
We made a simple webpage, which uses ajax to send http POST requests to our api.
The api is a node application which runs on a Raspberry Pi with a fix and from the web available IP. It sends two bytes over a serial port to an Arduino Mega with a custom shield (Why mega? I had one laying around and its multiple serial ports come in very handy for debugging). The first byte we call: "status byte" the second one: "data byte" (in dependence on Midi).
The Mega then runs code which interprets the serial data and controls all the actuators plus LEDs (P9823 / WS2812).
We also implemented a little hidden switch behind the tree, which counts the laps of the train to display the covered distance on the website later on. Therefore the Arduino sends the current lap count back to the Pi via uart.
There's also one digital output pin from the Arduino connected to a digital input of the Pi, to tell it whether the Mega is ready to receive data or not.
LED dot matrix and TelegramBotThe LED dot matrix display is generally one completely separate system. It utilizes an Arduino MKR1000 for connecting to the internet, which acts as the Telegram bot (see Telegram bot api documentation and Telegram Bot library) and also gets other data from the web (eg. weather, time, ...). The Telegram bot enables people online to send text to the display or to send their current location to show local weather conditions. Why Telegram? - We didn't want to put a simple text input to the webpage, so we could keep away 'spam bots' and as many 'trolls' as possible. And also we simply like Telegram and wanted to try it. ;)
The MKR1000 then connects via uart to an Arduino Nano, which controls the dot matrix (md_parola library).
A few things..The only things missing are: a power supply, some relays connected to mains voltage with four outlets and setting up the live stream.
I don't know whether I should have gone more into detail, but for now I think it's sufficient. If you want to know more about it, just leave a comment below. We've made some videos in the process, so if there's any interest we'd love to share them.
Speaking of sharing: We definitely want to share all our code, protocols and diagrams, but we hadn't the time to put them all together yet. We were kind of in a rush the last days building it (TIME MANAGEMENT!), so the code is ugly and incomprehensible. But since the project is quite limited to this time of the year, we wanted to post it as soon as possible. We're really ashamed of the dirty code, but we'll fix it soon and edit it in here. Till' then: We want to see Santa twerk.
I hope that we could bring some delight to you, your family and/or friends with our little project. It was a joy building and we're definitely not done yet. We had planed a lot more: more actuators, camera angles and statistics on the webpage. But we'll add them shortly.. so stay tuned. ;)
P.S. Excuse my bad english and supposably many mistakes. Kind regards from Germany. ;)
Serial_Data_Read.ino
Arduino#include "FastLED.h"
#include "leds_lookup_table.h"
#include <EEPROM.h>
#define DEBUG 0
#define READY_PIN 28
#define SANTA_PIN 5
#define TRAIN_PIN 39 //relay 3
#define LIGHT_CHAIN_PIN 35 //relay 1
#define SPOT_LIGHT_PIN 37
#define FRONT_LIGHT_PIN 41
#define LAP_COUNT_PIN 6
#define NUM_LEDS 55 //number of total P9823's
#define DATA_PIN 2
#define FADE_DELAY 50
#define FADE_TIME 2000
#define CHANCE_SANTA_SOLO 20 //chance of a santa solo in percent
#define PiSerial Serial1
#define DebugSerial Serial
//status bytes knnen werte von 1 bis 191 haben (0 = undefined)
#define LED_START 1
#define LED_END 55
#define TEXT_EINGABE 124
#define TWERKING_SANTA 69
#define TRAIN 56
#define LIGHT_CHAIN 57
#define SPARKLING 58
#define ANIMATIONS 59
#define TREE_BLACKOUT 60
//info/data bytes von 192 bis 254
#define OFF 192
#define ON 193
#define FARBEN_START 194
#define FARBEN_END 210
#define DATA_ERROR 255
struct ledStepStruct {
int rStep, gStep, bStep;
boolean fade;
};
byte data[2], rndLED, hueTemp;
char text[31];
boolean newText, santaFlag, trainFlag, lightChainFlag, sparklingFlag, sparkleOn, santaSoloFlag, pinActive = true;
const unsigned int fadeSteps = FADE_TIME / FADE_DELAY;
unsigned long currentMillis, lastFadeTime, santaTime, trainTime, lightChainTime, sparklingTime, lastSparkleTime, lastLapCount, currentTime, trainActionTime, lastTrainAction;
unsigned int lapCounter;
CRGB leds[NUM_LEDS], ledsAim[NUM_LEDS], ledBefore;
ledStepStruct ledsStep[NUM_LEDS];
void setup() {
PiSerial.begin(9600);
DebugSerial.begin(9600);
pinMode(READY_PIN, OUTPUT);
pinMode(SANTA_PIN, OUTPUT);
pinMode(TRAIN_PIN, OUTPUT);
pinMode(LIGHT_CHAIN_PIN, OUTPUT);
pinMode(SPOT_LIGHT_PIN, OUTPUT);
pinMode(FRONT_LIGHT_PIN, OUTPUT);
pinMode(LAP_COUNT_PIN, INPUT_PULLUP);
FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);
FastLED.setBrightness(70);
FastLED.clear();
FastLED.show();
digitalWrite(READY_PIN, HIGH);
digitalWrite(SANTA_PIN, LOW);
digitalWrite(TRAIN_PIN, LOW);
digitalWrite(LIGHT_CHAIN_PIN, HIGH);
digitalWrite(SPOT_LIGHT_PIN, LOW);
digitalWrite(FRONT_LIGHT_PIN, HIGH);
lightChainFlag = true;
//lapCounter = 200; //add counts up to now, since eeprom wasnt' implemented yet
//EEPROM.put(0, lapCounter);
EEPROM.get(0, lapCounter);
}
void loop() {
handleSerialCommunication();
handleData();
handleColorFade();
handleControl();
handleSparkling();
handleLapCount();
}
void handleLapCount() {
currentTime = millis();
if (!digitalRead(LAP_COUNT_PIN) && pinActive && currentTime - trainActionTime > 100) {
lapCounter++;
pinActive = false;
lastLapCount = currentTime;
PiSerial.print(lapCounter);
PiSerial.flush();
EEPROM.put(0, lapCounter);
//Serial.println(lapCounter);
}
if (digitalRead(LAP_COUNT_PIN) && currentTime - lastLapCount > 2000) {
pinActive = true;
}
}
void handleSparkling() {
if (sparklingFlag) {
if (millis() - sparklingTime > 20000) {
sparklingFlag = false;
leds[rndLED] = ledBefore;
FastLED.show();
sparkleOn = false;
}
else if (!sparkleOn && millis() - lastSparkleTime > 100) {
rndLED = random8(NUM_LEDS);
ledBefore = leds[rndLED];
leds[rndLED] += CRGB::White;
FastLED.show();
sparkleOn = true;
lastSparkleTime = millis();
}
else if (sparkleOn && millis() - lastSparkleTime > 50) {
leds[rndLED] = ledBefore;
FastLED.show();
sparkleOn = false;
lastSparkleTime = millis();
}
}
}
void handleControl() {
if (santaFlag) {
if (santaTime == 0) {
digitalWrite(SANTA_PIN, HIGH);
santaTime = millis();
}
else if (millis() - santaTime > 20000) {
digitalWrite(SANTA_PIN, LOW);
santaTime = 0;
santaFlag = false;
}
}
else if (santaSoloFlag) {
if (trainFlag) {
trainFlag = false;
digitalWrite(TRAIN_PIN, LOW);
}
if (lightChainFlag) {
digitalWrite(LIGHT_CHAIN_PIN, LOW);
}
FastLED.clear();
FastLED.show();
digitalWrite(FRONT_LIGHT_PIN, LOW);
FastLED.delay(1000);
digitalWrite(SPOT_LIGHT_PIN, HIGH);
FastLED.delay(500);
digitalWrite(SANTA_PIN, HIGH);
FastLED.delay(50);
digitalWrite(SANTA_PIN, LOW);
/*
for (int i; i < 500; i++) {
fill_rainbow(leds, NUM_LEDS, hueTemp, 1);
FastLED.show();
hueTemp++;
FastLED.delay(25);
}
FastLED.clear();
FastLED.show();
*/
for ( int k = 0; k < 20; k++ ) {
triangle();
}
digitalWrite(SPOT_LIGHT_PIN, LOW);
digitalWrite(SANTA_PIN, HIGH);
FastLED.delay(50);
digitalWrite(SANTA_PIN, LOW);
FastLED.delay(1000);
digitalWrite(FRONT_LIGHT_PIN, HIGH);
digitalWrite(LIGHT_CHAIN_PIN, HIGH);
lightChainFlag = true;
for (byte i = 0; i < NUM_LEDS; i++) {
leds[i] = ledsAim[i];
}
FastLED.show();
santaSoloFlag = false;
digitalWrite(READY_PIN, HIGH);
}
if (trainFlag) {
if (millis() - trainTime > 30000) {
trainFlag = false;
digitalWrite(TRAIN_PIN, LOW);
}
}
if (!lightChainFlag) {
if (millis() - lightChainTime > 60000) {
lightChainFlag = true;
digitalWrite(LIGHT_CHAIN_PIN, HIGH);
}
}
}
void handleColorFade() {
currentMillis = millis();
if (currentMillis - lastFadeTime >= FADE_DELAY) {
for (byte i = 0; i < NUM_LEDS; i++) {
if (ledsStep[i].fade) {
ledsStep[i].fade = false;
if (leds[i].r != ledsAim[i].r) {
ledsStep[i].fade = true;
int rTemp = (int)leds[i].r + (int)ledsStep[i].rStep;
if ((ledsStep[i].rStep > 0 && rTemp > ledsAim[i].r) || (ledsStep[i].rStep < 0 && rTemp < ledsAim[i].r) || rTemp > 255 || rTemp < 0) leds[i].r = ledsAim[i].r;
else leds[i].r = rTemp;
if ((ledsStep[i].rStep > 0 && leds[i].r > ledsAim[i].r) || (ledsStep[i].rStep < 0 && leds[i].r < ledsAim[i].r)) leds[i].r = ledsAim[i].r;
}
if (leds[i].g != ledsAim[i].g) {
ledsStep[i].fade = true;
int gTemp = (int)leds[i].g + (int)ledsStep[i].gStep;
if ((ledsStep[i].gStep > 0 && gTemp > ledsAim[i].g) || (ledsStep[i].gStep < 0 && gTemp < ledsAim[i].g) || gTemp > 255 || gTemp < 0) leds[i].g = ledsAim[i].g;
else leds[i].g = gTemp;
}
if (leds[i].b != ledsAim[i].b) {
ledsStep[i].fade = true;
int bTemp = (int)leds[i].b + (int)ledsStep[i].bStep;
if ((ledsStep[i].bStep > 0 && bTemp > ledsAim[i].b) || (ledsStep[i].bStep < 0 && bTemp < ledsAim[i].b) || bTemp > 255 || bTemp < 0) leds[i].b = ledsAim[i].b;
else leds[i].b = bTemp;
}
}
}
FastLED.show();
lastFadeTime = currentMillis;
}
}
void handleSerialCommunication() {
if (PiSerial.available() > 0) { //if there's any serial data
/*
if (data[0] == TEXT_EINGABE) { //if the upcoming data is a text; indicated by first data byte = 124
//inputString = PiSerial.readString();
for (byte textIndex = 0; textIndex < (data[1] - 193); textIndex++) { //194 = textlnge 1, ..., 224 = textlnge 30
text[textIndex] = PiSerial.read();
}
text[data[1] - 193] = '\0';
newText = true; //text saved
data[0] = 0;
data[1] = 0;
}
*/
//else if !!!!!!! wenn oben auskommentiert
if (PiSerial.peek() >= 1 && PiSerial.peek() <= 191) { //if first data byte is valid
PiSerial.readBytes(data, 2);
//if (data[0] == TEXT_EINGABE) digitalWrite(READY_PIN, LOW);
#if DEBUG
DebugSerial.println("Empfangen: " + (String)data[0] + ", " + (String)data[1]);
#endif
}
else { //else; first data byte is invalid (0 or greater than 191)
byte errorMessage = PiSerial.read();
//PiSerial.print(DATA_ERROR);
//PiSerial.print(errorMessage);
#if DEBUG
DebugSerial.print("DATA_ERROR: ");
DebugSerial.println(errorMessage);
#endif
}
}
}
void handleData() {
if (data[0] >= LED_START && data[0] <= LED_END) { //if data is LED data
if (data[1] < OFF || data[1] > FARBEN_END) { //if second data byte has invalid color
//invalid color/data byte
//PiSerial.print(DATA_ERROR);
//PiSerial.print(data[1]);
#if DEBUG
DebugSerial.print("DATA_ERROR_INVALID_COLOR: ");
DebugSerial.println(data[1]);
#endif
}
else { //color value is valid
//led mit farbe (in data[1]) ansteuern
byte i = data[0] - 1;
if (data[1] == OFF) ledsAim[i] = CRGB::Black;
else if (data[1] == ON) ledsAim[i] = CRGB::White; //hier warmwei einfgen
else ledsAim[i].setHue((data[1] - 194) * 16); //set aim color for led to chosen value
setLEDFade(i);
}
data[0] = 0;
data[1] = 0;
}
else if (data[0] == TWERKING_SANTA) {
if (!santaFlag) {
digitalWrite(READY_PIN, LOW);
if (random8(100) > CHANCE_SANTA_SOLO - 1) {
santaFlag = true;
digitalWrite(READY_PIN, HIGH);
}
else santaSoloFlag = true;
}
data[0] = 0;
data[1] = 0;
}
else if ( data[0] == TRAIN) {
currentTime = millis();
if (currentTime - lastTrainAction > 1000) {
if (data[1] == OFF && trainFlag) {
trainActionTime = currentTime;
lastTrainAction = trainActionTime;
digitalWrite(TRAIN_PIN, LOW);
trainFlag = false;
}
else if (data[1] == ON && !trainFlag) {
trainActionTime = currentTime;
lastTrainAction = trainActionTime;
digitalWrite(TRAIN_PIN, HIGH);
trainFlag = true;
trainTime = millis();
}
}
data[0] = 0;
data[1] = 0;
}
else if (data[0] == LIGHT_CHAIN) {
if (data[1] == OFF && lightChainFlag) {
digitalWrite(LIGHT_CHAIN_PIN, LOW);
lightChainFlag = false;
lightChainTime = millis();
}
else if (data[1] == ON && !lightChainFlag) {
digitalWrite(LIGHT_CHAIN_PIN, HIGH);
lightChainFlag = true;
}
data[0] = 0;
data[1] = 0;
}
else if (data[0] == SPARKLING) {
if (data[1] == ON && !sparklingFlag) {
sparklingFlag = true;
sparklingTime = millis();
}
else if (data[1] == OFF && sparklingFlag) {
sparklingFlag = false;
}
data[0] = 0;
data[1] = 0;
}
else if (data[0] == ANIMATIONS) {
digitalWrite(READY_PIN, LOW);
animations();
digitalWrite(READY_PIN, HIGH);
data[0] = 0;
data[1] = 0;
}
else if (data[0] == TREE_BLACKOUT) {
for (byte i = 0; i < NUM_LEDS; i++) {
ledsAim[i] = CRGB::Black;
setLEDFade(i);
}
data[0] = 0;
data[1] = 0;
}
/*
if (newText) { //handle text entry
//inputString.toCharArray(text, 31);
#if DEBUG
DebugSerial.println(text); //debug
DebugSerial.flush();
#endif
digitalWrite(READY_PIN, HIGH);
//text anzeigen
newText = false;
}
*/
}
void animations() {
fillSpiralCCW();
fillSpiralCW();
fillSpiralCCW();
fillSpiralCW();
for (byte i = 0; i < NUM_LEDS; i++) {
leds[i] = ledsAim[i];
}
FastLED.show();
}
void setLEDFade(byte i) {
if (leds[i] != ledsAim[i]) {
ledsStep[i].fade = true;
if (ledsAim[i].r != leds[i].r) {
ledsStep[i].rStep = ((int)ledsAim[i].r - (int)leds[i].r) / (int)fadeSteps;
if (ledsStep[i].rStep == 0) {
if (ledsAim[i].r > leds[i].r) ledsStep[i].rStep = 1;
else ledsStep[i].rStep = -1;
}
#if DEBUG
DebugSerial.println("rStep: " + (String)ledsStep[i].rStep); //debug
#endif
}
if (ledsAim[i].g != leds[i].g) {
ledsStep[i].gStep = ((int)ledsAim[i].g - (int)leds[i].g) / (int)fadeSteps;
if (ledsStep[i].gStep == 0) {
if (ledsAim[i].g > leds[i].g) ledsStep[i].gStep = 1;
else ledsStep[i].gStep = -1;
}
#if DEBUG
DebugSerial.println("gStep: " + (String)ledsStep[i].gStep); //debug
#endif
}
if (ledsAim[i].b != leds[i].b) {
ledsStep[i].bStep = ((int)ledsAim[i].b - (int)leds[i].b) / (int)fadeSteps;
if (ledsStep[i].bStep == 0) {
if (ledsAim[i].b > leds[i].b) ledsStep[i].bStep = 1;
else ledsStep[i].bStep = -1;
}
#if DEBUG
DebugSerial.println("bStep: " + (String)ledsStep[i].bStep); //debug
#endif
}
}
}
void fillSpiralCCW() {
FastLED.clear();
FastLED.show();
for (byte i = 0; i < (sizeof(spiralCCW) / sizeof(byte)); i++) {
leds[spiralCCW[i]].setHue(hueTemp);
FastLED.show();
FastLED.delay(30);
hueTemp += 2;
}
for (byte i = (sizeof(spiralCCW) / sizeof(byte)) - 1; i > 0; i--) {
leds[spiralCCW[i]] = CRGB::Black;
FastLED.show();
FastLED.delay(30);
}
}
void fillSpiralCW() {
FastLED.clear();
FastLED.show();
for (byte i = 0; i < (sizeof(spiralCW) / sizeof(byte)); i++) {
leds[spiralCW[i]].setHue(hueTemp);
FastLED.show();
FastLED.delay(30);
hueTemp += 2;
}
for (byte i = (sizeof(spiralCW) / sizeof(byte)) - 1; i > 0; i--) {
leds[spiralCW[i]] = CRGB::Black;
FastLED.show();
FastLED.delay(30);
}
}
void frameMarquee() {
FastLED.clear();
FastLED.show();
for (int j = 0; j < 400; j++) {
for (byte i = 0; i < (sizeof(frame) / sizeof(byte)); i++) {
leds[frame[i]].setHue(hueTemp);
hueTemp += 2;
}
hueTemp = 2 * j;
FastLED.show();
FastLED.delay(40);
}
FastLED.clear();
FastLED.show();
}
void innerFrame() {
for (int j = 0; j < 15; j++) {
hueTemp += 40;
for (byte i = 0; i < (sizeof(frame) / sizeof(byte)); i++) {
leds[frame[i]].setHue(hueTemp);
}
FastLED.show();
FastLED.delay(200);
for (byte i = 0; i < (sizeof(frame) / sizeof(byte)); i++) {
leds[frame[i]] = CRGB::Black;
}
hueTemp += 40;
for (byte i = 0; i < (sizeof(inner) / sizeof(byte)); i++) {
leds[inner[i]].setHue(hueTemp);
}
FastLED.show();
FastLED.delay(200);
for (byte i = 0; i < (sizeof(inner) / sizeof(byte)); i++) {
leds[inner[i]] = CRGB::Black;
}
}
FastLED.clear();
FastLED.show();
}
void triangle() {
hueTemp += 16;
for (byte i = 0; i < (sizeof(triangle1) / sizeof(byte)); i++) {
leds[triangle1[i]].setHue(hueTemp);
}
FastLED.show();
FastLED.delay(150);
for (byte i = 0; i < (sizeof(triangle1) / sizeof(byte)); i++) {
leds[triangle1[i]] = CRGB::Black;
}
hueTemp += 16;
for (byte i = 0; i < (sizeof(triangle2) / sizeof(byte)); i++) {
leds[triangle2[i]].setHue(hueTemp);
}
FastLED.show();
FastLED.delay(150);
for (byte i = 0; i < (sizeof(triangle2) / sizeof(byte)); i++) {
leds[triangle2[i]] = CRGB::Black;
}
hueTemp += 16;
for (byte i = 0; i < (sizeof(triangle3) / sizeof(byte)); i++) {
leds[triangle3[i]].setHue(hueTemp);
}
FastLED.show();
FastLED.delay(150);
for (byte i = 0; i < (sizeof(triangle3) / sizeof(byte)); i++) {
leds[triangle3[i]] = CRGB::Black;
}
hueTemp += 16;
for (byte i = 0; i < (sizeof(triangle4) / sizeof(byte)); i++) {
leds[triangle4[i]].setHue(hueTemp);
}
FastLED.show();
FastLED.delay(150);
for (byte i = 0; i < (sizeof(triangle4) / sizeof(byte)); i++) {
leds[triangle4[i]] = CRGB::Black;
}
}
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};
#define TEXT_SERIAL 1
#define WATCHDOG 1
#define DEBUG 0
#include <Arduino.h>
#include <Time.h>
#include <TimeLib.h>
#include <WiFi101.h>
//#include <WiFiUdp.h>
#include <SPI.h>
#include <TelegramBot.h>
//#include <time.h>
//#include <RTCZero.h>
#if TEXT_SERIAL
#include <wiring_private.h>
#endif
#if WATCHDOG
#include <Adafruit_SleepyDog.h>
#endif
#if TEXT_SERIAL
Uart textSerial (&sercom3, 0, 1, SERCOM_RX_PAD_1, UART_TX_PAD_0);
void SERCOM3_Handler() // Interrupt handler for SERCOM3
{
textSerial.IrqHandler();
}
#endif
#define ledPin 6
#define RBUFFSIZE 1000
#define MAX_MESSAGES 16
#define TIME 1
#define TEXT 2
#define WEATHER 3
#define STATS 4
/*
#define BEER_NAME 0
#define BEER_ID 1
*/
char responseBuffer[RBUFFSIZE];
int rbindex = 0;
boolean startCapture;
unsigned long lastTimeSentTime, lastTextSent, currentTime, lastClockSent;
// Initialize Wifi connection
const char ssid[] = ""; // network SSID
const char pass[] = ""; // network key
// Initialize Telegram BOT
const char BotToken[] = "";
char server[] = "api.openweathermap.org";
String eingabe; //Nachrichten[128];
char Nachrichten[MAX_MESSAGES][100];
byte numMessages, messageIndex, sendMessageIndex;
WiFiSSLClient client;
WiFiClient clienthttp;
TelegramBot bot(BotToken, client);
//RTCZero rtc;
//TelegramKeyboard keyboardStart;
/*
const char* row1Start[] = {"/help", "/wlan"};
const char* row2Start[] = {"/ip", "/signal"};
//const char* row3Start[] = {"/alloff"};
*/
time_t tClock = 0;
void setup() {
delay(3000);
#if TEXT_SERIAL
textSerial.begin(9600); // Begin Uart for sending text to LED matrix
#endif
Serial.begin(9600);
#if TEXT_SERIAL
pinPeripheral(0, PIO_SERCOM); // Assign pins 0 & 1 SERCOM functionality
pinPeripheral(1, PIO_SERCOM);
#endif
#if WATCHDOG
Watchdog.enable(10000);
#endif
// attempt to connect to Wifi network:
#if DEBUG
Serial.print(F("Verbine mit WLAN: "));
Serial.println(ssid);
#endif
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
delay(500);
}
#if DEBUG
Serial.println(F("Mit WLAN verbunden."));
#endif
/*
byte counterT = 0;
while (tClock == 0 && counterT < 6) {
delay(1000);
counterT++;
tClock = WiFi.getTime();
}
Serial.println(tClock);
*/
getCurrentTime();
if (tClock != 0) {
textSerial.write(TIME);
textSerial.write(hour(tClock));
textSerial.write(minute(tClock));
textSerial.write(second(tClock));
}
tClock = 0;
lastClockSent = millis();
bot.begin();
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
/*
keyboardStart.addRow(row1Start, 2);
keyboardStart.addRow(row2Start, 2);
//keyboardStart.addRow(row3Start, 1);
*/
message m = bot.getUpdates();
}
void loop() {
#if WATCHDOG
Watchdog.reset();
#endif
if ( WiFi.status() != WL_CONNECTED) {
//WiFi.disconnect();
while (WiFi.begin(ssid, pass) != WL_CONNECTED) {
#if DEBUG
Serial.print(".");
#endif
delay(1000);
}
}
currentTime = millis();
if (numMessages > 0 && currentTime - lastTextSent > 20000) {
textSerial.write(TEXT);
textSerial.print(String(Nachrichten[sendMessageIndex]));
lastTextSent = currentTime;
sendMessageIndex++;
numMessages--;
if (sendMessageIndex >= MAX_MESSAGES) sendMessageIndex = 0;
}
if (currentTime - lastTextSent > 120000) {
textSerial.write(TEXT);
textSerial.print("To send custom text, open/download the 'Telegram' App and add: TannenbaumBot.");
lastTextSent = currentTime;
}
if (currentTime - lastClockSent > 600000) {
tClock = 0;
getCurrentTime();
/*
byte counterT = 0;
while (tClock == 0 && counterT < 6) {
counterT++;
delay(500);
tClock = WiFi.getTime();
}
*/
if (tClock != 0) {
textSerial.write(TIME);
textSerial.write(hour(tClock));
textSerial.write(minute(tClock));
textSerial.write(second(tClock));
}
/*
textSerial.write(rtc.getHours());
textSerial.write(rtc.getMinutes());
textSerial.write(rtc.getSeconds());
*/
lastClockSent = currentTime;
}
message m = bot.getUpdates();
/*
if(millis() - lastTimeSentTime > 600000) {
textSerial.write(TIME);
textSerial.print(String(rtc.getEpoch()) + "\n");
lastTimeSentTime = millis();
}
*/
if (Serial.available() > 0) {
eingabe = Serial.readStringUntil('\n');
if (!eingabe.equalsIgnoreCase("i")) {
bot.sendMessage("", eingabe);
eingabe = "";
}
}
else if (eingabe.equalsIgnoreCase("i")) {
if (WL_CONNECTED) {
Serial.println(F("Mit WLAN verbunden"));
IPAddress ip = WiFi.localIP();
Serial.print(F("IP Address: "));
Serial.println(ip);
// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print(F("signal strength (RSSI):"));
Serial.print(rssi);
Serial.println(F(" dBm"));
eingabe = "";
}
else Serial.println(F("KEINE Verbindung zum WLAN"));
eingabe = "";
}
if ( m.chat_id != 0 ) {
if (1) { // if a message is received from unique Chats m.sender.equals("xxxxx")
//Serial.println("Location: " + m.location.lon + ", " + m.location.lat);
//Serial.println("Name: " + m.first_name);
//Serial.println(m.chat_id);
//Serial.println(m.sender);
#if DEBUG
time_t t = m.date.toInt();
t = t + 1 * 60 * 60;
Serial.print(m.sender);
Serial.print(F(" hat um "));
Serial.print(hour(t));
Serial.print(":");
Serial.print(minute(t));
Serial.print(F(" folgendes gesendet: "));
Serial.println(m.text);
#endif
if (m.location.lon.length() != 0) {
bot.sendMessage(m.chat_id, "Requesting weather data...");
#if DEBUG
Serial.println(F("Verbinde zu api.openweathermap.org"));
#endif
weatherHttpRequest(m.location.lat, m.location.lon, m.chat_id);
}
if (m.text.equalsIgnoreCase("/help")) {
bot.sendMessage(m.chat_id, "To send custom text, type: /text and your message. For example: '/text Santa rocks!'. To display the current weather in your area, simply send your location.");
}
/*
else if (m.text.equalsIgnoreCase("/watchdog")) {
while (1);
}
*/
else if (m.text.equalsIgnoreCase("/start")) {
bot.sendMessage(m.chat_id, "Welcome " + m.first_name + "! Send /help for more information.");
}
else if (m.text.startsWith("/text") || m.text.startsWith("/Text")) {
m.text.remove(0, 6);
m.text += "\n";
if (m.text.length() <= 2) {
bot.sendMessage(m.chat_id, "Sorry, your text is too short. Please send at least 2 characters.");
}
else if (m.text.length() >= 75) {
bot.sendMessage(m.chat_id, "Sorry, your text is too long. The maxmimal text length is 74 characters.");
}
else if (numMessages == MAX_MESSAGES) {
bot.sendMessage(m.chat_id, "Sorry, there are currently too many people sending messages to Santa. Try again later.");
}
else {
if (m.first_name.length() >= 21) m.first_name.remove(20);
eingabe = m.first_name + ": " + m.text;
eingabe.toCharArray(Nachrichten[messageIndex], 100);
eingabe = "";
numMessages++;
messageIndex++;
if (messageIndex >= MAX_MESSAGES) messageIndex = 0;
bot.sendMessage(m.chat_id, "Your text is on position " + String(numMessages) + " in the pipeline. It will be displayed in approximately " + String((numMessages) * 20) + " seconds.");
}
#if DEBUG
Serial.println("Der reine Text lautet: " + m.text);
#endif
}
/*
else if (m.text.startsWith("/bierID")) {
m.text.remove(0, 7);
m.text.trim();
beerHttpRequest(BEER_ID, m.text, m.chat_id);
}
else if (m.text.startsWith("/bier") || m.text.startsWith("/Bier")) {
m.text.remove(0, 6);
m.text.trim();
beerHttpRequest(BEER_NAME, m.text, m.chat_id);
}
*/
/*
else if (m.text.equalsIgnoreCase("/wlan")) {
bot.sendMessage(m.chat_id, ssid);
}
else if (m.text.equalsIgnoreCase("/ip")) {
IPAddress ip = WiFi.localIP();
bot.sendMessage(m.chat_id, String(ip[0]) + String(".") + \
String(ip[1]) + String(".") + \
String(ip[2]) + String(".") + \
String(ip[3]));
}
else if (m.text.equalsIgnoreCase("/signal")) {
long rssi = WiFi.RSSI();
bot.sendMessage(m.chat_id, String(rssi) + " dBm");
}
*/
}
else {
bot.sendMessage(m.chat_id, "Sie sind nicht befugt diesen Befehl zu verwenden.");
Serial.println(m.chat_id);
Serial.println(m.sender);
}
}
}
int findText(String needle, String haystack) {
int foundpos = -1;
for (int i = 0; i <= haystack.length() - needle.length(); i++) {
if (haystack.substring(i, needle.length() + i) == needle) {
foundpos = i;
}
}
return foundpos;
}
void weatherHttpRequest(String lat, String lon, String chatId) {
if (clienthttp.connect(server, 80)) {
#if DEBUG
Serial.println(F("Verbunden zum Server"));
#endif
clienthttp.println("GET /data/2.5/weather?lat=" + lat + "&lon=" + lon + "&APPID=**yourapikey** HTTP/1.1");
clienthttp.println("Host: api.openweathermap.org");
clienthttp.println("Connection: close");
clienthttp.println();
responseBuffer[0] = '\0';
rbindex = 0;
startCapture = false;
}
else {
bot.sendMessage(chatId, "Cannot connect to weather server.");
return;
}
char c;
unsigned long startTime = millis();
while (clienthttp.connected() && millis() - startTime < 7000) {
if (clienthttp.available()) {
c = clienthttp.read();
if (c == '{') {
startCapture = true;
}
if (startCapture && rbindex < RBUFFSIZE) {
responseBuffer[rbindex] = c;
rbindex++;
}
}
}
if (clienthttp.connected()) {
clienthttp.stop();
clienthttp.flush();
#if DEBUG
Serial.println(F("Client won't disconnect"));
#endif
bot.sendMessage(chatId, "Data couldn't be recieved.");
return;
}
#if DEBUG
Serial.println(F("Received bytes."));
//Serial.print(strlen(responseBuffer));
Serial.println(F("Disconnecting."));
#endif
clienthttp.stop();
clienthttp.flush();
StaticJsonBuffer<RBUFFSIZE> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(responseBuffer);
if (!root.success()) {
#if DEBUG
Serial.println(F("parseObject() fehlgeschlagen"));
#endif
bot.sendMessage(chatId, "Datenverarbeitung fehlgeschlagen.");
}
else {
char bufferw[20];
char bufferc[30];
root["weather"][0]["description"].prettyPrintTo(bufferw, sizeof(bufferw));
root["name"].prettyPrintTo(bufferc, sizeof(bufferc));
#if DEBUG
Serial.println("Schnee: " + String((float)root["snow"]["3h"]));
#endif
//bot.sendMessage(m.chat_id, "Aktuelle Wetter fr " + String(bufferc) + ":");
textSerial.write(TEXT);
eingabe = "Current weather in " + String(bufferc) + ": " + String(bufferw) + " at " + String(((double)root["main"]["temp"]) - 273.15, 1) + " C";
textSerial.print(eingabe);
lastTextSent = millis();
eingabe = "";
bot.sendMessage(chatId, "The current weather in your region will be displayed soon.");
#if DEBUG
Serial.print(F("Aktuelle Temperatur: "));
Serial.print(((double)root["main"]["temp"]) - 273.15);
Serial.println(F(" C"));
Serial.print(F("Beschreibung: "));
Serial.println(bufferw);
#endif
}
}
void getCurrentTime() {
if (clienthttp.connect("api.timezonedb.com", 80)) {
clienthttp.println("GET /v2/get-time-zone?key=**yourapikey**&format=json&by=zone&zone=Europe/London HTTP/1.1");
clienthttp.println("Host: api.timezonedb.com");
clienthttp.println("Connection: close");
clienthttp.println();
responseBuffer[0] = '\0';
rbindex = 0;
startCapture = false;
}
else {
Serial.println("Verbindung zum Zeitserver fehlgeschlagen");
return;
}
char c;
unsigned long startTime = millis();
while (clienthttp.connected() && millis() - startTime < 7000) {
if (clienthttp.available()) {
c = clienthttp.read();
if (c == '{') {
startCapture = true;
}
if (startCapture && rbindex < RBUFFSIZE) {
responseBuffer[rbindex] = c;
rbindex++;
}
}
}
if (clienthttp.connected()) {
clienthttp.stop();
clienthttp.flush();
#if DEBUG
Serial.println(F("Client won't disconnect"));
#endif
return;
}
#if DEBUG
Serial.println(F("Received bytes."));
//Serial.print(strlen(responseBuffer));
Serial.println(F("Disconnecting."));
#endif
clienthttp.stop();
clienthttp.flush();
StaticJsonBuffer<RBUFFSIZE> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(responseBuffer);
if (!root.success()) {
#if DEBUG
Serial.println(F("parseObject() fehlgeschlagen"));
#endif
}
else {
tClock = root["timestamp"];
Serial.println(tClock);
}
}
//wanted to implement the brewery db but couldn't make it work so fart
/*
void beerHttpRequest(boolean type, String request, String chatId) {
if (clienthttp.connect("api.brewerydb.com", 80)) {
#if DEBUG
Serial.println(F("Verbunden zum Server"));
#endif
if (type == BEER_NAME) clienthttp.println("GET /v2/search?q=" + request + "&type=beer&key=**yourapikey**&format=json HTTP/1.1");
else clienthttp.println("GET /v2/beer/" + request + "?key=**yourapikey**&format=json HTTP/1.1");
clienthttp.println("Host: api.brewerydb.com");
clienthttp.println("Connection: close");
clienthttp.println();
responseBuffer[0] = '\0';
rbindex = 0;
startCapture = false;
}
else {
bot.sendMessage(chatId, "Verbindung zur Brewery Databse nicht mglich.");
return;
}
char c;
unsigned long startTime = millis();
while (clienthttp.connected() && millis() - startTime < 7000) {
if (clienthttp.available()) {
c = clienthttp.read();
if (c == '{') {
startCapture = true;
}
if (startCapture && rbindex < RBUFFSIZE) {
responseBuffer[rbindex] = c;
rbindex++;
}
}
}
if (clienthttp.connected()) {
clienthttp.stop();
clienthttp.flush();
#if DEBUG
Serial.println(F("Client won't disconnect"));
#endif
bot.sendMessage(chatId, "Daten konnten nicht empfangen werden.");
return;
}
#if DEBUG
Serial.println(F("Received bytes."));
//Serial.print(strlen(responseBuffer));
Serial.println(F("Disconnecting."));
#endif
clienthttp.stop();
clienthttp.flush();
StaticJsonBuffer<RBUFFSIZE> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(responseBuffer);
if (!root.success()) {
#if DEBUG
Serial.println(F("parseObject() fehlgeschlagen"));
#endif
bot.sendMessage(chatId, "Datenverarbeitung fehlgeschlagen.");
}
else {
if (type == BEER_NAME) {
char bufferName[10][50]; // char bufferName[10][50];
char bufferID[10][9]; //char bufferID[10][6];
int results = root["totalResults"]; // int results = atoi(&root["totalResults"]);
if (results > 10) results = 10;
for (byte i = 0; i < results; i++) {
root["data"][i]["id"].prettyPrintTo(bufferID[i], sizeof(bufferID[i]));
root["data"][i]["name"].prettyPrintTo(bufferName[i], sizeof(bufferName[i]));
}
String beerMessage;
if (results > 0) {
beerMessage = "Hier die ersten " + String(results) + " Treffer. Meintest du:";
for (byte i = 0; i < results; i++) {
beerMessage += '\n';
beerMessage += String(bufferName[i]);
beerMessage += ": /bierID";
String IDtemp = String(bufferID[i]);
IDtemp.remove(0, 1);
IDtemp.remove(6, 1);
beerMessage += IDtemp;
}
}
else beerMessage = "Zu diesem Suchbegriff gab es leider keine Treffer.";
bot.sendMessage(chatId, beerMessage);
#if DEBUG
Serial.println(F("test"));
#endif
}
else {
char bufferDescription[1000];
char bufferStyle[50];
if (root["data"]["description"] != NULL) {
root["data"]["description"].prettyPrintTo(bufferDescription, sizeof(bufferDescription));
bot.sendMessage(chatId, bufferDescription);
#if DEBUG
Serial.println(bufferDescription);
#endif
}
if (root["data"]["style"]["description"] != NULL) {
root["data"]["style"]["description"].prettyPrintTo(bufferDescription, sizeof(bufferDescription));
root["data"]["style"]["name"].prettyPrintTo(bufferStyle, sizeof(bufferStyle));
bot.sendMessage(chatId, "Es handelt sich um ein: " + String(bufferStyle) + ".'\n'" + String(bufferDescription));
#if DEBUG
Serial.println("Es handelt sich um ein: " + String(bufferStyle) + ".'\n'" + String(bufferDescription));
#endif
}
}
}
}
*/
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <Time.h>
#include <TimeLib.h>
#include "Parola_Fonts_data.h"
#define MAX_DEVICES 8
#define CLK_PIN 13
#define DATA_PIN 11
#define CS_PIN 10
#define TIME 1
#define TEXT 2
#define WEATHER 3
#define STATS 4
#define NUM_TIMEZONES 8
MD_Parola P = MD_Parola(CS_PIN, MAX_DEVICES);
uint8_t scrollSpeed = 30; // default frame delay value
textEffect_t scrollEffect = PA_SCROLL_LEFT;
textPosition_t scrollAlign = PA_CENTER;
uint16_t scrollPause = 0; // in milliseconds
#define BUF_SIZE 100
char curMessage[BUF_SIZE] = "www.steuermeinentannenbaum.de"; //{0x01};
char newMessage[BUF_SIZE];
String newMessageString = "";
bool newMessageAvailable = false, showClock = false;
unsigned long lastNewMessageTime;
byte dataByte = 0, zoneIndex, timeBuffer[3];
struct timeZonesStruct {
int GMT;
String name;
};
timeZonesStruct timeZones[NUM_TIMEZONES] = {
{1, "Berlin"},
{0, "UK"},
{ -8, "LA"},
{ -6, "Texas"},
{ -5, "NYC"},
{11, "Sydney"},
{9, "Tokyo"},
{3, "Russia"}
};
/*
void readSerial(void)
{
static char *cp = newMessage;
while (Serial1.available())
{
cp = (char)Serial1.read();
if ((*cp == '\n') || (cp - newMessage >= BUF_SIZE-2)) // end of message character or full buffer
{
cp = '\0'; // end the string
// restart the index for next filling spree and flag we have a message waiting
cp = newMessage;
newMessageAvailable = true;
Serial.println(cp);
//Serial.flush();
}
else // move char pointer to next position
cp++;
}
}
*/
void readSerial(void) {
if (Serial.available()) {
if (dataByte == 0) dataByte = Serial.read();
else if (dataByte == TEXT) {
newMessageString = Serial.readStringUntil('\n');
newMessageString.toCharArray(newMessage, BUF_SIZE);
newMessageAvailable = true;
showClock = false;
lastNewMessageTime = millis();
dataByte = 0;
}
else if (dataByte == TIME) {
Serial.readBytes(timeBuffer, 3);
setTime(timeBuffer[0], timeBuffer[1], timeBuffer[2], day(), month(), year());
dataByte = 0;
}
}
}
void setup()
{
Serial.begin(9600);
randomSeed(analogRead(0));
P.begin();
P.setFont(ExtASCII);
P.displayClear();
P.displaySuspend(false);
//P.setScrollSpacing(1);
lastNewMessageTime = millis();
P.displayText(curMessage, scrollAlign, scrollSpeed, scrollPause, scrollEffect, scrollEffect);
//newMessage[0] = '\0';
}
void loop()
{
readSerial();
if (millis() - lastNewMessageTime > 30000) showClock = true;
if (P.displayAnimate())
{
if (showClock) {
P.setTextEffect(PA_SCROLL_DOWN, PA_SCROLL_DOWN);
P.setPause(5000);
newMessageString = timeZones[zoneIndex].name + ": ";
newMessageString += returnLocalTime(zoneIndex);
zoneIndex++;
if (zoneIndex >= NUM_TIMEZONES) zoneIndex = 0;
newMessageString.toCharArray(newMessage, BUF_SIZE);
strcpy(curMessage, newMessage);
newMessageString = "";
P.displayReset();
}
else if (newMessageAvailable)
{
P.setTextEffect(PA_SCROLL_LEFT, PA_SCROLL_LEFT);
P.setPause(0);
strcpy(curMessage, newMessage);
newMessageAvailable = false;
newMessageString = "";
}
P.displayReset();
}
}
String returnLocalTime(byte index) {
String temp;
int hourInt = hour() + timeZones[index].GMT;
if (hourInt < 0) hourInt = 24 + hourInt;
else if (hourInt > 23) hourInt -= 24;
if (hourInt < 10) temp += "0";
temp += String(hourInt) + ":";
if (minute() < 10) temp += "0";
temp += String(minute());
return temp;
}
// Data file for UTF-8 example user defined fonts
#ifndef FONTS_DATA_H
#define FONTS_DATA_H
MD_MAX72XX::fontType_t ExtASCII[] PROGMEM =
{
0, // 0 - 'Unused'
0, // 1 - 'Unused'
0, // 2 - 'Unused'
0, // 3 - 'Unused'
0, // 4 - 'Unused'
0, // 5 - 'Unused'
0, // 6 - 'Unused'
0, // 7 - 'Unused'
0, // 8 - 'Unused'
0, // 9 - 'Unused'
0, // 10 - 'Unused'
0, // 11 - 'Unused'
0, // 12 - 'Unused'
0, // 13 - 'Unused'
0, // 14 - 'Unused'
0, // 15 - 'Unused'
0, // 16 - 'Unused'
0, // 17 - 'Unused'
0, // 18 - 'Unused'
0, // 19 - 'Unused'
0, // 20 - 'Unused'
0, // 21 - 'Unused'
0, // 22 - 'Unused'
0, // 23 - 'Unused'
0, // 24 - 'Unused'
0, // 25 - 'Unused'
0, // 26 - 'Unused'
0, // 27 - 'Unused'
0, // 28 - 'Unused'
0, // 29 - 'Unused'
0, // 30 - 'Unused'
0, // 31 - 'Unused'
2, 0, 0, // 32 - 'Space'
1, 95, // 33 - '!'
3, 7, 0, 7, // 34 - '"'
5, 20, 127, 20, 127, 20, // 35 - '#'
5, 36, 42, 127, 42, 18, // 36 - '$'
5, 35, 19, 8, 100, 98, // 37 - '%'
5, 54, 73, 86, 32, 80, // 38 - '&'
2, 4, 3, // 39
3, 28, 34, 65, // 40 - '('
3, 65, 34, 28, // 41 - ')'
5, 42, 28, 127, 28, 42, // 42 - '*'
5, 8, 8, 62, 8, 8, // 43 - '+'
2, 128, 96, // 44 - ','
5, 8, 8, 8, 8, 8, // 45 - '-'
2, 96, 96, // 46 - '.'
5, 32, 16, 8, 4, 2, // 47 - '/'
5, 62, 81, 73, 69, 62, // 48 - '0'
3, 66, 127, 64, // 49 - '1'
5, 114, 73, 73, 73, 70, // 50 - '2'
5, 33, 65, 73, 77, 51, // 51 - '3'
5, 24, 20, 18, 127, 16, // 52 - '4'
5, 39, 69, 69, 69, 57, // 53 - '5'
5, 60, 74, 73, 73, 49, // 54 - '6'
5, 65, 33, 17, 9, 7, // 55 - '7'
5, 54, 73, 73, 73, 54, // 56 - '8'
5, 70, 73, 73, 41, 30, // 57 - '9'
1, 20, // 58 - ':'
2, 128, 104, // 59 - ';'
4, 8, 20, 34, 65, // 60 - '<'
5, 20, 20, 20, 20, 20, // 61 - '='
4, 65, 34, 20, 8, // 62 - '>'
5, 2, 1, 89, 9, 6, // 63 - '?'
5, 62, 65, 93, 89, 78, // 64 - '@'
5, 124, 18, 17, 18, 124, // 65 - 'A'
5, 127, 73, 73, 73, 54, // 66 - 'B'
5, 62, 65, 65, 65, 34, // 67 - 'C'
5, 127, 65, 65, 65, 62, // 68 - 'D'
5, 127, 73, 73, 73, 65, // 69 - 'E'
5, 127, 9, 9, 9, 1, // 70 - 'F'
5, 62, 65, 65, 81, 115, // 71 - 'G'
5, 127, 8, 8, 8, 127, // 72 - 'H'
3, 65, 127, 65, // 73 - 'I'
5, 32, 64, 65, 63, 1, // 74 - 'J'
5, 127, 8, 20, 34, 65, // 75 - 'K'
5, 127, 64, 64, 64, 64, // 76 - 'L'
5, 127, 2, 28, 2, 127, // 77 - 'M'
5, 127, 4, 8, 16, 127, // 78 - 'N'
5, 62, 65, 65, 65, 62, // 79 - 'O'
5, 127, 9, 9, 9, 6, // 80 - 'P'
5, 62, 65, 81, 33, 94, // 81 - 'Q'
5, 127, 9, 25, 41, 70, // 82 - 'R'
5, 38, 73, 73, 73, 50, // 83 - 'S'
5, 3, 1, 127, 1, 3, // 84 - 'T'
5, 63, 64, 64, 64, 63, // 85 - 'U'
5, 31, 32, 64, 32, 31, // 86 - 'V'
5, 63, 64, 56, 64, 63, // 87 - 'W'
5, 99, 20, 8, 20, 99, // 88 - 'X'
5, 3, 4, 120, 4, 3, // 89 - 'Y'
5, 97, 89, 73, 77, 67, // 90 - 'Z'
3, 127, 65, 65, // 91 - '['
5, 2, 4, 8, 16, 32, // 92 - '\'
3, 65, 65, 127, // 93 - ']'
5, 4, 2, 1, 2, 4, // 94 - '^'
5, 64, 64, 64, 64, 64, // 95 - '_'
2, 3, 4, // 96 - '`'
5, 32, 84, 84, 120, 64, // 97 - 'a'
5, 127, 40, 68, 68, 56, // 98 - 'b'
5, 56, 68, 68, 68, 40, // 99 - 'c'
5, 56, 68, 68, 40, 127, // 100 - 'd'
5, 56, 84, 84, 84, 24, // 101 - 'e'
4, 8, 126, 9, 2, // 102 - 'f'
5, 24, 164, 164, 156, 120, // 103 - 'g'
5, 127, 8, 4, 4, 120, // 104 - 'h'
3, 68, 125, 64, // 105 - 'i'
4, 64, 128, 128, 122, // 106 - 'j'
4, 127, 16, 40, 68, // 107 - 'k'
3, 65, 127, 64, // 108 - 'l'
5, 124, 4, 120, 4, 120, // 109 - 'm'
5, 124, 8, 4, 4, 120, // 110 - 'n'
5, 56, 68, 68, 68, 56, // 111 - 'o'
5, 252, 24, 36, 36, 24, // 112 - 'p'
5, 24, 36, 36, 24, 252, // 113 - 'q'
5, 124, 8, 4, 4, 8, // 114 - 'r'
5, 72, 84, 84, 84, 36, // 115 - 's'
4, 4, 63, 68, 36, // 116 - 't'
5, 60, 64, 64, 32, 124, // 117 - 'u'
5, 28, 32, 64, 32, 28, // 118 - 'v'
5, 60, 64, 48, 64, 60, // 119 - 'w'
5, 68, 40, 16, 40, 68, // 120 - 'x'
5, 76, 144, 144, 144, 124, // 121 - 'y'
5, 68, 100, 84, 76, 68, // 122 - 'z'
3, 8, 54, 65, // 123 - '{'
1, 119, // 124 - '|'
3, 65, 54, 8, // 125 - '}'
5, 2, 1, 2, 4, 2, // 126 - '~'
0, // 127 - 'Unused'
6, 20, 62, 85, 85, 65, 34, // 128 - 'Euro sign'
5, 28, 62, 124, 62, 28, // 129 - 'Heart' costum char!!!
2, 128, 96, // 130 - 'Single low 9 quotation mark'
5, 192, 136, 126, 9, 3, // 131 - 'f with hook'
4, 128, 96, 128, 96, // 132 - 'Single low 9 quotation mark'
8, 96, 96, 0, 96, 96, 0, 96, 96, // 133 - 'Horizontal ellipsis'
3, 4, 126, 4, // 134 - 'Dagger'
3, 20, 126, 20, // 135 - 'Double dagger'
4, 2, 1, 1, 2, // 136 - 'Modifier circumflex'
7, 35, 19, 104, 100, 2, 97, 96, // 137 - 'Per mille sign'
5, 72, 85, 86, 85, 36, // 138 - 'S with caron'
3, 8, 20, 34, // 139 - '< quotation'
6, 62, 65, 65, 127, 73, 73, // 140 - 'OE'
0, // 141 - 'Not used'
5, 68, 101, 86, 77, 68, // 142 - 'z with caron'
0, // 143 - 'Not used'
0, // 144 - 'Not used'
2, 3, 4, // 145 - 'Left single quote mark'
2, 4, 3, // 146 - 'Right single quote mark'
4, 3, 4, 3, 4, // 147 - 'Left double quote marks'
4, 4, 3, 4, 3, // 148 - 'Right double quote marks'
4, 0, 24, 60, 24, // 149 - 'Bullet Point'
3, 8, 8, 8, // 150 - 'En dash'
5, 8, 8, 8, 8, 8, // 151 - 'Em dash'
4, 2, 1, 2, 1, // 152 - 'Small ~'
7, 1, 15, 1, 0, 15, 2, 15, // 153 - 'TM'
5, 72, 85, 86, 85, 36, // 154 - 's with caron'
3, 34, 20, 8, // 155 - '> quotation'
7, 56, 68, 68, 124, 84, 84, 8, // 156 - 'oe'
0, // 157 - 'Not used'
5, 68, 101, 86, 77, 68, // 158 - 'z with caron'
5, 12, 17, 96, 17, 12, // 159 - 'Y diaresis'
2, 0, 0, // 160 - 'Non-breaking space'
1, 125, // 161 - 'Inverted !'
5, 60, 36, 126, 36, 36, // 162 - 'Cent sign'
5, 72, 126, 73, 65, 102, // 163 - 'Pound sign'
5, 34, 28, 20, 28, 34, // 164 - 'Currency sign'
5, 43, 47, 252, 47, 43, // 165 - 'Yen'
1, 119, // 166 - '|'
4, 102, 137, 149, 106, // 167 - 'Section sign'
3, 1, 0, 1, // 168 - 'Spacing diaresis'
7, 62, 65, 93, 85, 85, 65, 62, // 169 - 'Copyright'
3, 13, 13, 15, // 170 - 'Feminine Ordinal Ind.'
5, 8, 20, 42, 20, 34, // 171 - '<<'
5, 8, 8, 8, 8, 56, // 172 - 'Not sign'
0, // 173 - 'Soft Hyphen'
7, 62, 65, 127, 75, 117, 65, 62, // 174 - 'Registered Trademark'
5, 1, 1, 1, 1, 1, // 175 - 'Spacing Macron Overline'
3, 2, 5, 2, // 176 - 'Degree'
5, 68, 68, 95, 68, 68, // 177 - '+/-'
3, 25, 21, 19, // 178 - 'Superscript 2'
3, 17, 21, 31, // 179 - 'Superscript 3'
2, 2, 1, // 180 - 'Acute accent'
4, 252, 64, 64, 60, // 181 - 'micro (mu)'
5, 6, 9, 127, 1, 127, // 182 - 'Paragraph Mark'
2, 24, 24, // 183 - 'Middle Dot'
3, 128, 128, 96, // 184 - 'Spacing sedilla'
2, 2, 31, // 185 - 'Superscript 1'
4, 6, 9, 9, 6, // 186 - 'Masculine Ordinal Ind.'
5, 34, 20, 42, 20, 8, // 187 - '>>'
6, 64, 47, 16, 40, 52, 250, // 188 - '1/4'
6, 64, 47, 16, 200, 172, 186, // 189 - '1/2'
6, 85, 53, 31, 40, 52, 250, // 190 - '3/4'
5, 48, 72, 77, 64, 32, // 191 - 'Inverted ?'
5, 120, 20, 21, 22, 120, // 192 - 'A grave'
5, 120, 22, 21, 20, 120, // 193 - 'A acute'
5, 122, 21, 21, 21, 122, // 194 - 'A circumflex'
5, 120, 22, 21, 22, 121, // 195 - 'A tilde'
5, 120, 21, 20, 21, 120, // 196 - 'A diaresis'
5, 120, 20, 21, 20, 120, // 197 - 'A ring above'
6, 124, 10, 9, 127, 73, 73, // 198 - 'AE'
5, 30, 161, 161, 97, 18, // 199 - 'C sedilla'
4, 124, 85, 86, 68, // 200 - 'E grave'
4, 124, 86, 85, 68, // 201 - 'E acute'
4, 126, 85, 85, 70, // 202 - 'E circumflex'
4, 124, 85, 84, 69, // 203 - 'E diaresis'
3, 68, 125, 70, // 204 - 'I grave'
3, 68, 126, 69, // 205 - 'I acute'
3, 70, 125, 70, // 206 - 'I circumplex'
3, 69, 124, 69, // 207 - 'I diaresis'
6, 4, 127, 69, 65, 65, 62, // 208 - 'Capital Eth'
5, 124, 10, 17, 34, 125, // 209 - 'N tilde'
5, 56, 68, 69, 70, 56, // 210 - 'O grave'
5, 56, 70, 69, 68, 56, // 211 - 'O acute'
5, 58, 69, 69, 69, 58, // 212 - 'O circumflex'
5, 56, 70, 69, 70, 57, // 213 - 'O tilde'
5, 56, 69, 68, 69, 56, // 214 - 'O diaresis'
5, 34, 20, 8, 20, 34, // 215 - 'Multiplication sign'
7, 64, 62, 81, 73, 69, 62, 1, // 216 - 'O slashed'
5, 60, 65, 66, 64, 60, // 217 - 'U grave'
5, 60, 64, 66, 65, 60, // 218 - 'U acute'
5, 58, 65, 65, 65, 58, // 219 - 'U circumflex'
5, 60, 65, 64, 65, 60, // 220 - 'U diaresis'
5, 12, 16, 98, 17, 12, // 221 - 'Y acute'
4, 127, 18, 18, 12, // 222 - 'Capital thorn'
4, 254, 37, 37, 26, // 223 - 'Small letter sharp S'
5, 32, 84, 85, 122, 64, // 224 - 'a grave'
5, 32, 84, 86, 121, 64, // 225 - 'a acute'
5, 34, 85, 85, 121, 66, // 226 - 'a circumflex'
5, 32, 86, 85, 122, 65, // 227 - 'a tilde'
5, 32, 85, 84, 121, 64, // 228 - 'a diaresis'
5, 32, 84, 85, 120, 64, // 229 - 'a ring above'
7, 32, 84, 84, 124, 84, 84, 8, // 230 - 'ae'
5, 24, 36, 164, 228, 40, // 231 - 'c sedilla'
5, 56, 84, 85, 86, 88, // 232 - 'e grave'
5, 56, 84, 86, 85, 88, // 233 - 'e acute'
5, 58, 85, 85, 85, 90, // 234 - 'e circumflex'
5, 56, 85, 84, 85, 88, // 235 - 'e diaresis'
3, 68, 125, 66, // 236 - 'i grave'
3, 68, 126, 65, // 237 - 'i acute'
3, 70, 125, 66, // 238 - 'i circumflex'
3, 69, 124, 65, // 239 - 'i diaresis'
4, 48, 75, 74, 61, // 240 - 'Small eth'
4, 122, 9, 10, 113, // 241 - 'n tilde'
5, 56, 68, 69, 70, 56, // 242 - 'o grave'
5, 56, 70, 69, 68, 56, // 243 - 'o acute'
5, 58, 69, 69, 69, 58, // 244 - 'o circumflex'
5, 56, 70, 69, 70, 57, // 245 - 'o tilde'
5, 56, 69, 68, 69, 56, // 246 - 'o diaresis'
5, 8, 8, 42, 8, 8, // 247 - 'Division sign'
6, 64, 56, 84, 76, 68, 58, // 248 - 'o slashed'
5, 60, 65, 66, 32, 124, // 249 - 'u grave'
5, 60, 64, 66, 33, 124, // 250 - 'u acute'
5, 58, 65, 65, 33, 122, // 251 - 'u circumflex'
5, 60, 65, 64, 33, 124, // 252 - 'u diaresis'
4, 156, 162, 161, 124, // 253 - 'y acute'
4, 252, 72, 72, 48, // 254 - 'small thorn'
4, 157, 160, 160, 125, // 255 - 'y diaresis'
};
#endif
TelegramBot.cpp
Arduino#include "TelegramBot.h"
TelegramBot::TelegramBot(const char* token, Client &client) {
this->client = &client;
this->token=token;
}
void TelegramBot::begin() {
if(!client->connected()){
client->connect(HOST, SSL_PORT);
}
}
/************************************************************************************
* GetUpdates - function to receive messages from telegram as a Json and parse them *
************************************************************************************/
message TelegramBot::getUpdates() {
begin();
//Send your request to api.telegram.org
String getRequest = "GET /bot"+String(token)+"/getUpdates?limit=1&offset="+String(last_message_recived)+" HTTP/1.1";
client->println(getRequest);
client->println("User-Agent: curl/7.37.1");
client->println("Host: api.telegram.org");
client->println("Accept: */*");
client->println();
String payload = readPayload();
if (payload != "") {
message m;
StaticJsonBuffer<JSON_BUFF_SIZE> jsonBuffer;
JsonObject & root = jsonBuffer.parseObject(payload);
if(root.success()){
int update_id = root["result"][0]["update_id"];
update_id = update_id+1;
if(last_message_recived != update_id ){
String sender = root["result"][0]["message"]["from"]["username"];
String text = root["result"][0]["message"]["text"];
String chat_id = root["result"][0]["message"]["chat"]["id"];
String date = root["result"][0]["message"]["date"];
String first_name = root["result"][0]["message"]["from"]["first_name"];
String lon = root["result"][0]["message"]["location"]["longitude"];
String lat = root["result"][0]["message"]["location"]["latitude"];
m.sender = sender;
m.text = text;
m.chat_id = chat_id;
m.date = date;
m.first_name = first_name;
m.location.lon = lon;
m.location.lat = lat;
last_message_recived=update_id;
return m;
}else{
m.chat_id = "";
return m;
}
}
else{
Serial.println("");
Serial.println("Message too long, skipped.");
Serial.println("");
int update_id_first_digit=0;
int update_id_last_digit=0;
for(int a =0; a<3; a++){
update_id_first_digit= payload.indexOf(':',update_id_first_digit+1);
}
for(int a =0; a<2; a++){
update_id_last_digit= payload.indexOf(',',update_id_last_digit+1);
}
last_message_recived = payload.substring(update_id_first_digit+1,update_id_last_digit).toInt() +1;
}
}
}
// send message function
// send a simple text message to a telegram char
String TelegramBot::sendMessage(String chat_id, String text) {
if(chat_id!="0" && chat_id!=""){
StaticJsonBuffer<JSON_BUFF_SIZE> jsonBuffer;
JsonObject& buff = jsonBuffer.createObject();
buff["chat_id"] = chat_id;
buff["text"] = text;
String msg;
buff.printTo(msg);
return postMessage(msg);
} else {
Serial.println("Chat_id not defined");
}
}
// send a message to a telegram chat with a reply markup
String TelegramBot::sendMessage(String chat_id, String text, TelegramKeyboard &keyboard_markup, bool one_time_keyboard, bool resize_keyboard) {
StaticJsonBuffer<JSON_BUFF_SIZE> jsonBuffer;
JsonObject& buff = jsonBuffer.createObject();
buff["chat_id"] = chat_id;
buff["text"] = text;
JsonObject& reply_markup = buff.createNestedObject("reply_markup");
JsonArray& keyboard = reply_markup.createNestedArray("keyboard");
for (int a = 1 ; a <= keyboard_markup.length() ; a++){
JsonArray& row = keyboard.createNestedArray();
for( int b = 1; b <= keyboard_markup.rowSize(a) ; b++){
row.add(keyboard_markup.getButton(a,b));
}
}
reply_markup.set<bool>("one_time_keyboard", one_time_keyboard);
reply_markup.set<bool>("resize_keyboard", resize_keyboard);
reply_markup.set<bool>("selective", false);
String msg;
buff.printTo(msg);
// Serial.println(msg);
return postMessage(msg);
}
// gets the telegram json string
// posts the message to telegram
// returns the payload
String TelegramBot::postMessage(String msg) {
begin();
client->println("POST /bot"+String(token)+"/sendMessage"+" HTTP/1.1");
client->println("Host: api.telegram.org");
client->println("Content-Type: application/json");
client->println("Connection: close");
client->print("Content-Length: ");
client->println(msg.length());
client->println();
client->println(msg);
return readPayload();
}
// reads the payload coming from telegram server
// returns the payload string
String TelegramBot::readPayload(){
char c;
String payload="";
//Read the answer and save it in String payload
while (client->connected()) {
payload = client->readStringUntil('\n');
if (payload == "\r") {
break;
}
}
payload = client->readStringUntil('\r');
// Serial.println(payload);
return convertText(payload);
}
String TelegramBot::convertText(String unicodeStr){ //converts UTF-16 Unicode to extended Ascii. Emojis not supported
String out = "";
int len = unicodeStr.length();
char iChar;
char* error;
for (int i = 0; i < len; i++){
iChar = unicodeStr[i];
if(iChar == '\\'){ // got escape char
iChar = unicodeStr[++i];
if(iChar == 'u'){ // got unicode hex
char unicode[6];
unicode[0] = '0';
unicode[1] = 'x';
for (int j = 0; j < 4; j++){
iChar = unicodeStr[++i];
unicode[j + 2] = iChar;
}
long unicodeVal = strtol(unicode, &error, 16); //convert the string
if (unicodeVal == 0x20AC) unicodeVal = 0x80; // euro sign
//Serial.print("strtol: ");
//Serial.println(unicodeVal, HEX);
out += (char)unicodeVal;
} else if(iChar == '/'){
out += iChar;
} else if(iChar == 'n'){
out += '\n';
}
} else if (iChar == '<' && unicodeStr[i + 1] == '3') { //convert "<3" to costum set 'heart' char, ascii: 0x81
long charTemp = 0x81;
out += (char)charTemp;
i++;
} else {
out += iChar;
}
}
return out;
}
// Copyright Casa Jasmina 2016
// LGPL License
//
// TelegramBot library
// https://github.com/CasaJasmina/TelegramBot-Library
#ifndef TelegramBot_h
#define TelegramBot_h
#include <Arduino.h>
#include <ArduinoJson.h>
#include <Client.h>
#include <TelegramKeyboard.h>
#define HOST "api.telegram.org"
#define SSL_PORT 443
#define JSON_BUFF_SIZE 10000
struct locationStruct{
String lon;
String lat;
};
struct message{
String text;
String chat_id;
String sender;
String first_name;
String date;
locationStruct location;
};
class TelegramBot
{
public:
TelegramBot(const char* token, Client &client);
void begin();
String sendMessage(String chat_id, String text);
String sendMessage(String chat_id, String text, TelegramKeyboard &keyboard_markup, bool one_time_keyboard = true, bool resize_keyboard = true);
String postMessage(String msg);
message getUpdates();
private:
String readPayload();
String convertText(String unicodeStr);
const char* token;
int last_message_recived;
Client *client;
};
#endif
Comments