Telegram-Chatbot für die senseBox

Ein einfacher Chatbot, um die senseBox auszulesen, ist ein cooles Feature. Man kann zum Beispiel von überall die aktuellen Werte der senseBox abrufen. In diesem Projekt lernt ihr die Funktionen dieser Library an den richtigen Stellen zu bearbeiten, um über den Chatbot an der senseBox angeschlossene Aktoren zu steuern und Sensoren auszulesen.

Aufbau

Der Aufbau ist sehr einfach und besteht am Anfang aus der MCU mit WiFi Bee auf Steckplatz XBEE1. Später kommt dann noch der Sensor für Temperatur und Luftfeuchtigkeit (HDC1080) hinzu, welcher an einen der I2C/Wire Steckplätze angeschlossen wird.

Programmierung

Für dieses Projekt solltet ihr euch mindestens mit den ersten Schritten aus der senseBox Dokumentation auskennen. Euer Rechner sollte dementsprechend so konfiguriert sein, dass ihr die senseBox MCU über Arduino programmieren könnt (siehe Bereich „Erste Schritte“ im Materialbereich). Außerdem werden Grundlagen zur Implementierung eines Sensors vorausgesetzt.

Benötigte Bibliotheken installieren

Um den Chatbot verwenden zu können, brauchen wir eine Arduino Bibliothek, die Teile der HTTP-basierten Telegram Bot API implementiert. Diese gehört nicht zu den Standard Bibliotheken im Board Support Package der senseBox, weshalb sie zusätzlich integriert werden muss. Starte dazu die Arduino IDE und öffne dort den Bibliotheksverwalter unter Sketch -> Bibliothek einbinden -> Bibliotheken verwalten… (oder Shortcut Strg + Shift + i). Sucht und installiert dort die beiden Bibliotheken:

  1. UniversalTelegramBot von Brian Lough (Version 1.1.0 oder höher)
  2. ArduinoJson von Benoit Blanchon (Version 5.13.5; nicht Version 6 oder höher!)

Telegram-Bot erstellen

Falls du den Telegram Messenger noch nicht verwendest, installiere diesen auf deinem Smartphone oder Computer. (https://telegram.org/apps). Starte Telegram und suche über das Eingabefeld nach @BotFather und starte den Chat mit ihm. @BotFather ist selbst ein Bot und wird genutzt, um eigene Bots zu erstellen oder zu bearbeiten.

/images/projects/telegram_bot/01_botfather.png - Logo

Erstelle nun mit dem /newbot Befehl im Chat einen neuen Bot und gib ihm einen Namen.

/images/projects/telegram_bot/02_botname.png - Logo

Als nächstes braucht der Bot noch einen eindeutigen Nutzernamen (username). Hier wird der Name senseBoxMCU_testBot gewählt.

/images/projects/telegram_bot/03_botcreated.png - Logo

Wenn du einen Namen gefunden hast, der noch nicht vergeben ist, bekommst du eine Bestätigung das alles geklappt hat, wie oben dargestellt. Wichtige Informationen sind einmal der Chatlink zu deinem Bot, sowie den rot gedruckten Access Token. Letzteren musst du kopieren und gleich im Anwendungsbeispiel einfügen.

Beispiel 1: LED steuern

Als erstes Ziel soll die rote Onboard-LED auf der senseBox MCU über den Bot gesteuert werden. Als Grundlage wird dazu das FlashledBot Beispiel aus der UniversalTelegramBot Library genutzt (Das Original findet man über “Datei > Beispiele > Beispiele aus eigenen Bibiliotheken > UniversalTelegramBot > 101 > FlaschledBot”). Im folgenden wurde zunächst lediglich der WLAN Zugang (ssid und password), der Access Token (BOTtoken) und die Nummer des LED-Pins (7 für die rote LED auf dem Board) angepasst:

 #include <WiFiSSLClient.h>
 #include <WiFi101.h>
 #include <UniversalTelegramBot.h>

// Initialize Wifi connection to the router
char ssid[] = "senseBox-net";              // your network SSID (name)
char password[] = "senseBox-pw";           // your network key

const int ledPin = 7; //red LED onboard senseBoxMCU

// Initialize Telegram BOT
#define BOTtoken "879449240:AAGxBFMQvDpd50lQNQlYrLlmf5bDz1aXPsk"  // your Bot Token (Get from Botfather)

WiFiSSLClient client;
UniversalTelegramBot bot(BOTtoken, client);

int Bot_mtbs = 1000; //mean time between scan messages
long Bot_lasttime;   //last time messages' scan has been done
bool Start = false;
int ledStatus = 0;

void handleNewMessages(int numNewMessages) {
  Serial.println("handleNewMessages");
  Serial.println(String(numNewMessages));
  for(int i=0; i<numNewMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);
    String text = bot.messages[i].text;
    if (text == "/ledon") {
      digitalWrite(ledPin, HIGH);   // turn the LED on (HIGH is the voltage level)
      ledStatus = 1;
      bot.sendMessage(chat_id, "Led is ON", "");
    }
    if (text == "/ledoff") {
      ledStatus = 0;
      digitalWrite(ledPin, LOW);    // turn the LED off (LOW is the voltage level)
      bot.sendMessage(chat_id, "Led is OFF", "");
    }
    if (text == "/status") {
      if(ledStatus){
        bot.sendMessage(chat_id, "Led is ON", "");
      } else {
        bot.sendMessage(chat_id, "Led is OFF", "");
      }
    }
    if (text == "/start") {
      String from_name = bot.messages[i].from_name;
      if (from_name == "") from_name = "Anonymous";

      String welcome = "Welcome, " + from_name + "! This is your fancy senseBoxMCU bot \xF0\x9F\x91\xBD\n\n";
      welcome = welcome + "Use the following commands to control the red onboard LED:\n"; 
      welcome = welcome + "/ledon : to switch the Led ON \n"; 
      welcome = welcome + "/ledoff : to switch the Led OFF \n";
      welcome = welcome + "/status : Returns current status of LED \n";
      bot.sendMessage(chat_id, welcome, "Markdown");
    }
  }
}

void setup() {
  Serial.begin(9600);
  //while(!Serial);
  delay(2000);

  // attempt to connect to Wifi network:
  Serial.print("Connecting Wifi: ");
  Serial.println(ssid);
  while (WiFi.begin(ssid, password) != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  IPAddress ip = WiFi.localIP();
  Serial.println(ip);

  pinMode(ledPin, OUTPUT); // initialize digital ledPin as an output.
  delay(10);
  digitalWrite(ledPin, LOW); //initialize pin as off
}

void loop() {

  if (millis() > Bot_lasttime + Bot_mtbs)  {
    int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    while(numNewMessages) {
      Serial.println("got response");
      handleNewMessages(numNewMessages);
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    }
    Bot_lasttime = millis();
  }
}

Jetzt kann der Code auf das senseBox Board hochgeladen werden. Wechsel nun zu Telegram. Hier kann man nun mit dem Bot chatten, indem man zuerst seinen Kontakt auswählt (entweder durch Klicken auf den Link den man von BotFather erhält, oder man sucht den Bot über das Suchfeld). Nun kann man mit dem Chatten beginnen! Mit dem Befehl /start bekommt man eine Übersicht über die verfügbaren Befehle:

/images/projects/telegram_bot/03_botcreated.png - Logo

Wenn man nun /ledon (bzw. /ledoff) eingibt, kann man die rote LED auf der Platine neben dem USB-Eingang an- oder ausschalten.

/images/projects/telegram_bot/04_botcommunication.png - Logo

Im seriellen Monitor sollte jedes Mal eine Benachrichtigung kommen, wenn der Bot eine Nachricht bekommt.

/images/projects/telegram_bot/05_ledonoff.png - Logo

Schaut man im Sketch die Funktion handleNewMessages(…) ab Zeile 23 an sieht man, dass dort alle verfügbaren Kommandos die der Bot kennt definiert werden. Man kann hier die bekannten Kommandos ändern bzw. neu erstellen oder löschen.

Ein paar der Kommandos haben darüber hinaus zusätzliche Funktionen. /start wird beispielsweise automatisch am Anfang aufgerufen, wenn der Bot zum ersten Mal kontaktiert wird. Um das zu testen, kann man zum Beispiel den Chatverlauf mit dem Bot löschen und dann den Chat neu starten.

Beispiel 2: Sensor auslesen

Als nächstes Beispiel soll der Wert eines Sensors im Chat abgefragt werden. Hierbei soll die Temperatur und Luftfeuchte ausgegeben werden, sobald der Bot nach dem /status gefragt wird. Dazu wird der senseBox HDC1080 Sensor an einen der I2C Ports angeschlossen und das Beispiel mit den Funktionen, um den Sensor auszulesen, ergänzt. Zuerst braucht man dazu die Library für den Sensor. Wenn das senseBox Board Support Package installiert ist, kann man diese einfach durch den Befehl #include <Adafruit_HDC1000.h> einfügen. Dann muss noch Temperatur temp und Luftfeuchtigkeit humi als Variablen definiert werden. Dann kommt die eigentliche Arbeit, da die /status Befehl handleNewMessages() neu definiert werden müssen:

#include <WiFiSSLClient.h>
#include <WiFi101.h>
#include <UniversalTelegramBot.h>
#include <Adafruit_HDC1000.h>

// Initialize Wifi connection to the router
char ssid[] = "senseBox-net";              // your network SSID (name)
char password[] = "senseBox-pw";           // your network key

// Initialize Telegram BOT
#define BOTtoken "879449240:AAGxBFMQvDpd50lQNQlYrLlmf5bDz1aXPsk"  // your Bot Token (Get from Botfather)

WiFiSSLClient client;
UniversalTelegramBot bot(BOTtoken, client);

int Bot_mtbs = 1000; //mean time between scan messages
long Bot_lasttime;   //last time messages' scan has been done
bool Start = false;
int ledStatus = 0;

Adafruit_HDC1000 hdc;
float temp=0, humi=0;

void handleNewMessages(int numNewMessages) {
  Serial.println("handleNewMessages");
  Serial.println(String(numNewMessages));
  for(int i=0; i<numNewMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);
    String text = bot.messages[i].text;
    String response;

    if (text == "/status") {
      temp = hdc.readTemperature();
      humi = hdc.readHumidity();
      response = "The temperature is ";
      response += String(temp);
      response += "°C at ";
      response += String(humi);
      response += "% humidity";
      
      if(temp > 25 && temp <= 124){
        response += " \xF0\x9F\x94\xA5";
      } else if (temp < 5){
        response += " \xE2\x9D\x84";
      } else if (temp > 124) { 
        response = "Error reading sensor. Check if sensor is connected or replace cable/sensor.";
      } else {
        response += ".";
      }
      bot.sendMessage(chat_id, response, "");
    }
    if (text == "/start") {
      String from_name = bot.messages[i].from_name;
      if (from_name == "") from_name = "Anonymous";

      String welcome = "Welcome, " + from_name + "! This is your fancy senseBoxMCU bot \xF0\x9F\x91\xBD\n\n";
      welcome = welcome + "Use /status to read HDC1080 sensor measurements.\n";
      bot.sendMessage(chat_id, welcome, "Markdown");
    }
  }
}

void setup() {
  Serial.begin(9600);
  //while(!Serial);
  delay(2000);

  // attempt to connect to Wifi network:
  Serial.print("Connecting Wifi: ");
  Serial.println(ssid);
  while (WiFi.begin(ssid, password) != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  IPAddress ip = WiFi.localIP();
  Serial.println(ip);

  hdc.begin(0x40);
  temp = hdc.readTemperature();
  humi = hdc.readHumidity();
}

void loop() {

  if (millis() > Bot_lasttime + Bot_mtbs)  {
    int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    while(numNewMessages) {
      Serial.println("got response");
      handleNewMessages(numNewMessages);
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    }
    Bot_lasttime = millis();
  }
}

Nachdem der Code auf die MCU geladen ist, kann man mit dem Befehl /status Temperatur und Luftfeuchte im Chatfenster ausgeben lassen.

/images/projects/telegram_bot/06_serialout.png - Logo

Ein Klick auf das Lupensymbol in Arduino öffnet die Serielle Schnittstelle. Dort erhälst du Updates über die Kommunikation zwischen MCU und Telegramm.

Beispiel 3: Custom Keyboard erstellen

Eine weitere interessante Funktion des Chatbots, welche die UniversalTelegramBot Bibliothek bereitstellt, ist das Custom Keyboard. Um das zu zeigen, werden die beiden oberen Beispiele zusammengefügt. Dazu werden erneut die Kommandos für /ledon, /ledoff und /readSensor definiert. Beim /start Befehl wird der bot.sendMessage(…) Aufruf mit bot.sendMessageWithReplyKeyboard(…) ersetzt, um beim Start eine Tastatur mit unseren drei Funktionen zu erzeugen. Der entsprechende Teil des Codes sieht dann so aus:

void handleNewMessages(int numNewMessages) {
  Serial.println("handleNewMessages");
  Serial.println(String(numNewMessages));
  String response;
  for(int i=0; i<numNewMessages; i++) {
    String chat_id = String(bot.messages[i].chat_id);
    String text = bot.messages[i].text;
    if (text == "/ledon") {
      digitalWrite(ledPin, HIGH);   // turn the LED on (HIGH is the voltage level)
      ledStatus = 1;
      bot.sendMessage(chat_id, "Led is ON", "");
    }
    if (text == "/ledoff") {
      ledStatus = 0;
      digitalWrite(ledPin, LOW);    // turn the LED off (LOW is the voltage level)
      bot.sendMessage(chat_id, "Led is OFF", "");
    }
    if (text == "/readSensor") {
      temp = hdc.readTemperature();
      humi = hdc.readHumidity();
      response = "The temperature is ";
      response += String(temp);
      response += "°C at ";
      response += String(humi);
      response += "% humidity";
      
      if(temp > 25 && temp <= 124){
        response += " \xF0\x9F\x94\xA5";
      } else if (temp < 5){
        response += " \xE2\x9D\x84";
      } else if (temp > 124) { 
        response = "Error reading sensor. Check if sensor is connected or replace cable/sensor.";
      } else {
        response += ".";
      }
      bot.sendMessage(chat_id, response, "");
    }
    if (text == "/start") {
      String keyboardJson = "[[\"/ledon\", \"/ledoff\"],[\"/readSensor\"]]";
      bot.sendMessageWithReplyKeyboard(chat_id, "Select a function from the keyboard", "", keyboardJson, true);
    }
  }
}

Beim Start wird nun nicht mehr die Übersicht der Kommandos, sondern das Custom Keyboard angezeigt. Das kann man auch manuell über /start aufrufen.

/images/projects/telegram_bot/07_sensorstatus.png - Logo

Mit einem Klick auf die Tastatur wird nun der entsprechende Befehl ausgeführt.

Nun kennst du die entsprechenden Grundlagen, um den Bot zu benutzen. Viel Spaß beim Hacken!

Weitere Tipps

  • Eine Übersicht über alle Befehle aus der Bibliothek findet man in dieser Dokumentation (https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot).

  • Um mit dem Bot Emojis über die Lib zu verschicken, musst du die UTF-8 Notation verwenden (https://apps.timwhitlock.info/emoji/tables/unicode).