Swift2でビーコン受信

  • 37
    Like
  • 0
    Comment
More than 1 year has passed since last update.

Swift2でビーコン受信処理を書いてみます。
下のものをベースにSwift2に書き換えています。

参考:
https://sites.google.com/a/gclue.jp/swift-docs/ni-yinki100-ios/06-corelocation/004-ibeaconno-jian-chu

iBeacon受信

iOS側の受信処理は以下のようになります。

Beacon.swift
import Foundation
import CoreLocation

class Beacon: NSObject, CLLocationManagerDelegate {

    var myLocationManager:CLLocationManager!
    var myBeaconRegion:CLBeaconRegion!
    var myIds: NSMutableArray!
    var myUuids: NSMutableArray!
    var beaconRegionArray = [CLBeaconRegion]()

    static let shard = Beacon()

    let UUIDList = [
        "e2c56db5-dffb-48d2-b060-d0f5a71096e0"
    ]

    override init() {

        super.init()

        print("init")

        // ロケーションマネージャの作成.
        myLocationManager = CLLocationManager()

        // デリゲートを自身に設定.
        myLocationManager.delegate = self

        // セキュリティ認証のステータスを取得
        let status = CLLocationManager.authorizationStatus()

        // 取得精度の設定.
        myLocationManager.desiredAccuracy = kCLLocationAccuracyBest

        // 取得頻度の設定.(1mごとに位置情報取得)
        myLocationManager.distanceFilter = 1


        // まだ認証が得られていない場合は、認証ダイアログを表示
        if status == CLAuthorizationStatus.NotDetermined {
            print("didChangeAuthorizationStatus:\(status)");
            // まだ承認が得られていない場合は、認証ダイアログを表示
            myLocationManager.requestWhenInUseAuthorization()
        }

        for var i = 0; i < UUIDList.count; i++ {

            // BeaconのUUIDを設定.
            let uuid:NSUUID! = NSUUID(UUIDString:UUIDList[i].lowercaseString)

            // BeaconのIfentifierを設定.
            let identifierStr:String = "identifier" + i.description

            // リージョンを作成.
            myBeaconRegion = CLBeaconRegion(proximityUUID: uuid,  identifier: identifierStr)
            // majorId=0,minorId=0のビーコンのみ受信
            //myBeaconRegion = CLBeaconRegion(proximityUUID: uuid, major: CLBeaconMajorValue(0), minor: CLBeaconMinorValue(0), identifier: identifierStr)

            // ディスプレイがOffでもイベントが通知されるように設定(trueにするとディスプレイがOnの時だけ反応).
            myBeaconRegion.notifyEntryStateOnDisplay = false

            // 入域通知の設定.
            myBeaconRegion.notifyOnEntry = true

            // 退域通知の設定.
            myBeaconRegion.notifyOnExit = true

            beaconRegionArray.append(myBeaconRegion)

            myLocationManager.startMonitoringForRegion(myBeaconRegion)
        }

        // 配列をリセット
        myIds = NSMutableArray()
        myUuids = NSMutableArray()
    }

    /*
    (Delegate) 認証のステータスがかわったら呼び出される.
    */
    func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {

        print("didChangeAuthorizationStatus");

        // 認証のステータスをログで表示
        var statusStr = "";
        switch (status) {
        case .NotDetermined:
            statusStr = "NotDetermined"
            break
        case .Restricted:
            statusStr = "Restricted"
            break
        case .Denied:
            statusStr = "Denied"
            break
        case .AuthorizedAlways:
            statusStr = "AuthorizedAlways"
        case .AuthorizedWhenInUse:
            statusStr = "AuthorizedWhenInUse"
            for region in beaconRegionArray {
                manager.startMonitoringForRegion(region)
                manager.startRangingBeaconsInRegion(region)
            }
        }
        print(" CLAuthorizationStatus: \(statusStr)")

    }

    /*
    STEP2(Delegate): LocationManagerがモニタリングを開始したというイベントを受け取る.
    */
    func locationManager(manager: CLLocationManager, didStartMonitoringForRegion region: CLRegion) {

        print("didStartMonitoringForRegion");

        // STEP3: この時点でビーコンがすでにRegion内に入っている可能性があるので、その問い合わせを行う
        // (Delegate didDetermineStateが呼ばれる: STEP4)
        manager.requestStateForRegion(region);
    }

    /*
    STEP4(Delegate): 現在リージョン内にいるかどうかの通知を受け取る.
    */
    func locationManager(manager: CLLocationManager, didDetermineState state: CLRegionState, forRegion region: CLRegion) {

        print("locationManager: didDetermineState \(state)")

        switch (state) {

        case .Inside: // リージョン内にいる
            print("CLRegionStateInside:");

            // STEP5: すでに入っている場合は、そのままRangingをスタートさせる
            // (Delegate didRangeBeacons: STEP6)
            manager.startRangingBeaconsInRegion(region as! CLBeaconRegion)
            break;

        case .Outside:
            print("CLRegionStateOutside:")
            // 外にいる、またはUknownの場合はdidEnterRegionが適切な範囲内に入った時に呼ばれるため処理なし。
            break;

        case .Unknown:
            print("CLRegionStateUnknown:")
            // 外にいる、またはUknownの場合はdidEnterRegionが適切な範囲内に入った時に呼ばれるため処理なし。
        default:

            break;

        }
    }

    /*
    STEP6(Delegate): ビーコンがリージョン内に入り、その中のビーコンをNSArrayで渡される.
    */
    func locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion)
    {

        // 配列をリセット
        myIds = NSMutableArray()
        myUuids = NSMutableArray()

        // 範囲内で検知されたビーコンはこのbeaconsにCLBeaconオブジェクトとして格納される
        // rangingが開始されると1秒毎に呼ばれるため、beaconがある場合のみ処理をするようにすること.
        if(beacons.count > 0){

            // STEP7: 発見したBeaconの数だけLoopをまわす
            for var i = 0; i < beacons.count; i++ {

                let beacon = beacons[i] 

                let beaconUUID = beacon.proximityUUID;
                let minorID = beacon.minor;
                let majorID = beacon.major;
                let rssi = beacon.rssi;

                print("UUID: \(beaconUUID.UUIDString)");
                print("minorID: \(minorID)");
                print("majorID: \(majorID)");
                print("RSSI: \(rssi)");

                var proximity = ""

                switch (beacon.proximity) {

                case CLProximity.Unknown :
                    print("Proximity: Unknown");
                    proximity = "Unknown"
                    break

                case CLProximity.Far:
                    print("Proximity: Far");
                    proximity = "Far"
                    break

                case CLProximity.Near:
                    print("Proximity: Near");
                    proximity = "Near"
                    break

                case CLProximity.Immediate:
                    print("Proximity: Immediate");
                    proximity = "Immediate"
                    break
                }

                let myBeaconId = "MajorId: \(majorID) MinorId: \(minorID)  UUID:\(beaconUUID) Proximity:\(proximity)"
                myIds.addObject(myBeaconId)
                myUuids.addObject(beaconUUID.UUIDString)

                // 通知してみる
                NSNotificationCenter.defaultCenter().postNotificationName("beaconReceive", object: self, userInfo: ["MajorID":majorID,"MinorID":minorID,"Proximity:":proximity,"rssi": rssi])
            }
        }
    }

    /*
    (Delegate) リージョン内に入ったというイベントを受け取る.
    */
    func locationManager(manager: CLLocationManager, didEnterRegion region: CLRegion) {
        print("didEnterRegion");

        // Rangingを始める
        manager.startRangingBeaconsInRegion(region as! CLBeaconRegion)

    }

    /*
    (Delegate) リージョンから出たというイベントを受け取る.
    */
    func locationManager(manager: CLLocationManager, didExitRegion region: CLRegion) {
        NSLog("didExitRegion");

        // Rangingを停止する
        manager.stopRangingBeaconsInRegion(region as! CLBeaconRegion)
    }

}

注意

Beaconを使う際にはユーザの許可が必要です。
Info.plistにNSLocationWhenInUseUsageDescriptionのキーが必要です。
Valueは適当な値で構いません
スクリーンショット 2016-02-14 16.10.19.png

さらにソースコード上の下記メソッドでユーザの許可を促します。

myLocationManager.requestWhenInUseAuthorization()

端末側では受信するためには位置情報を許可している必要があります。
image1.PNG

バックグラウンドで受信するには?

バックグラウンドで受信するためには
Background ModesのLocation Updatesにチェックをつける必要があります。
スクリーンショット 2016-02-14 16.08.15.png

またInfo.plistにNSLocationWhenInUseUsageDescriptionキーの代わりに
NSLocationAlwaysUsageDescriptionキーが必要です。
スクリーンショット 2016-02-14 16.33.18.png

ソースコード上ではrequestWhenInUseAuthorizationメソッドの代わりに
requestAlwaysAuthorizationメソッドでユーザの許可を促します。

//myLocationManager.requestWhenInUseAuthorization()
myLocationManager.requestAlwaysAuthorization()

iBeacon発信

発信側はNodeJSでBeacon受発信のモジュールがあるので
手っ取り早く検証するにはそれをつかいます。
(もちろん本番で使う場合は専用のBeacon発信デバイスを使うことになるでしょう)

NodeJSのインストール手順に関しては省略します。
bleaconモジュールをインストールします。

beacon.sh
npm init
npm install --save bleacon

以下が送信用のコードとなります。
uuidはハイフン無し、小文字であることに注意です。

sender.js
Bleacon = require('bleacon');

var uuid = 'e2c56db5dffb48d2b060d0f5a71096e0'
var major = 0;
var minor = 0;
var measuredPower = -59;

Bleacon.startAdvertising(uuid, major, minor, measuredPower);

下記コマンドで起動します。

node sender.js

iBeaconはBluetoothの規格の一種なので
Macのシステム環境設定からBluetoothをオンにしていないと発信できないので注意です。
スクリーンショット 2016-02-14 17.09.35.png

uuidの生成

ちなみにMacであれば下記コマンドでuuidを新規作成することも可能です。

uuidgen

スクリーンショット 2016-02-14 17.06.03.png

iBeaconの挙動について

Beacon受信の挙動についてなのですが
下記のコールバック関数を確認する必要があります。
didEnterRegion:ビーコンの領域に入った(入るタイミングで1度のみ呼ばれる)
didRangeBeacons:ビーコンの領域内にいる(定期的に呼ばれる)
didExitRegion:ビーコンの領域から出た(出るタイミングで1度のみ呼ばれる、出るタイミングは不安定で即時に呼ばれない)

また、didRangeBeacons関数では
ビーコンからの距離を判定することができます
proximity:Unknown,Far,Near,Immediateの4段階
rssi:電波強度

ちなみにバックグラウンドで受信できるのは
didEnterRegionとdidExitRegionのみです。
厄介なのはdidExitRegionの判定がないと再度didEnterRegionが呼ばれないことです。
(この領域外判定に関してはOS側でされるものなので、アプリを終了して即時に再起動してもBeacon領域内として扱われます。)
しかも、didExitRegionは即時に呼ばれるわけではなく、呼ばれるタイミングにラグがあり不安定です
(Beacon発信機の電池を抜いたりBeacon領域を抜けても即時には呼ばれないので注意、これはiOSの仕様のようです)
代替策になってしまうのですが、didRangeBeaconsやらdidEnterRegionなどで受信チェックし、
タイマーなどで受信後、一定時間経過したら受信していない扱いにするほうが無難でしょう。