Developing The Internet of Things: WAHOO BALANCE Smart Body Scale

Posted by on in Tutorial

front_perspect

Keeping tack of personal health and fitness programs is a central aspect of the Internet of Things. Smart devices, aka Things, gather bioinformatic data from our bodies and from our daily routines. This data is collected and sorted by Connected Apps giving end users and doctors alike a data driven roadmap to provide preventative health behavior, remote monitoring, and diagnosis.

In this blog post, find out how Appmethod and the Bluetooth LE component streamlines the integration of the WAHOO Balance Smart Body Scale with a Connected App.

 

 Wahoo Balance Smart Body Scale App

screen_copy

The Wahoo Balance Smart Body Scale provides body weight data over Bluetooth LE.

Download

Project files for this demo application can be found here.

Bluetooth Low Energy (aka Smart Bluetooth) provides a new environment for devices with small amount of data to transfer and lower power consumption.  

Please refer to the Appmethod DocWiki for an overview of how to use the Bluetooth Low Energy Component.

The following steps will guide you through connecting to the Wahoo Balance Scale over Bluetooth LE implemented in C++.

 

Step 1: Create App UI

Structure2

 

First, from the Tool Palette add a TBluetoothLE Component to the Form Designer.
 
This demo app consists of three TPanels, one for the header, one for the body, and one for the log console.  
 
The HeaderPanel Align property is set to Top.

The BodyPanel Align property is set to Client.

The ConsolePanel Align property is set to Bottom.
 
The HeaderPanel contains a TLabel nested inside a TRectangle which displays the name of the app at the top of the screen. It also has a TToolBar which contains two TButtons; one to initiate the BluetoothLE connection and one to clear the memo console.

The BodyPanel consists of a TCircle and two nested TLabels. The label_WeightLabel will dynamically be set to display the converted weight data provided by the scale.

The ConsolePanel contains a TMemo which will display the raw Bluetooth LE data.

 

Event Handlers for the header buttons

1
2
3
4
5
//---------------------------------------------------------------------------
void __fastcall TForm2::ClearMemoBtnClick(TObject *Sender)
{
    memo_Console->Lines->Clear();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//---------------------------------------------------------------------------
void __fastcall TForm2::ConnectBtnClick(TObject *Sender)
{
    //- disable connection button during bluetooth discovery
    ConnectBtn->Enabled  =   false;
    //- reset weight label
    label_WeightData->Text   =   "0";
    //- connect to Wahoo Balance Scale
    GUID Devices[]          = {WEIGHT_DEVICE};
    //-  The EXISTINGARRAY macro defined in sysopen.h can be used to
    //      pass an existing array where an open array is expected.
    BluetoothLE1->DiscoverDevices(2500, EXISTINGARRAY(Devices));
    memo_Console->Lines->Add( "Connecting to Scale..." );
}

 Step 2: Set Wahoo Balance UUID

In the global space, define the UUID as follows:

 

1
2
3
4
5
6
7
8
//- Wahoo Fitness Bluetooth
// 0x1901 is the weight service
// 0x2B01 is the live weight characteristic readings
//      0x84ae0000 0001      - 38 lbs   17kg
// 0b 10000100101011100000000000000000
const TBluetoothUUID    WEIGHT_DEVICE           =   StringToGUID( "{00001901-0000-1000-8000-00805F9B34FB}" ),
                        WEIGHT_SERVICE          =   StringToGUID( "{00001901-0000-1000-8000-00805F9B34FB}" ),
                        WEIGHT_CHARACTERISTIC   =   StringToGUID( "{00002B01-0000-1000-8000-00805F9B34FB}" );

 

 

 Step 3: Add TBluetoothLE Component Event Handlers

Select the BluetoothLE1 component just added and from within the Object Inspector  open the events tab and double click the following events:

  1. OnCharacteristicRead
  2. OnEndDiscoverDevices

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//---------------------------------------------------------------------------
void __fastcall TForm2::BluetoothLE1CharacteristicRead(TObject * const Sender, TBluetoothGattCharacteristic * const ACharacteristic,
          TBluetoothGattStatus AGattStatus)
{
    if(AGattStatus != TBluetoothGattStatus::Success) {
        memo_Console->Lines->Add( "Error reading Characteristic " +
                                    ACharacteristic->UUIDName +
                                    ": " +
                                    IntToStr(static_cast<int>(AGattStatus)));
    }
    else {
        String LSValue      = IntToStr( ACharacteristic->GetValueAsInteger() );
        memo_Console->Lines->Add( "Value: " + LSValue);
        //- convert to pounds
        float wPounds       = ( ACharacteristic->GetValueAsInteger() >> 8 ) * 0.2205;
        //- update UI
        memo_Console->Lines->Add( String().sprintf( L"Pounds: %0.2f", wPounds ) );
        label_WeightData->Text = String().sprintf( L"%0.2f", wPounds );
    }
}

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//---------------------------------------------------------------------------
void __fastcall TForm2::BluetoothLE1EndDiscoverDevices(TObject * const Sender, TBluetoothLEDeviceList * const ADeviceList)
{
    memo_Console->Lines->Add(IntToStr(ADeviceList->Count) + " devices discovered:");
    for (int i = 0; i < ADeviceList->Count; i++) {
        memo_Console->Lines->Add(ADeviceList->Items[i]->DeviceName);
    }
    if(BluetoothLE1->DiscoveredDevices->Count > 0) {
        BTLEDevice = BluetoothLE1->DiscoveredDevices->First();
        if(BluetoothLE1->GetServices(BTLEDevice)->Count == 0) {
            memo_Console->Lines->Add("No services found!");
        }
        else {
            GetServiceAndCharacteristics();
        }
    }
    else {
        memo_Console->Lines->Add("Device not found.");
        //- re-inable bluetooth discovery
        ConnectBtn->Enabled  =   true;
    }
}

Step 4: Get Services and Characteristics

Once the ConnectBtnClick event handler has succesffuly discovered the Wahoo Balance Bluetooth device, the following function GetServiceAndCharacteristics() is called.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//---------------------------------------------------------------------------
void __fastcall TForm2::GetServiceAndCharacteristics(void)
{
    TBluetoothGattCharacteristicList    * CharList      = NULL;
    TBluetoothGattDescriptorList        * Descriptor    = NULL;
    //- log bluetooth device services and characteristics to the console
    for(int i = 0; i < BTLEDevice->Services->Count; i++) {
        CharList = BTLEDevice->Services->Items[i]->Characteristics;
        memo_Console->Lines->Add( BTLEDevice->Services->Items[i]->UUIDName +
                                    " : " +
                                    GUIDToString(BTLEDevice->Services->Items[i]->UUID) );
        for(int j = 0; j < CharList->Count; j++) {
            memo_Console->Lines->Add( "--> " + CharList->Items[j]->UUIDName +
                                        " : " +
                                        GUIDToString(CharList->Items[j]->UUID));
            Descriptor = CharList->Items[j]->Descriptors;
            for(int k = 0; k < Descriptor->Count; k ++) {
                memo_Console->Lines->Add( "--> " + Descriptor->Items[k]->UUIDName +
                                            " : " +
                                            GUIDToString(Descriptor->Items[k]->UUID));
            }
        }
    }
    //- create Bluetooth GATT Service handle
    BTGattService   = BTLEDevice->GetService(WEIGHT_SERVICE);
    //- ensure the Bluetooth GATT service is available
    if(BTGattService != NULL) {
        memo_Console->Lines->Add("Service found");
        BTGattWeightCharact = BTGattService->GetCharacteristic(WEIGHT_CHARACTERISTIC);
    } else {
        memo_Console->Lines->Add("Service not found");
    }
    if(BTGattWeightCharact != NULL) {
        //- start listening for new characteristic data
        BTLEDevice->SetCharacteristicNotification(BTGattWeightCharact, true);
    }
    else {
        memo_Console->Lines->Add("HRM Characteristic not found");
    }
}

 Step 5: Initalize State

Bluetooth connectivitiy state is maintained by a few variables from the main TForm class:

 

1
2
3
4
5
private:    // User declarations
    TBluetoothLEManager         * BTLEManager;
    TBluetoothLEDevice      * BTLEDevice;
    TBluetoothGattService       * BTGattService;
    TBluetoothGattCharacteristic    * BTGattWeightCharact;

They are initalized as null at the app's point of entry:

1
2
3
4
5
6
7
8
//---------------------------------------------------------------------------
__fastcall TForm2::TForm2(TComponent* Owner)
    : TForm(Owner)
{
    BTGattService       = NULL;
    BTGattWeightCharact = NULL;
    BTLEDevice          = NULL;
}

 

 

 

Tags: Appmethod


About
Gold User, Rank: 8, Points: 399
Brian Alexakis is a Product Marketing Manager at Embarcadero Technologies. He is focused on leveraging the connected world of technology to build new experiences for the Internet of Things.

Comments

Check out more tips and tricks in this development video: