IoTデバイス(?)として、ビーコン(iBeacon)という小さなデバイスがあります。
使い方としては顧客が近づくとビーコンから出ている電波をキャッチして、その場所にあった情報をスマートフォンなどに発信するといった利用方法が主なのですが、登場したころは対応機器がiPhoneだけでしたので、Windowsの世界は置いてけぼりとなっていました。
(※Androidはいつの間にか対応済み)
「Windows10デバイスでもiBeaconのデータを使った処理がしたい!」
ということで、調べました。
実際、ビーコンの仕組みとしては「Bluetooth Low Energy」(BLE)が使われていただけなので、Bluetoothに対応されていれば通信は可能だったはずでした。
ですが、調べたところ通信規格でかなり特殊なことを行っていたため、通常の方法ではうまく取得できませんでした。
そもそも当初Bluetoothはペアリング(接続後に相互通信)を前提としたように作られており、「一方通行」の情報発信用には作られていなかったのです。
(ペアリングの通信規格を拡張して無理やり処理した感じです。なので、ビーコンとペアリングできるけど、ひたすらエラーとなる。。。)
それが、やっとWindows10で対応が進んできました。
使うのは「Windows.Devices.Bluetooth.Advertisement」(Windows.Winmdの参照が必要)の名前空間になります。
(※Windows10 PreviewのころからGitHUBに存在はしていたが、まともに動かなかった。。。)
このクラスを利用することで、ビーコン特有の「アドバタイズメント・パケット」を取得、解析することが出来ます。
C#のコード
using System; using Windows.Devices.Bluetooth.Advertisement; using Windows.Storage.Streams; public class winBeacon { private BluetoothLEAdvertisementWatcher watcher; static void Main() { watcher = new BluetoothLEAdvertisementWatcher(); watcher.Received += OnAdvertisementReceived;//ここのイベント内で取得する watcher.Start(); Console.WriteLine("終了するには何かキーを押してください..."); Console.ReadKey(); watcher.Stop(); } private void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs) { //イベントの値から取得 DateTimeOffset timestamp = eventArgs.Timestamp; BluetoothLEAdvertisementType advertisementType = eventArgs.AdvertisementType; string rssi = eventArgs.RawSignalStrengthInDBm; string name = eventArgs.Advertisement.LocalName; Console.WriteLine(string.Format("timestamp:{0}", timestamp.ToString("HH\\:mm\\:ss\\.fff"))); Console.WriteLine(string.Format("rssi:{0}", rssi.ToString("D"))); } }
単純にはこのような形で簡単にRSSI(受信信号強度)の値などが取得できます。
ですが、iPhoneやAndroidのAPIではもっとさまざまな下記の値が取得できるようになっています。
- UUID(Universally Unique Identifier): ビーコンに設定されてい128bitの値
- メジャー値:ビーコンに設定されている整数
- マイナー値:ビーコンに設定されている整数
- measuredPower: 1mでのRSSI強度
- RSSI: 受信信号強度
- Accuracy: measuredPowerとRSSIから計算したビーコンとの距離(精度)
- proximity: ビーコンと距離(unknown(不明), immediate(すぐそこ), near(近い), far(遠い))
iPhoneやAndroidと共通のクロスプラットフォームアプリを作ろうと思ったら全然足りません。
なので、なんとか自力で専用クラスを作成して取得できるようにしました。
C#のコード
using System; using Windows.Devices.Bluetooth.Advertisement; using Windows.Storage.Streams; public class iBeacon { private const int MinimumLengthInBytes = 25;//最小の長さ private const int AdjustedLengthInBytes = -2;//CompanyID分の2桁ずれている為読み取り位置補正 //プロパティ public string Name { get; set; } public DateTimeOffset Timestamp { get; set; } public BluetoothLEAdvertisementType AdvertisementType { get; set; } public int ManufacturerId { get; set; } public int Major { get; set; } public int Minor { get; set; } public string UUID { get; set; } public short Rssi { get; set; } public short MeasuredPower { get; set; } public double ManufacturerReserved { get; set; } //精度(accuracy) public double Accuracy { get { return calcAccuracy(MeasuredPower, Rssi); } } //近接度(Proximity):近接(immidiate)、1m以内(near)、1m以遠(far)、不明(Unknown) public string Proximity { get { string _Proximity = "Unknown"; //Rssi未取得ならUnknown if (Rssi == 0) { return _Proximity; } //rssi値からProximityを判別 if (Rssi > -40) { _Proximity = "immidiate";//近接 } else if (Rssi > -59) { _Proximity = "near";//1m以内 } else { _Proximity = "far";//1m以遠 } return _Proximity; } } //コンストラクタ public iBeacon(){ ManufacturerId = -1; Major = -1; Minor = -1; Rssi = 0; UUID = ""; MeasuredPower = -1; ManufacturerReserved = -1.0; } //コンストラクタ2 public iBeacon(BluetoothLEAdvertisementReceivedEventArgs eventArgs){ //出力されているbyteデータから各値を抽出する var manufacturerSections = eventArgs.Advertisement.ManufacturerData; Timestamp = eventArgs.Timestamp; AdvertisementType = eventArgs.AdvertisementType; if (manufacturerSections.Count > 0) { var manufacturerData = manufacturerSections[0]; var data = new byte[manufacturerData.Data.Length]; iBeacon bcon = new iBeacon(); using (var reader = DataReader.FromBuffer(manufacturerData.Data)) { reader.ReadBytes(data); } //長さをチェック if (data == null || data.Length < MinimumLengthInBytes + AdjustedLengthInBytes) { return; } //イベントから取得 Rssi = eventArgs.RawSignalStrengthInDBm; Name = eventArgs.Advertisement.LocalName; ManufacturerId = manufacturerData.CompanyId; //バイトデータから抽出 //公式での出力値(Windowsでは2byteずれているので補正が必要) // Byte(s) WinByte(s) Name // -------------------------- // 0-1 none Manufacturer ID (16-bit unsigned integer, big endian) // 2-3 0-1 Beacon code (two 8-bit unsigned integers, but can be considered as one 16-bit unsigned integer in little endian) // 4-19 2-17 ID1 (UUID) // 20-21 18-19 ID2 (16-bit unsigned integer, big endian) // 22-23 20-21 ID3 (16-bit unsigned integer, big endian) // 24 22 Measured Power (signed 8-bit integer) // 25 23 Reserved for use by the manufacturer to implement special features (optional) //BigEndianの値を取得 UUID = BitConverter.ToString(data, 4 + AdjustedLengthInBytes, 16); // Bytes 2-17 MeasuredPower = Convert.ToSByte(BitConverter.ToString(data, 24 + AdjustedLengthInBytes, 1), 16); // Byte 22 //もし追加データがあればここで取得 if (data.Length >= MinimumLengthInBytes + AdjustedLengthInBytes + 1) { ManufacturerReserved = data[25 + AdjustedLengthInBytes]; // Byte 23 } //.NET FramewarkのEndianはCPUに依存するらしい if (BitConverter.IsLittleEndian) { //LittleEndianの値を取得 byte[] revData; revData = new byte[] { data[20 + AdjustedLengthInBytes], data[21 + AdjustedLengthInBytes] };// Bytes 18-19 Array.Reverse(revData); Major = BitConverter.ToUInt16(revData, 0); revData = new byte[] { data[22 + AdjustedLengthInBytes], data[23 + AdjustedLengthInBytes] };// Bytes 20-21 Array.Reverse(revData); Minor = BitConverter.ToUInt16(revData, 0); } else { //BigEndianの値を取得 Major = BitConverter.ToUInt16(data, 20 + AdjustedLengthInBytes); // Bytes 18-19 Minor = BitConverter.ToUInt16(data, 22 + AdjustedLengthInBytes); // Bytes 20-21 } } else { new iBeacon(); } } //精度を計算する protected static double calcAccuracy(short measuredPower, short rssi) { if (rssi == 0) { return -1.0; //nodata return -1. } double ratio = rssi * 1.0 / measuredPower; if (ratio < 1.0) { return Math.Pow(ratio, 10); } else { double accuracy = (0.89976) * Math.Pow(ratio, 7.7095) + 0.111; return accuracy; } } }
これをこのようにして使います。
C#のコード
private void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs) { //プロパティ値から取得 DateTimeOffset timestamp = eventArgs.Timestamp; BluetoothLEAdvertisementType advertisementType = eventArgs.AdvertisementType; iBeacon bcon = new iBeacon(eventArgs); string retBeaconData; retBeaconData = "{"; retBeaconData += string.Format("uuid:'{0}',", bcon.UUID);//"00000000-0000-0000-0000-000000000000" retBeaconData += string.Format("major:{0},", bcon.Major.ToString("D")); retBeaconData += string.Format("minor:{0},", bcon.Minor.ToString("D")); retBeaconData += string.Format("measuredPower:{0},", bcon.MeasuredPower.ToString("D")); retBeaconData += string.Format("rssi:{0},", bcon.Rssi.ToString("D")); retBeaconData += string.Format("accuracy:{0},", bcon.Accuracy.ToString("F6")); retBeaconData += string.Format("proximity:'{0}'", bcon.Proximity); retBeaconData += "}"; Console.WriteLine(string.Format("timestamp:{0}", timestamp.ToString("HH\\:mm\\:ss\\.fff"))); Console.WriteLine(retBeaconData); }
2バイト分ずれていたとか、BigEndianとLittieEndianを判定して処理しなければいけないとか、すごい罠だらけでしたが、何とかこれで取得できます。
※環境によって異なる動作になるかもしれません。訂正、指摘などあればコメントをお願いします。
Search index:
[iBeacon] Getting packet major and minor ID in Windows 10 Bluetooth LE Advertisement
[iBeacon] Convert the Bluetooth LE Advertising bytes packet of Windows 10