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"との共存はできません)。
- Bluetooth 加速度計サービス
- Bluetooth ボタンサービス
- Bluetooth 入出力端子サービス
- Bluetooth LEDサービス
- Bluetooth 温度計サービス
- Bluetooth 磁力計サービス
- Bluetooth その他 UARTサービス
BLEサービスに関連するブロックも用意されています。
- Bluetooth 接続されたとき
- Bluetooth 接続が切断されたとき
- Bluetooth データを受信したとき
- Bluetooth その他 UART 数値を文字で書き出す
- Bluetooth その他 UART 文字列を書き出す
- Bluetooth その他 UART write line
- Bluetooth その他 UART 名前と値を書き出す
- Bluetooth その他 UART つぎのいずれかの文字の手前まで読み取る
- 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 |
#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()の実装内容
// (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()の実装内容
// (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(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) のコンストラクタ
/**
* 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) のコンストラクタ
/**
* 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)
/**
* 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){}
ioPinServiceDataCharacteristic->setReadAuthorizationCallback(this, &MicroBitIOPinService::onDataRead);
matrixCharacteristic.setReadAuthorizationCallback(this, &MicroBitLEDService::onDataRead);
V2の場合
対象のキャラクタスティックのプロパティにmicrobit_propREADAUTH
を追加し、onDataRead()
をオーバーライドします。GattサーバーからonDataRead()
が呼び出されます。
virtual void onAuthorizeRead( const microbit_ble_evt_t *p_ble_evt);
onDataWritten()
ボタンサービス以外のサービスでは、onDataWritten()
コールバック関数が実装されています。
セントラルからの書き込みに対して、処理しています。
V1の場合
コールバック関数(onDataWritten()
)を実装し、GattサーバーのonDataWritten
関数で、コールバック関数(onDataWritten()
)を登録します。
void MicroBitXxxService::onDataWritten(const GattWriteCallbackParams *params) {}
_ble.gattServer().onDataWritten(this, &MicroBitUARTService::onDataWritten);
V2の場合
onDataWritten()
関数をオーバーライドします。GattサーバーからonDataWritten()
が呼び出されます。
/**
* 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
クラスのメンバー関数ではありません。
/**
* 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);
}
}
_ble.gattServer().onConfirmationReceived(on_confirmation);
V2の場合
onConfirmation()
関数をオーバーライドします。GattサーバーからonConfirmation()
が呼び出されます。
/**
* 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-microbitのpxt-microbit/libs/bluetooth/にあり、bluettoth.tsまたはbluetooth.cppで定義・実装されています。
lancaster-university/microbit-dalやlancaster-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サービスに関しては、他のブロックで、インスタンスを操作するために、インスタンス変数をファイル内で保持しています。
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) |
onBluetoothConnectedやonBluetoothDisconnectedの実装を確認すると、デバイスID:MICROBIT_ID_BLE
の値:MICROBIT_BLE_EVT_CONNECTED
や値:MICROBIT_BLE_EVT_DISCONNECTED
といったイベント識別子をregisterWithDal()
で登録しています。
これらのイベントが発生するとそれぞれのbody
(無名関数)がコールバックされる仕組みです。
/**
* 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.bleManager
のsetTransmitPower
関数を呼び出していることがわかります。
/**
* 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とで異なります。
- (V1) MicroBitBLEManager - microbit-dal/inc/bluetooth/MicroBitBLEManager.h
- (V2) MicroBitBLEManager - codal-microbit-v2/inc/bluetooth/MicroBitBLEManager.h
uBit.bleの正体
V1/V2ともにuBit.ble
は、BLEDevice
型(ble.h
)ですが、V2におけるuBit.ble
の実体は、MicrobitBLEManager
型(クラス)です。
→ ソースコード
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
がコールバックされるように登録しています。
/**
* 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
クラスの関数を呼び出しています。
//%
void uartWriteString(String data) {
startUartService();
uart->send(MSTR(data));
}
//%
String uartReadUntil(String del) {
startUartService();
return PSTR(uart->readUntil(MSTR(del)));
}
文字列型の相互変換(StringとManagedString)
String
とManagedString
とを相互に変換するために、MSTR()
やPSTR()
が使われています。
- MSTR() - StringからManagedStringへ変換
- PSTR() - ManagedStringからStringへ変換
残りの3つの関数(uartWriteLine
,uartWriteNumber
,uartWriteValue
)は、TypeScriptでuartWriteString
関数を呼び出しています。
/**
* 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エディタのシミュレーターで実行され、実機では実行されません。
/**
* 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++側で関連付けられた関数には、//%
だけを記述し、関数内に実装します。これが、実機で実行されます。
//%
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)とそのコメントにいくつかの例があります。
"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拡張機能に関するソースコードを確認しました。
- 標準のbluetooth拡張機能のブロックを列挙しました
- micro:bitのバージョンの違いを確認しました
- クラス構造を確認しました
- 各サービスの実装をソースコードで確認しました
- ブロックの実装をソースコードで確認しました
確認する中で、いくつかのヒントが得られました。
- BLEサービスを実装するためのクラス設計
- pxt.jsonの構成情報
- ブロック定義
これを参考に、独自のBLEサービスを実装することができそうです。