2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MakeCodeでmicro:bitのBLEサービスを実装したい:インサイド MakeCode for micro:bit - bluetooth編

Last updated at Posted at 2022-11-17

micro:bitでBluetooth

micro:bitのMakeCodeには、Bluetoothのブロックが標準(の拡張機能)で用意されています。これらのBLEサービス(ペリフェラル)を使ったプログラミングが簡単にできます。また、MakeCodeでは、独自の拡張機能を開発することが可能です。もちろん、標準(の拡張機能)にはないBLEサービスの開発と追加も可能です。

また、micro:bitには、V1とV2の2つのバージョンがあり、MakeCodeの内部で使用されているライブラリも、dalとcodalとに分かれています。それぞれのライブラリに則った設計と実装を行えば、独自のBLEサービスの実装も容易に行えます。

Bluetooth 拡張機能(標準)

標準(の拡張機能)で、次のBLEサービスのブロックが用意されています。
MakeCode for micro:bit エディタ を開き、拡張機能の追加で、"bluetooth"をキーワードに検索して、「bluetooth」を追加すると利用できます("Radio"との共存はできません)。

  1. Bluetooth 加速度計サービス
  2. Bluetooth ボタンサービス
  3. Bluetooth 入出力端子サービス
  4. Bluetooth LEDサービス
  5. Bluetooth 温度計サービス
  6. Bluetooth 磁力計サービス
  7. Bluetooth その他 UARTサービス

BLEサービスに関連するブロックも用意されています。

  1. Bluetooth 接続されたとき
  2. Bluetooth 接続が切断されたとき
  3. Bluetooth データを受信したとき
  4. Bluetooth その他 UART 数値を文字で書き出す
  5. Bluetooth その他 UART 文字列を書き出す
  6. Bluetooth その他 UART write line
  7. Bluetooth その他 UART 名前と値を書き出す
  8. Bluetooth その他 UART つぎのいずれかの文字の手前まで読み取る
  9. Bluetooth その他 送信強度を設定

これらを含むbluetoothに関する仕様は、プロファイル(Bluetooth Developer Studio Level 3 Profile Report)として、まとめられています。

micro:bit ランタイムとNordic nRF5 SDK

MakeCodeで、HEX形式ファイルをビルドする際に、micro:bitランタイムやNordic nRF5 SDKといったライブラリが必要です。これらのライブラリは、micro:bitのバージョンにより異なります。

micro:bit ランタイム nRF5
V1 dal Soft Device 110
V2 codal Soft Device 113

その為、拡張機能を独自に実装する際には、micro:bitの各バージョンに合わせたライブラリに対する実装が必要ですので、プリプロセッサ ディレクティブによる切り分けた実装を行います(MICROBIT_CODALがゼロ以外かどうか)。

MICROBIT_CODAL micro:bit
ゼロ V1 - DAL
ゼロ以外 V2 - CODAL
custom.h

#include "pxt.h"

//================================================================
#if MICROBIT_CODAL
//================================================================
// for V2 - codal
#include "MicroBitBLEManager.h"
#include "MicroBitBLEService.h"



//================================================================
#else // MICROBIT_CODAL
//================================================================
// for V1 - codal
#include "ble/BLE.h"



//================================================================
#endif // MICROBIT_CODAL
//================================================================

Bluetooth拡張機能の実装(の内部)

Bluetooth 拡張機能のソースコードは、GitHubで公開されていますので、V1とV2の実装を確認することができます。

micro:bit V1 lancaster-university/microbit-dal
micro:bit V2 lancaster-university/codal-microbit-v2

継承元

各BLEサービスのヘッダーファイル(inc/bluetooth/MicroBit*Service.h)で、継承元を確認できます。
V2の場合は、MicroBitBLEServiceクラスを継承しています。V2では、このMicroBitBLEServiceクラスがBLEサービス(ペリフェラル)に関する実装を提供している為です(V1の場合はuBit.bleに処理を委譲)。
また、入出力端子サービスのみ、MicroBitComponentクラスを継承してますが、idleTick()(V1)やidleCallback()(V2)を実装する為です。

BLEサービス V1 V2
加速度計サービス (なし) MicroBitBLEService
ボタンサービス (なし) MicroBitBLEService
入出力端子サービス MicroBitComponent MicroBitBLEService, MicroBitComponent
LEDサービス (なし) MicroBitBLEService
温度計サービス (なし) MicroBitBLEService
磁力計サービス (なし) MicroBitBLEService
UARTサービス (なし) MicroBitBLEService

idleコールバック

入出力端子サービスだけが、その継承元をMicroBitComponentクラスとしていますが、これは、MicroBitスケジューラからの定期的なコールバックを実装する為です。
V1の場合は、virtual void MicroBitComponent::idleTick()オーバーライドし、V2の場合は、virtual void MicroBitComponent::idleCallback()オーバーライドしています。
両者ともにその中で、同名のupdateBLEInputs()メソッドを呼び出すことによって、ピンの値を取得し、その値に変化があれば、BLEで、データ通知(Notify)しています。

V1 - idleTick()の実装内容

microbit-dal/source/bluetooth/MicroBitIOPinService.cpp
// (L.L.268-278)

/**
 * Periodic callback from MicroBit scheduler.
 *
 * Check if any of the pins we're watching need updating. Notify any connected
 * device with any changes.
 */
void MicroBitIOPinService::idleTick()
{
    if (ble.getGapState().connected)
        updateBLEInputs();
}

V2 - idleCallback()の実装内容

codal-microbit-v2/source/bluetooth/MicroBitIOPinService.cpp
// (L.L.282-299)

/**
 * Periodic callback from MicroBit scheduler.
 *
 * Check if any of the pins we're watching need updating. Notify any connected
 * device with any changes.
 */
void MicroBitIOPinService::idleCallback()
{
    if ( getConnected())
    {
        int pairs = updateBLEInputs( false);
        // If there's any data, issue a BLE notification.
        if ( pairs)
        {
            notifyChrValue( mbbs_cIdxDATA, (uint8_t *)ioPinServiceDataCharacteristicBuffer, pairs * sizeof(IOData));
        }
    }
}

idleコールバックの開始と停止

idleTick()idleCallback()をオーバーライドしただけでは、コールバックされません。
コールバックを開始するにはfiber_add_idle_component(MicroBitComponent * component)を、停止するにはfiber_remove_idle_component(MicroBitComponent * component)もしくはCodalComponent::removeComponent()を呼び出します。

実際には、コンストラクタ内でfiber_add_idle_component(this);と呼び出しています(V1/V2)。

V1での呼び出し元

idleTick()メソッドは、MicroBitFiber.h/MicroBitFiber.cppから呼び出されます。

項目 メソッド 説明
開始 int fiber_add_idle_component(MicroBitComponent *component); idleThreadComponents[]配列でインスタンスを保持します。
呼出元 void idle(); idleThreadComponents[]配列で保持するインスタンスのidleTick()メソッドを呼び出します。
停止 int fiber_remove_idle_component(MicroBitComponent *component); idleThreadComponents[]配列からインスタンスを除外します。

V2での呼び出し元

idleCallback()メソッドは、CodalComponent.h/CodalComponent.cppから呼び出されます。
尚、codal::CodalComponentクラスは、typedef codal::CodalComponent MicroBitComponent;によって、別名のMicroBitComponentとして定義されています。
inc/compat/MicroBitCompat.h

項目 メソッド 説明
開始 void fiber_add_idle_component(CodalComponent *c); MicroBitComponentクラスのstatusメンバー変数で、DEVICE_COMPONENT_STATUS_IDLE_TICKビットフラグを立てます。
呼出元 void component_callback(Event evt); statusメンバー変数のDEVICE_COMPONENT_STATUS_IDLE_TICKビットフラグが立っているインスタンスのidleCollback()メソッドを呼び出します。
解除 (なし) インスタンスの破棄で除外され(void CodalComponent::removeComponent())、呼び出しが停止します。

コンストラクタの引数

各BLEサービスのヘッダーファイル(inc/bluetooth/MicroBit*Service.h)で、コンストラクタの引数を確認できます。
全てのサービスでBLEDeviceインスタンスの参照を渡していますが、V2のクラス内部では、メンバー変数への保持も、アクセスもありません。これは単に引数の数と順序を統一しているだけのようです。統一されていることで、利用元の一つであるpxt-microbitのbluetoothブロックでは、バージョンの違いを切り分ける必要がありません(bluetooth.cpp)。
ボタンサービスUARTサービス以外は、センサー・ドライバのインスタンスの参照を渡しています。
ボタンサービスでは、ボタンA/Bのイベントをクラス内でlistenしています(V1/V2)。
UARTサービスでは、送受信バッファの既定値を変更できるようにしています(ボタンやセンサーを使用しません)。

BLEサービス V1 V2
加速度計サービス MicroBitAccelerometerService(BLEDevice &_ble, MicroBitAccelerometer &_acclerometer); MicroBitAccelerometerService( BLEDevice &_ble, codal::Accelerometer &_accelerometer);
ボタンサービス MicroBitButtonService(BLEDevice &_ble); MicroBitButtonService( BLEDevice &_ble);
入出力端子サービス MicroBitIOPinService(BLEDevice &_ble, MicroBitIO &_io); MicroBitIOPinService(BLEDevice &_ble, MicroBitIO &_io);
LEDサービス MicroBitLEDService(BLEDevice &_ble, MicroBitDisplay &_display); MicroBitLEDService( BLEDevice &_ble, MicroBitDisplay &_display);
温度計サービス MicroBitTemperatureService(BLEDevice &_ble, MicroBitThermometer &_thermometer); MicroBitTemperatureService( BLEDevice &_ble, MicroBitThermometer &_thermometer);
磁力計サービス MicroBitMagnetometerService(BLEDevice &_ble, MicroBitCompass &_compass); MicroBitMagnetometerService(BLEDevice &_ble, codal::Compass &_compass);
UARTサービス MicroBitUARTService(BLEDevice &_ble, uint8_t rxBufferSize = MICROBIT_UART_S_DEFAULT_BUF_SIZE, uint8_t txBufferSize = MICROBIT_UART_S_DEFAULT_BUF_SIZE); MicroBitUARTService(BLEDevice &_ble, uint8_t rxBufferSize = MICROBIT_UART_S_DEFAULT_BUF_SIZE, uint8_t txBufferSize = MICROBIT_UART_S_DEFAULT_BUF_SIZE);

BLE(Gattサーバー)の操作

ペリフェラルとしてBLEサービスを提供するために、Gattサーバーを操作しますが、V1とV2とではその実装方法が異なります。
Gattサーバーを操作する為に、V1の場合は、uBit.bleに委譲した実装を行い、V2の場合は、MicroBitBLEServiceクラスを継承した実装を行います。

サービスとキャラクタスティックの構築(コンストラクタ)

コンストラクタで、サービスとキャラクタスティックを構築しています。
V1の場合は、キャラクタスティックのハンドルを個々に保持しますが、V2の場合は、配列として保持し、その添え字でアクセスします。

BLEサービス V1 V2
加速度計サービス MicroBitAccelerometerService MicroBitAccelerometerService
ボタンサービス MicroBitButtonService MicroBitButtonService
入出力端子サービス MicroBitIOPinService MicroBitIOPinService
LEDサービス MicroBitLEDService MicroBitLEDService
温度計サービス MicroBitTemperatureService MicroBitTemperatureService
磁力計サービス MicroBitMagnetometerService MicroBitMagnetometerService
UARTサービス MicroBitUARTService MicroBitUARTService

UARTサービス (V1) のコンストラクタ

microbit-dal/source/bluetooth/MicroBitUARTService.cpp

/**
 * Constructor for the UARTService.
 * @param _ble an instance of BLEDevice
 * @param rxBufferSize the size of the rxBuffer
 * @param txBufferSize the size of the txBuffer
 *
 * @note defaults to 20
 */
MicroBitUARTService::MicroBitUARTService(BLEDevice &_ble, uint8_t rxBufferSize, uint8_t txBufferSize) : ble(_ble)
{
    rxBufferSize += 1;
    txBufferSize += 1;

    txBuffer = (uint8_t *)malloc(txBufferSize);
    rxBuffer = (uint8_t *)malloc(rxBufferSize);

    rxBufferHead = 0;
    rxBufferTail = 0;
    this->rxBufferSize = rxBufferSize;

    txBufferHead = 0;
    txBufferTail = 0;
    this->txBufferSize = txBufferSize;

    GattCharacteristic rxCharacteristic(UARTServiceRXCharacteristicUUID, rxBuffer, 1, rxBufferSize, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);

    txCharacteristic = new GattCharacteristic(UARTServiceTXCharacteristicUUID, txBuffer, 1, txBufferSize, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE);

    GattCharacteristic *charTable[] = {txCharacteristic, &rxCharacteristic};

    GattService uartService(UARTServiceUUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));

    _ble.addService(uartService);

    this->rxCharacteristicHandle = rxCharacteristic.getValueAttribute().getHandle();

    _ble.gattServer().onDataWritten(this, &MicroBitUARTService::onDataWritten);
    _ble.gattServer().onConfirmationReceived(on_confirmation);
}

UARTサービス (V2) のコンストラクタ

codal-microbit-v2/source/bluetooth/MicroBitUARTService.cpp

/**
 * Constructor for the UARTService.
 * @param _ble an instance of BLEDevice
 * @param rxBufferSize the size of the rxBuffer
 * @param txBufferSize the size of the txBuffer
 *
 * @note defaults to 20
 */
MicroBitUARTService::MicroBitUARTService(BLEDevice &_ble, uint8_t rxBufferSize, uint8_t txBufferSize)
{
    // Initialise our characteristic values.
    txBufferHead = 0;
    txBufferTail = 0;
    
    rxBufferSize += 1;
    txBufferSize += 1;

    // Allocate memory for rxBuffer, rx characteristic, txBuffer, tx characteristic
    int size = rxBufferSize + txBufferSize + 2 * MICROBIT_UART_S_ATTRSIZE;
    rxBuffer = (uint8_t *)malloc(size);
    txBuffer = rxBuffer + rxBufferSize + MICROBIT_UART_S_ATTRSIZE;
    
    memclr( rxBuffer, size);

    rxBufferHead = 0;
    rxBufferTail = 0;
    this->rxBufferSize = rxBufferSize;

    txBufferHead = 0;
    txBufferTail = 0;
    this->txBufferSize = txBufferSize;

    txValueSize  = 0;

    waitingForEmpty = false;

    // Register the base UUID and create the service.
    RegisterBaseUUID( base_uuid);
    CreateService( serviceUUID);

    // Create the data structures that represent each of our characteristics in Soft Device.
    CreateCharacteristic( mbbs_cIdxRX, charUUID[ mbbs_cIdxRX],
                          rxBuffer + rxBufferSize,
                          0, MICROBIT_UART_S_ATTRSIZE,
                          microbit_propWRITE | microbit_propWRITE_WITHOUT);

    CreateCharacteristic( mbbs_cIdxTX, charUUID[ mbbs_cIdxTX],
                          txBuffer + txBufferSize,
                          0, MICROBIT_UART_S_ATTRSIZE,
                          microbit_propINDICATE);
}

キャラクタスティックとそのプロパティ

各コンストラクタでは、サービスの仕様に合わせて、キャラクタスティックとそのプロパティを指定しています。
READAUTHプロパティに関しては、V1とV2とで仕様が異なりますので、詳細は「onDataRead()」の章を参照してください。

# プロパティ V1 V2
1 READ GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ microbit_propREAD
2 READAUTH (setReadAuthorizationCallback) microbit_propREADAUTH
3 NOTIFY GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY microbit_propNOTIFY
4 WRITE GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE microbit_propWRITE
5 WRITE_WITHOUT GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE microbit_propWRITE_WITHOUT
6 INDICATE GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE microbit_propINDICATE

加速度計サービス - MicroBitAccelerometerService

  • (V1) accelerometerData
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
  • (V1) accelerometerPeriod
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
  • (V2) mbbs_cIdxDATA
    microbit_propREAD | microbit_propNOTIFY
  • (V2) mbbs_cIdxPERIOD
    microbit_propREAD | microbit_propWRITE

ボタンサービス - MicroBitButtonService

  • (V1) buttonAData
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
  • (V1) buttonBData
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
  • (V2) mbbs_cIdxA
    microbit_propREAD | microbit_propNOTIFY
  • (V2) mbbs_cIdxB
    microbit_propREAD | microbit_propNOTIFY

入出力端子サービス - MicroBitIOPinService

  • (V1) ioPinServiceAD
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
  • (V1) ioPinServiceIO
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
  • (V1) ioPinServicePWM
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
  • (V1) ioPinServiceData
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
    | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
  • (V2) mbbs_cIdxADC
    microbit_propREAD | microbit_propWRITE
  • (V2) mbbs_cIdxIO
    microbit_propREAD | microbit_propWRITE
  • (V2) mbbs_cIdxPWM
    microbit_propWRITE
  • (V2) mbbs_cIdxDATA
    microbit_propREAD | microbit_propWRITE
    | microbit_propNOTIFY | microbit_propREADAUTH

LEDサービス - MicroBitLEDService

  • (V1) matrix
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
  • (V1) text
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
  • (V1) scrollingSpeed
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
  • (V2) mbbs_cIdxMATRIX
    microbit_propWRITE | microbit_propREAD | microbit_propREADAUTH
  • (V2) mbbs_cIdxTEXT
    microbit_propWRITE
  • (V2) mbbs_cIdxSPEED
    microbit_propWRITE | microbit_propREAD

温度計サービス - MicroBitTemperatureService

  • (V1) temperatureData
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
  • (V1) temperaturePeriod
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
  • (V2) mbbs_cIdxDATA
    microbit_propREAD | microbit_propNOTIFY
  • (V2) mbbs_cIdxPERIOD
    microbit_propREAD | microbit_propWRITE

磁力計サービス - MicroBitMagnetometerService

  • (V1) magnetometerData
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
  • (V1) magnetometerBearing
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
  • (V1) magnetometerPeriod
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
  • (V1) magnetometerCalibration
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
  • (V2) mbbs_cIdxDATA
    microbit_propREAD | microbit_propNOTIFY
  • (V2) mbbs_cIdxBEARING
    microbit_propREAD | microbit_propNOTIFY
  • (V2) mbbs_cIdxPERIOD
    microbit_propREAD | microbit_propWRITE
  • (V2) mbbs_cIdxCALIB
    microbit_propWRITE | microbit_propNOTIFY

UARTサービス - MicroBitUARTService

  • (V1) rx
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
    | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE
  • (V1) tx
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE
  • (V2) mbbs_cIdxRX
    microbit_propWRITE
    | microbit_propWRITE_WITHOUT
  • (V2) mbbs_cIdxTX
    microbit_propINDICATE

Gattサーバーからのコールバック

各サービスでは、Gattサーバーからのコールバック関数が実装されています。
V1の場合は、キャラクタスティックのハンドルをメンバー変数ので保持していますので、その値とパラメータのhandle値とを比較して、キャラクタスティックを特定します。V2の場合は、ヘルパー関数valueHandle()でハンドル値に変換して、特定します。

BLEサービス onDataRead onConfirmationReceived onDataWritten
加速度計サービス onDataWritten
ボタンサービス
入出力端子サービス onDataRead onDataWritten
LEDサービス onDataRead onDataWritten
温度計サービス onDataWritten
磁力計サービス onDataWritten
UARTサービス (V1)on_confirmation/(V2)onConfirmation onDataWritten

valueHandleの使用例(V2)

codal-microbit-v2/source/bluetooth/MicroBitUARTService.cpp
/**
  * A callback function for whenever a Bluetooth device writes to our RX characteristic.
  */
void MicroBitUARTService::onDataWritten(const microbit_ble_evt_write_t *params)
{
    if (params->handle == valueHandle( mbbs_cIdxRX))
    {
        uint16_t bytesWritten = params->len;
        
        // ...
        
    }
}

onDataRead()

入出力端子サービスLEDサービスでは、onDataRead()コールバック関数が実装されています。
入力値を最新化し、更新しています。

V1の場合
対象のキャラクタスティックのsetReadAuthorizationCallback関数によって、コールバック関数(onDataRead())を登録します。V2のようなプロパティは用意されていませんので、プロパティの指定は不要です。

void MicroBitXxxxService::onDataRead(GattReadAuthCallbackParams *params){}
microbit-dal/source/bluetooth/MicroBitIOPinService.cpp
    ioPinServiceDataCharacteristic->setReadAuthorizationCallback(this, &MicroBitIOPinService::onDataRead);
microbit-dal/source/bluetooth/MicroBitLEDService.cpp
    matrixCharacteristic.setReadAuthorizationCallback(this, &MicroBitLEDService::onDataRead);

V2の場合
対象のキャラクタスティックのプロパティにmicrobit_propREADAUTHを追加し、onDataRead()をオーバーライドします。GattサーバーからonDataRead()が呼び出されます。

codal-microbit-v2/inc/MicroBitBLEService.h
    virtual void onAuthorizeRead(       const microbit_ble_evt_t *p_ble_evt);

onDataWritten()

ボタンサービス以外のサービスでは、onDataWritten()コールバック関数が実装されています。
セントラルからの書き込みに対して、処理しています。

V1の場合
コールバック関数(onDataWritten())を実装し、GattサーバーのonDataWritten関数で、コールバック関数(onDataWritten())を登録します。

void MicroBitXxxService::onDataWritten(const GattWriteCallbackParams *params) {}
microbit-dal/source/bluetooth/MicroBitUARTService.cpp
    _ble.gattServer().onDataWritten(this, &MicroBitUARTService::onDataWritten);

V2の場合

onDataWritten()関数をオーバーライドします。GattサーバーからonDataWritten()が呼び出されます。

codal-microbit-v2/source/bluetooth/MicroBitUARTService.cpp

/**
  * A callback function for whenever a Bluetooth device writes to our RX characteristic.
  */
void MicroBitUARTService::onDataWritten(const microbit_ble_evt_write_t *params)
{
    // ...
}

onConfirmationReceived()

UARTサービスには、onConfirmationReceived()コールバック関数が実装されています。
TXバッファを空にし、イベント(MICROBIT_ID_NOTIFY, MICROBIT_UART_S_EVT_TX_EMPTY)を生成しています。

V1の場合
コールバック関数(on_confirmation())を実装し、GattサーバーのonConfirmationReceived関数で、コールバック関数(on_confirmation())を登録します。
尚、on_confirmation()は、MicroBitUARTServiceクラスのメンバー関数ではありません。

microbit-dal/source/bluetooth/MicroBitUARTService.cpp

/**
  * A callback function for whenever a Bluetooth device consumes our TX Buffer
  */
void on_confirmation(uint16_t handle)
{
    if(handle == txCharacteristic->getValueAttribute().getHandle())
    {
        txBufferTail = txBufferHead;
        MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_UART_S_EVT_TX_EMPTY);
    }
}

microbit-dal/source/bluetooth/MicroBitUARTService.cpp
    _ble.gattServer().onConfirmationReceived(on_confirmation);

V2の場合

onConfirmation()関数をオーバーライドします。GattサーバーからonConfirmation()が呼び出されます。

codal-microbit-v2/source/bluetooth/MicroBitUARTService.cpp

/**
  * A callback function for whenever a Bluetooth device consumes our TX Buffer
  */
void MicroBitUARTService::onConfirmation( const microbit_ble_evt_hvc_t *params)
{
    if ( params->handle == valueHandle( mbbs_cIdxTX))
    {
        txBufferTail = ( (int)txBufferTail + txValueSize) % txBufferSize;
        txValueSize = 0;
        bool async = !waitingForEmpty;
        MicroBitEvent(MICROBIT_ID_NOTIFY, MICROBIT_UART_S_EVT_TX_EMPTY);
        if ( async)
            sendNext();
    }
}

MakeCodeのBluetoothブロック

MakeCodeのBluetoothブロックのソースコードは、microsoft/pxt-microbitpxt-microbit/libs/bluetooth/にあり、bluettoth.tsまたはbluetooth.cppで定義・実装されています。
lancaster-university/microbit-dallancaster-university/codal-microbit-v2で実装されているクラス(ライブラリ)が、bluetooth.cppから呼び出されています。

サービスの開始(追加)

startXxxService関数でサービスを追加し、開始することができます。単にクラスのインスタンスを生成するだけで、サービスが有効化される仕組みになっています。

サービス開始 bluetooth.ts bluetooth.cpp
加速度計サービス void startAccelerometerService()
ボタンサービス void startButtonService()
入出力端子サービス void startIOPinService()
LEDサービス void startLEDService()
温度計サービス void startTemperatureService()
磁力計サービス void startMagnetometerService()
UARTサービス void startUartService()

UARTサービスに関しては、他のブロックで、インスタンスを操作するために、インスタンス変数をファイル内で保持しています。

pxt-microbit/libs/bluetooth/bluetooth.cpp
namespace bluetooth {
    MicroBitUARTService *uart = NULL;
    // ...

    /**
    *  Starts the Bluetooth UART service
    */
    //% help=bluetooth/start-uart-service
    //% blockId=bluetooth_start_uart_service block="bluetooth uart service"
    //% parts="bluetooth" advanced=true
    void startUartService() {
        if (uart) return;
        // 61 octet buffer size is 3 x (MTU - 3) + 1
        // MTU on nRF51822 is 23 octets. 3 are used by Attribute Protocol header data leaving 20 octets for payload
        // So we allow a RX buffer that can contain 3 x max length messages plus one octet for a terminator character
        uart = new MicroBitUARTService(*uBit.ble, 61, 60);
    }

    // ...
}

Bluetoothのブロック

Bluetooth共通のブロックとして、次の3つがあります。

ブロック bluetooth.ts bluetooth.cpp
接続されたとき void onBluetoothConnected(Action body)
接続が切断されたとき void onBluetoothDisconnected(Action body)
送信強度を設定 void setTransmitPower(int power)

onBluetoothConnectedonBluetoothDisconnectedの実装を確認すると、デバイスID:MICROBIT_ID_BLEの値:MICROBIT_BLE_EVT_CONNECTEDや値:MICROBIT_BLE_EVT_DISCONNECTEDといったイベント識別子をregisterWithDal()で登録しています。
これらのイベントが発生するとそれぞれのbody(無名関数)がコールバックされる仕組みです。

pxt-microbit/libs/bluetooth/bluetooth.cpp

    /**
     * Register code to run when the micro:bit is connected to over Bluetooth
     * @param body Code to run when a Bluetooth connection is established
     */
    //% help=bluetooth/on-bluetooth-connected weight=20
    //% blockId=bluetooth_on_connected block="on bluetooth connected" blockGap=8
    //% parts="bluetooth"
    void onBluetoothConnected(Action body) {
        registerWithDal(MICROBIT_ID_BLE, MICROBIT_BLE_EVT_CONNECTED, body);
    }    

     /**
     * Register code to run when a bluetooth connection to the micro:bit is lost
     * @param body Code to run when a Bluetooth connection is lost
     */
    //% help=bluetooth/on-bluetooth-disconnected weight=19
    //% blockId=bluetooth_on_disconnected block="on bluetooth disconnected"
    //% parts="bluetooth"
    void onBluetoothDisconnected(Action body) {
        registerWithDal(MICROBIT_ID_BLE, MICROBIT_BLE_EVT_DISCONNECTED, body);
    } 

setTransmitPowerの実装を確認すると、uBit.bleManagersetTransmitPower関数を呼び出していることがわかります。

pxt-microbit/libs/bluetooth/bluetooth.cpp

    /**
    * Sets the bluetooth transmit power between 0 (minimal) and 7 (maximum).
    * @param power power level between 0 (minimal) and 7 (maximum), eg: 7.
    */
    //% parts=bluetooth weight=5 help=bluetooth/set-transmit-power advanced=true
    //% blockId=bluetooth_settransmitpower block="bluetooth set transmit power %power"
    void setTransmitPower(int power) {
        uBit.bleManager.setTransmitPower(min(MICROBIT_BLE_POWER_LEVELS-1, max(0, power)));
    }

このuBit.bleManagerの実装は、V1とV2とで異なります。

uBit.bleの正体
V1/V2ともにuBit.bleは、BLEDevice型(ble.h)ですが、V2におけるuBit.bleの実体は、MicrobitBLEManager型(クラス)です。
ソースコード

codal-microbit-v2/inc/bluetooth/MicroBitBLEManager.h

class MicroBitBLEManager;
typedef MicroBitBLEManager BLEDevice;

UARTサービスに関するブロック

UARTサービスに関するブロックとして、次の3つがあります。

ブロック bluetooth.ts bluetooth.cpp
データを受信したとき void onUartDataReceived(String delimiters, Action body)
手前まで読み取る String uartReadUntil(String del)
文字列を書き出す void uartWriteString(String data)
数値を文字で書き出す export function uartWriteNumber(value: number): void
write line export function uartWriteLine(data: string): void
名前と値を書き出す export function uartWriteValue(name: string, value: number): void

onUartDataReceived関数では、MicroBitUARTServiceクラスのeventOn()で受信待ちの区切り文字を指定し、イベント(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_DELIM_MATCH)で、bodyがコールバックされるように登録しています。

pxt-microbit/libs/bluetooth/bluetooth.cpp

    /**
    * Registers an event to be fired when one of the delimiter is matched.
    * @param delimiters the characters to match received characters against.
    */
    //% help=bluetooth/on-uart-data-received
    //% weight=18 blockId=bluetooth_on_data_received block="bluetooth|on data received %delimiters=serial_delimiter_conv"
    void onUartDataReceived(String delimiters, Action body) {
      startUartService();
      uart->eventOn(MSTR(delimiters));
      registerWithDal(MICROBIT_ID_BLE_UART, MICROBIT_UART_S_EVT_DELIM_MATCH, body);
    }

uartWriteString関数やuartReadUntil関数もまたMicroBitUARTServiceクラスの関数を呼び出しています。

pxt-microbit/libs/bluetooth/bluetooth.cpp

    //%
    void uartWriteString(String data) {
        startUartService();
    	uart->send(MSTR(data));
    }    

    //%
    String uartReadUntil(String del) {
        startUartService();
        return PSTR(uart->readUntil(MSTR(del)));
    }    

文字列型の相互変換(StringとManagedString)
StringManagedStringとを相互に変換するために、MSTR()PSTR()が使われています。

  • MSTR() - StringからManagedStringへ変換
  • PSTR() - ManagedStringからStringへ変換

残りの3つの関数(uartWriteLine,uartWriteNumber,uartWriteValue)は、TypeScriptでuartWriteString関数を呼び出しています。

pxt-microbit/libs/bluetooth/bluetooth.ts

    /**
    *  Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device.
    */
    //% help=bluetooth/uart-write-line weight=79
    //% blockId=bluetooth_uart_line block="bluetooth uart|write line %data" blockGap=8
    //% parts="bluetooth" advanced=true
    export function uartWriteLine(data: string): void {
        uartWriteString(data + serial.NEW_LINE);
    }

    /**
     * Prints a numeric value to the serial
     */
    //% help=bluetooth/uart-write-number weight=79
    //% weight=89 blockGap=8 advanced=true
    //% blockId=bluetooth_uart_writenumber block="bluetooth uart|write number %value"
    export function uartWriteNumber(value: number): void {
        uartWriteString(value.toString());
    }

    /**
     * Writes a ``name: value`` pair line to the serial.
     * @param name name of the value stream, eg: x
     * @param value to write
     */
    //% weight=88 weight=78
    //% help=bluetooth/uart-write-value advanced=true
    //% blockId=bluetooth_uart_writevalue block="bluetooth uart|write value %name|= %value"
    export function uartWriteValue(name: string, value: number): void {
        uartWriteString((name ? name + ":" : "") + value + NEW_LINE);
    }

TypeScriptからC++の呼び出し
pxtではTypeScriptからC++を呼び出すしくみが備わっています。

TypeScript側でブロックの定義をし、//% shim=bluetooth::uartWriteStringで、C++側の関数に関連付けます。
尚、このTypeScript側の関数内の処理は、MakeCodeエディタのシミュレーターで実行され、実機では実行されません。

pxt-microbit/libs/bluetooth/bluetooth.ts

    /**
    *  Writes to the Bluetooth UART service buffer. From there the data is transmitted over Bluetooth to a connected device.
    */
    //% help=bluetooth/uart-write-string weight=80
    //% blockId=bluetooth_uart_write block="bluetooth uart|write string %data" blockGap=8
    //% parts="bluetooth" shim=bluetooth::uartWriteString advanced=true
    export function uartWriteString(data: string): void {
        console.log(data)
    }

C++側で関連付けられた関数には、//%だけを記述し、関数内に実装します。これが、実機で実行されます。

pxt-microbit/libs/bluetooth/bluetooth.cpp

    //%
    void uartWriteString(String data) {
        startUartService();
    	uart->send(MSTR(data));
    }

BLEサービスを実装するためのヒント

MakeCode for micro:bitで提供される標準(の拡張機能)であるbluetoothの実装を確認しましたが、確認する中で得られた関連するヒントを残しておきます。

BLEサービスを実装するためのクラス設計(例)

架空のOneサービスを実装するためのクラス設計の例を次のUML図に示しました。尚、図中のnamespace(dal::, codal::)は不要です。V1/V2のクラスモジュールを区別するために便宜上、namespaceを記しています。

MicroBitComponentを継承したMicroBitOneServiceBase仮想クラスを定義し、バージョンに依存する部分をMicroBitOneServiceクラスで、実装します。BLEサービスの振る舞いが複雑な場合、仮想クラス(基底クラス)を設けることで、ソースコードの重複を少なくすることができそうです。

MicroBitOneServiceBase仮想クラスの継承元はどちらか一方です
dal/codal(V1/V2)の両者を1つのクラス図で説明しているため、MicroBitOneServiceBase仮想クラスが多重継承しているように見えますが、V1/V2用にそれぞれ、どちらか一方を継承します。

ソースコード上では、プリプロセッサ ディレクティブによる切り分けた実装を行います(#if MICROBIT_CODAL/#else/#endif)。
「micro:bit ランタイムとNordic nRF5 SDK」

bluetoothに関する構成情報(pxt.json)

MakeCodeのブロック(ライブラリ)やMakeCodeエディタで作成したプログラム(ブロックベースやテキストベース)は、Microsoft Programming Experience Toolkit (略してPXT)を使ってビルドされますが、そのビルドに関する構成情報をpxt.jsonファイルで定義します。

例えば、bluetooth拡張機能のpxt-microbit/libs/bluetooth/pxt.jsonでは、次のようにbluetoothに関する構成情報が定義されています。

  • "config" - bluetoothに関する設定
  • "optionalConfig" - BLEスタック(内部ライブラリ)の設定
  • "userConfigs" - MakeCodeエディタの「プロジェクトの設定」で変更可能な項目の定義

尚、名称が「"microbit-dal"」となっているキーがありますが、 DAL(V1)CODAL(V2) とで共通です。

これ以外にも、設定可能な項目として、サンプルコード(lancaster-university/microbit-samples)とそのコメントにいくつかの例があります。

pxt-microbit/libs/bluetooth/pxt.json

    "yotta": {
        "config": {
            "microbit-dal": {
                "bluetooth": {
                    "enabled": 1
                }
            }
        },
        "optionalConfig": {
            "microbit-dal": {
                "stack_size": 1280,
                "gatt_table_size": "0x700"
            }
        },
        "userConfigs": [
            {
                "description": "Disable Bluetooth Event Service",
                "config": {
                    "microbit-dal": {
                        "bluetooth": {
                            "event_service": 0
                        }
                    }
                }
            }
        ]
    }

ブロック定義

拡張機能のブロックの実装は、Defining blocksの仕様に従って、TypeScript(*.ts)やC++(*.cpp)で実装します。
MakeCodeエディタがブロックとして認識するために、JsDoc形式のコメントとアノテーション(//%)を関数に記述します。C++(*.cpp)側のみでの実装も可能ですが、MakeCodeエディタが警告を表示することがありますので、TypeScript(*.ts)側の関数でコメントとアノテーションを記述し、C++(*.cpp)側では、//%のみのアノテーションを記述することを推奨します。

プログラムのエラー : Cannot read properties of undefined (reading '<functionName>')

C++(*.cpp)側のみで、ブロックの定義を記述すると、MakeCodeエディタでプログラムのエラーが発生します。

まとめ

MakeCodeでmicro:bitのBLEサービスを実装したい為、標準のbluetooth拡張機能に関するソースコードを確認しました。

  1. 標準のbluetooth拡張機能のブロックを列挙しました
  2. micro:bitのバージョンの違いを確認しました
  3. クラス構造を確認しました
  4. 各サービスの実装をソースコードで確認しました
  5. ブロックの実装をソースコードで確認しました

確認する中で、いくつかのヒントが得られました。

  • BLEサービスを実装するためのクラス設計
  • pxt.jsonの構成情報
  • ブロック定義

これを参考に、独自のBLEサービスを実装することができそうです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?