COCOA 受信(EN API 仕様)の実際 Windows + C# 版

既に COCOA も廃止になっていて、実測することはできないのですが、EN API のアドバタイズ受信機も Windows + C# で作成できます。当時は Android を使って受信させていたのですが、iBeacon の受信機と同じように Windows で作ることができます。

電文フォーマットがわかっているので、受信データを解析すればよいだけです。iBeacon とは違って、16 bit のサービス UUID(0xFD6F)を持っているので、これを選別してデータを受信します。

using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Storage.Streams;

// BLEのスキャナ
BluetoothLEAdvertisementWatcher watcher;

Main(args);

void Main(string[] args)
{
    Console.WriteLine("COCOA Check");
    watcher = new BluetoothLEAdvertisementWatcher()
    {
        ScanningMode = BluetoothLEScanningMode.Passive
    };
    // スキャンしたときのコールバックを設定
    watcher.Received += Watcher_Received;
    // スキャン開始
    watcher.Start();
    // キーが押されるまで待つ
    Console.WriteLine("Press any key to continue");
    Console.ReadLine();
}

void Watcher_Received(
    BluetoothLEAdvertisementWatcher sender,
    BluetoothLEAdvertisementReceivedEventArgs args)
{

    var uuids = args.Advertisement.ServiceUuids;
    var mac = string.Join(":",
                BitConverter.GetBytes(args.BluetoothAddress).Reverse()
                .Select(b => b.ToString("X2"))).Substring(6);
    var rssi = args.RawSignalStrengthInDBm;
    var time = args.Timestamp.ToString("yyyy/MM/dd HH:mm:ss.fff");
    
    if (uuids.Count == 0) return;
    // 0xFD6F は Exposure Notification のサービスUUID
    if (uuids.FirstOrDefault(t => t.ToString() == "0000fd6f-0000-1000-8000-00805f9b34fb") == Guid.Empty) return;

    foreach (var it in args.Advertisement.DataSections)
    {
        if ( it.DataType == 0x16 && it.Data.Length >= 2 + 16)
        {
            byte[] data = new byte[it.Data.Length];
            DataReader.FromBuffer(it.Data).ReadBytes(data);
            if ( data[0] == 0x6f && data[1] == 0xfd)
            {
                byte[] rpi = data[2..18];
                Console.WriteLine($"{time} [{tohex(rpi)}] {rssi} dBm {mac}");
            }
        }
    }

    string tohex( byte[] data )
    {
        return BitConverter.ToString(data).Replace("-", "").ToLower();
    }
}

1. args.Advertisement.ServiceUuids で Service UUID 0xFD6F を確認します。
2. args.Advertisement.DataSections で AD Type 0x16(Service Data – 16-bit UUID)を探します。
3. DataSections(Service Data) から Service Data UUID 0xFD6F を探し出します。
4. 見つかった Service Data から RPI 情報を取り出します

BluetoothLEAdvertisementWatcher で受信するときに、args.Advertisement.ServiceUuids とか args.Advertisement.DataSections のように複数の Service UUID が取れるような感じになっていますが、BLE アドバタイズは 32 bytes までしかないので、実質 1 つしか送れません。何故こうなっているのかわからないのですが、拡張のためでしょうか? 確か、GATT の場合は Service UUID をペリフェラルに問い合わせるモードがあるのでそのためかもしれません。

ひとまず、Service UUID である 0xFD6F を 2 回探していますが、そういう電文フォーマットになっているからです。Service Data UUID のほうの 0xFD6F は自由に決められるので、この値じゃなくても良いはずなのですが、COCOA ではこうなっています。

この 16 bit Service UUID を使ったフォーマットは、Bluetooth SIG で 16 bit Company ID を持った会社であれば自由に作れるので、iBeacon のように色々な用途で使えます。温度センサーをアドバタイズで発信するとか、位置情報を発信するとか、そういう用途です。GATT コネクションの Notify よりも手軽ので、受信側を問わなければ結構使い道があると思うのですが、いまのところあまり使われていないようです。と云うか、受信機をアプリ等で作らないといけないので、何か専門用途に使っていてあまり外部流出しないのでしょう。EN API の場合のように一般的なアプリが受信するというパターンは珍しいのだと思います。

受信データを全部見る場合

Android の場合は ScanCallback を使って受信した生データを取得することができるのですが、BluetoothLEAdvertisementWatcher クラスでは生データという形でみることはできません。ただし、Advertisement.DataSections を使って中身をみることができます…というのは既に書いてあるのですが、すべての AD セクションを表示するコードは以下のようになります。

    // できるだけ raw data を全部表示する場合
    foreach (var section in args.Advertisement.DataSections)
    {
        byte[] buf = new byte[section.Data.Length];
        DataReader.FromBuffer(section.Data).ReadBytes(buf);
        Console.WriteLine($"AD 0x{section.DataType:X2} len={buf.Length} data={BitConverter.ToString(buf)}");
    }
AD 0x01 len=1 data=1A
AD 0x03 len=2 data=6F-FD
AD 0x16 len=22 data=6F-FD-01-01-01-01-02-02-02-02-03-03-03-03-04-04-04-04-05-05-05-05
  • AD Type: 1byte
  • Length: 1byte
  • Data: n bytes

という形ででているのが良く分かります。

実測状態

ちょっと、m5stack で COCOA 発信機を作らないといけないので、その後で。
受信した状態はこんな感じになります。

RPI 情報は適当に埋めてあるので、そのデータが流れています。COCOA が動作しているとすると、こんな感じで RPI 情報が取得できるわけです。

カテゴリー: 開発, FolkBears パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

*