Arduino×Discordでお手軽IoTを実現してみた話


# ドアセンサーを作ってみる
　自室のドアが開閉した際にスマホ/PCで通知を受け取れたら面白いのでは、と思い作ってみました。

# 今回使うあれそれ
【必須】
・Arduino(要Wi-Fiモジュール)
・Discordアカウント
・リードスイッチ

【任意】
・HTTPサーバーを用意できる((根気||知識)&&サーバー機)


<br>
　ログの保存/閲覧のためにわざわざ何かこしらえるのは面倒なので、既存のチャットツールへBotに投稿させその履歴をログとします。SlackやLINEなんかもBotがあるらしいんですが今回はDiscordで実装します。

　HTTPサーバーはArduinoとDiscordの仲介を担います。Arduinoから直接Discordへ投げてもいいんですが、後々の拡張性を考えて間に自前サーバーを挟んでます。

# 簡略図
![2019-11-14_15h17_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164419/e20cb185-484e-a9a8-a292-d40196ba1012.png)


# 実装
## Arduino
　今回はWi-FiモジュールとArduino互換機が合体した「ESP-12E」を使いました。ESP8266が搭載されています。
　画像ではUNOになってますがそのあたりは心の目で見てください。

![2019-11-14_21h36_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164419/5635e2f4-12d3-ed30-08e4-37f81a9ae8dc.png)
　スイッチがアナログピンに繋がっているのは完全に気まぐれです。特にこれといった理由はありません。それからESP-12Eはボード上に書かれた番号とGPIOの番号が一致しないので使う場合はそのあたり気を付けましょう。


```c:ESP.ino
#include <ESP8266WiFi.h>
#include <Switches.h>

bool isOpen = true;
// Wi-Fi(5GHzには非対応)
const char* ssid = "SSID";
const char* password = "パスワード";

// Server
const char* host   = "192.168.0.16";//サーバー機のローカルIP
const int httpPort = 80;
const char* path   = "/Hoge";

WiFiClient client;


//インジケータLED(フルカラー)用のピンを指定
int pinR=5;
int pinG=4;

void setup() {

  pinMode(pinR, OUTPUT);//D1ピン
  pinMode(pinG, OUTPUT);//D2ピン

  Serial.begin(115200);
  WIFIconnecting();


}

void WIFIconnecting() {
  // WIFI_AP, WIFI_STA, WIFI_AP_STA or WIFI_OFF
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFiに接続しました。");
  Serial.print( "IPアドレス： ");
  Serial.println( WiFi.localIP());
  Serial.print( "ゲートウェイ： ");
  Serial.println(WiFi.gatewayIP());
  Serial.print( "サブネットマスク： ");
  Serial.println(WiFi.subnetMask());
}

void loop() {

  int val = analogRead(0);
  bool current = val == 1024 ? false : true;

  if (isOpen != current) {
    Serial.println(!isOpen);
    isOpen = !isOpen;
    post();
  }

  switch (isOpen) {//インジケータ用LEDの点灯。カソードコモン/アノードコモンで適宜書き換え
    case true:
      digitalWrite(pinR, LOW);
      digitalWrite(pinG, HIGH);
      Serial.println("OPEN");
      break;

    case false:
      digitalWrite(pinR, HIGH);
      digitalWrite(pinG, LOW);
      Serial.println("CLOSE");
  }

  delay(200);
}

void post() {
  path = isOpen ? "/open" : "/close";
  if (true) {
    Serial.println("送信中");

    if (!client.connect(host, httpPort)) {
      Serial.println("サーバーへのアクセスに失敗しました。");
      return;
    }
    int httpcode = client.print(String("POST ") + path + " HTTP/1.1\r\n" +
                                "Host: " + host + "\r\n" +
                                "Content-Length:" + "0" + "\r\n" +
                                "Connection: close\r\n\r\n" +
                                "piyo");
    Serial.println(httpcode);
    unsigned long timeout = millis();
    while (client.available() == 0) {
      if (millis() - timeout > 10000) {
        Serial.println(">>> タイムアウトしました。");
        return;
      }
    }
    Serial.println("送信成功");
    //    while (client.available()) {
    //      //char c = client.read();
    //      //Serial.print("OK");
    //    }
    Serial.println("");
  }
}
```

　ググって出て来たコードを切り貼りして書き足したものです。

　フルカラーLED楽しいです。
<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">インジケータつけました <a href="https://t.co/vxiABrwisl">pic.twitter.com/vxiABrwisl</a></p>&mdash; うなせん王国@パンソロ補佐 (@unasenohkoku) <a href="https://twitter.com/unasenohkoku/status/1194630375659540482?ref_src=twsrc%5Etfw">November 13, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<br>
最終的にはこんな感じで取り付けます。

![2019-11-14_23h01_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164419/2ed31aca-7185-4ffb-de62-db81680135b9.png)


## Discord
　こちらの記事を参考にウェブフック用のURLを取得してください。POSTリクエストでJSON投げるだけでBotがテキストチャンネルにメッセージを投稿してくれるようになります。
　[DiscordにWebhookでいろいろ投稿する](https://qiita.com/Eai/items/1165d08dce9f183eac74)

　
　

## HTTPサーバー

　C#で書きました。上記で取得したURLを使います。postToDiscord()メソッド内にコピペしましょう。

```c#:Program.cs
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;

class Program
{
    static async System.Threading.Tasks.Task Main(string[] args)
    {
        try
        {
            HttpListener listener = new HttpListener();
            listener.Prefixes.Add("http://192.168.0.16:80/");
            listener.Start();
            while (true)
            {
                

                HttpListenerContext context = listener.GetContext();
                HttpListenerRequest request = context.Request;
                HttpListenerResponse res = context.Response;

                Console.WriteLine(request.RawUrl);

                await postToDiscord(request.RawUrl);

                res.StatusCode = 200;
                byte[] content = Encoding.UTF8.GetBytes("HELLO");
                res.OutputStream.Write(content, 0, content.Length);
                res.Close();

                

            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: " + ex);
        }
    }

    static async System.Threading.Tasks.Task postToDiscord(string str)
    {
        // POSTする対象のURL
        string url = "ウェブフック用URL";
        // POSTメソッドで渡すパラメータ
        var json = "{ \"content\" : \"hoge\"}"; 

        switch (str) {
            case "/open":   json = "{ \"content\" : \"OPEN!!\"}";
                break;
            case "/close":  json = "{ \"content\" : \"CLOSE...\"}";
                break;
            default:        json = "{ \"content\" : \"UNKNOWN_MESSAGE\"}";
                break;
        }

        using (var client = new HttpClient())
        {
            var content = new StringContent(json, Encoding.UTF8, "application/json");
            var response = await client.PostAsync(url, content);
        }
    }
}

```

# 完成
こんな感じで動いてくれます。
【プロトタイプ】
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ドアセンサー+ロガーのプロトタイプが完成しました <a href="https://t.co/WkzDbU9Jdv">pic.twitter.com/WkzDbU9Jdv</a></p>&mdash; うなせん王国@パンソロ補佐 (@unasenohkoku) <a href="https://twitter.com/unasenohkoku/status/1194484961627144193?ref_src=twsrc%5Etfw">November 13, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<br>
【完成品】
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/soA7KLPDQi">pic.twitter.com/soA7KLPDQi</a></p>&mdash; うなせん王国@パンソロ補佐 (@unasenohkoku) <a href="https://twitter.com/unasenohkoku/status/1194673371390152705?ref_src=twsrc%5Etfw">November 13, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# うまく動かないときのあれこれ
#### 開発環境やプログラムが管理者権限で動いていない
　サーバープログラム起動時にエラーが発生します。

#### サーバー機のファイアウォールが邪魔している
　サーバーが稼働しているのにリクエストがはじかれる場合はFWの設定を疑いましょう。

#### ローカルIPが固定されていない
　動いてたのに再起動したら動かなくなったぞ、とかいう場合はたぶんIPの不一致が原因です。

# おまけ
　今回VS2019で初めて実行ファイルを生成しようとしたんですがVS2010とかと違って勝手に生えてこないんですね。泣きそうになりながら.exeを探してました。メニューの[ビルド]から発行とやらを選択する必要があるようで。いやぁ知らんかった。

# おまけ2
今回のきったねえ配線で最後を飾りたいと思います。お疲れ様でした。
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://twitter.com/hashtag/%E3%80%86%E3%81%AE%E3%82%B0%E3%83%AB%E3%83%BC%E3%82%AC%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#〆のグルーガン</a> <a href="https://t.co/DVvBFJLHJC">pic.twitter.com/DVvBFJLHJC</a></p>&mdash; うなせん王国@パンソロ補佐 (@unasenohkoku) <a href="https://twitter.com/unasenohkoku/status/1194668258521042946?ref_src=twsrc%5Etfw">November 13, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>