Twilio Wirelessを使って、ラズパイのLEDを点ける

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

# はじめに

本記事は、2017年11月1日に開催された、SIer版のIoTLTでライブコーディングしたネタの一部を解説したものです。
当日使ったスライドは[こちら](https://speakerdeck.com/mobilebiz/sier-iotlt-2017nian-11yue-1ri)にあります。

# 何をするのか

**Twilio Wirelessを使って、ラズパイに繋いだLEDを点灯させます（見た目は凄い地味w）。**
![ラズパイ（成功）.jpg](https://qiita-image-store.s3.amazonaws.com/0/86046/84337e10-40c1-2c6a-43da-c5ef12f2ab34.jpeg)

3G USBドングル経由でTwilioから指示が飛んで、黄色いLEDが光ります。
それだけですｗ
ラズパイに対してプッシュする技術は色々ありますが、代表的なのは常に通信状態をキープしつつ、サーバーから指示を出す方法です。しかし、この方法だと通信状態をキープするためのしくみが必要になるので、あまり美しくありません。
今回の方法は、Twilio WirelessのCommand機能を使っているので、通信が切れている状態でもサーバーからプッシュすることができます。

# 準備

今回使う機器は以下の通りです。
![ラズパイ部品.jpg](https://qiita-image-store.s3.amazonaws.com/0/86046/4de16264-397e-4643-70d8-cb475eb828a1.jpeg)

- USBドングル： [AK-020](https://soracom.jp/products/ak-020/)
- Raspberry Pi 3 Model B
- ミニブレッドボード
- 抵抗（抵抗値はいくつでも大丈夫です）
- LED
- ジャンパーケーブル（2本）

![Twilio SIM.jpg](https://qiita-image-store.s3.amazonaws.com/0/86046/c87d9571-283b-6920-2a2a-fa268f185ae1.jpeg)

- Twilio SIM： [こちら](https://jp.twilio.com/console/wireless/orders/new)から購入可能です（購入には、Twilioアカウントが必要です）

# 作業手順

今回は以下の手順でセットアップしていきます。
1. Twilio SIMの設定
2. Raspberry Piの設定
3. 配線
4. プログラミング
5. テスト

では、はじめましょう。

# ハンズオン

## 1. Twilio SIMの設定

- Twilioの管理コンソールにログインします。
- Programmable Wireless -> 料金プラン を選択します。
- `Create a Rate Plan`ボタンを押します。
- UNIQUE NAME欄に、`CommandOnly`と入力します。
- Enabled Servicesでは、MESSAGING(Programmable SMS and Commands)のみチェックをして、他は外します。
- International RoamingのENABLEDをチェックし、MESSAGING(Programmable SMS and Commands)のみチェックを入れます。
- `Create`ボタンを押します。
- Programmable Wireless -> SIM を選択します。
- `Register a SIM Card`ボタンを押します。
- REGISTRATION CODE欄に、SIMカードの裏面にかかれている10文字の文字列を入力します。
- `Register SIM Card`ボタンを押します。
- 利用規約のダイアログが表示されるので、`Accept and Continue`ボタンを押します。
- `Next: Add a Unique Name`ボタンを押します。
- UNIQUE NAME欄に、`SIM01`と入力し、`Next: Add a Rate Plan`ボタンを押します。

多分、このあたりで管理コンソールがハングアップするので、あとはCurlコマンドで設定をします。

- まずは、SIMカードに割り当てられた、DEから始まる文字列（SID）を調べます。
- 次に、管理コンソールのホーム -> 設定を選択します。
- APIクレデンシャルにかかれている、ACCOUNT SIDとAUTH TOKENを調べます。
- 以下のCurlコマンドを実行します。

```lang:curl
curl -X POST https://wireless.twilio.com/v1/Sims/DEから始まるSIMのSID \
-d 'UniqueName=SIM01' \
-d 'FriendlyName=SIM01' \
-d 'RatePlan=CommandOnly' \
-d 'Status=active' \
-u 'ACから始まるACCOUNT SID:AUTH TOKEN'
```

これでSIMが使えるようになりました。
USBドングルに、SIMカードを装着しましょう。
![ラズパイ（USBドングル）.jpg](https://qiita-image-store.s3.amazonaws.com/0/86046/e32eb668-bc55-081e-3045-99dc0dd2b82f.jpeg)

## Raspberry Piの設定

- USBドングルを、Raspberry PiのUSBポート（外側の上段）に接続します。
- Raspberry Piにログインします。
ログインまでの手順は今回は割愛しますので、詳しくは以下のリンクなどを参考にしてください。
- http://hirazakura.hatenablog.com/entry/raspberrypi/setup/first
- https://qiita.com/takabye/items/03ad86a23226a12e4417

USBドングルを使えるようにするために、以下の手順を実施します。
以下のサイトを参考にさせていただきました。
http://mag.switch-science.com/2016/08/22/raspberry-pi-soracom-air-sms/

### ejectのインストールとusbの設定

```lang:sh
sudo apt-get install eject
eject /dev/sr0
sudo modprobe usbserial vendor=0x15eb product=0x7d0e
```

まずはここまででエラーが出ないことを確認したら、毎回起動時にUSBの初期化を行うために、/etc/rc.localの`exit 0`の直前に以下のコードを記載しておきます。

```lang:/etc/rc.local
i=0
while [ $i -lt 15 ]; do
  if [ -e /dev/sr0 ]; then
    break
  else
    sleep 1
  fi
  i=$((i+1))
done

eject /dev/sr0
sudo modprobe usbserial vendor=0x15eb product=0x7d0e
```

USBが正しく認識されると、ドングルのライトが緑色に点灯します。

## 配線

続いて配線をしていきます。

基本的に次の配線図通りに繋いでいただければOKです。
LEDのプラスとマイナスを間違えないようにしてください。足の長い方がプラスになり、抵抗につながっています。
![ラズパイ配線図（LEDのみ）.png](https://qiita-image-store.s3.amazonaws.com/0/86046/d6ea9d58-ba2b-9ace-4725-2ebbe4fb3cfe.png)

## プログラミング

では最後にプログラムを作っていきます。
今回作成が必要なのは、Twilio側の指示を出すためのプログラムと、Raspberry Pi側で指示を受けてLEDのON/OFFを行うプログラムの2つです。

まずは、Raspberry Piのプログラムから作っていきましょう。

Raspberry Piにログインした状態で、以下の作業をします。

```lang:sh
cd
vi sms.py
```

エディタが開くので、まずはアルファベットの`i`を入力して、挿入モードにします。
続いて、以下のコードをコピペします。

```lang:sms.py
# coding: utf-8

'''Twilio Programmable Wireless Command using Raspberry Pi, Twilio Wireless SIM and ABIT AK-020
'''

import sys
import time
import serial
import re
import threading
import RPi.GPIO as GPIO
LED = 3 # ボード上の3番ピン(GPIO2)

class Board:

  # コンストラクタ
  def __init__(self):
    # ピンのモードをそれぞれ出力用と入力用に設定
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(LED, GPIO.OUT)
    self.ledoff()

  def ledon(self):
    # LEDを点ける
    GPIO.output(LED, GPIO.HIGH)

  def ledoff(self):
    # LEDを消す
    GPIO.output(LED, GPIO.LOW)

class Sms:
  # コンストラクタ
  def __init__(self):
    # SMSメッセージかどうかをチェックする文字列パターン
    self.pattern = re.compile('\+CMGL: [0-9]+,"REC UNREAD"')

    # シリアル通信の設定
    self.serial = serial.Serial(
      '/dev/ttyUSB0',
      460800,
      timeout = 5,
      xonxoff = False,
      rtscts = False,
      dsrdtr = False,
      bytesize = serial.EIGHTBITS,
      parity = serial.PARITY_NONE,
      stopbits = serial.STOPBITS_ONE
    )

    # ATZ：モデム初期化
    self.serial.write('ATZ\r')
    self.check_response_isok()
    # AT+CFUN：機能モードを設定（1:Full Functionaly）
    self.serial.write('AT+CFUN=1\r')
    self.check_response_isok()
    # AT+CGDCONT：APNの書き込み
    self.serial.write('AT+CGDCONT=1,"IP","wireless.twilio.com"\r')
    self.check_response_isok()
    # AT+CMGF：SMSを設定（1:テキストモード）
    self.serial.write('AT+CMGF=1\r')
    self.check_response_isok()

  # ディストラクタ
  def __del__(self):
    # シリアル通信を閉じる
    self.serial.close()

  # ウェイト
  def wait_response(self):
    time.sleep(1)
    while self.serial.inWaiting() == 0:
      time.sleep(0.5)

  # OK応答の確認
  def check_response_isok(self):
    self.wait_response()
    r = self.serial.read(self.serial.inWaiting()).split('\r\n')
    if len(r) < 2 or r[-2] != 'OK':
      raise Exception(r)

  # プロンプト応答の確認
  def check_response_isprompt(self):
    self.wait_response()
    r = self.serial.read(self.serial.inWaiting()).split('\r\n')
    if len(r) < 1 or r[-1] != '> ':
      raise Exception(r)

  # 読み捨て
  def dispose_response(self):
    self.wait_response()
    self.serial.read(self.serial.inWaiting())

  # メッセージの受信
  def receive_message(self):
    self.serial.write('AT+CMGL="REC UNREAD"\r\n')
    self.wait_response()
    r = self.serial.read(self.serial.inWaiting()).split('\r\n')

    if len(r) < 2 or r[-2] != 'OK':
      raise Exception(r)

    messages = []
    is_message = False

    for line in r:
      if is_message:
        messages.append(line)

      if line and self.pattern.match(line):
        is_message = True
      else:
        is_message = False

    return messages

class MainThread(threading.Thread):
    def __init__(self, n):
        super(MainThread, self).__init__()
        self.n = n

    def run(self):
        print " === start thread === "
        # 指定された回数だけ受信メッセージ取得をループ
        for i in range(self.n):
            messages = sms.receive_message()
            for message in messages:
                if message.upper() == 'ON':
                    board.ledon()
                elif message.upper() == 'OFF':
                    board.ledoff()
                print message

        board.ledoff()
        print " === end thread === "

if __name__ == '__main__':
    board = Board()
    sms = Sms()
    th_main = MainThread(30) # 30 * ループ1回につき2秒 = 60秒間実行
    th_main.start()
```

貼り付けが終わったら、`ESC`を押して挿入モードを抜けて、`wq`で保存して終了します。
では、念のために実行をしてみましょう。

`python sms.py`
しばらくすると、` === start thread ===`と表示されれば、待受開始です。
`=== end thread ===`でプログラムは終了となります。　なおプログラムは数分で終了します。

次にTwilio側のプログラムを作成していきます。

- Twilioの管理コンソールにログインし、Runtime -> Functions -> Configureを選択します。
- `Enable ACCOUNT_SID and AUTH_TOKEN`にチェックをいれて、`Save`ボタンを押します。

- Runtime -> Functions -> Manageを選択します。
- `Create a Function`ボタンを押します。
- New Functionsダイアログが開いたら、`Blank`を選択して、`Create`ボタンを押します。
- Functions Name欄に、`sendCommand`と入力します。
- PATH欄には、`/sendCommand`と入力します。
- PATH欄の右側にある、コピーアイコンを押して、URLをメモ帳にコピーしておきます。
- 事前にCODE欄に書かれていたコードを一度消して、以下のコードをコピペします。

```lang:sendCommand.js
exports.handler = function(context, event, callback) {
  const command = event.command || 'On'
  const request = require('request')
  const options = {
    method: 'POST',
  	url: 'https://wireless.twilio.com/v1/Commands',
  	headers: {
      'cache-control': 'no-cache',
      authorization: 'Basic ' + new Buffer(context.ACCOUNT_SID + ':' + context.AUTH_TOKEN).toString('base64'),
      'content-type': 'application/x-www-form-urlencoded' },
    form: {
      Sim: 'SIM01',
      Command: command
    }
  };

  request(options, function (error, response, body) {
  	if (error) {
      console.log(error)
      callback(error)
    } else {
      console.log(response)
      callback(null, 'OK')
    }
  });
};
```

`Save`ボタンを押して、コードをデプロイします。しばらくして、デプロイ完了の緑色のバナーが表示されれば、準備は完了です。

## テスト

ではいよいよテストをしましょう。
- まずは、Raspberry Pi側で、先程のsms.pyを実行しておきます。
- 次に、新しくブラウザを開き、先程メモしておいたTwilio側のプログラムのURLを入力して実行します。
LEDが点灯すれば成功です。
![ラズパイ（成功）.jpg](https://qiita-image-store.s3.amazonaws.com/0/86046/f9719cc6-37f5-9482-2da6-a3162e920efc.jpeg)

ちなみに、LEDを消灯させるには、メモしておいたURLに、`?command=Off`を付加して実行します。