LoginSignup
1
3

More than 3 years have passed since last update.

家庭用サイクルトレーナーにmicro:bitをつなぐ(その4)- スマホ・アプリでケイデンスやスピードを表示できるようにBLEのGATTサービス(FTMP/FTMS)を実装する

Last updated at Posted at 2021-02-19

家庭用サイクルトレーナーにmicro:bitをつなぐ(その4)

前回までの記事(その1その2その3)では、家庭用サイクルトレーナーのワイヤレス運動測量計とmicro:bitをつなぎ、MicroBitComponentを継承し、ワイヤレス運動測量計のSTEP信号からケイデンスとスピードを求めるモジュールをドライバー化したMicroBitIndoorBikeStepSensorクラスを作成しました。

リンク

  1. 家庭用サイクルトレーナーにmicro:bitをつなぐ(その1)
    ワイヤレス運動測量計から出力されるクランク回転センサーの信号をモニターする
  2. 家庭用サイクルトレーナーにmicro:bitをつなぐ(その2)
    STEP信号からワイヤレス運動測量計に表示されているスピードをmicro:bitで推定する
  3. 家庭用サイクルトレーナーにmicro:bitをつなぐ(その3)
    STEP信号からケイデンスとスピードを求めるモジュールをドライバー(アイドル・コンポーネント)として作成する

これまで、ケイデンスやスピードをUSBシリアル通信でモニターしていましたが、これをBLE通信でスマホ・アプリで表示できるようにGATTサービスであるFTMP/FTMSとして実装します。

IMG_20210219_081420.jpg

仕様書の入手と斜め読み

FTMP - Fitness Machine Profile 1.0
FTMS - Fitness Machine Service 1.0

FTMP - Fitness Machine Profile

まずは、仕様書を入手し、FTMP(Fitness Machine Profile)の方をサラッと読みましょう。
次のようなことが書かれています。

Indoor Bike(室内バイク)のようなフィットネスマシンからデータを取得したり、制御したりできます。
実装するサービスは、Fitness Machine Service(必須)、User Data Service(任意)、Device Information Service(任意)の3つです。Fitness Machine Serviceにおいて、その特性(Characteristics)として、Fitness Machine Feature(必須)やIndoor Bike Data(任意)、Training Status(必須)、Fitness Machine Control Point(任意)、Fitness Machine Status(任意)を実装します。
Fitness Machine Control Point特性を実装した場合、Procedure(Op Code)Request ControlResetの実装は必須です。

FTMS - Fitness Machine Service

続いて、FTMS(Fitness Machine Service)の方もサラッと読みましょう。
次のようなことが書かれています。

Fitness Machine Service(FTMS)は、トレーニング関連のデータを提供したり(例えば、Indoor Bike Data特性)、トレーニング状況(Training Status特性)やマシン状態(Fitness Machine Status特性)を提供したりできます。また、Fitness Machine Control Point特性を公開することにより、マシンを遠隔操作できます。
Fitness Machine Feature特性で、実装するサービスがサポートする状況に合わせたビット値を定義します。
Indoor Bike Data特性は、Flags Fieldと各データ項目とで構成されており、Flags Fieldでサポート(提供)するデータ項目を定義します。
Instantaneous Speed Fieldは、必ずデータ項目として含まれます。
Training Status特性は、フラグとトレーニング状況及び場合によってはトレーニング状況に関する文字列で構成されます。
Fitness Machine Control Point特性は、Op Code(1オクテット)とParameter(0~18オクテット)とで構成されており、Op Code0x00(Request Control)0x01(Reset)0x07(Start or Resume)0x08(Stop or Pause)の実装は必須です。また、各要求に対して、Op Code0x80(Response Code)と、Request Op CodeResult Code等を応答として返す必要があります。
Fitness Machine Control Point特性を実装した場合、Fitness Machine Status特性も実装します。

ケイデンスとスピードに対応したFTMSの実装

ソースコード

ソースコードのビルドは、Arm Mbed オンライン - https://ide.mbed.com/compiler/で可能です。

Source URLに https://github.com/jp-96/microbit-digital-capture4.git と入力し、Programとしてインポートして、コンパイルします。

構成ファイル - mbed_app.json

BLEを利用しますので、構成ファイル(mbed_app.json)を次のように記述します。また、"MICROBIT_BLE_DEVICE_INFORMATION_SERVICE=1",で、Device Information Serviceを有効化しています。

mbed_app.json
{
    "macros": [
        "MICROBIT_BLE_OPEN=1",
        "MICROBIT_BLE_DFU_SERVICE=0",
        "MICROBIT_BLE_EVENT_SERVICE=0",
        "MICROBIT_BLE_DEVICE_INFORMATION_SERVICE=1",

        "MICROBIT_SD_GATT_TABLE_SIZE=0x340"
    ]
}

アプリの構造 - main.cpp

前回作成したMicroBitIndoorBikeStepSensorクラスと今回作成したMicroBitIndoorBikeStepServiceとを組み合わせます。

main.cpp
#include "MicroBit.h"
#include "MicroBitIndoorBikeStepSensor.h"
#include "MicroBitIndoorBikeStepService.h"

MicroBit uBit;
MicroBitIndoorBikeStepSensor *sensor;
MicroBitIndoorBikeStepService *service;

void setup()
{
    sensor = new MicroBitIndoorBikeStepSensor(uBit);
    service = new MicroBitIndoorBikeStepService(uBit, *sensor);
    sensor->idleTick();
}

int main()
{
    // Initialise the micro:bit runtime.
    uBit.init();

    // Insert your code here!
    create_fiber(setup);

    // If main exits, there may still be other fibers running or registered event handlers etc.
    // Simply release this fiber, which will mean we enter the scheduler. Worse case, we then
    // sit in the idle task forever, in a power efficient sleep.
    release_fiber();
}

MicroBitIndoorBikeStepServiceクラスの実装

今回、実装したMicroBitIndoorBikeStepServiceクラスについて、その初期化(コンストラクタ)と、FTMSの各特性の実装を順に説明します。

ケイデンスとスピードへの対応

スピードに関しては、必ずサポートしなければならない為、フラグでのサポート指定は不要です。ケイデンスへの対応をする為、次のようにビットフラグを立てています。

MicroBitIndoorBikeStepService.h
:
:
/*
# Bit Definitions for the Indoor Bike Data Characteristic
#                                          000 (bits 13-15) Reserved for Future Use
#                                             0 (bit 12) Remaining Time Present
#                                              0 (bit 11) Elapsed Time Present
#                                               0 (bit 10) Metabolic Equivalent Present
#                                                0 (bit  9) Heart Rate Present
#                                                 0 (bit  8) Expended Energy Present
#                                                  0 (bit  7) Average Power Present
#                                                   0 (bit  6) Instantaneous Power Present
#                                                    0 (bit  5) Resistance Level Present
#                                                     0 (bit  4) Total Distance Present
#                                                      0 (bit  3) Average Cadence present
#                                                       1 (bit  2)*Instantaneous Cadence (uint16, 1/minute with a resolution of 0.5)
#                                                        0 (bit  1) Average Speed present
#                                                         0 (bit  0) More Data
#                                          5432109876543210 */
#define FTMP_FLAGS_INDOOR_BIKE_DATA_CHAR 0b0000000000000100
:
:
/*
# Definition of the bits of the Fitness Machine Features field
#                                                  000000000000000 (bits 17-31) Reserved for Future Use
#                                                                 0 (bit 16) User Data Retention Supported
#                                                                  0 (bit 15) Force on Belt and Power Output Supported
#                                                                   0 (bit 14) Power Measurement Supported
#                                                                    0 (bit 13) Remaining Time Supported
#                                                                     0 (bit 12) Elapsed Time Supported
#                                                                      0 (bit 11) Metabolic Equivalent Supported
#                                                                       0 (bit 10) Heart Rate Measurement Supported
#                                                                        0 (bit  9) Expended Energy Supported
#                                                                         0 (bit  8) Stride Count Supported
#                                                                          0 (bit  7) Resistance Level Supported
#                                                                           0 (bit  6) Step Count Supported
#                                                                            0 (bit  5) Pace Supported
#                                                                             0 (bit  4) Elevation Gain Supported
#                                                                              0 (bit  3) Inclination Supported
#                                                                               0 (bit  2) Total Distance Supported
#                                                                                1 (bit  1)*Cadence Supported
#                                                                                 0 (bit  0) Average Speed Supported
#                                                  10987654321098765432109876543210 */
#define FTMP_FLAGS_FITNESS_MACINE_FEATURES_FIELD 0b00000000000000000000000000000010

/*
# Definition of the bits of the Target Setting Features field
#                                                  000000000000000 (bits 17-31) Reserved for Future Use
#                                                                 0 (bit 16) Targeted Cadence Configuration Supported
#                                                                  0 (bit 15) Spin Down Control Supported
#                                                                   0 (bit 14) Wheel Circumference Configuration Supported
#                                                                    0 (bit 13) Indoor Bike Simulation Parameters Supported
#                                                                     0 (bit 12) Targeted Time in Five Heart Rate Zones Configuration Supported
#                                                                      0 (bit 11) Targeted Time in Three Heart Rate Zones Configuration Supported
#                                                                       0 (bit 10) Targeted Time in Two Heart Rate Zones Configuration Supported
#                                                                        0 (bit  9) Targeted Training Time Configuration Supported
#                                                                         0 (bit  8) Targeted Distance Configuration Supported
#                                                                          0 (bit  7) Targeted Stride Number Configuration Supported
#                                                                           0 (bit  6) Targeted Step Number Configuration Supported
#                                                                            0 (bit  5) Targeted Expended Energy Configuration Supported
#                                                                             0 (bit  4) Heart Rate Target Setting Supported
#                                                                              0 (bit  3) Power Target Setting Supported
#                                                                               0 (bit  2) Resistance Target Setting Supported
#                                                                                0 (bit  1) Inclination Target Setting Supported
#                                                                                 0 (bit  0) Speed Target Setting Supported
#                                                  10987654321098765432109876543210 */
#define FTMP_FLAGS_TARGET_SETTING_FEATURES_FIELD 0b00000000000000000000000000000000
:
:

Advertising Data の書き換えと追加

コンストラクタの初めで、Advertising Data関係のAppearanceCOMPLETE_LOCAL_NAMEとを書き換え、FTMSに関するAdvertising Dataを登録しています。

MicroBitIndoorBikeStepService.cpp
MicroBitIndoorBikeStepService::MicroBitIndoorBikeStepService(MicroBit &_uBit, MicroBitIndoorBikeStepSensor &_indoorBike, uint16_t id)
    : uBit(_uBit), indoorBike(_indoorBike)
{
    :
    :
    // BLE Appearance and LOCAL_NAME
    uBit.ble->gap().accumulateAdvertisingPayload(GapAdvertisingData::GENERIC_CYCLING);
    if (BLE_DEVICE_LOCAL_NAME_CHENGE)
    {
        uBit.ble->gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME
            , (const uint8_t *)BLE_DEVICE_LOCAL_NAME, sizeof(BLE_DEVICE_LOCAL_NAME)-1);
    }

    // FTMS - Service Advertising Data
    const uint8_t FTMS_UUID[sizeof(UUID::ShortUUIDBytes_t)] = {0x26, 0x18};
    uBit.ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, FTMS_UUID, sizeof(FTMS_UUID));
    uint8_t serviceData[2+1+2];
    struct_pack(serviceData, "<HBH", 0x1826, 0x01, 1<<5);
    uBit.ble->accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, serviceData, sizeof(serviceData));
    :
    :

}

FTMSサービスの構造と登録

全ての特性(Characteristic)のUUIDや送受信データサイズ、プロパティ等は、FTMP/FTMSの仕様書等で定義されていますので、それに従って、FTMSの特性(GattCharacteristic)を定義します。
定義した特性のリストを引数に指定したサービス(GattService)を定義し、uBit.ble->addService(service);で登録します。
サービスの登録後、各特性のハンドルをメンバー変数に保持します。

MicroBitIndoorBikeStepService.cpp
MicroBitIndoorBikeStepService::MicroBitIndoorBikeStepService(MicroBit &_uBit, MicroBitIndoorBikeStepSensor &_indoorBike, uint16_t id)
    : uBit(_uBit), indoorBike(_indoorBike)
{
    :
    :
    // Caractieristic
    GattCharacteristic  indoorBikeDataCharacteristic(
        UUID(0x2AD2)
        , (uint8_t *)&indoorBikeDataCharacteristicBuffer, 0, indoorBikeDataCharacteristicBufferSize
        , GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
    );
    GattCharacteristic  fitnessMachineControlPointCharacteristic(
        UUID(0x2AD9)
        , (uint8_t *)&fitnessMachineControlPointCharacteristicBuffer, 0, fitnessMachineControlPointCharacteristicBufferSize
        , GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE|GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_INDICATE
    );
    GattCharacteristic  fitnessMachineFeatureCharacteristic(
        UUID(0x2ACC)
        , (uint8_t *)&fitnessMachineFeatureCharacteristicBuffer, 0, fitnessMachineFeatureCharacteristicBufferSize
        , GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
    );
    GattCharacteristic  fitnessMachineStatusCharacteristic(
        UUID(0x2ADA)
        , (uint8_t *)&fitnessMachineStatusCharacteristicBuffer, 0, fitnessMachineStatusCharacteristicBufferSize
        , GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
    );
    GattCharacteristic  fitnessTrainingStatusCharacteristic(
        UUID(0x2AD3)
        , (uint8_t *)&fitnessTrainingStatusCharacteristicBuffer, 0, fitnessTrainingStatusCharacteristicBufferSize
        , GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
    );

    // Set default security requirements
    indoorBikeDataCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL);
    fitnessMachineControlPointCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL);
    fitnessMachineFeatureCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL);
    fitnessMachineStatusCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL);
    fitnessTrainingStatusCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL);

    // Service
    GattCharacteristic *characteristics[] = {
        &indoorBikeDataCharacteristic,
        &fitnessMachineControlPointCharacteristic,
        &fitnessMachineFeatureCharacteristic,
        &fitnessMachineStatusCharacteristic,
        &fitnessTrainingStatusCharacteristic,
    };
    GattService service(
        UUID(0x1826), characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *)
    );
    uBit.ble->addService(service);

    // Characteristic Handle
    indoorBikeDataCharacteristicHandle = indoorBikeDataCharacteristic.getValueHandle();
    fitnessMachineControlPointCharacteristicHandle = fitnessMachineControlPointCharacteristic.getValueHandle();
    fitnessMachineFeatureCharacteristicHandle = fitnessMachineFeatureCharacteristic.getValueHandle();
    fitnessMachineStatusCharacteristicHandle = fitnessMachineStatusCharacteristic.getValueHandle();
    fitnessTrainingStatusCharacteristicHandle = fitnessTrainingStatusCharacteristic.getValueHandle();
    :
    :
}

読み取りプロパティを含む特性の初期値設定

サーバーからの読み取りプロパティ(GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ)を含む特性の初期値をコンストラクタで、設定しています。
struct_pack()は、ヘルパー関数です。

MicroBitIndoorBikeStepService.cpp
MicroBitIndoorBikeStepService::MicroBitIndoorBikeStepService(MicroBit &_uBit, MicroBitIndoorBikeStepSensor &_indoorBike, uint16_t id)
    : uBit(_uBit), indoorBike(_indoorBike)
{
    :
    :
    // GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
    uint8_t fitnessMachineFeatureBuff[fitnessMachineFeatureCharacteristicBufferSize];
    struct_pack(fitnessMachineFeatureBuff
        , "<II"
        , FTMP_FLAGS_FITNESS_MACINE_FEATURES_FIELD
        , FTMP_FLAGS_TARGET_SETTING_FEATURES_FIELD
    );
    uBit.ble->gattServer().write(fitnessMachineFeatureCharacteristicHandle
        ,(uint8_t *)&fitnessMachineFeatureBuff, fitnessMachineFeatureCharacteristicBufferSize);
    uint8_t fitnessTrainingStatusBuff[fitnessTrainingStatusCharacteristicBufferSize];
    struct_pack(fitnessTrainingStatusBuff
        , "<BB"
        , FTMP_FLAGS_TRAINING_STATUS_FIELD_00_STATUS_ONLY
        , FTMP_VAL_TRAINING_STATUS_01_IDEL
    );
    uBit.ble->gattServer().write(fitnessTrainingStatusCharacteristicHandle
        ,(uint8_t *)&fitnessTrainingStatusBuff, fitnessTrainingStatusCharacteristicBufferSize);
    :
    :
}

イベントハンドラの設定

コンストラクタの最後でイベントハンドラを登録しています。

Fitness Machine Control Point特性は、サーバーからの書き込みプロパティ(GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE)を含む特性ですので、サーバーから書き込みがあると、このインスタンスのMicroBitIndoorBikeStepService::onDataWritten関数が呼び出されます。

MicroBitIndoorBikeStepSensorクラスのインスタンスと連携する為、そのインスタンスのEvent Bus IDMICROBIT_INDOOR_BIKE_STEP_SENSOR_EVT_DATA_UPDATEイベントを登録しています。
また、Fitness Machine Control Point特性への書き込みに対して、検証済みの場合、独自のMicroBitEventを生成していますので(後述)、そのハンドラ(MicroBitIndoorBikeStepService::onFitnessMachineControlPoint)を定義しています。

MicroBitIndoorBikeStepService.cpp
MicroBitIndoorBikeStepService::MicroBitIndoorBikeStepService(MicroBit &_uBit, MicroBitIndoorBikeStepSensor &_indoorBike, uint16_t id)
    : uBit(_uBit), indoorBike(_indoorBike)
{
    :
    :
    // GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE - Fitness Machine Control Point Characteristic
    uBit.ble->onDataWritten(this, &MicroBitIndoorBikeStepService::onDataWritten);

    // Microbit Event listen
    if (EventModel::defaultEventBus)
    {
        EventModel::defaultEventBus->listen(this->indoorBike.getId(), MICROBIT_INDOOR_BIKE_STEP_SENSOR_EVT_DATA_UPDATE
            , this, &MicroBitIndoorBikeStepService::indoorBikeUpdate, MESSAGE_BUS_LISTENER_IMMEDIATE);
        EventModel::defaultEventBus->listen(this->id, MICROBIT_EVT_ANY
            , this, &MicroBitIndoorBikeStepService::onFitnessMachineControlPoint, MESSAGE_BUS_LISTENER_IMMEDIATE);
    }

}

Indoor Bike Data の通知(notify)

MicroBitIndoorBikeStepSensorクラスのインスタンスからのイベント(MicroBitEvent)を受けて、スピードとケイデンスをindoorBikeDataCharacteristicHandleハンドルにnotify()関数で書き込んでいます。
実際には、この実装だけで、スマホ・アプリのBLEツールでスピードとケイデンスを表示することが可能です。

MicroBitIndoorBikeStepService.cpp
void MicroBitIndoorBikeStepService::indoorBikeUpdate(MicroBitEvent e)
{
    if (uBit.ble->getGapState().connected)
    {
        uint8_t buff[indoorBikeDataCharacteristicBufferSize];
        struct_pack(buff, "<HHH",
            FTMP_FLAGS_INDOOR_BIKE_DATA_CHAR,
            this->indoorBike.getSpeed100(),
            this->indoorBike.getCadence2()
        );
        uBit.ble->gattServer().notify(indoorBikeDataCharacteristicHandle
            , (uint8_t *)&buff, indoorBikeDataCharacteristicBufferSize);
    }
}

Fitness Machine Control Point特性への書き込み時の振る舞い

Fitness Machine Control Point特性への書き込みがあると、MicroBitIndoorBikeStepService::onDataWritten関数が呼び出されますので、ここで、Op Codeの分類とデータ検証を行い、Op Code0x80(Response)で応答しています。データ検証済み(FTMP_RESULT_CODE_CPPR_01_SUCCESS)であれば、独自イベントを生成し、結果的にMicroBitIndoorBikeStepService::onFitnessMachineControlPoint関数が呼び出されます。

尚、本関数の末尾で、デバッグ用にUSBシリアル通信で、書き込まれたデータを出力しています。

MicroBitIndoorBikeStepService.cpp
void MicroBitIndoorBikeStepService::onDataWritten(const GattWriteCallbackParams *params)
{
    if (params->handle == fitnessMachineControlPointCharacteristicHandle && params->len >= 1)
    {
        uint8_t responseBuffer[3];
        responseBuffer[0] = FTMP_OP_CODE_CPPR_80_RESPONSE_CODE;
        uint8_t *opCode=&responseBuffer[1];
        opCode[0]=params->data[0];
        uint8_t *result=&responseBuffer[2];
        result[0] = FTMP_RESULT_CODE_CPPR_03_INVALID_PARAMETER;
        uint16_t eventValue = MICROBIT_EVT_ANY;
        switch (opCode[0])
        {
        case FTMP_OP_CODE_CPPR_00_REQUEST_CONTROL:
            eventValue=FTMP_EVENT_VAL_OP_CODE_CPPR_00_REQUEST_CONTROL;
            if (params->len == 1)
            {
                result[0] = FTMP_RESULT_CODE_CPPR_01_SUCCESS;
            }
            break;

        case FTMP_OP_CODE_CPPR_01_RESET:
            eventValue=FTMP_EVENT_VAL_OP_CODE_CPPR_01_RESET;
            if (params->len == 1)
            {
                result[0] = FTMP_RESULT_CODE_CPPR_01_SUCCESS;
            }
            break;

        case FTMP_OP_CODE_CPPR_07_START_RESUME:
            eventValue=FTMP_EVENT_VAL_OP_CODE_CPPR_07_START_RESUME;
            if (params->len == 1)
            {
                result[0] = FTMP_RESULT_CODE_CPPR_01_SUCCESS;
            }
            break;

        case FTMP_OP_CODE_CPPR_08_STOP_PAUSE:
            eventValue=FTMP_EVENT_VAL_OP_CODE_CPPR_08_STOP_PAUSE;
            if (params->len == 2)
            {
                this->stopOrPause = params->data[1];
                result[0] = FTMP_RESULT_CODE_CPPR_01_SUCCESS;
            }
            break;

        default:
            result[0] = FTMP_RESULT_CODE_CPPR_02_NOT_SUPORTED;
            break;

        }

        // Response - Fitness Machine Control Point
        uBit.ble->gattServer().write(fitnessMachineControlPointCharacteristicHandle
                , (const uint8_t *)&responseBuffer, sizeof(responseBuffer));

        // Fire MicroBit Event
        if (result[0]==FTMP_RESULT_CODE_CPPR_01_SUCCESS)
        {
            new MicroBitEvent(this->id, eventValue);
        }

        // Debug - USB Serial
        uBit.serial.printf("CP:%" PRIu32 ", eventValue[%d], opCode[0x%02X], result[0x%02X], data", (uint32_t)system_timer_current_time(), eventValue, opCode[0], result[0]);
        for (int i=0; i<params->len; i++)
        {
            uBit.serial.printf(", 0x%02X", params->data[i]);
        }
        uBit.serial.printf("\r\n");

    }
}

Op Code別の振る舞い

Fitness Machine Control Point特性への書き込みがあると、最終的にMicroBitIndoorBikeStepService::onFitnessMachineControlPointが呼び出されますので、この中で、Op Codeに従って、それぞれの振る舞いを実装しています。

MicroBitIndoorBikeStepService.cpp
void MicroBitIndoorBikeStepService::sendTrainingStatusIdle(void)
{
    static const uint8_t buff[]={FTMP_FLAGS_TRAINING_STATUS_FIELD_00_STATUS_ONLY, FTMP_VAL_TRAINING_STATUS_01_IDEL};
    uBit.ble->gattServer().notify(this->fitnessTrainingStatusCharacteristicHandle
        , (uint8_t *)&buff, sizeof(buff));
}

void MicroBitIndoorBikeStepService::sendTrainingStatusManualMode(void)
{
    static const uint8_t buff[]={FTMP_FLAGS_TRAINING_STATUS_FIELD_00_STATUS_ONLY, FTMP_VAL_TRAINING_STATUS_0D_MANUAL_MODE};
    uBit.ble->gattServer().notify(this->fitnessTrainingStatusCharacteristicHandle
        , (uint8_t *)&buff, sizeof(buff));
}

void MicroBitIndoorBikeStepService::sendFitnessMachineStatusReset(void)
{
    static const uint8_t buff[]={FTMP_OP_CODE_FITNESS_MACHINE_STATUS_01_RESET};
    uBit.ble->gattServer().notify(this->fitnessMachineStatusCharacteristicHandle
        , (uint8_t *)&buff, sizeof(buff));
}

void MicroBitIndoorBikeStepService::onFitnessMachineControlPoint(MicroBitEvent e)
{
    switch (e.value)
    {
    case FTMP_EVENT_VAL_OP_CODE_CPPR_00_REQUEST_CONTROL:
        // # 0x00 M Request Control
        // #define FTMP_EVENT_VAL_OP_CODE_CPPR_00_REQUEST_CONTROL
        // (NOP)
        break;
    case FTMP_EVENT_VAL_OP_CODE_CPPR_01_RESET:
        // # 0x01 M Reset
        // #define FTMP_EVENT_VAL_OP_CODE_CPPR_01_RESET
        this->sendTrainingStatusManualMode();
        break;
    case FTMP_EVENT_VAL_OP_CODE_CPPR_07_START_RESUME:
        // # 0x07 M Start or Resume
        // #define FTMP_EVENT_VAL_OP_CODE_CPPR_07_START_RESUME
        this->sendTrainingStatusManualMode();
        break;
    case FTMP_EVENT_VAL_OP_CODE_CPPR_08_STOP_PAUSE:
        // # 0x08 M Stop or Pause [UINT8, 0x01-STOP, 0x02-PAUSE]
        // #define FTMP_EVENT_VAL_OP_CODE_CPPR_08_STOP_PAUSE
        this->sendTrainingStatusIdle();
        break;
    default:
        break;
    }

}

スマホ・アプリからの接続

FTMSの実装ができているかどうかを確認する為、まずは、BLEツールを使って接続します。
さらに、バーチャルライドアプリにも接続してみます。

BLEツール - nRF Connect for Mobile

BLEツールとして、Android用のnRF Connect for Mobileを使用します。
https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp

アプリを起動すると、STEP:BITというデバイスが表示されますので、Connectで接続します。
接続に成功するとSTEP:BITタブに切り替わりますので、Fitness Machineをタップし、Indoor Bike Dataの右横のアイコンをタップして、NOTIFYを受け入れるようにします。ペダリング(ワイヤレス運動測量計)に連動して、Instantaneous SpeedInstantaneous Cadenceが表示されます。表示の単位は、km/hper minであり、FTMSサービス上での単位をツールが変換して表示しています。

バーチャルライドアプリ - OneLap

OneLap - https://www.onelap.com/
OneLap Japan - https://www.onelapjapan.jp/

各社からバーチャルライドアプリ(サービス)が提供されており、ZwiftやKinomap、RGT Cyclingなどを試しています。その中でも、中国発のOneLapというサービスが日本の代理店を通じて、サービスが提供されています。
2021年2月現在、3か月間の無償クーポンが発行されていますので、今が、お試しのチャンスです。また、有償プランとしては、月額費用も他のサービスと比較するとかなりリーズナブルな設定です。

OneLapを起動し、Deviceを選択します。各社の室内トレーナーから選択できますが、OtherのFTMSを選択します。また、パワーとスピードを選べますが、FTMSで、パワーの実装をしていませんので、スピードの方を選んでください。

ところが、実際に、ペダリングしてみると分かりますが、スピードの値が、ワイヤレス運動測量計の値よりもかなり速い値で表示されます。

バーチャルライドアプリのスピード補正

スピードを補正する必要がありそうですので、まずは、常に100.00km/hになるようにスピードの値を固定します(*speed100 = 10000;)。

MicroBitIndoorBikeStepSensor.cpp
void calcIndoorBikeData(uint32_t crankIntervalTime, uint32_t* cadence2, uint32_t* speed100)
{

    if (crankIntervalTime==0)
    {
        *cadence2 = 0;
        *speed100 = 0;
    }
    else
    {
        *cadence2 = (uint32_t)( (uint64_t)K_STEP_CADENCE / crankIntervalTime );
        *speed100 = 10000; //(uint32_t)( (uint64_t)K_STEP_SPEED   / crankIntervalTime );
    }
}

すると、OneLap側では、738km/hと表示されますので、7.38倍の速さで表示されていることが分かります。
ただし、走行モードになると、感覚としては、5倍程度の速さで表示されているようです。
ですので、ケイデンスから算出したスピードを5から7で割ったスピードの補正値を返すようにすれば良いようです。

*speed100 = (uint32_t)( (uint64_t)K_STEP_SPEED / crankIntervalTime / 5);

Screenshot_2021-02-15-08-54-01-645_com.magene.onelapUS.jpg

おわりに

・FTMP/FTMSの仕様書を斜め読みしました
・ケイデンスとスピードに対応したFTMSの実装をしました
・スマホ・アプリから接続し、ケイデンスとスピードを確認しました
・バーチャルライドアプリでのスピードはその値の補正が必要であることがわかりました

次回(その5)は、スピードからパワーを推定し、FTMSとしてパワーを利用できるように改良します。
また、一定時間、バーチャルライドアプリと接続していると、Micro:BitがPanic状態もしくは無応答状態になってしまうので、そのあたりの改善も行います。

1
3
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
1
3