HomebridgeとArduinoをXBeeでつなぐ

前回の記録: Siriで赤外線リモコン操作を実現する Raspberry PiはUSB接続のWi-Fiドングルで無線通信出来ていたが,ArduinoはEthernetシールドにLANケーブルを接続していた. このLANケーブルが足かせとなり,Arduinoを好きな位置に置くことが出来なかった. そこで今回,Raspberry PiとArduino間通信にXBeeを用いることで双方の無線化を実現. 電源さえ取れればArduinoを好みの場所に置くことが出来るようになった.

目指すところ

Raspberry PiとArduino間通信にXBeeを使う
前回の記録"Siriで赤外線リモコン操作を実現する"では部屋のペンダントライトをSiriで操作するまでを記録した. そこではRaspberry PiとArduino間の通信にHTTPを利用していたため,無線機能の無いArduinoにはLANケーブルの接続が必要だった. 手元には以前遊んでそのままになっていたXBeeが2台. このXBee2台を有効活用すべく,Raspberry PiとArduino間通信をHTTPからZigBeeへ切り替えた. ここにはその道筋を記す.

使用機器

手順としてはまずXBeeのセットアップ. その後Raspberry PiとArduinoにXBeeを接続. Raspberry PiではPythonでシリアル通信出来るようにプログラム. Arduino側ではシリアル通信を開き,受け取ったデータに応じてON/OFF制御を行う,と言ったところ.

XBeeのセットアップ

XBeeのセットアップに関しては次の記録を参照. XBeeに触る 上の記録では2台のXBeeがペア(1対1)通信を行えるようセットアップする方法を紹介している. セットアップ後はXBeeをシリアルケーブルと同等に扱うことが出来るようになる.
つまり,無線通信のプロトコルどうこうといった部分を気にせず,いつも通りSerial通信をするだけでXBeeが勝手に無線通信し合ってくれるようになるのだ.

Raspberry Piのセットアップ

セットアップ済みXBeeを載せたXBee USB ExplorerをUSBケーブルでRaspberry Piへ接続.
私は3Mの両面テープで壁に貼り付けた
それではお手持ちのPCからRaspberry PiへSSH接続. ssh ユーザ名@ホスト名.local SSH接続したら,Raspberry Piに次のコマンドを入力. lsusb USB接続されている機器一覧が表示される. その中に
...
Future Technology International, ~ Ltd Serial (UART) ...
...
という表示があればそれがXBee USB Explorer. きちんと認識されていることが分かる. さて,久しぶりのRaspberry Pi(Arch Linux)なので一応パッケージを更新. sudo pacman -Syu 更新を終えたら,Pythonでシリアル通信を実現するライブラリ"pySerial"をインストール sudo pacman -S python-pyserial これでPythonを使ってシリアル通信を行う事が出来るようになった. XBeeが繋がっているので,シリアル通信=無線通信(ZigBee)ということになる. さて,簡単な文字のやり取りでON/OFF制御ができれば良いので,ここでは文字Tを送ったらON,Fを送ったらOFF,Sを送ったら現在の状態を返してもらう,という仕様にしよう. ここで返してもらう"状態"とはライトがONならば1,OFFならば0の整数とする. nano -w send_ascii.py send_ascii.pyという名前のプログラムを作る. これはASCII文字をシリアル出力する簡単なPythonスクリプトだ.

send_ascii.py

import sys
import serial

if len(sys.argv) == 1:
        sys.exit()

ser = serial.Serial('/dev/ttyUSB0')
ser.write(sys.argv[1].encode('utf-8'))

if sys.argv[1] == 'S':
        res = ord(ser.read())
        if res == 1:
                print(res)

ser.close()
例えばASCII文字のTを送りたい場合は次のようにする. python send_ascii.py T 引数の文字Tがシリアルポート/dev/ttyUSB0に送信される. つまり,XBeeに文字Tが流れ,無線として飛び,もう一方のXBeeに渡る仕組み.

スクリプトについて少し解説

serial.Serial()がシリアル通信を始める合図. 引数にポートとボーレートを渡す. ここでは/dev/ttyUSB0というポート名が"XBee USB Explorer"を指す. ボーレートは省略すると9600bpsとなる(Arduinoも同じボーレートなので都合が良い). 指定したポートへデータを送信する時はwrite()を使う. 指定したポートからデータを受信する時はread()を使う. やっていることはシンプルで,受け取った引数sys.argv[1]をUTF-8でエンコードして送信,もし引数がSだった場合,相手の返答(状態)を受け取る必要があるのでread()で受信をしている. Arduinoから受け取った値はASCIIコードなのでord()で整数(0か1)へと変換. 1(ON)ならば"1"と表示,0(OFF)ならば何も表示しないようにしてある(この挙動が後々役立つ). 最後はclose()で開いたポートを閉じる.
文字を送信するPythonスクリプトはこれで完成. 次は受け手となるArduinoのセットアップ.

Arduinoのセットアップ

ArduinoにXBeeの付いたXBeeシールド(私はSeeedのXBee Shieldを使っている)を載せる.
シールド上のジャンパピンは2つとも外す.
ジャンパピンを外すことでArduinoとXBeeの通信を切ることが出来る. Arduinoにスケッチをアップロードする際は必ず通信を切ること. そうしないとXBeeにArduinoのバイナリデータが流れ込んで,アップロードエラーになってしまう.
下のスケッチをアップロードしよう.

HomebridgeXBeeController.ino

#include <SPI.h>
#include <IRremote.h>

IRsend irsend;
int state = 0;

unsigned int light_on[] = {/* ONコマンド */};
unsigned int light_off[] = {/* OFFコマンド */};

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    
    if (c == 'T') {
      irsend.sendRaw(light_on, sizeof(light_on)/sizeof(light_on[0]), 38);
      state = 1;
    }
    else if (c == 'F') {
      irsend.sendRaw(light_off, sizeof(light_off)/sizeof(light_off[0]), 38);
      state = 0;
    }
    else if (c == 'S') {
      Serial.write(state);
    }
  }
}
/* ONコマンド *//* OFFコマンド */の部分には赤外線コード(RAWデータ)を配列の形で書き込む(詳細はこちら"赤外線コードの送信 - 再現"の項).

スケッチについて少し解説

Serial.begin()でシリアル通信がはじまる. 引数にはボーレートを. Pythonスクリプトと同じ9600bpsを渡している. Serial.available()でデータを受信したかどうかが分かる. つまりXBeeからデータが来ていれば,if文の中に入る. Serial.read()で受信したデータを読む. 受信したデータがTならばON信号をirsend.sendRaw()で送信する. この函数はArduinoのD3ピンに繋がったIR-LEDを光らせる. Fが来たらOFF信号を,Sが来たらライトの状態stateをSerial.write()でRaspberry Piへ送信している.
アップロードが完了したらジャンパピンを次のように接続する.
ジャンパピンを上図のように接続することでArduinoのRxがXBeeのTxと,ArduinoのTxがXBeeのRxと接続する. XBeeから入ってきた信号はXBeeのTxを通ってArduinoのRx(受信ポート)へと入る. またArduinoの出力(Tx)はXBeeのRxへと入りRaspberry Piへと送信される.
SeeedのXBee Shieldは上図のような回路構成になっている. 3列のピンヘッダ1番上はXBeeのRxピンに繋がっている. 1番下はTxピンに繋がっている. 真ん中のヘッダピンはArduinoのディジタルI/Oピンと同じ並びで繋がっている(右からD0,D1,D2...と繋がっている).
さて,お次は赤外線LED(IR-LED)の接続.
回路はとてもシンプル. ArduinoのD3ピンとGNDの間に抵抗とIR-LEDを直列につなぐだけ.
本当はブレッドボード上でテストを行うべきだが,私はいきなりシールドにIR-LEDと抵抗器をはんだ付けしてしまった. テストの時は面倒でもブレッドボード上で回路を組んだ方が賢明なのかもしれない.
これでArduinoのセットアップは完了.

合わせてテスト

Raspberry Piで次のコマンドを実行しよう. python send_ascii.py T これで文字TがXBeeを通ってArduinoへと送信されるはず. この文字を受信したArduinoはIR-LEDからON信号を送信するので,部屋のライトが点灯する.

エラーになった場合

上のコマンドを実行してPermission deniedエラーが出力された場合について.
Traceback (most recent call last):
  File "/usr/lib/python3.5/site-packages/serial/serialposix.py", line 265, in open
    self.fd = os.open(self.portstr, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
PermissionError: [Errno 13] Permission denied: '/dev/ttyUSB0'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "xbee_test.py", line 3, in <module>
    s = serial.Serial('/dev/ttyUSB0', 9600)
  File "/usr/lib/python3.5/site-packages/serial/serialutil.py", line 236, in __init__
    self.open()
  File "/usr/lib/python3.5/site-packages/serial/serialposix.py", line 268, in open
    raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
serial.serialutil.SerialException: [Errno 13] could not open port /dev/ttyUSB0: [Errno 13] Permission denied: '/dev/ttyUSB0'
現在ログイン中のユーザーにUSBポート/dev/ttyUSB0を操作する権限を与えれば解決する. ls -l /dev/ttyUSB0 とすると該当USBポートの権限について表示される. crw-rw---- 1 root uucp 188, 0 Dec 10 10:12 /dev/ttyUSB0 見るとrootとuucpグループに操作の権限がある. 自分をuucpグループに追加すればPermissionうんぬん言われないはず. sudo gpasswd -a $(whoami) uucp これでログイン中のユーザー(自分)をuucpグループに追加できた. ユーザー情報を更新するには再ログインする必要がある. 一旦ログアウトしよう. logout そして,SSHで再接続(再ログイン)する. その後,再び python xbee_on.py T でエラー無く動くはず.
部屋のライトが点灯したら,ライトの状態を取得してみよう. python send_ascii.py S ON状態なので1が返ってくるはずだ. 次のコマンドでライトを消灯できる. python send_ascii.py F 再び文字Sを送って状態を取得してみよう. 今度はOFFなので0が返ってくるはず. 何も表示されなければ正常だ. ここまで上手く行っていれば,後はHomebridgeに追加するだけで目標達成だ.

Homebridgeへの追加

今回はコマンドでON/OFF制御できるHomebridgeのプラグイン"homebridge-cmdswitch2"をインストールした(この記事を参考: npmでパッケージをインストールする). npm install -g homebridge-cmdswitch2 さて,Homebridgeのconfigを編集しよう. nano /var/homebridge/config.json 次の例を参考にファイルを編集しよう.

/var/homebridge/config.json

{
    "bridge": {
        "name": "Homebridge",
        "username": "CC:22:3D:E3:CE:30",
        "port": 51826,
        "pin": "031-45-154"
    },

    "description": "This is an example configuration file with one fake accessor$

    "platforms": [{
        "platform": "cmdSwitch2",
        "name": "CMD Switch",
        "switches": [{
            "name": "Light",
            "on_cmd": "python ~/send_ascii.py T",
            "off_cmd": "python ~/send_ascii.py F",
            "status_cmd": "python ~/send_ascii.py S"
        }]
    }]
}
homebridge-httpと違い,accessoriesではなくplatforms内に設定を書く点に注意. そしてこのプラグインはstatus_cmdを実行した時に何か出力があればON,何も出力が無ければOFFだと認識するようになっている. send_ascii.pyも1が返ってきた時にのみ出力するようになっているので,都合が良い. configを編集し終えたらCtrl+x,y,Enterで保存. Homebridgeを再起動させよう. sudo systemctl restart homebridge 試しにHomeアプリを開きライトが正常に動作しているか確認,ちゃんとON/OFF操作も出来るか確かめてみよう. HTTPの時より反応速度はやや遅い印象だが,置き場所のシビアな赤外線LEDを載せたArduinoを比較的自由な位置に置くことが出来るようになったので利便性が増した. 目標達成だ!
8872662773723104100 http://www.storange.jp/2017/02/homebridgearduinoxbee.html http://www.storange.jp/2017/02/homebridgearduinoxbee.html HomebridgeとArduinoをXBeeでつなぐ 2017-02-28T22:25:00+09:00 http://www.storange.jp/2017/02/homebridgearduinoxbee.html Hideyuki Tabata 200 200 72 72