【Rover C ProをObnizで動かす】遠隔操作できる自宅監視カメラ(BLE)
- 2021.05.06
- IoT

前回の続きです.
前回はRover Cをラズパイから操作しました.
今回はそのラズパイをObnizからBLE通信で接続し、RoverCを操作できるようにします.
BLEについて
「BluetoothLowEnergy」の略.
色々妥協して消費電力を節約することに特化した接続方式です.
制御する側を「セントラル」、そいて制御され情報などを発信する側を「ペリフェラル」と呼びます.

BLE接続手順
・アドバタイズ(Advertise)
私を見つけて欲しいとペリフェラル側が電波(アドバタイズパケット)を発する行為
・スキャン(Scan)
セントラルが周囲のペリフェラル(のアドバタイズパケット)を探す
・コネクト(Connect)
セントラル側から要求し、ペリフェラル側から応答を返すことで接続が完了する.
・ディスコネクト(Disconnect)
切断.
基本的には上記流れをアルゴリズムとしてプログラムに落とし込めれば接続可能です.
より詳しい部分は様々なサイトで公開されているため割愛致します.
個人的に導入として分かりやすかったサイトを以下に載せておきます.
https://jellyware.jp/kuragemd/bluejelly/ble_guide.html
ペリフェラル(Raspberry Pi)のプログラム
以下にBLE通信(ペリフェラル)の雛形を載せておきます.
onWriteRequest_cmd関数に受け取ったデータを解析し、デバイスに書き込む処理を書くことで
今回に限らず、さまざまな操作を行えるはずです.
・main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
import time from pybleno import * import wiringpi as pi import peripheral # BLE関連 SERVICE_UUID = '00001623-1212-EFDE-1623-785FEABCD123' READ_CHARACTERISTIC_UUID = '00001626-1212-EFDE-1623-785FEABCD123' COMMAND_CHARACTERISTIC_UUID = '00001625-1212-EFDE-1623-785FEABCD123' ### BLEの各状態時の処理をここに記載 ### def onStateChange(state): ### アドバタイジングスタート前準備 pyblenoのメソッドをオーバーライドする :param state: :return: ### print('on -> stateChange: ' + state) if (state == 'poweredOn'): bleno.startAdvertising(‘test’, [SERVICE_UUID]) else: bleno.stopAdvertising() def onAdvertisingStart(error): ### アドバタイジングスタート pyblenoのメソッドをオーバーライドする :param error: :return: ### print('on -> advertisingStart1: ' + ('error ' + error if error else 'success')) if not error: print('onAdvertisingStart') # 作成したクラスをサービスUUIDとしてまとめてセットする bleno.setServices([ BlenoPrimaryService({ 'uuid': SERVICE_UUID, 'characteristics': [ tomosoftCharacteristic_cmd, tomosoftCharacteristic_read, ] }) ]) def onWriteRequest_cmd(data, offset, withoutResponse, callback): ### COMMAND_CHARACTERISTIC_UUIDにデータが書き込まれると呼び出される :param data: :param offset: :param withoutResponse: :param callback: :return: ### print('data catch:' + str(data)) tomosoftCharacteristic_cmd._value = data if tomosoftCharacteristic_read._updateValueCallback: print('Sending notification with value-cmd : ' + str(data)) # notifyに書き込まれた値を知らせる notificationBytes = str(tomosoftCharacteristic_cmd._value).encode() tomosoftCharacteristic_read._updateValueCallback(notificationBytes) # 取得したdataを使った処理等を書く if __name__ == '__main__': bleno = Bleno() # stateChangeをオーバーライド bleno.on('stateChange', onStateChange) # advertisingStartをオーバーライド bleno.on('advertisingStart', onAdvertisingStart) tomosoftCharacteristic_read = peripheral.ReadCharacteristic(READ_CHARACTERISTIC_UUID) tomosoftCharacteristic_cmd = peripheral.CommandCharacteristic(COMMAND_CHARACTERISTIC_UUID) # writeRequestをオーバーライド tomosoftCharacteristic_cmd.on('writeRequest', onWriteRequest_cmd) bleno.start() while True: time.sleep(1) |
・peripheral.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
import time from pybleno import * import wiringpi as pi class ReadCharacteristic(Characteristic): ### 読み込み 値の変更をnotifyで知らせる ### def __init__(self, READ_CHARACTERISTIC_UUID): ### 初期化時にキャラクタリスティック(read)を設定 ### Characteristic.__init__(self, { 'uuid': READ_CHARACTERISTIC_UUID, 'properties': ['read', 'notify'], 'value': None }) self._value = 0 self._updateValueCallback = None def onSubscribe(self, maxValueSize, updateValueCallback): print('ApproachCharacteristic - onSubscribe') self._updateValueCallback = updateValueCallback def onUnsubscribe(self): print('ApproachCharacteristic - onUnsubscribe') self._updateValueCallback = None class SettingCharacteristic(Characteristic): def __init__(self, SETTING_CHARACTERISTIC_UUID): ### 初期化時にキャラクタリスティック(write)を設定 ### Characteristic.__init__(self, { 'uuid': SETTING_CHARACTERISTIC_UUID, 'properties': ['write'], 'value': None }) #オーバーライド用 def onWriteRequest(self, data, offset, withoutResponse, callback): callback(self.RESULT_SUCCESS) class CommandCharacteristic(Characteristic): def __init__(self, COMMAND_CHARACTERISTIC_UUID): Characteristic.__init__(self, { 'uuid': COMMAND_CHARACTERISTIC_UUID, 'properties': ['write'], 'value': None }) #オーバーライド用 def onWriteRequest(self, data, offset, withoutResponse, callback): callback(self.RESULT_SUCCESS) |
セントラル(Obniz)のプログラム
Obniz公式サイトのドキュメントを参考に作成
https://obniz.com/ja/doc/reference/common/ble/
各種IDに合わせて専用のボタンなどを作成しておいてください.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
/**----------------------------------------------------------------------------- * BLE側処理 */ let SERVICE_UUID = "00001623-1212-EFDE-1623-785FEABCD123"; let SETTING_CHARACTERISTIC_UUID = "00001624-1212-EFDE-1623-785FEABCD123"; //操作用キャラクタリスティック let COMMAND_CHARACTERISTIC_UUID = "00001625-1212-EFDE-1623-785FEABCD123"; let READ_CHARACTERISTIC_UUID = "00001626-1212-EFDE-1623-785FEABCD123"; // デバイス名 document.getElementById('device_name').value = “test”; // 対象ペリフェラルを保持 var mobile_peripheral; obniz.onconnect = async function () { /**----------------------------------------------------------------------------- * BLE側処理 */ //スキャン開始 document.getElementById('btn_scan').addEventListener('click', async function() { console.log("scan"); await obniz.ble.initWait(); var target = { localName: document.getElementById('device_name').value, // uuids: document.getElementById('device_name').value }; console.log(target); //指定のデバイスでスキャン開始 obniz.ble.scan.start(target); displayCustom("scan_start"); //指定のデバイスが見つかったらスキャン終了 obniz.ble.scan.onfind = async function (peripheral) { mobile_peripheral = peripheral; console.log("found"); console.log(peripheral); obniz.ble.scan.end(); //エラーをキャッチ mobile_peripheral.onerror = function (err) { console.log("error : " + err.message); }; }; //スキャン終了時に呼び出される obniz.ble.scan.onfinish = function (peripheral) { console.log("scan timeout!"); displayCustom("scan_finish"); }; }); //接続 document.getElementById("btn_connect").addEventListener('click', async function() { try { //接続できたら mobile_peripheral.onconnect = function () { console.log("connected"); displayCustom(document.getElementById('device_name').value + "connected"); console.log(document.getElementById('device_name').value); console.log("number of services=" + mobile_peripheral.services.length); var count = 0; for (var service of mobile_peripheral.services) { console.log("service.uuid[" + count + "] = " + service.uuid); count++; } //サービスを取得 var service = mobile_peripheral.getService(SERVICE_UUID); if (!service) { console.log("service not found"); console.log("error = " + service.uuid); return; } console.log(service.uuid); count = 0; for (var characteristic of service.characteristics) { console.log( "characteristic.uuid[" + count + "] = " + characteristic.uuid ); count++; } // 変更があれば通知を受け取る var notify = service.getCharacteristic(READ_CHARACTERISTIC_UUID); notify.registerNotifyWait((data) => { console.log("notify with data " + data.join(",")); }); //切断要求 //peripheral.disconnectWait(); }; } catch (e) { console.error(e); } mobile_peripheral.ondisconnect = function () { console.log("closed"); }; mobile_peripheral.connect(); }); //切断 document.getElementById('btn_disconnect').addEventListener('click', async function() { //サービスを取得 var service = mobile_peripheral.getService(SERVICE_UUID); mobile_peripheral.disconnect(); mobile_peripheral.ondisconnect = function(){ console.log("closed"); } }); }; |
結果
ラズパイ側でmain.pyを実行.
Obnizもプログラムを実行することで簡単にラズパイへ接続することができた.

データの書き込み
Obnizドキュメントを参考:https://obniz.com/ja/doc/reference/common/ble/central-read-write
バイナリデータを文字列にして送るのが簡単そうです.
obnizコード内のサービスが取得できる位置に以下の処理を加えます.
・例:
1 2 3 4 5 6 |
//データ配列を作成 var dataArray = [0x08, 0x00, 0x81, 0x01, 0x11, 0x51, 0x00, 0x32]; //キャラクタリスティクスを取得 var characteristic = service.getCharacteristic(SETTING_CHARACTERISTIC_UUID); //書き込み characteristic.writeWait(dataArray); |
また、ラズパイ側main.pyのonWriteRequest_cmdコールバックに、取得したデータをコンソール表示する処理を追加します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def onWriteRequest_cmd(data, offset, withoutResponse, callback): """ COMMAND_CHARACTERISTIC_UUIDにデータが書き込まれると呼び出される :param data: :param offset: :param withoutResponse: :param callback: :return: """ print('data catch:' + str(data)) tomosoftCharacteristic_cmd._value = data if tomosoftCharacteristic_read._updateValueCallback: print('Sending notification with value-cmd : ' + str(data)) # notifyに書き込まれた値を知らせる notificationBytes = str(tomosoftCharacteristic_cmd._value).encode() tomosoftCharacteristic_read._updateValueCallback(notificationBytes) # ここを追加 print('data catch:' + str([hex(i) for i in data])) |
実行結果が以下.
データの受信に成功しています.

終わりに
今回はObniz、Raspberry PiによるBLE通信についてまとめました.
これで実質ObnizからRover Cに向けてデータを書き込めるようになったので、次はObniz側の入力つまりはコントローラの作成に関してまとめようと思います.
-
前の記事
Rover C ProをRaspberryPiとObnizで動かしてみる(その1) 2021.05.05
-
次の記事
【Obnizクラウド】レポジトリを使って外部ファイル(css,jsなど)を読み込む 2021.05.10