M5Stack から Slack に Yo! する

この記事は、M5Stack Advent Calendar 2018 – Qiita  の 10日目の記事です。
以前つくってみた M5Stack から Slack へ投稿する記事の焼き直しなのですが、もう少し詳しく書きます。というか、以前作ったやつをコピペしたら動かないということで、ちょっと書き直し。

仕組み

Slack へ投稿する方法として、Web APIを使って直接投稿することもできるのですが、なかなか面倒ので WebHook を使います。HTTP で手軽にアクセスできる API を用意しておいてアクセスする方法ですね。WebHook 内で複雑なアクセスを肩代わりしてくれるので、組み込み側に専用のライブラリを用意する必要がなくなります。

というわけで、

  • SlackのWebHookを作る
  • 作った WebHook に HTTPS で送る

2手順で実現します。

SlackのWebHookを作る

ブラウザで slack にログインしている状態で左上の「Manage」をクリックします。

検索ボックスで「WebHook」を入力して「Incoming WebHooks」を選択。

「Add Configuration」をクリックして WebHook を作ります。

投稿先のチャンネルを指定して、WebHook の URL を作成します。

投稿するデータは JSON 形式で送信して、宛先の URL が https://hooks.slack.com/services/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 な感じになります(このURL自体はすでに無効にしてあるので大丈夫)。

投稿するのがテキストだけであれば、次のように text だけに設定

{"text": "This is a line of text in a channel.\nAnd this is another line of text."}

投稿時のアイコンやユーザー名も指定できます。実はチャネル名も指定できるので、WebHook 自体はチャンネルごとに作らなくてもひとつだけ作っておけばOKです。

{
 "text":"From M5Stack Yo!",
 "icon_emoji":":ghost:",
 "username":"m5stackpost"
}

これで WebHook の準備は完了

証明書をハードコードする

色々調べると M5Stackから HTTP 接続する場合に HTTPClient を使うのですが、Slack の WebHook は HTTPS 接続になっています。セキュリティ接続になる場合、公開鍵の証明書が必要になってきます。

実は例のうんこボタンやエリクソンの証明書の期限切れの問題もここに関わる訳ですが、証明書の使い方が「暗号化」と「ライセンス期限」との2つを混在させてしまっている問題があります。この部分、暗号化だけ使うのであれば有効期限は無視してよいし、ライセンス期限の制限だけなれば証明書の有効期限だけをチェックすればよいわけです。

さて、今回は Slack のサーバーとの暗号化通信となるので、サーバー側の証明書を M5Stack 側に埋め込んで暗号化できるようにします。

Google Chrome で slack のサイトを開いた状態でサーバーの証明書を表示させます。

「証明書のパス」のタブを開いてルート証明書か中間証明書を選択して「証明書の表示」をさせます。どちらの証明書を使っても構いません。

証明書をテキストファイルに保存します。

C++ のコードに埋め込んでしまうので、BASE64 形式で保存します。

中身をメモ帳で開くと、こんな感じになっています。

この証明書を M5Slack のコードに埋め込んで、HTTPS 接続するときに使います。

M5Stackのコード

Slack に Yo するコードはこんな感じです。
WiFi に接続するための SSID とパスワードは適宜変えてください。
Slack に送るための WebHook のところ「/services/XXXXX/YYYYY」も書き換えます。

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <M5Stack.h>

WiFiMulti wifi;
HTTPClient http;

#define WIFI_SSID "<WiFiのSSID>"
#define WIFI_PASS "<WiFiのパスワード>"
const char *server = "hooks.slack.com";
const char *json = "{\"text\":\"From M5Stack Yo!\",\"icon_emoji\":\":ghost:\",\"username\":\"m5stackpost\"}";
const char *json2 = "{\"text\":\"From M5Stack Hello!!!\",\"icon_emoji\":\":ghost:\",\"username\":\"m5stackpost\"}";

// ルート証明書
const char* slack_root_ca= \
"-----BEGIN CERTIFICATE-----\n" \
"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" \
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" \
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" \
"QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" \
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" \
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" \
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" \
"CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" \
"nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" \
"43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" \
"T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" \
"gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" \
"BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" \
"TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" \
"DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" \
"hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" \
"06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" \
"PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" \
"YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" \
"CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" \
"-----END CERTIFICATE-----\n" ;
  
// 中間証明書
const char * slack_sub_ca = 
"-----BEGIN CERTIFICATE-----\n" 
"MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\n" 
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" 
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" 
"QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\n" 
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\n" 
"U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" 
"ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\n" 
"nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\n" 
"KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n" 
"/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\n" 
"kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n" 
"/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\n" 
"AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\n" 
"aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\n" 
"Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\n" 
"oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\n" 
"QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\n" 
"d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\n" 
"xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\n" 
"CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n" 
"5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n" 
"8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n" 
"2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\n" 
"c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\n" 
"j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n" 
"-----END CERTIFICATE-----\n" ;

void init() {
  M5.begin(true, false, true);
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(65, 10);
  M5.Lcd.println("Slack example");
  M5.Lcd.setCursor(3, 35);
  M5.Lcd.println("Press A button");
}

void setup() {
  init();
}

void sendYo() 
{
  // put your setup code here, to run once:
  wifi.addAP(WIFI_SSID, WIFI_PASS);
  M5.begin();
  M5.Lcd.println("send slack");
  
  while (wifi.run() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(".");
  }
  M5.Lcd.println("wifi connect ok");
  // post slack
  // https://hooks.slack.com/services/XXXXX/YYYYY
  M5.Lcd.println("connect slack.com");
  http.begin( server, 443, "/services/XXXXX/YYYYY", slack_sub_ca );
  http.addHeader("Content-Type", "application/json" );
  http.POST((uint8_t*)json, strlen(json));
  M5.Lcd.println("post hooks.slack.com");
}

void sendYa() 
{
  // put your setup code here, to run once:
  wifi.addAP(WIFI_SSID, WIFI_PASS);
  M5.begin();
  M5.Lcd.println("send slack");
  
  while (wifi.run() != WL_CONNECTED) {
    delay(500);
    M5.Lcd.printf(".");
  }
  M5.Lcd.println("wifi connect ok");
  // post slack
  // https://hooks.slack.com/services/XXXXX/YYYYY
  M5.Lcd.println("connect slack.com");
  http.begin( server, 443, "/services/XXXXX/YYYYY", slack_sub_ca );
  http.addHeader("Content-Type", "application/json" );
  http.POST((uint8_t*)json2, strlen(json2));
  M5.Lcd.println("post hooks.slack.com");
}

void loop() {
  M5.update();
  if (M5.BtnA.wasReleased()) {
    sendYo();
  } else if (M5.BtnB.wasReleased()) {
    sendYa();
  } else if (M5.BtnC.wasReleased()) {
    init();
  }
}

Aボタンで「Yo」を送って、Bボタンで「Yha!」を送ります。
Cボタンで画面クリアですね。

いちいち WiFi にログインしているのが悪いのか(たぶん接続を切っていないから、WiFiMulti がクローズされていないのだと思う)、ボタンを2回押さないと Yo が送れません。そのあたりは今後改良ということで。

実行結果

M5Stack の表示

Slack クライアントの表示

カテゴリー: 開発 パーマリンク