家庭用サイクルトレーナーにmicro:bitをつなぐ(その4)
前回までの記事(その1、その2、その3)では、家庭用サイクルトレーナーのワイヤレス運動測量計とmicro:bitをつなぎ、MicroBitComponentを継承し、ワイヤレス運動測量計のSTEP信号からケイデンスとスピードを求めるモジュールをドライバー化したMicroBitIndoorBikeStepSensorクラスを作成しました。
リンク
- 家庭用サイクルトレーナーにmicro:bitをつなぐ(その1)
ワイヤレス運動測量計から出力されるクランク回転センサーの信号をモニターする - 家庭用サイクルトレーナーにmicro:bitをつなぐ(その2)
STEP信号からワイヤレス運動測量計に表示されているスピードをmicro:bitで推定する - 家庭用サイクルトレーナーにmicro:bitをつなぐ(その3)
STEP信号からケイデンスとスピードを求めるモジュールをドライバー(アイドル・コンポーネント)として作成する
これまで、ケイデンスやスピードをUSBシリアル通信でモニターしていましたが、これをBLE通信でスマホ・アプリで表示できるようにGATTサービスであるFTMP/FTMSとして実装します。
仕様書の入手と斜め読み
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 Control
とReset
の実装は必須です。
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 Code
の0x00(Request Control)
、0x01(Reset)
、0x07(Start or Resume)
、0x08(Stop or Pause)
の実装は必須です。また、各要求に対して、Op Code
の0x80(Response Code)
と、Request Op Code
とResult 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を有効化しています。
{
"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とを組み合わせます。
#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の各特性の実装を順に説明します。
ケイデンスとスピードへの対応
スピードに関しては、必ずサポートしなければならない為、フラグでのサポート指定は不要です。ケイデンスへの対応をする為、次のようにビットフラグを立てています。
:
:
/*
# 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
関係のAppearance
とCOMPLETE_LOCAL_NAME
とを書き換え、FTMS
に関するAdvertising Data
を登録しています。
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::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::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 ID
とMICROBIT_INDOOR_BIKE_STEP_SENSOR_EVT_DATA_UPDATE
イベントを登録しています。
また、Fitness Machine Control Point
特性への書き込みに対して、検証済みの場合、独自のMicroBitEvent
を生成していますので(後述)、そのハンドラ(MicroBitIndoorBikeStepService::onFitnessMachineControlPoint
)を定義しています。
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ツールでスピードとケイデンスを表示することが可能です。
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 Code
の0x80(Response)
で応答しています。データ検証済み(FTMP_RESULT_CODE_CPPR_01_SUCCESS
)であれば、独自イベントを生成し、結果的にMicroBitIndoorBikeStepService::onFitnessMachineControlPoint
関数が呼び出されます。
尚、本関数の末尾で、デバッグ用にUSBシリアル通信で、書き込まれたデータを出力しています。
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
に従って、それぞれの振る舞いを実装しています。
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 Speed
とInstantaneous Cadence
が表示されます。表示の単位は、km/h
やper 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;
)。
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);
おわりに
・FTMP/FTMSの仕様書を斜め読みしました
・ケイデンスとスピードに対応したFTMSの実装をしました
・スマホ・アプリから接続し、ケイデンスとスピードを確認しました
・バーチャルライドアプリでのスピードはその値の補正が必要であることがわかりました
次回(その5)は、スピードからパワーを推定し、FTMSとしてパワーを利用できるように改良します。
また、一定時間、バーチャルライドアプリと接続していると、Micro:BitがPanic状態もしくは無応答状態になってしまうので、そのあたりの改善も行います。