Slack bot na ESP8266

W pracy często zamawiamy sobie kebaba na obiad. Kiedy w końcu na recepcję dostawca przywiezie upragnione jedzenie trzeba jakoś powiadomić wszystkich, że kebaby dojechały :P Jak to mówią potrzeba jest matką wynalazków – tak właśnie powstał kebabowy przycisk wysyłający powiadomienie na Slacku :)

Tworzymy aplikację

Aby utworzyć aplikację należy wejść na stronę https://api.slack.com/, a następnie wcisnąć przycisk „Start Building”, a następnie „Create New App”.

W okienku podajemy nazwę bota oraz workspace, do którego chcemy dodać aplikację.

Teraz dodajemy odpowiednie funckjonalności. My chcemy tylko wysyłać wiadomość na kanał, więc wystarczy nam feature o nazwie „Incoming webhooks”. W okienku tworzenia takiego webhooka musimy podać kanał na który będą postowane wiadomości:

Po utworzeniu webhooka dostaniemy jego adres URL, dzięki czemu możemy przetestować jego działanie za pomocą Postmana.

Do postmana importujemy następujący CURL:

curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' URL_DO_WEBHOOKA

Implementacja w ESP8266

Podłączenie

Ja podłączyłem sobie przycisk łączący piny GND oraz D6 (akurat tak pasował :P) + rezystor podciągający 10K pomiędzy pinami D6, a 3V3.

Kod programu

Do implementacji bota wykorzystamy bibliotekę ESP8266HTTPClient (standardowo dołączoną do SDK NodeMCU dla Arduino).

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

ustalamy pin, do którego podłączony jest przycisk:

#define BUTTON_PIN  D6

teraz dodajemy kilka przydatnych stałych:

const char* host = "hooks.slack.com";
const char* fingerprint = "C1 0D 53 49 D2 3E E5 2B A2 61 D5 9E 6F 99 0D 3D FD 8B B2 B3";
const char *ssid = "Nazwa sieci WiFi";
const char *password = "Hasło sieci WiFi";
const char* webhookUrl = "/services/TUTAJ/KLUCZE/API";

poza standardowymi ssid i password ważnymi polami są fingerprint oraz webhookUrl. W webhookUrl podajemy adres URL na jaki ma zostać wysłane zapytanie. Znajdziemy go w sekcji „Incoming webhooks” w ustawieniach aplikacji na samym dole. WAŻNE: Nie podajemy w nim części hosta, czyli https://hooks.slack.com.

Bardzo ważnym parametrem jest fingerprint. Niestery NodeMCU słabo radzi sobie z połączeniami SSL, dlatego też musimy mu podać ręcznie jaki jest fingerprint certyfikatu SSL używanego przez hosta z którym chcemy się połączyć. Taki fingerprint możemy odczytać np. na stronie https://www.grc.com/fingerprints.htm podając w okienku adres hooks.slack.com. Z wyniku musimy usunąć dwukropki i zastąpić je spacjami.

W funkcji setup() standardowo łączymy się z wifi i ustawiamy piny:

void setup() {
  Serial.begin(9600);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("Polaczono z WiFi");
  pinMode(BUTTON_PIN, INPUT);
}

W funkcji loop jedyną rzeczą, której potrzebujemy jest nasłuchiwanie wciśnięcia przycisku kebabowego:

void loop() {
  if(!digitalRead(BUTTON_PIN)) {
    sendMessageToAll("Kebaby :)");
  }
}

Teraz czas na funkcję sendMessageToAll, która wyśle wiadomość na kanał:

void sendMessageToAll(String message) {
  HTTPClient https;
  Serial.println("[HTTPS] Wysylanie wiadomosci...");
  if(https.begin(host, 443, webhookUrl, true, fingerprint)) {
      https.addHeader("Content-Type", "application/json");
      Serial.print("[HTTPS] POST...\n");
      int httpCode = https.POST("{\"text\":\"<!channel> " + message + "\"}");
      if (httpCode > 0) {
        Serial.printf("[HTTPS] POST... kod odp.: %d\n", httpCode);
        if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
          String payload = https.getString();
          Serial.println(payload);
        }
      } else {
        Serial.printf("[HTTPS] POST... blad: %s\n", https.errorToString(httpCode).c_str());
      }
      https.end();
    } else {
      Serial.printf("[HTTPS] Blad polaczenia\n");
    }
}

Są tu 3 ważne wysołania:

https.begin, https.addHeader oraz https.POST.

Pierwsza z nich inicjuje połączenie. Podajemy w niej hosta, port serwera, url, czy request będzie HTTPS? oraz fingerprint certyfikatu.

Kolejna dodaje nagłówek. W naszym przypadku jest to nagłówek „Content-type” z wartością „application/json”, który oznacza, że wysyłamy dane JSONem.

Ostatnia wykonuje zapytanie typu POST na danym połączeniu. Jako parametr przyjmuje zawartość zapytania. W tym momencie serwer zwraca nam odpowiedź, którą logujemy na port szeregowy. Jeżeli kod odpowiedzi HTTP to 200, a wiadomość ok, to na naszym kanale powinna pojawić się nowa wiadomość.

PS. W naszym przypadku mamy małego JSONa i możemy go wysłać jako zahardcodowany string, ale jeżeli potrzebujemy przesłać jakąś większą strukturę danych warto zapoznać się z biblioteką ArduinoJson.