BLE 장치의 내부 구조 및 소프트웨어

2015.06.26 10:46:19

안드로이드 운영체제-블루투스 연결 장치 개발과 프로그래밍(4)


안드로이드 운영체제 4.3 버전부터는 BLE(Bluetooth Low Energy)에 대한 지원을 하고 있어 쉽게 안드로이드와 아이폰에 호환되는 앱세서리 장치를 개발할 수 있게 되었다. 아이폰에서와 마찬가지로 BLE에 관련된 프로파일 및 연결 방식은 동일하게 다루어지나 그 차이에 대해서는 좀 더 살펴볼 필요가 있다. 


지난호에는 안드로이드폰에서 BLE 장치 프로그래밍에 대해 살펴봤고 이번호에서는 안드로이드와 연결되는 BLE 장치의 내부 구조 및 소프트웨어에 대해 살펴 보도록 하겠다.


안드로이드와 블루투스 장치


그림1. 아이비콘의 동작 예


그림2. 음식물의 상태를 검사하는 앱세서리


그림1은 아이비콘의 동작 모습이며 그림2는 각종 환경을 모니터링하는 앱세서리 장치, 그리고 마지막으로 그림3은 온도 및 센서를 포함하고 있는 앱세서리 장치를 볼 수 있다.


그림2. 음식물의 상태를 검사하는 앱세서리


안드로이드 장치 프로그래밍


지금부터는 TI(Texa Instrument)에서 제공하고 있는 센서태그(SensorTag·그림4)라는 장치를 이용하여 프로그램을 작성하는 방법에 대해 살펴보도록 하겠다.


그림4. 센서태그 장치의 그림


센서태그라는 장치는 TI에서 CC2541이라는 블루투스 프로세서를 이용하여 개발에 대한 하드웨어 및 소프트웨어 개발 검토를 할 수 있도록 판매하는 일종의 개발보드이다.
이 개발보드에는 8051 프로세서를 기반으로 블루투스의 통신을 처리하는 CC2540 블루투스 칩과 가속도 센서, 자이로 센서, 기압계, 온도 센서 등을 내장하고 있다.


블루투스에 대한 기능 뿐만 아니라 센서 처리하는 부분에 대해서 살펴볼 수 있기 때문에 초기 개발시에 유용한 장치라고 하겠다.

블루투스에 관련된 컴포넌트들을 추가한다.




import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothManager;




블루투스 및 BLE에 관련된 컴포넌트들을 추가한다. android.bluetooth.BluetoothGatt를 통하여 BLE 프로파일에 대한 처리를 할 수 있도록 구성한다.


 MainActivity


센서태그를 처리하기 위해서 구성된 MainActivity 즉, 주 프로그램의 시작부분이다.



@Override
  public void onCreate(Bundle savedInstanceState) {
      Log.i(TAG, "onCreate");
    // 프로그램 시작
    requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
      super.onCreate(savedInstanceState);


    // BLE 장치를 지원하는지 검사한다.
    if   (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE))   {
        Toast.makeText(this, R.string.ble_not_supported,   Toast.LENGTH_LONG).show();
        mBleSupported = false;
    }

    // 블루투스 매니저 시작, API 18 버전 이상
      mBluetoothManager = (BluetoothManager)   getSystemService(Context.BLUETOOTH_SERVICE);
      mBtAdapter = mBluetoothManager.getAdapter();

    // BT 장치를 지원하는지 검사
    if   (mBtAdapter == null) {
        Toast.makeText(this, R.string.bt_not_supported,   Toast.LENGTH_LONG).show();
        mBleSupported = false;
    }

    // 장치에 관련된 정보를 저장하기 위한 컨테이너
      mDeviceInfoList = new ArrayList<BleDeviceInfo>();
    Resources res = getResources();
      mDeviceFilter = res.getStringArray(R.array.device_filter);

    // UI 초기화
      mScanView = new ScanView();
      mSectionsPagerAdapter.addSection(mScanView, "BLE Device   List");
      mSectionsPagerAdapter.addSection(new HelpView("help_scan.html",   R.layout.fragment_help, R.id.webpage), "Help");

    // 블루투스에 관련된 이벤트 수신 컴포넌트 등록
    mFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
      mFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
    mFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
  }




BluetoothGattCallback에 대한 콜백 함수를 등록한다. 콜백 함수를 통해서 BLE 장치의 연결 및 처리에 관련된 기능들을 처리할 수 있도록 한다.




private BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {

      @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
      if   (mBluetoothGatt == null) {
          Log.e(TAG, "mBluetoothGatt not created!");
          return;
      }

        BluetoothDevice device = gatt.getDevice();
      String   address = device.getAddress();
        Log.d(TAG, "onConnectionStateChange (" + address + ")   " + newState + " status: " + status);

      try {
          switch (newState) {
          case BluetoothProfile.STATE_CONNECTED:
// 장치가 연결됨
          broadcastUpdate(ACTION_GATT_CONNECTED,   address, status);
            break;
          case BluetoothProfile.STATE_DISCONNECTED:
            // 장치 연결이 끊어짐
            broadcastUpdate(ACTION_GATT_DISCONNECTED, address, status);
            break;
          default:
          Log.e(TAG, "New state not   processed: " + newState);
            break;
        }
      }   catch (NullPointerException e) {
          e.printStackTrace();
      }
    }

      @Override
    // BLE 서비스 발견 및 처리
    public   void onServicesDiscovered(BluetoothGatt gatt, int status) {
        BluetoothDevice device = gatt.getDevice();
     broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED,device.getAddress(),status);
    }

      @Override
    // BLE 서비스 속성 변경 처리
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
     broadcastUpdate(ACTION_DATA_NOTIFY,characteristic,BluetoothGatt.GATT_SUCCESS);
    }

      @Override
// BLE 서비스 속성 읽기
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
     broadcastUpdate(ACTION_DATA_READ,characteristic,status);
    }

      @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic   characteristic, int status) {
     broadcastUpdate(ACTION_DATA_WRITE,characteristic,status);
    }

      @Override
    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
      mBusy = false;
        Log.i(TAG, "onDescriptorRead");
    }

      @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor   descriptor, int status) {
      mBusy = false;
        Log.i(TAG, "onDescriptorWrite");
    }
  };




 센서 태그 내부의 프로파일을 구성하는 UUID



UUID_ACC_SERV = fromString("f000aa10-0451-4000-b000-000000000000"),
UUID_ACC_DATA = fromString("f000aa11-0451-4000-b000-000000000000"),
UUID_ACC_CONF = fromString("f000aa12-0451-4000-b000-000000000000"),
UUID_ACC_PERI = fromString("f000aa13-0451-4000-b000-000000000000"),

UUID_HUM_SERV = fromString("f000aa20-0451-4000-b000-000000000000"),
UUID_HUM_DATA = fromString("f000aa21-0451-4000-b000-000000000000"),
UUID_HUM_CONF = fromString("f000aa22-0451-4000-b000-000000000000"),
UUID_HUM_PERI = fromString("f000aa23-0451-4000-b000-000000000000"),




블루투스 4.0은 다양한 장치를 지원하기 위해 새로운 프로파일을 정의하고 사용할 수 있게 하는 유연한 구조를 가지고 있다.
그림 5는 온도계의 정보를 다루는 프로파일의 예이다.


그림5. 온도계의 정보를 다루는 프로파일


온도계에서 필요한 정보는 온도/시간 정보다. 스마트폰에서는 온도에 대한 정보를 블루투스를 통해 온도계에 요청하고 측정된 온도/시간 정보를 전달받는다. 이것은 마치 클라이언트/서버 구조에서 요청하고 정보를 받는 구조와 유사한 구조로 동작한다.


온도계와 같은 장치는 종류나 사양이 다양하기 때문에 하나의 프로파일로 정의하기가 어렵다. 따라서 블루투스 4.0에서는 온도계와 같은 다양한 장치를 정의하기 위해 사용자가 프로파일을 정의하고 사용할 수 있게 하는 사용자 정의 프로파일을 만들 수 있는 구조를 제공, 다양한 장치에 대한 프로파일을 정의하고 구현할 수 있게 됐다. 사용자 정의 프로파일을 이용해 다양한 장치를 지원하는 기능은 대단히 유용하게 사용된다.


그림6. 온도계에 대한 정보 속성


우선 사용자 정의 프로파일은 속성 프로토콜(ATT, Attribute Protocol)을 통해 어떤 정보를 전달할 것인지에 대한 정보를 교환한다. 그림 6은 온도계 정보에 대한 정의로, 온도계 정보를 정의하기 위한 UUID(universally unique identifier), 배터리의 잔량을 알려주는 배터리 상태, 그리고 온도에 대한 정보 등으로 구성됐다.


그림7. 온도계 프로파일의 세부 정의


블루투스 4.0에서는 프로파일에 대한 고유성을 확보하기 위해 UUID를 많이 사용하는데, 이는 온도계에 관한 정보를 지정하는 프로파일이다. 온도계나 심박계와 같은 일부 장치의 경우에는 사전 스펙에 정의된 프로파일을 통해 사용할 수도 있다.


앞서 설명한 온도계 프로파일은 속성 정의 프로파일(Generic Attribute Profile)의 세부 정의에 의해 구현된다.
이 프로파일을 통해 초기에 스마트폰과 장치가 통신할 때 어떤 정보가 전달될 것인지 확인되며, 정보 또한 전송/수신된다.


속성 정의 프로파일은 장치에 대한 정보, 센서, 세부 속성 정보 등을 제공하며 스마트폰에서 실제 데이터를 전달받아 처리할 수 있도록 구성돼 있다.


 BLE 장치에 대한 정보를 관리하는 소스


BLE 장치가 연결되면 BLE 장치로의 연결 상태 및 전파 세기 정보(RSSI)를 관리하도록 구성한다.



public class BleDeviceInfo {
  // Data
  private BluetoothDevice   mBtDevice;
  private int mRssi;

  public   BleDeviceInfo(BluetoothDevice device, int rssi) {
    mBtDevice = device;
    mRssi = rssi;
  }

  public BluetoothDevice   getBluetoothDevice() {
    return mBtDevice;
  }

  public int getRssi() {
    return mRssi;
  }

  public void updateRssi(int   rssiValue) {
    mRssi = rssiValue;
  }

}




BLE 장치 연결 및 관련 이벤트 정의 문자열


BLE 장치가 연결되면 많은 이벤트가 발생한다.
GATT가 연결되고, 연결 해제시의 이벤트, 프로파일에 대한 데이터 읽기, 쓰기를 다음 이벤트 정의 문자열을 통해 관리하게 된다.




public final static   String ACTION_GATT_CONNECTED   ="ti.android.ble.common.ACTION_GATT_CONNECTED";
  public final static String   ACTION_GATT_DISCONNECTED =   "ti.android.ble.common.ACTION_GATT_DISCONNECTED";
  public final static String   ACTION_GATT_SERVICES_DISCOVERED =   "ti.android.ble.common.ACTION_GATT_SERVICES_DISCOVERED";
  public final static String ACTION_DATA_READ   = "ti.android.ble.common.ACTION_DATA_READ";
  public final static String   ACTION_DATA_NOTIFY = "ti.android.ble.common.ACTION_DATA_NOTIFY";
  public final static String   ACTION_DATA_WRITE = "ti.android.ble.common.ACTION_DATA_WRITE";
  public final static String EXTRA_DATA =   "ti.android.ble.common.EXTRA_DATA";
  public final static String EXTRA_UUID =   "ti.android.ble.common.EXTRA_UUID";
  public final static String EXTRA_STATUS =   "ti.android.ble.common.EXTRA_STATUS";
  public final static String   EXTRA_ADDRESS = "ti.android.ble.common.EXTRA_ADDRESS";




BLE 장치에서 장치에 가지고 있는 프로파일을 read Characteristic, write Characteristic 함수를 통하여 읽고, 쓰도록 한다. 프로파일이 가지고 있는 내용, 즉 센서의 경우 센서의 속성과 크기를 읽어 들어 처리할 수 있도록 준비하는 과정이다.

 



public   void readCharacteristic(BluetoothGattCharacteristic characteristic) {
   if   (!checkGatt())
   return;
    mBusy = true;
      mBluetoothGatt.readCharacteristic(characteristic);
  }

  public boolean writeCharacteristic(BluetoothGattCharacteristic   characteristic, byte b) {
   if   (!checkGatt())
   return   false;
   
    byte[] val = new byte[1];
    val[0] = b;
    characteristic.setValue(val);

    mBusy = true;
    return   mBluetoothGatt.writeCharacteristic(characteristic);
  }




enableSensors 함수는 센서를 동작시키는 코드다. 즉, 스마트폰에서 센서에 대한 처리를 제어할 수 있도록 구성하고, 센서 정보를 업데이트 할 수 있도록 구성하는 함수이다. 이 기능을 이용하면 BLE 장치에 내장된 다양한 장치를 스마트폰에서 끄거나 켤 수 있도록 구성할 수 있다. 필요한 기능만 활성화 시키는 것이 가능하다는 것이다.




private void   enableSensors(boolean enable) {
   for   (Sensor sensor : mEnabledSensors) {
   UUID   servUuid = sensor.getService();
   UUID   confUuid = sensor.getConfig();
   
   if   (confUuid == null)
   break;
   //   기압계에 대한 처리
 if   (confUuid.equals(SensorTag.UUID_BAR_CONF) && enable) {
 calibrateBarometer();
 }
 
   BluetoothGattService   serv = mBtGatt.getService(servUuid);
   BluetoothGattCharacteristic   charac = serv.getCharacteristic(confUuid);
   byte   value =  enable ?   sensor.getEnableSensorCode() : Sensor.DISABLE_SENSOR_CODE;
   mBtLeService.writeCharacteristic(charac,   value);
 mBtLeService.waitIdle(GATT_TIMEOUT);
   }
   
  }




enableNotifications 함수는 센서에 대한 자동 노티피케이션 처리를 할 수 있도록 구성하는 함수다. 설정한 주기에 따라 자동으로 스마트폰으로 정보를 전달하고 스마트폰에서 처리하도록 구성하는 것이다.




private void   enableNotifications(boolean enable) {
   for   (Sensor sensor : mEnabledSensors) {
   UUID   servUuid = sensor.getService();
   UUID   dataUuid = sensor.getData();
   BluetoothGattService   serv = mBtGatt.getService(servUuid);
   BluetoothGattCharacteristic   charac = serv.getCharacteristic(dataUuid);
   
   mBtLeService.setCharacteristicNotification(charac,enable);
 mBtLeService.waitIdle(GATT_TIMEOUT);
   }
  }




끝으로


지금까지 센서태그 장치를 이용하여 안드로이드 프로그램을 작성하는 부분에 대해서 살펴봤다. 다음호에서는 하드웨어 내부의 소스와 안드로이드 앱간의 통신에 대한 세부 내용에 대해서 살펴보도록 하겠다.


라영호 대표 _ 주식회사 테뷸라


Copyright ⓒ 첨단 & automationasia.net



상호명(명칭) : ㈜첨단 | 등록번호 : 서울,아54000 | 등록일자 : 2021년 11월 1일 | 제호 : 오토메이션월드 | 발행인 : 이종춘 | 편집인 : 임근난 | 본점 : 서울시 마포구 양화로 127, 3층, 지점 : 경기도 파주시 심학산로 10, 3층 | 발행일자 : 2021년 00월00일 | 청소년보호책임자 : 김유활 | 대표이사 : 이준원 | 사업자등록번호 : 118-81-03520 | 전화 : 02-3142-4151 | 팩스 : 02-338-3453 | 통신판매번호 : 제 2013-서울마포-1032호 copyright(c)오토메이션월드 all right reserved