見出し画像

プログラムと電子工作・LINEに通知

M5StickC Plus は IoT と整合性が非常に高いです。何らかの事象が発生したときスマートフォンに通知できれば、様々な応用が考えられます。

例えば、人が入室したときに通知する、温室の気温が高くなりすぎたら通知するなど、様々なリモートセンシングに活用できます。

今回は、M5StickC Plus に Web クライアントを実装します。M5StickC Plus のボタンを押したことを、Web クライアントで LINE に通知します。LINE が提供する LINE Notify API を利用します。


目標

  • M5StickC Plus で Web クライアントを動作させます。

  • M5StickC Plus のボタンを押すと、LINE にメッセージを通知します。

部品・機材

使用する部品は次のとおりです。「Hello, World!」と同じですが、Wi-Fi ルータとスマートフォンが必要です。

電子部品

  • M5StickC Plus 1台

開発用機材

  • PC(Windows10 または 11)、開発環境 Arduino-IDE 導入ずみ

  • USB-A・USB-C ケーブル

  • Wi-Fi ルータ

  • スマートフォン(LINE アプリをインストールずみ)

LINE の準備

(1) LINE グループを作成

  1. スマートフォンで LINE を開く。

  2. トーク →トークルームを作成→グループ

  3. 「友だちを選択」しないで、「次へ」
    ・グループ名:LINE Notify テスト
    ・自分しかメンバーがいないトークルームができる。

(2) LINE Notify にユーザ登録

  1. PC のブラウザで LINE Notify https://notify-bot.line.me/ja/ にアクセスする。

  2. 下の方の「サービスを登録する>」をクリックする。

  3. LINE のユーザ名とパスワードでログインする。
    ・スマートフォンの LINE に本人確認の通知が届く。

  4. 右上のユーザ名をクリック →マイページ

  5. [トークンを発行する]
    ・トークン名:LINE Notify テスト ※(1)3.と同じもの
    ・[発行する]
    ※トークンは一度しか表示されない、保存しておくこと。

(3) LINEグループに LINE Notify を追加

  1. スマートフォンで LINE アプリを開く。

  2. トーク →「LINE Notify テスト」を開く。

  3. [≡]→招待

  4. 「LINE Notify」を選択し、[招待]

開発手順

  1. PC と M5StickC Plus を USBケーブルで接続する。

  2. Arduino-IDE でスケッチ line_notify.ino を開く。

  3. 検証・コンパイルする。

  4. M5StickC Plus に書き込む。

  5. WiFi ルータに接続できたら、上ボタン(押しボタンA)あるいは横ボタン(押しボタンB)を押して LINE 通知する。

  6. スマートフォンの LINE アプリに通知が届くことを確認する。

スケッチ

line_notify.ino

#include <M5StickCPlus.h>
#include <WiFiClientSecure.h>
#include <ssl_client.h>
#include <UrlEncode.h>

#define SECURE  /*安全な通信*/

#define LHEIGHT 25       /*1行の高さ(px)M5StickCPlus:25、M5StickC:15*/

//  それぞれのWi-Fi環境を設定する。
const char* ssid = "xxxxxxxx";  /*SSID*/
const char* password = "xxxxxxxx";  /*PASSWORD*/

//  LINE設定
const char* host = "notify-api.line.me"; // LINE NOTIFY ホスト
const char* token = "*******************************************"; // LINE トークン

#if defined(SECURE)
//  GlobalSign は notify-api.line.me のルート認証局。
const char* root_ca = 
"-----BEGIN CERTIFICATE-----\n"
"MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G\n"
"A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp\n"
"Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4\n"
"MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG\n"
"A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI\n"
"hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8\n"
"RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT\n"
"gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm\n"
"KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd\n"
"QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ\n"
"XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw\n"
"DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o\n"
"LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU\n"
"RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp\n"
"jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK\n"
"6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX\n"
"mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs\n"
"Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH\n"
"WD9f\n"
"-----END CERTIFICATE-----\n";  /*GlobalSign*/
#endif

void line_notify(String);
//------------------------------------------------------------------------------
//  setup()
void setup() {
    //  電源ON時に 1回だけ実行する処理をここに書く。
    M5.begin();               /*M5を初期化する*/
    M5.Lcd.setTextSize(2);    /*文字サイズはちょっと大きめ*/
    M5.Lcd.setRotation(3);    /*上スイッチが左になる向き*/

    M5.Lcd.print("line_notify");

    Serial.begin(115200);   /*デバッグ用のシリアル通信を初期化する*/

    //  WiFiアクセスポイントに接続する。
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {  /*Wi-Fi AP接続待ち*/
        delay(500);
        M5.Lcd.print(".");
    }
    M5.Lcd.fillScreen(BLACK);  /*背景を黒にする*/
    M5.Lcd.setCursor(0, LHEIGHT*0);  /*1行目*/
    M5.Lcd.print("WiFi OK");

    Serial.print("WiFi connected\r\nIP address: ");
    Serial.println(WiFi.localIP());
}
//------------------------------------------------------------------------------
//  loop()
void loop() {
    //  自動的に繰り返し実行する処理をここに書く。
    //  ボタンの状態を更新する。
    M5.update();

    //  上ボタン(ボタンA)をチェックする。
    if (M5.BtnA.wasPressed()) {
        Serial.println("Pushed A (Top)");
        line_notify("Pushed A (Top)");
    }
    else if (M5.BtnB.wasPressed()) {
        Serial.println("Pushed B (Side)");
        line_notify("Pushed B (Side)");
    }
    delay(1);
}
//------------------------------------------------------------------------------
//  LINE Notify
//      in: String msg 通知する文
void line_notify(String msg) {
    WiFiClientSecure client;

#if defined(SECURE)
    client.setCACert(root_ca);
#else
    client.setInsecure();  /*サーバの検証をスキップする*/
#endif

    if (!client.connect(host, 443)) {
        Serial.println("cannot connect LINE Notify API.");
        return; /*FIN*/
    }
    String query = String("message=") + urlEncode(msg);
    String request = String() + 
                     "POST /api/notify HTTP/1.1\r\n" + 
                     "Host: " + host + "\r\n" + 
                     "Authorization: Bearer " + token + "\r\n" + 
                     "Content-Length: " + query.length() + "\r\n" + 
                     "Content-Type: application/x-www-form-urlencoded\r\n\r\n" + 
                     query + "\r\n";
    Serial.println(request);

    //  HTTPS リクエストを送信する。
    client.print(request);

    //  HTTPS レスポンス・ヘッダを受信する。
    while (client.connected()) {
        String line = client.readStringUntil('\n');
        Serial.println(line);

        if (line == "\r") {
            break;
        }
    }

    //  HTTPS レスポンス・ボディを受信する。
    //  {"status":200,"message":"ok"} だったら、LINE NOTIFY が成功である。
    while (client.available()) {
        String line = client.readStringUntil('\n');
        Serial.println(line);
    }
    client.stop();
}

WiFiClientSecure クラス

  • WiFiClientSecure クラスは TLS(SSL)を使用して安全に接続します。これはブラウザが HTTPS(URL に https://… を指定するアクセス)で通信する仕組みと同等です。

  • 安全な通信のためには、setCACert() 関数で、通信先サーバの証明書を指定する必要があります。

通信先サーバの証明書

通信先サーバのルート証明書は、次の方法によって取得できます。Google Chrome で notify-bot.line.me の証明書の取得方法を記します。

  1. PC の Chrome で https://notify-bot.line.me/ へアクセスする。

  2. URL 表示左の鍵マークをクリックする。

  3. (鍵マーク)「この接続は保護されています」をクリックする。

  4. 「証明書は有効です」をクリックする。

  5. 「詳細」タブをクリックする。
    ・証明書の階層が表示される。
        ▽GlobalSign
            ▽GlobalSign RSA OV SSL CA 2018
                *.line.me

  6. 一番上の GlobalSign を選んで、[エクスポート]をクリックする。
    ・GlobalSign.crt という証明書ファイルが得られる。安全なブラウザによって得られたサーバ証明書は正当であると言える。
    ・スケッチではテキストとして、定数 root_ca にセットしている。

安全でない通信

setCACert() 関数でサーバ証明書を指定する代わりに、setInsecure() 関数でサーバ証明書の検証をスキップすることができます。通信先のサーバ証明書を無視するので、DNS が詐称されるなどの脅威には対応できません。

結果

  • 押しボタンを押すたびに、スマートフォンの LINE アプリに通知が届きます。(図1)

図1 スマートフォンの LINE アプリ

参考

所感

  • setCACert() 関数を使えば安全で、setInsecure() 関数を使うと安全ではないと書きましたが、実際のところそう思っているだけで確認できていません。セキュリティというものは実際に確認できないことがあります。

  • スケッチに埋め込まれる root_ca の 1文字を別の文字に改ざんしてみると、LINE 通知が失敗("cannot connect LINE Notify API.")になる場合もあるし、成功する場合もあります。本当に機能しているのかと疑いたくなります。

ライセンス

このページのソースコードは、複製・改変・配布が自由です。営利目的にも使用してかまいませんが、何ら責任を負いません。


この記事が気に入ったらサポートをしてみませんか?