読者です 読者をやめる 読者になる 読者になる

ESP-WROOM-02(Arduino互換)とTwilioで電話をかける

まえがき

(読み飛ばしてかまいません)

2016年夏、息子がヘボコン・ワールドチャンピオンシップにに参加し、Arduino AGのファウンダーの一人であるDavid Cuartielles氏によりArduino賞に選ばれました。そして、副賞としてArduino/Genuinoスタータキットを2セット頂きました。

このイベントはとても盛り上がり、家族のいい思い出となりました。

自分は小学生の頃(40年以上前^^;)から電子工作を始めており、仕事はソフトウェア開発であるものの、ここ十数年PIC、ArduinoRaspberry Pi等いろんなことができるデバイスが簡単に入手できる環境になってきて、手を出したいと思っていました。そこに息子がArduinoをもらうというきっかけがあったので、これ幸いと息子と一緒にArduinoをいじり始めました。

電話を始めとするコミュニケーション・クラウド・サービスであるTwilioにも馴染みがあるので、ヘボコンArduinoTwilioというキーワードの組み合わせで何かできないかと思い、考えたシナリオはこうです。ヘボコンが攻撃を受けたら搭載されたArduinoがTwilio経由で電話を操縦者にかけて、攻撃されていることを音声合成で報告するというものです。

システム 

全体のシステムは下図の通りです。

 

f:id:tsun226:20170206213636p:plain

構成:

ESP-WROOM-02WiFi機能を持ったAdruino互換CPU(今回は開発用のスイッチサイエンス製ESPr Developerを使用)

R … ルータ(iPhoneテザリングを利用)

Twilio … (REST APIによりTwilioデベロッパーセンターのTwiML Binを利用)

動作:

  1. ヘボコン上のスイッチに敵のヘボコンが当たり、プログラムが起動する。
  2. Arduino(互換)がWiFi→ルータ→インターネット経由でTwilio REST APIに接続し、架電のリクエストをする。
  3. Twilioは事前に登録されている指示に従い架電し、音声合成によりメッセージを伝える。

 この記事では、Twilioの設定(TwiML)とESP-WROOM-02(Arduino互換)で動作させるプログラム(スケッチ)について主に説明します。

 

Twilioの設定

Twilioの基本的な使い方については、説明を省略します(Twilioのサイトを参照してください)。

TwiMLの作成

  1. Twilioサイトにログインし、デベロッパーセンター → TwiML Bins を選択します。
  2. 「+」マークをクリックし、TwiML Binを新規作成します。
  3. ConfigurationのFRIENDLY NAMEに任意の名前を、TWIMLには下記内容を記載します。
    「メッセージ」の部分が音声合成されます。任意のメッセージに置き換えてください。

    <?xml version="1.0" encoding="UTF-8"?>
    <Response>
    <Say voice="alice" language="ja-JP" >
    メッセージ
    </Say>
    </Response> 

  4. Save をクリックして登録します。

  5. たった今作成したTwiML Binをクリックしてもう一度内容を表示させると、URLが生成されています。
    このURLはスケッチで使われますので、コピーしておいてください。
  6. Cancelをクリックして閉じます。

ESP_WROOM-02のスケッチ

Mac上のArduino IDE 1.8.1を使いました。

  1. Arduino IDEESP-WROOM-02を開発するための準備をします。
    次のページを参考にしました。
    ESP-WROOM-02開発ボードをArduino IDEで開発する方法
  2. 次の内容のスケッチを作成します。

    #ifdef ESP8266
    extern "C" {
      #include "user_interface.h"
    }
    #endif

    #include <ESP8266WiFi.h>
    #include <WiFiClientSecure.h>

    ADC_MODE(ADC_VCC);

    WiFiClientSecure client;

    // Twilio Call parameters
    const char* asid = "アカウントSID";
    const char* authorization = "Basic ベーシック認証情報";
    const char* twimlUrl = "TwiML BinsのURL";
    const char* to = "通知先電話番号";
    const char* from = "発信元電話番号";

    // Router
    const char* ssid = "ルータのSSID";
    const char* pass = "ルータのパスワード";

    // Twilio REST API endpoint
    const char* twilioHost = "api.twilio.com";
    const char* twilioPath0 = "/2010-04-01/Accounts/";
    const char* twilioPath1 = "/Calls.json";


    void setup() {
      Serial.begin(115200);
      if (wifiConnection()) {
        httpsPost();
      }
    }

    void loop() {

    }

    boolean wifiConnection() {
      WiFi.begin(ssid, pass);
      int count = 0;
      Serial.print("Connecting Wi-Fi");
      while (count < 50) {
        if (WiFi.status() == WL_CONNECTED) {
          Serial.println();
          Serial.println("Connected. IP ADDRESS: " + WiFi.localIP().toString());
          return(true);
        }
        delay(500);
       Serial.print(".");
        count++;
      }
      Serial.println("Timed out.");
      return(false);
    }

    void httpsPost() {
      Serial.println("Connecting to Twilio API endpoint...");
      if (client.connect(twilioHost, 443)) {
        Serial.println("Connected.");
        String data = "Url=" + (String)twimlUrl + "&To=" + (String)to + "&From=" + (String)from;
        String header = "POST " + (String)twilioPath0 + asid +       (String)twilioPath1 + " HTTP/1.1\r\n" +
        "Host: " + (String)twilioHost + "\r\n" +
        "Authorization: " + (String)authorization + "\r\n" +
        "User-Agent: ESP8266/1.0\r\n" +
        "Connection: close\r\n" +
        "Content-Type: application/x-www-form-urlencoded;\r\n" +
        "Content-Length: " + data.length();
        client.println(header);
        client.println();
        client.println(data);
        delay(10);
        Serial.print("Waiting Twilio API response...");
        String response = client.readString();
        int bodypos = response.indexOf("\r\n\r\n") + 4;
        Serial.println();
        Serial.println("RESPONSE: " + response.substring(bodypos));
        return;
      } else {
        Serial.println("ERROR");
        return;
      }
    }

     

動作の様子(デモ動画)

 上記のシステムを少しアレンジしたものの、動作の様子です。

 

とても簡単に、小さなモジュールが電話をかけ音声合成でメッセージを伝えるシステムを作ることができました。

WiFi機能を持つArduino(互換)を使えばインターネットに接続できるので、インターネット・サービスを利用するIoTモジュールを簡単に試作することができますね。

Twilioを使った気象情報架電通知システムの試作

1. 概要

気象情報の変化を市区町村別、かつ注意報/警報/特別警報別のアカウントに投稿する、既存のツイッターbotを拡張し、登録してある電話番号に架電して音声合成で通知するシステムを試作しました。

ユーザは下記情報を登録し、該当する気象情報が発生した時に、架電通知されます。

  • 電話番号
  • エリア(市区町村)
  • 通知して欲しい情報
    ー 注意報/警報/特別警報別
    ー 発表/解除/継続

 

Pubsubhubbubプロトコルにより気象庁の実証実験で提供されている気象情報が当システムに通知され、その情報を解析してメッセージを構成し、Twilio APIに架電と音声合成による通知を要求し、Twilioがユーザに対し架電通知を行います。

f:id:tsun226:20160416140220p:plain

 

2. 入力

気象庁の実証実験では、Googleが運用するAlert Hubを介してpubsubhubbubプロトコルによるPush方式で、都道府県単位の気象情報の変化の概要がAtom形式で通知されます。

② この概要情報には詳細情報(市区町村単位の注意報/警報/特別警報)へのリンクが含まれており、概要情報に含まれる都道府県名が、当システムが架電通知またはツイートするエリアを含むときに、HTTP GETによりXML形式の詳細情報を取得します。

f:id:tsun226:20160416140253p:plain

 

3. システム

 4つのDockerコンテナにより構成されています。

いずれのコンテナにおいてもnginxが稼働しており、HTTPアクセスによりperlまたはphpスクリプトが起動され、処理されます。

 今回、既存システムにPhoneコンテナDockwilioコンテナを追加して、システムの拡張を行いました。

f:id:tsun226:20160416194919p:plain

 

3.1. AlertSubコンテナ(既存)

2.で述べたAlert Hubや気象庁からの情報を受け取り、解析して、エリア毎*1に通知する要素(発表/解除/継続、注意報/警報/特別警報、注意報警報の内容)を構成し、PhoneコンテナTweetコンテナに通知します(図の③、⑥)。

*1  架電通知またはツイート対象として登録されているエリアのみ対象にします。

 

3.2. Phoneコンテナ

登録ユーザ情報から、通知して欲しい条件がAlertSubコンテナから通知されたエリア、発表/解除/継続、注意報/警報/特別警報に一致するユーザを検索し、通知要素を元に構成したメッセージと電話番号をDockwilioコンテナに通知します(図の④)。

 

3.3. Dockwilioコンテナ

Phoneコンテナから通知された情報に従い、架電するようにTwilio APIに要求し(図の⑤)、その後TwilioからのHTTPS GETアクセスに対しメッセージを音声合成して伝える旨通知します(図の⑥)。

  • Twilioは通話をはじめとする各種コミュニケーション手段を提供するクラウドサービスです。
  • DockwilioコンテナはTwilioを使った架電と音声合成メッセージ通知を簡単にできるDockerコンテナです。

 

3.4. Tweetコンテナ(既存)

AlertSubコンテナから通知された情報のエリア、注意報/警報/特別警報に対応するツイッターのアカウントに、構成したメッセージを投稿するようにTwitter APIに要求します(図の⑦)。

 

AlertSub, Phone, Tweetのコンテナは、emerry/jmaをカスタマイズしたものです。

Dockwilioコンテナのリポジトリemerry/dockwilioです。

当システムは試作システムで、一般にサービスを提供していません。

Dockwilio - Twilioを制御するDockerコンテナ(実験バージョン ) = コンテナ仕様編 =

更新:ver.2015.12.12リリースに伴い一部変更しました。

 

Dockwilioは、Twilioを使って簡単に架電&音声合成メッセージ再生をすることができるDockerのコンテナです。

仕様を説明します。

 

仕組みについては概要編を、実行例については簡単手順編を参照してください。

 

Dockwilioは次の2つの機能を持っています。

  • TwilioREST APIにに対し架電を要求する。
  • 上記要求によるTwilioからのHTTPアクセスに対し、音声メッセージのTwiMLを返す。

実験バージョンでは<Say>動詞1回実行のみサポートします。

コンテナはcentos6がベースで、内部で2つのnginxプロセスが稼働します。

 

1. 設定ファイル

Twilioの認証情報や発信元電話番号等を設定します。

ファイル名:dwilio.conf

 dwilio.conf はYAMLで記述します。

設定する内容については 4.パラメータ で説明します。

2. Dockwilioコンテナの起動

  • -d を指定し、detachedします。
  • -p 443:443 を指定し、TwilioからのTwiML要求をコンテナにフォワードします。
  • -v {設定ファイルのディレクトリ}:/etc/dockwilio:ro を指定し、設定ファイルのあるディレクトリをマウントします。
  • イメージは emerry/dockwilio です。

        起動例:

docker run -d -p 443:443 -v /dockwilio_config_dir:/etc/dockwilio:ro -v /etc/localtime:/etc/localtime:ro --name dwilio emerry/dockwilio

3. 架電&音声メッセージ要求

架電先電話番号、再生する音声メッセージ等を指定します。

ホスト内からDockwilioTCP 80番ポートに対し、YAMLまたはJSON形式でHTTP POSTします。

指定する内容は 4.パラメータ で説明します。

4. パラメータ

1.設定ファイル、3.架電&音声メッセージ要求 で設定/指定できるパラメータは下記の通りです。

パラメータ機能設定ファイル架電要求時備考
account_sid Account SID  
auth_token Auth Token  
from 発信元電話番号 E.164形式
twiml_url_base ホストのURL 必須 N/A https:で始まりファイルパスは含まない
basic_user BASIC認証のユーザ名 N/A 指定しない場合はBASIC認証を行わない
basic_password BASIC認証のパスワード N/A 指定しない場合はBASIC認証を行わない
call_timeout 呼び出し待ち時間(秒) デフォルトは60秒
language <Say>動詞の言語指定 デフォルトは ja-JP
voice <Say>動詞の音声指定 デフォルトは alice
syslog_facility syslogのfacility 必須  
twiml_ssl_crt TwiMLアクセス用SSL CERTIFICATEファイル名 必須 N/A 設定ファイルと同じディレクトリに配置
twiml_ssl_key TwiMLアクセス用SSL KEYファイル名 必須 N/A 設定ファイルと同じディレクトリに配置
debug デバッグ出力フラグ(true/false) N/A デフォルトはfalse
to 架電先電話番号 E.164形式
say 音声合成するメッセージ  

5. 戻り値

架電処理終了後の戻り値は、架電要求と同じ書式です。

codestatusmessage備考
0 completed  

正常終了

1 busy   話中
no-answer   無応答
-1 canceled   キャンセルされた(システムの問題)
failed   失敗した(システムの問題)
(なし)   Service_Twilio_RestException ライブラリのエラー(システムまたはプログラムの問題)
その他     エラーと警告の辞書参照

Dockwilio - Twilioを制御するDockerコンテナ(実験バージョン ) = 簡単手順編 =

更新:ver.2015.12.12リリースに伴い一部変更。

 

Dockwilioは、Twilioを使って簡単に架電&音声合成メッセージ再生をすることができるDockerのコンテナです。

概要編に続き、実行するための簡単手順を説明します。

 

前提:

  • Dockerエンジンがホストにインストールされている。
  • Twilioにアカウントがあり、発信用電話番号を購入してある(Account SID, Auth Token、電話番号の情報が必要)。
  • TwiMLアクセスのためのSSLサーバ証明書を用意してある。
  • Dockwilioへの架電要求はホスト内から行う。

なお、Dockwilioイメージはcentos6がベースとなっており、内部で2つのnginxプロセスが稼働します。

 

1. 準備

TwilioからTwiMLを取得するためのHTTPSアクセスを許可します。

# iptables -A INPUT -p tcp -m tcp -dport 443 -j ACCEPT 

 

Dockwilioの設定ファイルを作成します。

ファイル名は「dwilio.conf」でYAML形式で記述します。

account_sid: ACxxxxxxxxxxxx
auth_token: xxxxxxxxxxxxx
from: +8150xxxxxxxx
twiml_url_base: https://hostname.example/
syslog_facility: local0
twiml_ssl_crt: fullchain.pem
twiml_ssl_key: privkey.pem

twiml_url_base の hostname.example にはDockwilioが稼働するホストのFQDNを指定します。

 2. Dockwilioコンテナの起動

docker run -d -p 443:443 -v /dockwilio_config_dir:/etc/dockwilio:ro -v /etc/localtime:/etc/localtime:ro --name dwilio emerry/dockwilio

 ここで、/dockwilio_config_dir は設定ファイル(dwilio.conf)およびSSL証明書のあるディレクトリです。

コンテナを起動すると、コンテナのIPアドレスがsyslog(local0)に出力されます。

Nov 22 11:32:20 4acb42090932 logger: IP address of Dockwilio: inet 172.17.0.51/16 scope global eth0

 3. 架電と音声メッセージ再生の実行

 ホスト内からDockwilioコンテナに対しHTTP POSTすることで(YAMLまたはJSON形式のいずれか)、架電の要求をします。

URLは「http://DockwilioコンテナのIPアドレス/say.{yaml,json}」で、架電の終了後に結果とともに戻ります。

 

下記はcurlコマンドを使用した例ですが、Content-length を指定する必要があるためlsコマンドで予めサイズを調べています。

YAMLの例:

$ ls -l post_data.yaml
-rw-r--r-- 1 user users 49 Nov 21 12:59 2015 post_data.yaml

$ cat post_data.yaml
to: +8180xxxxxxxx
say: Hello. How are you doing?

$ curl -XPOST http://172.17.0.51/say.yaml -H 'Content-length: 49' --data-binary @post_data.yaml
code: 0
status: completed
message: 

JSONの例:

$ ls -l post_data.json
-rw-r--r-- 1 user users 62 Nov 21 13:06 post_data.json

$ cat post_data.json
{
"to": "+818046684833",
"say": "Hello. How are you doing?"
}

$ curl -XPOST http://172.17.0.51/say.json -H 'Content-length: 62' --data-binary @post_data.json
{“code”:”0”,”status”:”completed”,”message”:””}

 

 仕様についてはコンテナ仕様編を参照してください。

 

 

 

Dockwilio - Twilioを制御するDockerコンテナ(実験バージョン ) = 概要編 =

Twilioは、電話サービスを使ったアプリケーションを作って提供するためのCloudサービスです。

しかし、Twilioを使うためにはWebサーバを用意し、そこにアプリケーション(TwiMLと呼ばれるXML)を配置しなければならないというのが、初学者にはわかりづらくハードルを高くしていると思われます。

例えば、電話をかけて音声メッセージを伝えるだけの場合、もしTwilioに相手の電話番号とメッセージ内容をAPIに伝えるだけでできれば簡単なのですが、実際にはメッセージ内容をWebサーバに配置し、それからREST APIに架電要求しなければなりません。

f:id:tsun226:20160416001253p:plain

 

このような単純なアプリケーションを実現するためにだけにWebサーバを構築し、TwiMLの管理(生成、削除)をしなければならないのは、ちょっと面倒ですね。

 

そこで作ったのがTwilioを制御するためのDockerコンテナであるDockwilioです。

Dockwilioへの1回の要求で、架電と音声メッセージの再生を行えるものです。

 

具体的には、設定ファイルにTwilioの認証情報等を記述してDockwilioを起動しておき、HTTP POST により相手の電話番号やメッセージ内容等を伝えると、Twilioを制御して架電し、音声合成によるメッセージを再生します。

Dockwilioは常駐するコンテナで、HTTP POSTにより何度でも架電することも可能です(同時架電も可能)。

また、TwiMLの生成と削除はDockwilio内部で行われ、ユーザが意識する必要もありません。

f:id:tsun226:20160416001429p:plain

Dockwilioは、Dockerのコンテナにより構築されるサービスにおいて、架電&音声メッセージによる通知が必要なときにパーツとして組み合わせるのに適しています。

 

使い方については簡単手順編 を、仕様についてはコンテナ仕様編を参照してください。

 

気象特別警報・警報・注意報のTwitter bot

気象庁が実証実験として行っている、気象庁防災情報XMLPubSubHubbubによるPuSH通知を受信して、各地の気象特別警報・警報・注意報をTwitterに投稿するbotを作成しました(対象地域は少ないですけど、、、)。

 

仕様

ユーザが必要な情報のみ通知を受けられるように、市区町村毎の特別警報・警報・注意報別にアカウントを作成しました。

ユーザは、知りたい地域・気象情報種別のアカウントをフォローし、即時に知りたいものについては通知設定をすると便利です。

 

2015年8月現在、以下のアカウントが稼働しています。

 

地域注意報警報特別警報
東京都新宿区 @shinjuku_wa @shinjuku_ww @shinjuku_wew
東京都品川区 @shinagawa_wa @shinagawa_ww @shinagawa_wew
東京都葛飾区 @katsushika_wa @katsushika_ww @katsushika_wew
神奈川県横浜市 @yokohama_wa @yokohama_ww @yokohama_wew
Minato City, Tokyo (English) @minato_ewa @minato_eww @minato_ewew

 

今のところ、気象庁による防災情報の発表から投稿までは、2〜3分程度の遅延が発生しています。

なお、当botTwitterに投稿する情報の範囲は次の通りです。

注意報

大雨注意報、大雪注意報、風雪注意報、雷注意報、強風注意報、波浪注意報、融雪注意報、洪水注意報、高潮注意報、濃霧注意報、乾燥注意報、なだれ注意報、低温注意報、霜注意報、着氷注意報、着雪注意報

警報

暴風雪警報、大雨警報、洪水警報、暴風警報、大雪警報、波浪警報、高潮警報

特別警報

暴風雪特別警報、大雨特別警報、暴風特別警報、大雪特別警報、波浪特別警報、高潮特別警報


※ 河川洪水、土砂災害、地震津波、噴火等の情報は対象外です。

 

気象庁防災情報XML配信の仕組み

XML形式の防災情報を、push方式のPubSubHubbubプロトコルにより、少ない遅延で配信しています。

PubSubHubbubGoogleのエンジニアが考案した、publisher-hub-subscriberモデルのデータ配信のプロトコルです。情報提供者(publisher)がhubにデータを送信するとすぐに購読者(subscriber)に伝える仕組みで、最近はブログの更新情報を検索サイトに速やかに伝えるため等に使われています。

 

気象庁防災情報XML(実証実験)においては、次のような構成となっています。

  • publisher - 気象庁
  • hub - Google Alert Hub (いくつかの国の気象情報等を配信している)
  • subscriber - 登録された受信者(含、当システム)

 f:id:tsun226:20150815001522p:plain 

 

システムの構成

さくらのVPS(CentOS6)の上で稼働する、次の2つのdockerコンテナにより構成されています。

  1. PuSH受信(PubSubHubbubのsubscriber)、詳細情報XMLを取得
  2. Twitter投稿

2つのコンテナのイメージは同一で、CentOS6にnginx, Perl-FastCGI をインストールしたものです。

各コンテナ上で起動されるperl cgiによりそれぞれの異なる機能が提供されています。

 

f:id:tsun226:20150816205202p:plain

 

今回プログラムを作成するにあたってPubSubHubbubの情報を検索をましたが、 publisherの情報ばかりヒットし、必要とするsubscriberの情報は少なく、その中でもperl cgiを使ったものにはなかなか出会えませんでした。

その数少ない perl cgi による subscriber についての記事の中の下記を、参考にしました。(・◇・)ほっほ さんありがとうございます。

 

気象庁の防災情報XMLを受信するぞ - (。・ω・。)ノ・☆':*;':*

 

日本語投稿テスト

1年前は、Safari on Mac からはてなブログに日本語記事を投稿できなかったのだけど、どうだろう。