본문 바로가기

파이어몽키

[따라하기] 건강데이터 수집 및 기록 시스템 #1 - BLE 기반 스마트 체중계에서 실시간 데이터 받기


위 동영상(건강데이터 수집 및 기록 시스템 데모) 중 스마트 체중계의 체중정보를 모바일에서 실시간으로 받아오는 기능을 따라하기로 진행합니다.


스마트 체중계에서 체중정보 수신앱 만들기

❑ 앱소개

앱소개

이 앱은 스마트체중계와 블루투스 LE로 연결 해 체중정보를  실시간으로 받아와 화면에 표시하는 앱입니다.

따라하기의 목적

스마트 체중계 연결을 통해 블루투스 LE 인터페이스를 제공하는 다양한 기기(또는 센서)에 연결하고 데이터를 수신하는 방법을 이해합니다.


따라하기에서는 다음 내용을 다룹니다.

  • RAD Studio로 멀티-디바이스(안드로이드, iOS, 윈도우, OS X) 앱 프로젝트 생성 및 화면 디자인
  • TBluetoothLE 컴포넌트로 스마트 체중계와 연결 및 데이터 수신(구독)
  • 실 기기에 배포 및 테스트(안드로이드 폰, 아이폰)

따라하기

0, 사전 준비사항

1, 빈 멀티-디바이스 애플리케이션 프로젝트 생성

2, 메인 UI 제작

3, 블루투스LE 컴포넌트 추가

4, 주변의 디바이스 탐색

5, 탐색된 디바이스 목록에서 디바이스 선택 후 서비스 탐색

6, 탐색된 서비스 목록에서 서비스를 선택 후 특성 구독

7, 특성 데이터 수신 후 체중 데이터 가져오기

8, 블루투스 권한설정 

9, 디바이스에 배포 및 테스트

0, 사전 준비사항

개발도구 준비하기

델파이(또는 RAD Studio)를 이용해 실습 진행합니다.(이 실습은 델파이 10 시애틀 버전으로 작성되었습니다. 델파이 XE8 이상에서 따라할 수 있습니다.) 

만약, 델파이가 설치되지 않았다면 아래의 페이지에서 델파이를 다운로드 받아 설치 후 진행하시기 바랍니다. 

스마트 체중계 준비하기

따라하기에서는 Wahoo Balance Bluetooth Smart Scale 제품을 이용해 실습을 진행합니다.

https://www.underarmour.com/en-us/wahoo-balance-bluetooth-smart-scale/pid1276806-100-OSFA


Wahoo Balance Bluetooth Smart Scale은 다음과 같은 디바이스 이름과 UUID를 갖습니다.

디바이스 이름 : Wahoo Scale v1.3

서비스 UUID : 00001901-0000-1000-8000-00805F9B34FB

특성 UUID : 00002B01-0000-1000-8000-00805F9B34FB

위 정보는 구현에 필요한 정보입니다.(시중의 다른 스마트 체중계는 위 값들이 다릅니다.)

BLE 구조에 대한 소개

블루투스 LE는 프로파일(Profile), 서비스(Service), 특성(Characteristic) 기반으로 아래 그림과 같이 프로파일은 하나 이상의 서비스를 갖고, 서비스는 하나이상의 특성을 갖는 계층구조로 구성되어 있습니다.



(출처: GENERIC ATTRIBUTE PROFILE (GATT) - https://developer.bluetooth.org/TechnologyOverview/Pages/GATT.aspx)


블루투스 LE 인터페이스를 제공하는 장비와 연결하고 데이터를 받기 위해 다음 과정이 필요합니다.(실제 구현도 다음 단계대로 진행합니다.)

1, 주변의 블루투스 LE 프로파일을 탐색합니다.

2, 탐색된 프로파일에서 서비스를 탐색합니다.

3, 탐색된 서비스에서 특성을 찾아 구독(subscribe)합니다.

4, 구독한 특성에서 발생되는 특성값에서 필요한 데이터를 추출합니다.

1, 빈 멀티-디바이스 애플리케이션 프로젝트 생성

  1. File > New > Multi-Device Application - Delphi 메뉴를 선택 하고, Blank Application을 선택해 프로젝트를 생성합니다.
  2. File > Save all 메뉴를 선택 해 유닛이름을 "MainForm.pas"로 프로젝트 이름을 "SmartScale.dproj"로 저장합니다.

2, 메인 UI를 제작

모바일 앱 스타일로 화면 구성하기 위해 폼디자이너 스타일을 Android로 변경합니다.


다음화면과 표를 이용해 체중정보 수신 앱 메인 UI를 개발합니다.


상위 오브젝트 

오브젝트 

속성 

값(또는 설명) 

 Form1

 ToolBar1

 

 

 Layout1

 Align

 Top
 Height 81
 Memo1

 Align

 Client

 ToolBar1

 Label1

 Align

 Client

 Text

 스마트 체중계 데모

 TextSettings.HorzAlign

 Center

 Switch1 Align Right
 Layout1 Text1

 Align

 Center

 Text

 0.0
 TextSettings.Font.Size

 30

 TextSettings.HorzAlign Trailing
 Width 114
 Text1 Text2 Position.X 120
 Text Kg

 TextSettings.HorzAlign

 Leading

3, 블루투스 LE 컴포넌트 추가

블루투스 LE 컴포넌트 추가

Tool Palette에서 TBluetoothLE 컴포넌트 선택 해 폼에 추가합니다.


필요한 변수, 메소드, 상수 추가

구현에 필요한 변수(디바이스, 서비스, 특성), 메소드 선언(시작, 중지), 상수(장비명과 서비스, 특성 UUID)를 아래와 같이 추가합니다.

  private
    { Private declarations }
    FBLEDevice: TBluetoothLEDevice;
    FBLEGattService: TBluetoothGattService;
    FBLEGattChar: TBluetoothGattCharacteristic;

    procedure StartScale; // 체중계 연결 동작 시작(블루투스LE 활성화, 주변 디바이스 탐색)
    procedure StopScale; // 체중계 연결 동작 중지(블루투스LE 비활성화, 특성 구독 해제)
  public
    { Public declarations }
  end;

const
  ScaleDeviceName = 'Wahoo';

  WEIGHT_SERVICE: TBluetoothUUID          = '{00001901-0000-1000-8000-00805F9B34FB}';
  WEIGHT_CHARACTERISTIC: TBluetoothUUID   = '{00002B01-0000-1000-8000-00805F9B34FB}';

메소드 구현부 생성

StartScale, StopScale 메소드 구현부를 자동 생성하기 위해 procedure StartScale;에 마우스 커서를 옮기고 키보드에서 Ctrl + Shift + C를 동시에 누릅니다.


4, 주변의 디바이스 탐색

주변 디바이스 탐색

Switch1의 OnSwitch 이벤트 핸드러 생성(Switch1 컴포넌트 선택 > Object Inspector 창에서 Event 탭 선택 > OnSwitch 우측의 콤보박스 더블클릭) 후 아래 코드를 입력합니다.

procedure TForm1.Switch1Switch(Sender: TObject);
begin
  if Switch1.IsChecked then
    StartScale
  else
    StopScale;
end;

StartScale 메소드에서 블루투스 LE를 활성화하고, 주변 디바이스를 탐색하는 코드를 다음과 같이 입력합니다.
(StopScale은 제일 마지막에 다시 구현합니다.)

procedure TForm1.StartScale;
begin
  BluetoothLE1.Enabled := True;
  BluetoothLE1.DiscoverDevices(1000); // 1초(1000 ms)동안 주변 디바이스 탐색, 완료 시 OnEndDiscoveryDevices 이벤트 발생
end;

10.3 이상

implementation

uses
  System.Permissions,
{$IFDEF ANDROID}
  Androidapi.JNI.Os,
  Androidapi.JNI.JavaTypes,
  Androidapi.Helpers,
{$ENDIF}
  FMX.DialogService;


procedure TForm1.StartScale;
begin
  lblWeight.Text := '0';
  Memo1.Lines.Clear;
  // Device 발견

{$IFDEF ANDROID}
  if Switch1.IsChecked then
  begin
    PermissionsService.RequestPermissions(
      [
        JStringToString(TJManifest_permission.JavaClass.BLUETOOTH),
        JStringToString(TJManifest_permission.JavaClass.BLUETOOTH_ADMIN)
     ],
      procedure(const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>)
        begin
          if (Length(AGrantResults) = 2)
          and (AGrantResults[0] = TPermissionStatus.Granted)
          and (AGrantResults[1] = TPermissionStatus.Granted) then
          begin
            BluetoothLE1.Enabled := True;
            BluetoothLE1.DiscoverDevices(1000);
            Memo1.Lines.Add('DiscoverDeivces');
          end
          else
          begin
            Switch1.IsChecked := False;
            TDialogService.ShowMessage('블루투스 권한이 없습니다.');
          end;
        end);
  end
  else
  begin
    BluetoothLE1.CancelDiscovery;
    BluetoothLE1.Enabled := False;
  end;
{$ELSE}
  BluetoothLE1.Enabled := True;
  BluetoothLE1.DiscoverDevices(1000, [Weight_Device]);
  Memo1.Lines.Add('DiscoverDeivces');
{$ENDIF}
end;

5, 탐색된 디바이스 목록에서 디바이스 선택 후 서비스 탐색

StartScale 메소드의 DiscoverDevices 메소드 호출하면 (주변의 디바이스 탐색 후) OnEndDiscoverDevices 이벤트가 발생합니다. 

OnEndDiscoverDevices 이벤트는 파라메터로 탐색된 디바이스 목록(ADeviceList)을 제공합니다.

이 디바이스 목록에서 'Wahoo'(위에서 ScaleDeviceName 상수로 선언)로 시작하는 디바이스를 선택합니다.

디바이스를 선택했다면, 디바이스에서 제공하는 서비스 탐색을 시도합니다.

procedure TForm1.BluetoothLE1EndDiscoverDevices(const Sender: TObject;
  const ADeviceList: TBluetoothLEDeviceList);
var
  Device: TBluetoothLEDevice;
begin
  if ADeviceList.Count = 0 then
  begin
    Memo1.Lines.Add('발견된 디바이스가 없습니다.');
    Switch1.IsChecked := False; // 디바이스 미발견 시 스위치 끄기
    Exit;
  end;

  FBLEDevice := nil;
  Memo1.Lines.Add('Device List');
  for Device in ADeviceList do
  begin
    Memo1.Lines.Add(' - ' + Device.DeviceName); // 로그-디바이스 이름

    // 디바이스 이름으로 디바이스 선택
    if Device.DeviceName.StartsWith(ScaleDeviceName) then
    begin
      FBLEDevice := Device;
      Memo1.Lines.Add('디바이스를 찾았습니다.');
    end;
  end;

  if FBLEDevice = nil then
    Exit;

  // [TIP] 서비스 발견이 실패하는 경우가 종종 있음
    // 실패한 경우 한번더 발견요청
  if not FBLEDevice.DiscoverServices then
    FBLEDevice.DiscoverServices; // 서비스 탐색 완료 시 OnEndDiscoverServices 이벤트 발생
end;

6, 탐색된 서비스 목록에서 서비스를 선택 후 특성 구독

서비스 탐색(DiscoverServices)이 완료되면 OnEndDiscoverServices 이벤트가 발생합니다.

OnEndDiscoverServices 이벤트는 파라메터로 탐색된 서비스 목록(AServiceList)을 제공합니다.

이 서비스 목록에서 서비스 UUID와 같은 UUID를 갖는 서비스를 선택합니다.

선택한 서비스에서 특성 UUID로 특성을 찾습니다.

특성에서 체중정보를 포함한 특성데이터를 수신할 수 있도록 구독(SubscribeToCharacteristic) 합니다.

procedure TForm1.BluetoothLE1EndDiscoverServices(const Sender: TObject;
  const AServiceList: TBluetoothGattServiceList);
var
  Service: TBluetoothGattService;
begin
  Memo1.Lines.Add('Service List');
  for Service in AServiceList do
  begin
    Memo1.Lines.Add(' - ' + Service.UUID.ToString); // 로그-디바이스 이름
    if Service.UUID = WEIGHT_SERVICE then
    begin
      FBLEGattService := Service;
      Memo1.Lines.Add('서비스를 찾았습니다.');
    end;
  end;

  if FBLEGattService = nil then
  begin
    Memo1.Lines.Add('서비스를 찾지 못했습니다.');
    Exit;
  end;

  // [TIP][예외] 특성목록을 생성하기 위해 아래 코드 호출
    // GetCharacteristic 바로 호출 시 특성을 가져오지 못해
    // GetCharacteristics을 먼저 호출
  BluetoothLE1.GetCharacteristics(FBLEGattService);

  FBLEGattChar := BluetoothLE1.GetCharacteristic(FBLEGattService, WEIGHT_CHARACTERISTIC);
  if FBLEGattChar = nil then
  begin
    Memo1.Lines.Add('서비스 특성을 찾지 못했습니다.');
    Exit;
  end;

  if BluetoothLE1.SubscribeToCharacteristic(FBLEDevice, FBLEGattChar) then // 구독하면 OnCharacteristicRead 이벤트를 통해 특성 값을 받음
    Memo1.Lines.Add('특성에 구독했습니다.'); 
end;

7, 특성 데이터 수신 후 체중 데이터 가져오기

특성에 구독(SubscribeToCharacteristic)하면 TBluetooth 컴포넌트의 OnCharacteristicRead 이벤트로 특성 값을 받을 수 있습니다. 스마트 체중계의 경우 체중계의 체중이 변경될 때마다 특성 값을 받을 수 있습니다.

Wahoo Balance Blueeoth Smart Scale은 다음과 같은 코드로 특성값에서 체중 정보를 가져옵니다.

(특성 값을 분석하는 방법은 서비스마다 다릅니다.)

procedure TForm1.BluetoothLE1CharacteristicRead(const Sender: TObject;
  const ACharacteristic: TBluetoothGattCharacteristic;
  AGattStatus: TBluetoothGattStatus);
var
  Weight: Single;
begin
  Weight := (ACharacteristic.GetValueAsInteger shr 8) / 10;
    // 특성 값에서 앞의 3byte 이용, 마지막 숫자는 소숫점이므로 10으로 나누기

  Text1.Text := Format('%3.1f', [Weight]);
end;

마지막으로 앞에서 선언한 StopScale에 특성 구독을 해지하고, 블루투스 LE를 비활성화하는 코드를 추가합니다.

procedure TForm1.StopScale;
begin
  if Assigned(FBLEDevice) and Assigned(FBLEGattChar) then
    BluetoothLE1.UnSubscribeToCharacteristic(FBLEDevice, FBLEGattChar);
  BluetoothLE1.Enabled := False;
end;

8, 블루투스 권한설정

(이 과정은 안드로이드 플랫폼을 사용하지 않는 경우 생략할 수 있습니다.)

비콘은 블루투스 LE(Low Energy) 기반으로 통신합니다.  블루투스 사용권한을 설정합니다.

Project > Options 메뉴를 선택 후 Uses Permissions 화면으로 이동합니다.


Bluetooth와 Bluetooth admin 두개의 권한을 사용하도록 설정합니다.


9, 디바이스에 배포 및 테스트

처음 디바이스에 배포하는 경우 먼저 모바일 개발환경 설정하고 진행하기 바랍니다.


개발을 완료했습니다. 이제 준비된 개발환경으로 플랫폼 선택 후 Run > Run Without Debugging 메뉴를 이용해 실행하고 테스트 합니다.



테스트 방법:

  1. 디바이스의 블루투스 기능을 킵니다.
  2. 앱을 실행 후 스위치를 커서 주변의 블루투스 LE를 탐색해 스마트 체중계와 연결합니다.
    (메모 컨트롤에 "디바이스를 찾았습니다." > "서비스를 찾았습니다." > "특성에 구독했습니다." 메시지가 표시되면 정상 연결된 것입니다.)
  3. 체중계에 올라가면 실시간으로 앱에 체중정보가 표시되는 것을 확인합니다.