meideru blog

家電メーカーで働いているmeideruのブログです。主に技術系・ガジェット系の話を書いています。

ドアの開閉をLINEに通知してくれるデバイスを作ってみた(ESP-WROOM-02)

      2019/03/28

ドアが開いたらLINEに通知を送ってくれるデバイス

社会人になって一人暮らしを始めました。

留守中、知らない人が家に出入りしているのではないかと心配になることがあります。

そんな問題を解決するために、ESP-WROOM-02を用いてとあるデバイスを開発しました。

ドアの開閉を検知してLINEに通知を送ってくれるデバイスです。

今日は作り方を紹介したいと思います。

概要

私のTwitterにアップしたものと同じですが、デバイスの動作をYouTubeにあげました。

 

マグネット式のドアセンサーが開閉を検知して、Wi-Fi経由でLINEに通知を送ってくれるという、とてもシンプルな作りです。

主な部品

抵抗や基板、タクトスイッチ等の細かい部品の説明は省かせていただきます。

主な部品は以下の2つです。

  • ESP-WROOM-02-DEV(Wi-Fiモジュール)
  • MCD-14AG(磁石式ドアセンサ)

ESP-WROOM-02-DEV(Wi-Fiモジュール)

ESP-WROOM-02の開発ボード

秋月電子: ESP-WROOM-02-DEV

 

Wi-Fiモジュールが含まれたマイコンです。

Arduinoと互換があるのでスケッチから書き込みができます。

 

実は、このマイコンは過去に使ってことがあります。シンプルでとても使いやすいです。

世間では「IoT(Internet of Things)」というワードが話題になっています。IoTとは、ありとあらゆるものがインターネットにつながる仕組みのことです。今日はIoTに関する記事です。ESP-WROOM-02の開発ボードのWi-Fi機能を試しに使ってみました。ESP-WROOM-02とはESP...

MCD-14AG(磁石式ドアセンサ)

MCD-14AG(磁石式ドアスイッチ)

秋月電子: MCD-14AG

 

磁石でスイッチの開閉を判断できるデバイスです。

ドアが開いているときにLEDが点灯するので、デバッグがしやすいです。

仕組み

ドアが開いたらLINEに通知を送ってくれるデバイス

雑多な資料ですが、仕組みは上の画像のようなイメージです。

  1. MCD-14AG(ドアセンサ)の信号の変化をESP-WROOM-02-DEVが検知
  2. ESP-WROOM-02-DEVがWi-Fiルーター経由で自宅サーバーにPOST通信
  3. 自宅サーバーがREST APIを用いてLINEのMessaging APIを叩く
  4. Messaging APIからスマホのLINEアプリに向けてメッセージが送られて来る

自宅サーバーを経由してMessaging APIを叩いている理由は、自宅サーバーにログを残したいからです。いつドアが開閉されたのかをテキストベースで残すようにしています。

レシピ

デバイスの回路図

ドアが開いたらLINEに通知を送ってくれるデバイス

 

とてもシンプルな回路です。

型番が違いますが、SPS-320がドアセンサのことです。これをマイコンに向けてプルアップセンサでつないでいます。

デバイス側のソースコード

#include <ESP8266WiFi.h>

// WiFi設定 (2.4GHz)
const char *ssid     = "hogehoge";
const char *password = "hogehoge";

// Server設定
const char *host     = "xxx.xxx.xxx.xxx";
const int   port     = xx;
const char *path     = "/add_log.php";
String post_password = "hogehoge"; // サーバーとの合言葉

// その他設定
const int           door_senser_pin    = 14;
const unsigned long connect_timeout_ms = 10000;
const unsigned long receive_timeout_ms = 10000;

typedef enum {
  OPEN,
  CLOSE,
} DOOR_STATUS;

static bool wifi_setup();
static String ip_to_string(uint32_t ip);
static bool notice(DOOR_STATUS status);
static void print_info_log(String str);
static void print_debug_log(String str);
static void print_error_log(String str);

WiFiClient client;

void setup()
{
  Serial.begin(115200);
  pinMode(door_senser_pin, INPUT_PULLUP);

  while(wifi_setup() == false) {
    print_error_log("Failed to initialize wifi");
    delay(1000);
  }
}

static bool wifi_setup()
{
  unsigned long now = 0;

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  print_info_log("Trying to connect");
  now = millis();
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
    Serial.print(".");
    if (millis() - now > connect_timeout_ms) {
      print_error_log("Timed out");
      return false;
    }
  }
  Serial.println("");
  
  print_info_log("Connection succeeded");
  print_info_log(String("IP Address: ") + ip_to_string(WiFi.localIP()));
  print_info_log(String("Gateway: ") + ip_to_string(WiFi.gatewayIP()));

  return true;
}

String ip_to_string(uint32_t ip)
{
  String result = "";

  result += String((ip & 0xFF), 10);
  result += ".";
  result += String((ip & 0xFF00) >> 8, 10);
  result += ".";
  result += String((ip & 0xFF0000) >> 16, 10);
  result += ".";
  result += String((ip & 0xFF000000) >> 24, 10);

  return result;
}

void loop()
{
  bool ret;

  // Wait until the door opens
  while(1) {
    while(digitalRead(door_senser_pin) == 1) {
      delay(100);
    }
    delay(20);
    if (digitalRead(door_senser_pin) == 1) {
      break;
    }
  }

  // Notify that the door has opened
  print_info_log("Detected door opening");
  ret = notice(OPEN);
  if (ret == false) {
    print_error_log("Notification failed");
  }

  // Wait until the door closes
  while(1) {
    while(digitalRead(door_senser_pin) == 0) {
      delay(100);
    }
    delay(20);
    if (digitalRead(door_senser_pin) == 0) {
      break;
    }
  }

  // Notify that the door is closed
  print_info_log("Detected door closing");
  ret = notice(CLOSE);
  if (ret == false) {
    print_error_log("Notification failed");
  }
}

static bool notice(DOOR_STATUS status)
{
  String snd_msg = "PASSWORD=" + post_password;
  String res_msg = "";
  unsigned long now = 0;
  
  print_debug_log("Preparing to send");
  if (client.connect(host, port) == false) {
    print_error_log("Connection failed");
    return false;
  }

  if (status == OPEN) {
    snd_msg += "&STATUS=OPEN";
  } 
  else if (status == CLOSE) {
    snd_msg += "&STATUS=CLOSE";
  }
  else {
    print_error_log("Invalid status");
    return false;
  }

  print_debug_log("Trying to send data");
  client.print(String("POST ") + path + " HTTP/1.1\r\n" +
               "Host: " + host + ":" + port + "\r\n" +
               "Content-Type: application/x-www-form-urlencoded\r\n" +
               "Content-Length:" + String(snd_msg.length()) + "\r\n\r\n" +
               snd_msg);

  print_debug_log("Trying to receive data");
  now = millis();
  while (client.available() == 0) {
    if (millis() - now > receive_timeout_ms) {
      print_error_log("Timed out");
      client.stop();
      return false;
    }
  }

  while (client.available()) {
    res_msg = res_msg + (char)client.read();
  }
  
  if (res_msg.startsWith("HTTP/1.1 200 OK")) {
    print_debug_log("Result: OK");
    return true;
  } else {
    print_debug_log("Result: NG");
    return false;
  }
}

static void print_info_log(String str)
{
  Serial.println("[INFO] " + str);
}

static void print_debug_log(String str)
{
  Serial.println("[DEBUG] " + str);
}

static void print_error_log(String str)
{
  Serial.println("[ERROR] " + str); 
}

 

上部のグローバル変数を書き換えれば、Wi-FiのSSIDやパスワード等の設定が変更できます。

デバッグやエラー等のログをターミナルに出力するようになっています。

サーバー側のソースコード

<?php
// [POST]
// PASSWORD
// STATUS

// サーバー側の設定
$password = "hogehoge";  // デバイスとの合言葉
$dir_path = "/logs/";

// LINE Messaging APIの設定
$access_token = "hogehoge";
$to = "hogehoge";

date_default_timezone_set('Asia/Tokyo');

if (!(isset($_POST['PASSWORD']) && isset($_POST['STATUS']))) {
    header("HTTP/1.1 403 Forbidden");
    exit;
}

if ($_POST['PASSWORD'] !== $password) {
    header("HTTP/1.1 403 Forbidden");
    exit;
}

if (!make_log_file($_POST['STATUS'], $dir_path)) {
    header("HTTP/1.1 403 Forbidden");
    exit;
}

if ($_POST['STATUS'] == "OPEN") {
    $msg = "[" . date("Y/m/d H:i:s") . "]" . "\n" . "OPEN";
    send_message_to_line($access_token, $to, $msg);
} elseif ($_POST['STATUS'] == "CLOSE") {
    $msg = "[" . date("Y/m/d H:i:s") . "]" . "\n" . "CLOSE";
    send_message_to_line($access_token, $to, $msg);
}
?>

<?php
function make_log_file($status, $dir_path)
{
    if (!($status === "OPEN" || $status === "CLOSE")) {
        return false;
    }

    // 空ファイルだけ作成
    $date = date("Y_m_d_H_i_s");
    $fileName = $dir_path . $date . "_" . $status . ".txt";
    $fp = fopen($fileName, "wb");
    fclose($fp);

    return true;
}

function send_message_to_line($access_token, $to, $msg) 
{
    $data   = [
        'to' => $to, 
        'messages' => [['type' => 'text', 'text' => $msg]]
    ];
    $header = [
        'Content-Type: application/json; charser=UTF-8',
        'Authorization: Bearer ' . $access_token
    ];

    $ch = curl_init('https://api.line.me/v2/bot/message/push');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE));
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
    curl_exec($ch);
    curl_close($ch);
}
?>

 

サーバー側にログを残してからLINE Messaging APIを叩く仕組みになっています。

課題・改善点

スリープモードできるようにしたい

ESP-WROOM-02-DEVの現在の作りでは、常時ドアセンサの変化をチェックする作りになっています。

ハッキリ言って電力の無駄です・・・w

常時スリープモードに入れといて、ドアセンサの信号の変化を検出したら割り込みで起きて通知を投げる作りに変更したいです。

私はモチベーションがないのでやらないので、誰かやってくださいw

ターミナル経由で設定を行えるようにしたい

現状、Wi-FIのSSIDやパスワード等の設定を変更するには、いちいちファームを書き換えなくてはならない作りになっています。

これらの設定をターミナル(UART)経由で行えるようにできたら便利だと思います。

これも同様にモチベーションがないのでやりませんw

誰かやってくださいw

まとめ

ESP-WROOM-02はとても使いやすく、特に困ることはありませんでした。

オススメですので、みなさんも使ってみてください。

※ 何か疑問点等があればお気軽にコメントにお書きください!

 - 技術系