SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Webアプリケーション開発技術の新潮流スタディーズ

IoT技術の代表「BLE:Bluetooth Low Energy」の動作原理を理解してみよう【後編】

Webアプリケーション開発技術の新潮流スタディーズ 第6回

Application層

最後に、ここまでで説明した内容をふまえてアプリケーション開発者がApplication層を実装する必要があります。 スマートフォンアプリを作る場合、各OSごとに用意されているBLE開発のためのフレームワークを利用することになります。2016年5月現在、iOS/Androidではセントラルとペリフェラル、Windows 8ではセントラル側の実装が可能のようです。

ここからはプラットフォームごとに内容が異なるため、本稿では詳細について説明しませんが、ここまでで開設したBLEの概念が理解できていれば、実装はさほど難しくはないはずです。雰囲気をつかむために、iOS用のフレームワーク「Code Bluetooth」を使ったSwiftのコードを見てみましょう。ここではiPhoneアプリ(セントラル)が周辺機器(ペリフェラル)とやりとりすることを想定しています。

iOS用のフレームワーク「Code Bluetooth」を使ったSwiftのコードワイド表示
import UIKit
import Foundation
import CoreBluetooth

class ViewController: UIViewController, CBCentralManagerDelegate,
CBPeripheralDelegate {

    var manager : CBCentralManager!
    var peripherals : [String: CBPeripheral] = [:]

    override func viewDidLoad() {
        super.viewDidLoad()
        self.manager = CBCentralManager(delegate: self, queue: nil)
    }

    // Centralの状態が変わったときに呼ばれる
    func centralManagerDidUpdateState(central: CBCentralManager) {
        if central.state == CBCentralManagerState.PoweredOn {
            // Peripheralのスキャンを開始する(全てのPeripheralを対象にする)
            print("Start scanning peripherals...")
            self.manager.scanForPeripheralsWithServices(nil, options: nil)
        } else if central.state == CBCentralManagerState.Unsupported {
            print("Unsupported")
        }
    }

    // CentralがPeripheralを発見したときに呼ばれる
    func centralManager(central: CBCentralManager, didDiscoverPeripheral peripheral: CBPeripheral,
        advertisementData: [String : AnyObject], RSSI: NSNumber) {
            print("found peripheral[" + peripheral.identifier.UUIDString + "]")
            peripheral.delegate = self
            // Peripheralに接続する
            manager.connectPeripheral(peripheral, options: nil)
            peripherals[peripheral.identifier.UUIDString] = peripheral
    }

    // CentralがPeripheralと接続したときに呼ばれる
    func centralManager(central: CBCentralManager, didConnectPeripheral peripheral: CBPeripheral) {
        print("connected to peripheral[" + peripheral.identifier.UUIDString + "]")
        // PeripheralのServiceを検索する
        peripheral.discoverServices(nil)
    }

    // CentralがPeripheralと切断したときに呼ばれる
    func centralManager(central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: NSError?) {
        print("disconnected from peripheral[" + peripheral.identifier.UUIDString + "]")
        self.peripherals[peripheral.identifier.UUIDString] = nil
    }

    // PeripheralのServiceを発見したときに呼ばれる
    func peripheral(peripheral: CBPeripheral, didDiscoverServices error: NSError?) {
        peripheral.services?.forEach { service in
            // Serviceに含まれるCharacteristicを検索する
            print("found service[" + service.UUID.UUIDString + "] of peripheral[" + peripheral.identifier.UUIDString + "]")
            peripheral.discoverCharacteristics(nil, forService: service)
        }
    }

    // Serviceに含まれるCharacteristicを発見したときに呼ばれる
    func peripheral(peripheral: CBPeripheral, didDiscoverCharacteristicsForService service: CBService, error: NSError?) {
        service.characteristics?.forEach { characteristic in
            print("found characteristic[" + characteristic.UUID.UUIDString + "] of service[" + service.UUID.UUIDString + "]")
            if (!characteristic.properties.intersect(CBCharacteristicProperties.Read).isEmpty) {
                // Characteristicの値を取得する
                peripheral.readValueForCharacteristic(characteristic)
            }
        }
    }

    // Characteristicの値を取得したときに呼ばれる
    func peripheral(peripheral: CBPeripheral, didUpdateValueForCharacteristic characteristic: CBCharacteristic, error: NSError?) {
        if let value = characteristic.value {
            let str : String = NSString(data : value, encoding : NSUTF8StringEncoding) as! String
            print("found value[" + str + "] of characteristic[" + characteristic.UUID.UUIDString + "]")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

非同期処理が多いためとっつきにくそうですが、慣れてしまえばそれほど大したことはありません。

電池寿命を考慮する

長期運用を考えると電池寿命を考慮した設計が必須になります。ここで、改めて電池寿命を左右するパラメータを洗い出してみましょう。

  • advInterval(Advertisingの周期)
  • scanInterval(Scanningの周期)
  • connInterval(接続確立後のポーリングの周期)
  • connSlaveLatency(Peripheral側にデータがない場合に受信を無視できる回数)
  • Push通知するか否か

実は、これらの値はプラットフォームによって設定できる範囲はまちまちです。例えば、iOSではconnIntervalは20ms以上などとガイドラインに明記されています。気になるところは、あらかじめ確認しておくとよいでしょう。

上位フレームワークの利用を検討する

アプリケーションによっては、BLEの全機能を利用するのではなくビーコン信号を受信する程度で事足りる場合があります。 スマートデバイスの位置測定やプッシュ型広告がその一例です。 その場合、以下のような上位フレームワーク(プロトコル)を利用することで、開発を迅速に進められます。ぜひ検討してみてください。

iBeacon
Appleの開発した技術で、iOS上でビーコン信号を送受信し内容とおおまかな距離を知ることができます。独自のプロトコルでラップしていますが、他のデバイスでも解析することは可能なようです。
Eddystone
Googleの開発した技術で、同じくビーコンを扱います。アドバタイズにURLを含めることができるという特徴があります。こちらはApache 2.0ライセンスでGitHubにソースが公開されています。

このような「ビーコン」の内部実装は、BLEの機能のうちAdvertisingパケットのみを使ったブロードキャスト通信となっています。

まとめ

以上、少々長めではありましたがBLEの仕様を一通り眺めました。説明の都合上、ここでは紹介しきれなかった概念もいくつかあるので必要に応じてキャッチアップしてください。

今回はBluetoothに限定した話でしたが、代替の通信手段としてはWi-Fiなどもありますし、IoTはハードウェアやクラウドなど様々な技術やサービスの組み合わせによって実現可能です。 柔軟で素晴らしいアイデアを生み出すきっかけのひとつとして、本記事がお役に立てば幸いです。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
Webアプリケーション開発技術の新潮流スタディーズ連載記事一覧

もっと読む

この記事の著者

鳥居 陽介(株式会社ワークスアプリケーションズ)(トリイ ヨウスケ)

株式会社ワークスアプリケーションズ所属。イケてるアプリケーションを死ぬほど楽に作るために研究を続ける日々。社内での立ち位置は「フロントエンドのナウい人」。最近エバンジェリストという肩書きが付いた。趣味は作曲とスノーボード。 Blog: http://jinjor-labo.hatenablog.com/ ...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/9492 2016/07/25 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー