前回の記事で、Raspberry Pi上でRuby(irb)からD-Busを使ってBLEデバイスに接続し、値を読み取るというところまでやりましたが、今回はPeripheralからのNotificationを受け取って値の変化を検知するところまでを実装してみました。前回はirbから試してみましたが、今回はスクリプトにまとめてあります。また、対象のBLEデバイスは前回同様にLightBlueで擬似Peripheralデバイスを作成して使用しています。
スクリプト全体
まずはスクリプト全体を掲載しておきます。
require 'bundler/setup' require 'dbus' SERVICE_PATH = '/org/bluez' ADAPTER = 'hci0' DEVICE_IF = 'org.bluez.Device1' SERVICE_IF = 'org.bluez.GattService1' CHARACTERISTIC_IF = 'org.bluez.GattCharacteristic1' PROPERTIES_IF = 'org.freedesktop.DBus.Properties' HEARTRATE_SERVICE_UUID = '0000180d-0000-1000-8000-00805f9b34fb' HEARTRATE_UUID = '00002a37-0000-1000-8000-00805f9b34fb' log = Logger.new('ble_notification.log') bus = DBus::SystemBus.instance bluez = bus.service('org.bluez') adapter = bluez.object("#{SERVICE_PATH}/#{ADAPTER}") adapter.introspect puts 'Discoverying Nodes...' adapter.StartDiscovery sleep(10) nodes = adapter.subnodes connected_device = nil begin nodes.each do |node| device = bluez.object("#{SERVICE_PATH}/#{ADAPTER}/#{node}") device.introspect properties = device.GetAll(DEVICE_IF)[0] name = properties['Name'] address = properties['Address'] rssi = properties['RSSI'] next if name.nil? || rssi.nil? next unless name.downcase.include?('heart rate') puts "Connecting to the device: #{address} #{name} RSSI:#{properties['RSSI']}" begin device.Connect puts 'Connected. Resolving Services...' device.introspect while !properties['ServicesResolved'] do sleep(0.5) device.introspect properties = device.GetAll(DEVICE_IF)[0] end puts 'Resolved.' connected_device = device break rescue => e puts e end end raise 'No device connected.' if connected_device.nil? heartrate_service = nil nodes = connected_device.subnodes nodes.each do |node| service = bluez.object("#{connected_device.path}/#{node}") service.introspect properties = service.GetAll(SERVICE_IF)[0] uuid = properties['UUID'] next unless uuid == HEARTRATE_SERVICE_UUID heartrate_service = service end nodes = heartrate_service.subnodes nodes.each do |node| characteristic = bluez.object("#{heartrate_service.path}/#{node}") characteristic.introspect properties = characteristic.GetAll(CHARACTERISTIC_IF)[0] uuid = properties['UUID'] next unless uuid == HEARTRATE_UUID characteristic.StartNotify characteristic.default_iface = PROPERTIES_IF characteristic.on_signal('PropertiesChanged') do |_, v| log.info("#{heartrate_service.path.split('/')[4]} #{v}") end end main = DBus::Main.new main << bus puts 'Monitoring Heart Rate...' main.run rescue Interrupt => e puts e puts "Interrupted." rescue => e puts e ensure connected_device.Disconnect unless connected_device.nil? adapter.StopDiscovery end
それでは各パートごとに説明していきます。
Peripheralへの接続
まずはBluetoothアダプタのDiscoveryModeをONにし、BLEデバイスを検索します。今回はとりあえず10秒間は検索のために待機しています。
bus = DBus::SystemBus.instance bluez = bus.service('org.bluez') adapter = bluez.object("#{SERVICE_PATH}/#{ADAPTER}") adapter.introspect puts 'Discoverying Nodes...' adapter.StartDiscovery sleep(10) nodes = adapter.subnodes
そして見つかった各デバイスのデバイス名を確認して、対象のデバイスであれば接続します。そして配下のGATTサービスが検知されて ServicesResolved
のプロパティがtrueになるのを待ってから次の処理に進みます。
nodes.each do |node| device = bluez.object("#{SERVICE_PATH}/#{ADAPTER}/#{node}") device.introspect properties = device.GetAll(DEVICE_IF)[0] name = properties['Name'] address = properties['Address'] rssi = properties['RSSI'] next if name.nil? || rssi.nil? next unless name.downcase.include?('heart rate') puts "Connecting to the device: #{address} #{name} RSSI:#{properties['RSSI']}" begin device.Connect puts 'Connected. Resolving Services...' device.introspect while !properties['ServicesResolved'] do sleep(0.5) device.introspect properties = device.GetAll(DEVICE_IF)[0] end puts 'Resolved.' connected_device = device break rescue => e puts e end end
GATTサービスの取得
次にデバイス配下の各サービスのUUIDを確認し、今回対象とするUUIDを持つサービスを取得します。
nodes = connected_device.subnodes nodes.each do |node| service = bluez.object("#{connected_device.path}/#{node}") service.introspect properties = service.GetAll(SERVICE_IF)[0] uuid = properties['UUID'] next unless uuid == HEARTRATE_SERVICE_UUID heartrate_service = service end
GATTキャラクタリスティックの取得とシグナル検知時のコールバック設定
サービス配下の各キャラクタリスティックのUUIDを確認し、今回対象とするUUIDを持つキャラクタリスティックを特定したら、Peripheralからのシグナルを検知して処理を行うためのコールバックの設定を行います。
nodes = heartrate_service.subnodes nodes.each do |node| characteristic = bluez.object("#{heartrate_service.path}/#{node}") characteristic.introspect properties = characteristic.GetAll(CHARACTERISTIC_IF)[0] uuid = properties['UUID'] next unless uuid == HEARTRATE_UUID characteristic.StartNotify characteristic.default_iface = PROPERTIES_IF characteristic.on_signal('PropertiesChanged') do |_, v| log.info("#{heartrate_service.path.split('/')[4]} #{v}") end end
上記のコールバック設定部分については、まず StartNotify
メソッドでPeripheralからのシグナルの送信を開始しています。
characteristic.StartNotify
そして on_signal
メソッドでシグナル検知時のコールバックを指定するのですが、検知対象のインタフェースはデフォルトインタフェースになるので、先に default_iface
メソッドでデフォルトインタフェースを指定しておきます。値の変更は D-Bus の org.freedesktop.DBus.Properties
インタフェースで検知します。
characteristic.default_iface = PROPERTIES_IF
シグナルとしては PropertiesChanged
シグナルになりますので、 on_signal
メソッドでシグナルを指定してコールバックを設定します。今回は検知時の処理としてはデバイスのアドレスと値をログに出力しています。
characteristic.on_signal('PropertiesChanged') do |_, v| log.info("#{heartrate_service.path.split('/')[4]} #{v}") end
非同期処理の開始
そして最後にイベントループでの非同期処理を開始します。
main = DBus::Main.new main << bus main.run
これでPeripheral側からシグナルが送信されると、下記のようにログに出力されるようになります。
I, [2017-06-14T18:40:31.151687 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 60]} I, [2017-06-14T18:40:32.163818 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 60]} I, [2017-06-14T18:40:33.110175 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 60]} I, [2017-06-14T18:40:34.121697 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 80]} I, [2017-06-14T18:40:35.134417 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 80]} I, [2017-06-14T18:40:36.146261 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 80]} I, [2017-06-14T18:40:37.159118 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 100]} I, [2017-06-14T18:40:38.103964 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 100]} I, [2017-06-14T18:40:39.184016 #3589] INFO -- : dev_7C_12_A1_23_8C_21 {"Value"=>[0, 100]}