【GMOリサーチではエンジニアを募集中です】採用情報はこちら!

ビーコンを使ったiOSアプリを作ってみる(1)

ビーコンを使ったiOSアプリを作ってみる(1)

こんにちは!システム部の田代と申します。

今年の4月、コロナ禍の中で入社して以降ほぼリモートで仕事をしています。

メンテナンスのために久しぶりに出社したらオフィスのフロアを間違えました。

業務では専らPHPを使っていますが、プライベートではiOSアプリを作っています。

今回はビーコンを使ったアプリを作ってみたく、その過程を記事にしようと思います。

まずはビーコンの基礎知識から。

ビーコン(Beacon)とは

ビーコンは、地上にある無線局などから発射される電波(あるいはIR(赤外線)のような高周波の電磁波)を航空機・船舶・自動車などの移動体に搭載された機器で受信することにより、位置をはじめとした各種情報を取得するための設備である。

Wikipediaより引用

というものですが、モバイル機器で使用するビーコンは、主に低消費電力の近距離無線技術「Bluetooth Low Energy」(BLE)を利用した位置情報測定技術、またはそれを利用したデバイスのことを言います。

BLEを搭載したビーコンは一定の間隔でBLEのブロードキャスト通信の信号を送り続けます。

省電力化されているので小型の電池でも最長で数年間信号を送り続けることができます。

BLE技術を活用したビーコン規格にはAppleの「iBeacon」とgoogleの「Eddystone」があります。

GPSとビーコンの違い

位置情報測定技術としては他にGPSがあります。

ということで、GPSとビーコンの違いを調べました。

GPSは「Global Positioning System」の略で全地球測位システムとも呼ばれる地球上での位置(緯度、経度)を測位するシステムです。

マップアプリやカーナビで利用されています。

GPSは衛星から電波を受信するので広範囲の測定が可能ですが、都心部などのビルが多い場所や山間部の谷間、また屋内などでは衛星の電波受信が弱く誤差が大きくなる場合があります。

対してビーコンは発信される信号の受信範囲が限られますが、範囲内であれば屋内、地下でも測定ができます。

また、受信した信号の強度によってビーコンとの距離を数メートルの単位で割り出すこともできます。

このようにビーコンはGPSと比べて狭い範囲で数メートルの細かい位置測定をすることができるものになっています。 

ビーコンは既にいろいろな業界で活用されていて、身近なところだとお店の近くを通るとアプリを通じて通知が届くものがあります。お店に設置されたビーコンの信号をスマホが検出して通知が届く仕組みです。

アプリ作成の準備

基礎知識はこれくらいにして、さっそくアプリ作成の準備に取り掛かっていきます。 

用意するもの

まず肝心のビーコンデバイスを用意します。

こちらを用意しました。

4㎝弱の小型のビーコンです。

開発環境

Mac OS Catalina
Xcode11(Swift)
iPhone XR(検証用)

です。

アプリ作成の手順

準備が整ったところで、早速アプリを作っていきましょう。

まずはiPhoneがビーコンの発信する信号を受信できるようにするところまでやってきます。

ビーコンとの通信はiPhoneの実機を使う必要があるので、Xcodeのシミュレーターは使用しません。

1.Xcodeでプロジェクト作成

Xcodeでプロジェクトを作ります。

まず、アプリで位置情報を使用するためにはユーザーから許可をしてもらう必要があります。

info.plist(アプリの基本設定ファイル)に以下を記述します。

keyNSLocationWhenInUseUsageDescription
typeString
Value位置情報の通知(何のために使うか書く)

これはユーザーに位置情報を「アプリを使用中のみ許可」を求めるものになります。

ViewControllerにコードを書いていきます。

位置情報関係を扱うCoreLocationフレームワークをimportします。

ビーコンとの通信もこのフレームワークを利用します。

import CoreLocation

class ViewController: UIViewController {

CoreLocationにあるCLLocationManagerクラスを使用します。

これはアプリへ位置情報の通知を管理するクラスです。

ビーコンの信号の受信もこのクラスが担います。

class ViewController: UIViewController {
   
    // CoreLocation
    var locationManager : CLLocationManager!

    override func viewDidLoad() {
        super.viewDidLoad()
       
        // ロケーションマネージャーオブジェクトを作成
        self.locationManager = CLLocationManager();

        // 位置情報の認証ステータスを取得
        let status = CLLocationManager.authorizationStatus()
       
        // 位置情報の認証が許可されていない場合は認証ダイアログを表示
        if (status != CLAuthorizationStatus.authorizedWhenInUse) {
            locationManager.requestWhenInUseAuthorization()
        }
    }

2.アプリを起動して確認する

これで一度アプリをビルドして起動してみます。

初めてアプリを起動して、位置情報の利用許可をしていないので位置情報の許可を求めるダイアログが出ました。

ここで許可をすることで、アプリ内で位置情報を使用することができます。

3.ビーコンとの通信を作る

位置情報を利用できるようになったので、ビーコンとの通信を作っていきます。

CLBeaconRegionはビーコンを検出するために使用する領域を定義します。

ビーコンのUUID(今回使用するビーコンの固有識別IDで製造時に設定されているもの)をCLBeaconRegionに渡して初期化します。

これで指定したUUIDのビーコン信号が領域内で検出できた場合にCoreLocationがアプリに通知します。

CLLocationManagerDelegateプロトコルの使用を宣言してデリゲート先を自身にします。

class ViewController: UIViewController, CLLocationManagerDelegate {
   
    // CoreLocation
    var locationManager : CLLocationManager!
    var beaconRegion : CLBeaconRegion!

    override func viewDidLoad() {
        super.viewDidLoad()
       
        // ロケーションマネージャーオブジェクトを作成
        self.locationManager = CLLocationManager();

        // デリゲートを自身に設定
        self.locationManager.delegate = self;
       
        // 位置情報の認証ステータスを取得
        let status = CLLocationManager.authorizationStatus()
       
        // 位置情報の認証が許可されていない場合は認証ダイアログを表示
        if (status != CLAuthorizationStatus.authorizedWhenInUse) {
            locationManager.requestWhenInUseAuthorization()
        }
       
        // 受信対象のビーコンのUUIDを設定
        let uuid:UUID? = UUID(uuidString: “FDA50693-A4E2-4FB1-AFCF-C6EB07647825”)
       
        // ビーコン領域の初期化
        beaconRegion = CLBeaconRegion(uuid: uuid!, identifier: “BeaconApp”)

    }
}

locationManagerメソッドを書いていきます。

何をしているのかざっくり書くと、デバイスのビーコン検出領域内に対象ビーコンの信号があったら、そのビーコンとの距離を観測して、信号がなくなったらビーコンの観測を止めます。

    // 位置情報の認証ステータス変更
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
       
        if (status == .authorizedWhenInUse) {
            // ビーコン領域の観測を開始
            self.locationManager.startMonitoring(for: self.beaconRegion)
        }
    }

    // ビーコン領域の観測を開始
    func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
        // ビーコン領域のステータスを取得
        self.locationManager.requestState(for: self.beaconRegion)
    }
   
    // ビーコン領域のステータスを取得
    func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for inRegion: CLRegion) {

        switch (state) {
        case .inside: // ビーコン領域内
            // ビーコンの測定を開始
            self.locationManager.startRangingBeacons(satisfying: self.beaconRegion.beaconIdentityConstraint)
            break
        case .outside: // ビーコン領域外
            break

        case .unknown: // 不明
            break

        }
    }
       
    // ビーコン領域に入った時
    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        // ビーコンの位置測定を開始
        self.locationManager.startRangingBeacons(satisfying: self.beaconRegion.beaconIdentityConstraint)
       
    }

    // ビーコン領域から出た時
    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        // ビーコンの位置測定を停止
        self.locationManager.stopRangingBeacons(satisfying: self.beaconRegion.beaconIdentityConstraint)
    }
   
    // ビーコンの位置測定
    func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion){
       
        for beacon in beacons {
           
            print(“uuid:\(beacon.uuid)”)
            print(“major:\(beacon.major)”)
            print(“minor:\(beacon.minor)”)
            if (beacon.proximity == CLProximity.immediate) {
                print(“proximity:immediate”)
            }
            if (beacon.proximity == CLProximity.near) {
                print(“proximity:near”)
            }
            if (beacon.proximity == CLProximity.far) {
                print(“proximity:Far”)
            }
            if (beacon.proximity == CLProximity.unknown) {
                print(“proximity:unknown”)
            }
            print(“accuracy:\(beacon.accuracy)”)
            print(“rssi:\(beacon.rssi)”)
            print(“timestamp:\(beacon.timestamp)”)

        }

検出できたらビーコンの位置を測定して取得した情報をコンソールに出力するようにしています。

4.完成!アプリを再度起動してみる

これで、ビーコンとアプリを起動します。

ビーコンを検出して測定ができました!

timestampを見ると1秒おきに測定しているようです。

ビーコンの位置測定で取得できる情報

uuidビーコンの識別子
major細分化された識別子
minor更に細分化された識別子
proximityビーコンとの相対距離(電波強度から推定距離を計算)Immediate 至近距離(1m未満)Near 近距離(1~3m)Far(3m以上)Unknown(計測不能)
accuracy精度(推定誤差)
rssi電波強度

終わりに

取得できる情報の詳細はもう少し調べてみるとして、iPhoneとビーコンとの通信ができたの

で、この仕組みを使って何かプロダクトっぽいものを作っていこうと思います。

次回に続く!

開発ブログカテゴリの最新記事