micro:bitをペリフェラルとしてパソコンとBLE接続し、ペリフェラルではイベントにより接続/切断を受け取ります。パソコンへはNotifyによりデータを定期的に通知します。パソコンはWindows10の環境で動作し、C#によりプログラムを開発します。

作成アプリの動作説明

パソコンからのint型データをmicro:bitにBLEインタフェースを用いて送信し、micro:bitからパソコンにfloat型データをNotifyを使って、1秒ごとに送信します。なおmicro:bitはビッグエンディアン、パソコンはリトルエンディアンのためエンディアン変換を行います。

BLEインタフェース

BLEインタフェースで使用するService UUIDとCharacteristic UUIDを次に示します。

Service UUID:19b10010-e8f2-537e-4f6c-d104768a1214
Characteristic UUID:19b10011-e8f2-537e-4f6c-d104768a1214  Write
Characteristic UUID:19b10012-e8f2-537e-4f6c-d104768a1214  Read | Notify

  • パソコン → micro:bit int型(12345678)
  • パソコン ← micro:bit float型 (0から1秒ごとに0.5増加)

ペリフェラルアプリの作成

ペリフェラルとして作成されるmicro:bitのアプリを次に示します。

  • 9行目でNotifyとして使用するCharacteristic を定義します。
  • 28行目で接続のイベントハンドラ「blePeripheralConnectHandler」を登録します。
  • 29行目で切断のイベントハンドラ「blePeripheralDisconnectHandler」を登録します。
  • 45行目で1秒間を計測して、49行目のsetValueメソッドでNotifyとしてデータを送信します。

notifytest.ino

#include <BLEPeripheral.h>

BLEPeripheral blePeripheral = BLEPeripheral();

// create service
BLEService accelService = BLEService("19b10010-e8f2-537e-4f6c-d104768a1214");
BLEUnsignedIntCharacteristic controlCharacteristic = BLEUnsignedIntCharacteristic("19b10011-e8f2-537e-4f6c-d104768a1214",
    BLEWrite);
BLEFloatCharacteristic accelCharacteristic = BLEFloatCharacteristic("19b10012-e8f2-537e-4f6c-d104768a1214",
    BLERead | BLENotify);

bool connectflg = false;
int timer;
int notifytime = 1000;
float notifydata = 0.0;

void setup() {
  Serial.begin(9600);

  blePeripheral.setDeviceName("notifytest");

  blePeripheral.setAdvertisedServiceUuid(accelService.uuid());

  blePeripheral.addAttribute(accelService);
  blePeripheral.addAttribute(controlCharacteristic);
  blePeripheral.addAttribute(accelCharacteristic);

  blePeripheral.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  blePeripheral.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  // BLE init
  blePeripheral.begin();
  Serial.println(F("notifytest Peripheral"));
}

void loop() {
  blePeripheral.poll();

  if (connectflg) {
    if (controlCharacteristic.written()) {
      getContolCharacteristicValue();
    }

    timer ++;
    if (timer >= (notifytime / 50))
    {
      timer = 0;
      notifydata += 0.5;
      accelCharacteristic.setValue(notifydata);
    }
  }
  delay(50);
}
void getContolCharacteristicValue() {
  // received from central...
  Serial.println("receive data!");
  if (controlCharacteristic.value()) {
    int controldata = controlCharacteristic.value();
    Serial.println(controldata);
  }
}

void blePeripheralConnectHandler(BLECentral& central) {
  connectflg = true;
  Serial.print(F("Connected event, central: "));
  Serial.println(central.address());
}

void blePeripheralDisconnectHandler(BLECentral& central) {
  connectflg = false;
  Serial.print(F("Disconnected event, central: "));
  Serial.println(central.address());
}

セントラルアプリの作成

セントラルとして作成されるパソコンのアプリを次に示します。

  • 89行目のToInt32メソッドにより、送信するリトルエンディアンのint型データをビッグエンディアンのint型データに変換します。
  • 107行目のToFloatメソッドにより、受信したビッグエンディアンのfloat型データをリトルエンディアンのfloat型データに変換します。
  • 113行目から141行目までがエンディアン関連メソッドとなります。
using System;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Storage.Streams;

namespace NotifyTest
{
    class Program
    {
        static void Main(string[] args)
        {
            SensorModule sensormodule = new SensorModule();
            sensormodule.Start();
        }
    }

    class SensorModule
    {
        const string SENSOR_NAME = "notifytest";
        // Bluetooth®サービス
        static Guid UUID_TEMP_SERV = new Guid("19b10010-e8f2-537e-4f6c-d104768a1214");
        // 計測センサデータやモジュール内部ステータスをパソコンに通知
        static Guid UUID_TEMP_DATA = new Guid("19b10012-e8f2-537e-4f6c-d104768a1214");
        // コマンド制御
        static Guid UUID_TEMP_CONF = new Guid("19b10011-e8f2-537e-4f6c-d104768a1214");

        private BluetoothLEAdvertisementWatcher advWatcher;

        public void Start()
        {
            Console.WriteLine("Start");
            this.advWatcher = new BluetoothLEAdvertisementWatcher();
            this.advWatcher.ScanningMode = BluetoothLEScanningMode.Passive;

            this.advWatcher.Received += this.Watcher_Received;

            // スキャン開始
            this.advWatcher.Start();
            Thread.Sleep(10000);
            this.advWatcher.Stop();
            Console.WriteLine("Stop");
        }

        private void Watcher_Received(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
        {
            this.CheckArgs(args);
        }

        public async void CheckArgs(BluetoothLEAdvertisementReceivedEventArgs args)
        {
            Console.WriteLine("Received");
            var dev = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);

            if (dev.Name == SENSOR_NAME)
            {
                // 検出
                try
                {
                    Console.WriteLine($"Service Find!");

                    // スキャンStop
                    this.advWatcher.Stop();

                    BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);

                    var services = await device.GetGattServicesForUuidAsync(UUID_TEMP_SERV);
                    var characteristics = await services.Services[0].GetCharacteristicsForUuidAsync(UUID_TEMP_DATA);
                    var characteristics1 = await services.Services[0].GetCharacteristicsForUuidAsync(UUID_TEMP_CONF);

                    if (characteristics.Status == GattCommunicationStatus.Success)
                    {
                        var gattCharacteristic = characteristics.Characteristics.First();

                        // CCCD への書き込みが必要。これがないとイベントハンドラが呼ばれない。
                        GattCommunicationStatus status
                            = await gattCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.Notify);

                        if (status == GattCommunicationStatus.Success)
                        {
                            gattCharacteristic.ValueChanged += Changed_data;
                        }
                    }
                    // モーション(動き)検知 100ms間隔(モーション系センサのみ)
                    var writer = new DataWriter();
                    writer.WriteInt32(ToInt32(BitConverter.GetBytes(12345678), 0, Endian.Big));
                    await characteristics1.Characteristics[0].WriteValueAsync(writer.DetachBuffer(), GattWriteOption.WriteWithoutResponse);

                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Exception...{ex.Message})");
                }
            }

        }

        public void Changed_data(GattCharacteristic sender, GattValueChangedEventArgs eventArgs)
        {
            Console.WriteLine($"characteristicChanged...Length={eventArgs.CharacteristicValue.Length}");
            byte[] data = new byte[eventArgs.CharacteristicValue.Length];
            Windows.Storage.Streams.DataReader.FromBuffer(eventArgs.CharacteristicValue).ReadBytes(data);

            var tmp = ToFloat(data, 0, Endian.Little);
            Console.WriteLine($"characteristicChanged...{tmp}");

            return;
        }

        enum Endian
        {
            Little, Big
        }
        static byte[] Reverse(byte[] bytes, Endian endian)
        {
            if (BitConverter.IsLittleEndian ^ endian == Endian.Little)
                return bytes.Reverse().ToArray();
            else
                return bytes;
        }
        static int ToInt32(byte[] value, int startIndex, Endian endian)
        {
            byte[] sub = GetSubArray(value, startIndex, sizeof(int));
            return BitConverter.ToInt32(Reverse(sub, endian), 0);
        }
        static float ToFloat(byte[] value, int startIndex, Endian endian)
        {
            byte[] sub = GetSubArray(value, startIndex, sizeof(float));
            return BitConverter.ToSingle(Reverse(sub, endian), 0);
        }

        // バイト配列から一部分を抜き出す
        static byte[] GetSubArray(byte[] src, int startIndex, int count)
        {
            byte[] dst = new byte[count];
            Array.Copy(src, startIndex, dst, 0, count);
            return dst;
        }
    }
}

アプリの実行

ペリフェラルアプリの実行結果

ペリフェラルアプリ実行時のシリアルモニタの表示を次に示します。

セントラルアプリの実行結果

コマンドプロンプトで実行したセントラルアプリの実行結果を次に示します。