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

