Quantcast
Channel: Sarina DuPont, Product Manager RAD Studio
Viewing all articles
Browse latest Browse all 132

Creating a Bluetooth LE cloud enabled luggage scale application

$
0
0

Every time I travel somewhere, I am concerned that my luggage might exceed the weight limit. With RAD Studio XE7 and the Wahoo Bluetooth Scale, I built a luggage scale application that allows me to weigh my luggage and store data about my luggage contents in the cloud.

The app UI consists of a TTabControl with 3 tabs:

  • WeightScale - for weighing my luggage and saving the weight and associated notes
  • Logged Data - for retrieving existing luggage data and displaying it in a list
  • Settings - for displaying my bluetooth data

In addition to the various UI components, the app also contains a TBluetoothLE component, a TStyleBook component (for loading and assigning my custom Jet style) and the following BaaS components:

  • TKinveyProvider (AppKey, AppSecret and MasterSecret match my Kinvey.com credentials)
  • TBackendQuery (connected to TKinveyProvider)
  • TBackendStorage (connected to TKinveyProvider)
  • TRestResponseDataSetAdapter
  • TFDMemTable

The scale I am working with is the Wahoo Balance scale.

Here is the Object Pascal code for my application:

unit WeightScaleForm1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Ani, FMX.StdCtrls, System.Bluetooth, FMX.Layouts,
  FMX.Memo, FMX.Controls.Presentation, FMX.Edit, FMX.Objects, IPPeerClient, IPPeerServer,
  System.Tether.Manager, System.Bluetooth.Components, FMX.TabControl,
  REST.Backend.ServiceTypes, REST.Backend.MetaTypes, System.JSON,
  REST.Backend.KinveyServices, REST.OpenSSL, REST.Backend.KinveyProvider,
  REST.Backend.Providers, REST.Backend.ServiceComponents, FMX.ListView.Types,
  Data.Bind.Components, Data.Bind.ObjectScope, REST.Backend.BindSource,
  FMX.ListView, FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Param,
  FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf,
  REST.Response.Adapter, Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client,
  System.Rtti, System.Bindings.Outputs, Fmx.Bind.Editors, Data.Bind.EngExt,
  Fmx.Bind.DBEngExt, Data.Bind.DBScope;

type

  TSensorContactStatus = (NonSupported, NonDetected, Detected);

  THRMFlags = record
    HRValue16bits: boolean;
    SensorContactStatus: TSensorContactStatus;
    EnergyExpended: boolean;
    RRInterval: boolean;
  end;

  TfrmWeightMonitor = class(TForm)
    BluetoothLE1: TBluetoothLE;
    TabControl1: TTabControl;
    WeightScale: TTabItem;
    History: TTabItem;
    Panel1: TPanel;
    Memo1: TMemo;
    Panel2: TPanel;
    btnConnect: TButton;
    lblDevice: TLabel;
    lblWeight: TLabel;
    btnDisconnect: TButton;
    Settings: TTabItem;
    ToolBar1: TToolBar;
    Label1: TLabel;
    ToolBar2: TToolBar;
    ToolBar3: TToolBar;
    Label3: TLabel;
    btnSave: TButton;
    KinveyProvider1: TKinveyProvider;
    BackendStorage1: TBackendStorage;
    AppTitle: TLabel;
    title: TLabel;
    lbs: TLabel;
    ListView1: TListView;
    BackendQuery1: TBackendQuery;
    FDMemTable1: TFDMemTable;
    RESTResponseDataSetAdapter1: TRESTResponseDataSetAdapter;
    FDMemTable1_id: TWideStringField;
    FDMemTable1Weight: TWideStringField;
    FDMemTable1_acl: TWideStringField;
    FDMemTable1_kmd: TWideStringField;
    BindSourceDB1: TBindSourceDB;
    BindingsList1: TBindingsList;
    LinkListControlToField1: TLinkListControlToField;
    FDMemTable1LuggageDetails: TWideStringField;
    ImageControl1: TImageControl;
    StyleBook1: TStyleBook;
    Image1: TImage;
    procedure btnConnectClick(Sender: TObject);
    procedure BluetoothLE1EndDiscoverDevices(const Sender: TObject; const ADeviceList: TBluetoothLEDeviceList);
    procedure BluetoothLE1CharacteristicRead(const Sender: TObject; const ACharacteristic: TBluetoothGattCharacteristic;
      AGattStatus: TBluetoothGattStatus);
    procedure btnDisconnectClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure LinkListControlToField1FilledListItem(Sender: TObject;
      const AEditor: IBindListEditorItem);
    procedure FormCreate(Sender: TObject);
    procedure HistoryClick(Sender: TObject);
  private
    { Private declarations }
    FBLEDevice: TBluetoothLEDevice;
    FWeightGattService: TBluetoothGattService;
    FWeightMeasurementGattCharacteristic: TBluetoothGattCharacteristic;
    procedure GetServiceAndCharacteristics;
  public
    { Public declarations }
  end;

const
  ScaleDeviceName = 'Wahoo Scale';
  ServiceUUID = '';
  CharactUUID = '';

  // 0x1901 is the weight service
  // 0x2B01 is the live weight characteristic readings    0x84ae0000 0001      - 38 lbs   17kg
  // 0b 10000100101011100000000000000000

  Weight_Device: TBluetoothUUID = '{00001901-0000-1000-8000-00805F9B34FB}';
  Weight_Service: TBluetoothUUID = '{00001901-0000-1000-8000-00805F9B34FB}';
  Weight_Characteristic: TBluetoothUUID  = '{00002B01-0000-1000-8000-00805F9B34FB}';

var
  frmWeightMonitor: TfrmWeightMonitor;

implementation

{$R *.fmx}
{$R *.iPhone4in.fmx IOS}

procedure TfrmWeightMonitor.btnConnectClick(Sender: TObject);
begin
  lblWeight.Text := '0';
  lblDevice.Text := '';
  Memo1.Lines.Clear;
  BluetoothLE1.DiscoverDevices(3500, [Weight_Device]);
end;

procedure TfrmWeightMonitor.btnDisconnectClick(Sender: TObject);
begin
If FBLEDevice <> nil then
  BluetoothLE1.UnSubscribeToCharacteristic(FBLEDevice, FWeightMeasurementGattCharacteristic);
  btnDisconnect.Enabled := false;  // disable disconnect from subscribe scale button
  btnConnect.Enabled := true;  // enable connect to scale button
end;

//store luggage notes in the cloud
procedure TfrmWeightMonitor.btnSaveClick(Sender: TObject);
var
  LJSON : TJSONObject;
  ACreatedObject: TBackendEntityValue;
  luggagedetails	: string;
begin
    LJSON := TJSONObject.Create;
if InputQuery('Luggage BaaS Cloud Store', 'Enter Notes about your Luggage', luggagedetails)
 then
     LJSON.AddPair('Weight', lblWeight.Text);
     LJSON.AddPair('LuggageDetails', luggagedetails);
     BackendStorage1.Storage.CreateObject('luggagecloud', LJSON, ACreatedObject);
     ShowMessage('Luggage weight and notes stored');
end;

procedure TfrmWeightMonitor.FormCreate(Sender: TObject);
begin
  BackendQuery1.Execute;
end;

procedure TfrmWeightMonitor.BluetoothLE1EndDiscoverDevices(const Sender: TObject;
  const ADeviceList: TBluetoothLEDeviceList);
var
  I: Integer;
begin
  // log
  Memo1.Lines.Add(ADeviceList.Count.ToString +  ' devices discovered:');
  for I := 0 to ADeviceList.Count - 1 do Memo1.Lines.Add(ADeviceList[I].DeviceName);

  if BluetoothLE1.DiscoveredDevices.Count > 0 then
  begin
    FBLEDevice := BluetoothLE1.DiscoveredDevices.First;
    ShowMessage('Connected to Wahoo Scale');
    if BluetoothLE1.GetServices(FBLEDevice).Count = 0 then
    begin
      Memo1.Lines.Add('No Weight services found!');
      lblWeight.Text := 'No Weight services found!';
    end
    else begin
      Memo1.Lines.Add('Wahoo Services Found: '+ IntToStr(BluetoothLE1.GetServices(FBLEDevice).Count));
      btnDisconnect.Enabled := true;  // enable disconnect from subscribe scale button
      btnConnect.Enabled := false;  //disable connect to scale button
      // get Weight Service and Characteristic
      GetServiceAndCharacteristics;
    end;
  end
  else
    lblDevice.Text := 'Weight Device not found';
end;

procedure TfrmWeightMonitor.GetServiceAndCharacteristics;
begin
  FWeightGattService := nil;
  FWeightMeasurementGattCharacteristic := nil;
  // get Weight Service by UUID
  FWeightGattService := BluetoothLE1.GetService(FBLEDevice, Weight_SERVICE);

  if FWeightGattService <> nil then begin
    memo1.Lines.Add('Service: '+FWeightGattService.UUID.ToString);
    // get Weight Characteristic
    Memo1.Lines.Add('Looking Char: '+Weight_CHARACTERISTIC.ToString);
    BluetoothLE1.GetCharacteristics(FWeightGattService);
    FWeightMeasurementGattCharacteristic := BluetoothLE1.GetCharacteristic(FWeightGattService,Weight_CHARACTERISTIC);

    if FWeightMeasurementGattCharacteristic <> nil then begin
      Memo1.Lines.Add('Char: '+FWeightMeasurementGattCharacteristic.UUID.ToString);
      // subscribe to the weight service
      BluetoothLE1.SubscribeToCharacteristic(FBLEDevice, FWeightMeasurementGattCharacteristic);
      Memo1.Lines.Add('Subscribed to Weight Measurement Characteristic');
    end
    else begin
      Memo1.Lines.Add('Weight Char not found');
      lblWeight.Text := 'Weight Char not found';
    end
  end
  else begin
    Memo1.Lines.Add('Weight Service not found');
    lblWeight.Text := 'Weight Service not found';
  end;
end;

//query BaaS provider for existing luggage data
procedure TfrmWeightMonitor.HistoryClick(Sender: TObject);
begin
  BackendQuery1.Execute;
end;

//display weight icon next to each row in TListview
procedure TfrmWeightMonitor.LinkListControlToField1FilledListItem(
  Sender: TObject; const AEditor: IBindListEditorItem);
var
  LItem: TListViewItem;
begin
  LItem := AEditor.CurrentObject As TListViewItem;
  LItem.BitmapRef := ImageControl1.Bitmap;
end;

procedure TfrmWeightMonitor.BluetoothLE1CharacteristicRead(const Sender: TObject;
  const ACharacteristic: TBluetoothGattCharacteristic; AGattStatus: TBluetoothGattStatus);
var
  LSValue: string;
  HexValue: string;
  WeightPounds : double;
begin
  if AGattStatus <> TBluetoothGattStatus.Success then
    Memo1.Lines.Add('Error reading Characteristic ' + ACharacteristic.UUIDName + ': ' + Ord(AGattStatus).ToString)
  else
  begin
    LSValue := IntToStr(ACharacteristic.GetValueAsInteger);
    Memo1.Lines.Add(ACharacteristic.UUID.ToString + ' String: ' + LSValue);
    // calculate weight - characteristic is in hectograms
    WeightPounds := (ACharacteristic.GetValueAsInteger shr 8) * 0.2205;
    Memo1.Lines.Add('Weight (pounds): '+Format('%8.2f',[WeightPounds]));
    lblWeight.Text := Format('%8.2f',[WeightPounds]);
  end;
end;

end.


Viewing all articles
Browse latest Browse all 132

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>