Twilioとobnizでポケベルを作る

:::note alert
2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
:::

# はじめに

これは、2019年3月25日に開催されたSIer IoTLTで発表したネタ記事です。
https://iotlt.connpass.com/event/122499/

このネタを書くきっかけになったのは、以下の記事を拝見したからです。
[【114106→ｱｲｼﾃﾙ】平成の最後に最新のWebRTCでポケベルを作ってみた【体験編】](https://dotstud.io/blog/latest-webrtc-pokeberu-end-of-heisei/)

今年9月にいよいよポケベルが終わってしまうということで、ポケベルを知らない世代にもポケベルを知ってもらおうという趣旨ですね。
とても素敵です。
でもですね、電話と言ったら[Twilio](https://twilio.kddi-web.com)じゃないですか。
で、居ても立ってもいられなくなり、Twilioでポケベルを作ってみました。
肝心の配信先（ポケベル）はというと、IoT業界では有名なみんな大好き[obniz](https://obniz.io)を使わせてもらいました。

# 準備するもの

## obniz

![index-obniz-front.png](https://qiita-image-store.s3.amazonaws.com/0/86046/d77719d0-6159-1c22-87a9-77ebd4239119.png)
いわゆるマイコンボードなのですが、WiFiやBLEがついていて、簡単なディスプレイもあります。さらに、WiFiにさえ繋いでしまえば、外部からもアクセスが可能なので、今回のような使い方はとても向いています。
[公式サイト](https://obniz.io/)経由でも購入が可能（Amazonで5,980円）ですので、ぜひ1台持っていると便利ですよ。

## 圧電ブザー

![圧電ブザー.jpg](https://qiita-image-store.s3.amazonaws.com/0/86046/6ead9fee-0595-a22b-6f10-718fba8b7659.jpeg)

なくても構いませんが、本当のポケベルでもメッセージを着信したときにはブザー音がなるので、あるとよりリアルになります。
基本ほとんどの圧電ブザーが使えるはずですが、今回は秋葉原のマルツで調達しました(税込み302円)。秋葉原まで行けない方は、[マルツオンライン](https://www.marutsu.co.jp/pc/i/1099864/)でも購入可能です。

## Twilioアカウント

今回は着信がメインとなりますので、Twilioのトライアルアカウントでも動作します。ただし、トライアルアカウントのポイントがなくなった時点で使えなくなりますので、継続して使いたい場合はアカウントのアップグレードをお願いいたします。
また、トライアルアカウントでは、電話をかけたときにトライアルアカウントである旨のガイダンスが流れます。

トライアルアカウントの作成は、[こちら](https://twilio.kddi-web.com/signup/)から行います。

# obniz側の設定

## 圧電ブザーの取り付け

もし圧電ブザーをお持ちの方は、電源が刺さっていない状態で以下の作業を行います。

- 圧電ブザーをお持ちの方は、ブザーの＋（プラス）信号線（おそらく赤い方）をobnizの1番ピンに接続します。
- 同じくー（GND）信号線（おそらく黒色）をobnizの0番ピンに接続します。

![s_IMG_5616.JPG](https://qiita-image-store.s3.amazonaws.com/0/86046/fb07e0da-f672-bcdb-3ba5-e290810d8c64.jpeg)


## マイページへの登録

もしobnizの初期設定が完了していない方は、[こちらの記事](https://obniz.io/doc/quickstart)を参考にセットアップを終わらせておいてください。
また、今回はobnizクラウドを利用しますので、[こちらのページ](https://obniz.io/users_signin)から購入したobnizをマイページ上に登録しておいてください。
![obnizマイページ (1).png](https://qiita-image-store.s3.amazonaws.com/0/86046/edd72daa-6680-4ef2-0848-77e52ad188c7.png)

## AccessTokenの生成

先程書いたように、obnizはWiFiに接続するだけで外部からアクセスが可能になります。便利な半面、8桁のID（obniz ID）がわかれば、誰でも外部から接続できてしまうため、セキュリティ上の問題があります。
そのため、それぞれのobnizに**AccessToken**を設定し、外部からのアクセス時にobniz IDと併せてAccessTokenも指定することでセキュリティを強化できます。
*AccessTokenを生成したobnizについては、以後AccessTokenも一緒に設定しなければエラーになりますので注意してください。*

- obnizのマイページにログインします。
- 登録したobnizの**設定**ボタンを押します。
- **Access Control**を選択し、**Generate Access Token**を押します。

![obniz_APIKey_Before.png](https://qiita-image-store.s3.amazonaws.com/0/86046/ed05822a-86aa-8f3b-1ab3-7c9581b75bd3.png)

- AccessTokenが生成されるので、生成された文字列をメモ帳などに控えておきます。なお、画面には文字列の一部しか表示されていないので、かならずすべての文字列をコピーしておきます。

![obniz_APIKey_After.png](https://qiita-image-store.s3.amazonaws.com/0/86046/f3ff5040-7db6-957e-4079-6210a42ea50b.png)

## WebAppの作成

次に、obnizクラウド上にWebアプリケーションを作成します。Webアプリケーションといっても、obniz上でホストされるHTMLページです。

- obnizのマイページにログインします。
- **リポジトリ**の**新規作成**を押します。
- **新しいプログラム**ダイアログが開くので、**タイプ**は「WebApp」を選択、**アクセスレベル**は「公開」、**ファイル名**には「getCode」と入力して**作成**ボタンを押します。

![スクリーンショット 2019-03-26 16.19.54.png](https://qiita-image-store.s3.amazonaws.com/0/86046/4df5a8cd-0360-dd31-174b-9127f1ea26d7.png)

- 表示されるHTMLをすべて削除し、以下のHTMLを貼り付けます。

```html
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://obniz.io/js/jquery-3.2.1.min.js"></script>
    <script src="https://unpkg.com/obniz@2.0.1/obniz.js" crossorigin="anonymous"></script>
  </head>
  <body>

    <div id="obniz-debug"></div>

    <script>
      let request = {};
      if (typeof req === "object") {
        request = req;
      }
      console.log(request.body);
      
      const dayOfWeek = new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
      const month = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
      
      const now = new Date();
      const date = `${dayOfWeek[now.getDay()]} ${now.getDate()} ${month[now.getMonth()]} '${String(now.getFullYear()).slice(2)}`;
      const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}\n`;
      
      const obniz = new Obniz("[XXXX-XXXX]", {access_token: "[AccessToken]"});
      obniz.onconnect = async () => {
        
        // 圧電スピーカーが用意できているかたはこの後のコメントブロックを解除すると音がでます。
        /*
        const speaker = obniz.wired("Speaker", {signal: 1, gnd: 0});
        speaker.play(440);
        await obniz.wait(500);
        speaker.stop();
        */

        obniz.resetOnDisconnect(false);  
        obniz.display.clear();
        obniz.display.print(date);
        obniz.display.print(time);
        obniz.display.print(request.body.message);

      }
    </script>
  </body>
</html>
```

![スクリーンショット 2019-03-26 16.31.20.png](https://qiita-image-store.s3.amazonaws.com/0/86046/19d6dc72-742e-d4dd-2f38-667084a52d66.png)

- 26行目の[XXXX-XXXXX]の部分をご自分のobniz IDに書き換えます。
- 同じく26行目の[AccessToken]を、先程保存しておいたAccessTokenに書き換えます。
- ブザーをお持ちの方は、途中のコメントアウト（/\*と\*/）を削除してください。

**ここでobnizの電源を投入し、WiFiに接続されていることを確認します。**

- 右上にある**実行**ボタンを押します。
- この時点ではメッセージを受け取っていないので、以下のようなエラー画面が表示されます。
- ブザーを装着している場合は、ブザー音も鳴ります。

![obniz_execute_error.png](https://qiita-image-store.s3.amazonaws.com/0/86046/a5f5bdd9-8fc4-dba5-59d9-56804d2ebe2f.png)

これでポケベル部分はできました。

では次に、今作成したWebアプリケーションをTwilioから呼び出す際のWebhookを設定します。

- obnizのマイページにログインします。
- **イベント**の**新規作成**を押します。

![obniz_mypage_event.png](https://qiita-image-store.s3.amazonaws.com/0/86046/4fbc632a-e078-d715-058b-8bcf1ab3d75c.png)

- **名前**欄には、「displayMessage」、**実行するWebApp**は、先ほど作成した「getCode.html」、**きっかけ**は「Webhook」を選びます。

![スクリーンショット 2019-03-27 12.34.39.png](https://qiita-image-store.s3.amazonaws.com/0/86046/e3d8f2a3-a177-43fd-13d8-3f85ec0eac8f.png)

- **作成**ボタンを押して設定を保存します。
- **Webhook**の**Endpoint**が払い出させるので、URLを保存しておきます。

# Twilio側の処理

今回、Twilioは以下の処理をします。

1. 050番号に着信する。
2. ガイダンスを流す。
3. キーを受け付ける。
4. 受け付けたキーを文字に変換して、obnizを呼び出す。

1について、電話番号を購入する必要があります。
4については、Twilio Functionsを使ってプログラミングをします。
2と3については、Twilio Studioを使います。

## 電話番号を購入する

すでに電話番号を購入している人は、その番号を使うこともできます。
もしまだ購入していない人は、以下の手順で番号を購入します。

- ブラウザで[ログイン画面](https://jp.twilio.com/login/kddi-web)を表示し、ご自分のIDとパスワードでログインをします。
- 管理コンソールの左側にあるボタンアイコンを押すと、スライドメニューが表示されます。

![002 (1).png](https://qiita-image-store.s3.amazonaws.com/0/86046/e9bd6295-1b6e-9eb7-fdf4-1075b4d610c3.png)

- スライドメニューの一覧から、**電話番号**を選択します。

![005 (1).png](https://qiita-image-store.s3.amazonaws.com/0/86046/7a3e89e8-540a-8c9e-2340-fdcbecdc80d6.png)
- **Phone Numbers**メニューの中の**Buy a Number**を選択します。
- 国のプルダウンから「Japan（+81）」を選択し、**検索**ボタンを押します。

![006.png](https://qiita-image-store.s3.amazonaws.com/0/86046/e3e0c258-960e-f8fa-5301-62c4c28b2834.png)

- 一覧表示されたリストの中から、TYPEがローカルになっている（108円）の番号を一つ選び、**購入**ボタンを押します。
- この番号を購入しますか？というダイアログが出たら、**この番号を購入する。**を押します。
- Congratulationのダイアログが表示されたら、購入完了です。**閉じる**ボタンを押します。
- Phone Numbersの中のManage Numbersを選択し、今購入した電話番号が表示されることを確認します。

## Functionsでobnizを呼び出す部分を作成する

- **DEVELOPER TOOLS**の中にある**Runtime**を選択します。
- その中の**Functions**を選択し、さらに**Configure**を選びます。
- **Dependencies**の設定内にある＋アイコンを1回押して、枠を追加します。
- **NAME**欄に「request-promise」、**VERSION**欄に「4.2.4」と入力します。
- 「Twilio」の**VERSION**を「3.25.0」に変更します。

![スクリーンショット 2019-03-27 13.59.00.png](https://qiita-image-store.s3.amazonaws.com/0/86046/cf8129c4-e888-360f-d7bb-c790dcfbc688.png)

- **Save**ボタンを押して、設定を保存します。

### Twilio Functionsの作成

つぎに、電話口で入力された数字をカタカナに変換して、obnizクラウドのWebhookを呼び出すためのFunctionを作ります。
ポケベルの変換表は機種によって若干の違いがあるようですが、今回は[こちら](http://www.macco.co.jp/~ktaimail/pocket/p.html)の変換表を利用しました（小文字には対応していません）。

- Twilioの管理コンソールで、先ほどと同じように**Runtime**の**Functions**を選択します。
- さらに**Manage**を選択し、**Create a new Function**もしくは、赤い＋アイコンを押して、新しいFunctionを作成します。
- テンプレートを選択するダイアログが表示されるので、**Blank**を選択した後、**Create**ボタンを押します。

<img width="802" alt="スクリーンショット 2018-08-08 21.56.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/9fbea563-33cc-5480-c583-09f617e23339.png">

- **FUNCTION NAME**欄に「PostMessage」と入力します。
- **PATH**欄に「/post-message」と入力します。
- **CODE**欄に予め書かれているコードをすべて削除し、以下のコードを貼り付けます。

```javascript
const rp = require('request-promise');
exports.handler = function(context, event, callback) {
    // コードを受け取る
    const code = event.code || '';

    // 変換表
    const pocketbellTable = {
        "11":"ｱ", "12":"ｲ", "13":"ｳ", "14":"ｴ", "15":"ｵ", "16":"A", "17":"B", "18":"C", "19":"D", "10":"E",
        "21":"ｶ", "22":"ｷ", "23":"ｸ", "24":"ｹ", "25":"ｺ", "26":"F", "27":"G", "28":"H", "29":"I", "20":"J",
        "31":"ｻ", "32":"ｼ", "33":"ｽ", "34":"ｾ", "35":"ｿ", "36":"K", "37":"L", "38":"M", "39":"N", "30":"O",
        "41":"ﾀ", "42":"ﾁ", "43":"ﾂ", "44":"ﾃ", "45":"ﾄ", "46":"P", "47":"Q", "48":"R", "49":"S", "40":"T",
        "51":"ﾅ", "52":"ﾆ", "53":"ﾇ", "54":"ﾈ", "55":"ﾉ", "56":"U", "57":"V", "58":"W", "59":"X", "50":"Y",
        "61":"ﾊ", "62":"ﾋ", "63":"ﾌ", "64":"ﾍ", "65":"ﾎ", "66":"Z", "67":"?", "68":"!", "69":"-", "60":"/",
        "71":"ﾏ", "72":"ﾐ", "73":"ﾑ", "74":"ﾒ", "75":"ﾓ", "76":"¥", "77":"&", "78":"", "79":"☎", "70":"",
        "81":"ﾔ", "82":"(", "83":"ﾕ", "84":")", "85":"ﾖ", "86":"*", "87":"#", "88":" ", "89":"♥", "80":"",
        "91":"ﾗ", "92":"ﾘ", "93":"ﾙ", "94":"ﾚ", "95":"ﾛ", "96":"1", "97":"2", "98":"3", "99":"4", "90":"5",
        "01":"ﾜ", "02":"ｦ", "03":"ﾝ", "04":"ﾞ", "05":"ﾟ", "06":"6", "07":"7", "08":"8", "09":"9", "00":"0"
    };

    let message = '';
    for (let i=0; i<code.length; i+=2) {
        let str = code.substr(i, 2);
        if (pocketbellTable[str]) {
            message += pocketbellTable[str];
        }
    }

    const options = {
        uri: '[ここにobnizクラウドのイベントWebhookのURLを指定します]',
        method: 'POST',
        form: {
            message: message
        },
        headers: {
        }
    };

    rp(options)
    .then((body) => {
        callback(null, 'OK');
    })
    .catch((err) => {
        callback(err);
    });
};
```

- 29行目の[ここにobnizクラウドのイベントWebhookのURLを指定します]の部分を、先程控えておいたobnizクラウドのイベントで払い出されたURLに置き換えます。
- **Save**ボタンを押します。
- デプロイが自動で実行され、しばらくするとデプロイ完了の緑色のバナーが表示されれば完成です。



## Twilio Studioで着信フローを作成する

## フローの読み込み

- 管理コンソールの左側のスライドメニューから、Studioを選択します。
- 赤いプラスアイコンをクリックするか、**Create a new flow**を選択して、新しいフローを作成します。
- **FLOW NAME**に「PostMessage」と入力して、**NEXT**ボタンを押します。
- **New Flow**ダイアログが表示されるので、**Import from JSON**を選択して、**Next**ボタンを押します。

![スクリーンショット 2019-01-07 16.57.36.png](https://qiita-image-store.s3.amazonaws.com/0/86046/b70b1156-98b9-cb54-d5b1-3edb8118ba06.png)

- **New Flow**ダイアログが開きますので、1行目の{}を削除して、以下のJSONを貼り付けます。

```json
{
  "description": "Display Message",
  "states": [
    {
      "type": "InitialState",
      "name": "Trigger",
      "properties": {
        "offset": {
          "x": 0,
          "y": 0
        },
        "flow_url": "https://webhooks.twilio.com/v1/Accounts/ACxxxxx/Flows/FWxxxxx"
      },
      "transitions": [
        {
          "event": "incomingMessage",
          "conditions": [],
          "next": null,
          "uuid": "5dcdc231-0b7c-42ab-8f11-68a8f5bf8997"
        },
        {
          "event": "incomingCall",
          "conditions": [],
          "next": "FFff25f5b57237b689a4839d18d9b7caf9",
          "uuid": "e3f190f6-1aa6-4c87-b2bf-770fb6314163"
        },
        {
          "event": "incomingRequest",
          "conditions": [],
          "next": null,
          "uuid": "f05c85d0-9a6a-47e3-a6a0-575cf5d803fe"
        }
      ],
      "sid": "FFd51d1d512e68f9099ffb6576265e7fff"
    },
    {
      "type": "SayPlay",
      "name": "SayGuide",
      "properties": {
        "offset": {
          "x": 80,
          "y": 160
        },
        "say": "こちらは東京テレメッセージです。送りたいメッセージをテンキーで入力してください。\n最後にシャープを押してください。",
        "play": null,
        "voice": "Polly.Takumi",
        "language": "ja-JP",
        "loop": 1
      },
      "transitions": [
        {
          "event": "audioComplete",
          "conditions": [],
          "next": "FFd279178a20fda6faa4b67dca30eaa759",
          "uuid": "a4e308ee-0081-4074-84e7-3032084e5287"
        }
      ],
      "sid": "FFff25f5b57237b689a4839d18d9b7caf9"
    },
    {
      "type": "Gather",
      "name": "GatherCode",
      "properties": {
        "offset": {
          "x": -170,
          "y": 400
        },
        "timeout": 10,
        "finish_on_key": "#",
        "stop_gather": true,
        "number_of_digits": 30,
        "save_response_as": null,
        "say": null,
        "play": null,
        "voice": "alice",
        "language": "en",
        "loop": 1,
        "hints": null,
        "gather_language": "en"
      },
      "transitions": [
        {
          "event": "keypress",
          "conditions": [],
          "next": "FF643f145fffa917d4565354d0585bdd13",
          "uuid": "4b1d54d5-a701-4dbb-b006-85d95fbad42c"
        },
        {
          "event": "speech",
          "conditions": [],
          "next": null,
          "uuid": "61a3bea6-e71a-4875-a434-6329c110e67a"
        },
        {
          "event": "timeout",
          "conditions": [],
          "next": null,
          "uuid": "3218594f-e2e2-490b-894c-59a879fdb5eb"
        }
      ],
      "sid": "FFd279178a20fda6faa4b67dca30eaa759"
    },
    {
      "type": "Function",
      "name": "PostMessage",
      "properties": {
        "offset": {
          "x": 290,
          "y": 420
        },
        "url": "",
        "timeout": null,
        "parameters": [
          {
            "key": "code",
            "value": "{{widgets.GatherCode.Digits}}"
          }
        ]
      },
      "transitions": [
        {
          "event": "success",
          "conditions": [],
          "next": "FF85fc069cecba6fd448788cafe7a39b80",
          "uuid": "612b61a7-639f-4a01-b19a-3191f5bd5bde"
        },
        {
          "event": "fail",
          "conditions": [],
          "next": null,
          "uuid": "e63e5fb1-9d4c-407f-b632-be42c9a1de88"
        }
      ],
      "sid": "FF643f145fffa917d4565354d0585bdd13"
    },
    {
      "type": "SayPlay",
      "name": "SayThanks",
      "properties": {
        "offset": {
          "x": 270,
          "y": 640
        },
        "say": "メッセージをお送りいたしました。ご利用ありがとうございました。",
        "play": null,
        "voice": "Polly.Takumi",
        "language": "ja-JP",
        "loop": 1
      },
      "transitions": [
        {
          "event": "audioComplete",
          "conditions": [],
          "next": null,
          "uuid": "f538e4e1-7076-4602-a825-a482dd5a6f6e"
        }
      ],
      "sid": "FF85fc069cecba6fd448788cafe7a39b80"
    }
  ]
}
```
- **Next**ボタンを押します。

フローが読み込まれたら、一部のウィジェットを修正します。

- **PostMessage**と書かれたウィジェットを選択します。
![スクリーンショット 2019-03-27 14.45.06.png](https://qiita-image-store.s3.amazonaws.com/0/86046/15327669-30da-824b-49fd-c69d0881648e.png)


- 右側に表示されるプロパティページの**FUNCTION URL**のプルダウンから「PostMessage」を選択します。
- **Save**ボタンを押します。
- 画面上部の**Publish**ボタンを押します。
- 確認ダイアログが開くので、**Publish**ボタンを押します。

## 050番号に処理を割り当てる

最後に、今作成したStudioフローを050番号に割り当てます。

- Twilioの管理コンソールから**Phone Numbers**を選択します。
- 先程購入した050番号を選択します。
- **通話着信時**のプルダウンを「Studio Flow」にして、その右側のプルダウンは先程作成した「PostMessage」を選択します。

![PhoneNumber_Setup.png](https://qiita-image-store.s3.amazonaws.com/0/86046/a5abaa4f-d8f2-8715-f5e3-61ea4a6a3515.png)

- **保存**ボタンを押します。

## テスト

では早速テストをしてみましょう。
ポケベル変換表は次のとおりです。
![スクリーンショット 2019-03-25 15.40.51.png](https://qiita-image-store.s3.amazonaws.com/0/86046/eff512a6-8b2d-9d41-8ec8-15f690433050.png)

- 購入した050番号に電話をかけます。
- ガイダンスが流れるので、ガイダンスが終わったらメッセージを数字キーで入力して最後に#を押します（たとえば、40582937293089#）。
- obnizのブザーがなって、メッセージが表示されることを確認します。

![s_MicrosoftTeams-image.png](https://qiita-image-store.s3.amazonaws.com/0/86046/962bba4d-1492-2566-1f2e-0efe9e1c0b33.png)


# まとめ

- obnizはディスプレイが標準でついているので、余計なコストがかかりません。
- WiFiさえつながれば、たとえば社内に設置していても外部からアクセスが可能です。
- Twilio StudioやFunctionsを使うと、サーバーを設置しなくても実現が可能です。
- Twilioにかかる料金は、ほぼ電話番号代（108円/月）と着信料（1円/分）だけです。

---
### Twilio（トゥイリオ）とは

https://twilio.kddi-web.com
Twilioは音声通話、メッセージング（SMS/チャット）、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウドAPIサービスです。初期費用不要な従量課金制で、各種開発言語に対応しているため、多くのハッカソンイベントやスタートアップなどにも、ご利用いただいております。