본문 바로가기

BLE/안드로이드 앱

[Noise Detector] 어플리케이션 (1) : 시스템 흐름

노르딕에서는 BLE(Bluetooth Low Energy) 어플리케이션 개발을 위해 nRF Toolbox 어플리케이션의 BleProfile 모듈을 제공합니다. BleProfile 모듈은 BleProfileActivity, BleProfileServiceReadyActivity, BleProfileExpandableListActivity, BleMulticonnectProfileServiceReadyActyvity 4가지 버전의 액티비티를 제공하며, 개발자는 이 중에서 한가지를 선택하여 사용할 수 있습니다. 

BleProfileManager는 연결 상태를 관리하고, 데이터를 송수신(Notification/Indication)하는 역할을 담당하며, BleProfileManager의 객체는 액티비티에 생성될 수도 있고, 서비스에서 생성될 수도 있습니다. 위 4개의 액티비티 중에 서비스를 사용하지 않는 BleProfileActivity, BleProfileExpandableListActivity는 해당 액티비티에서 BleProfileManager의 객체를 생성하고, 서비스를 사용하는 BleProfileServiceReadyActivity, BleMulticonnectProfileServiceReadyActyvity는 해당 액티비티와 연결되는 서비스에서 BleProfileManager 객체를 생성합니다.


BleProfileActivity는 시각화뿐만 아니라 BleProfileManager에서 전송된 데이터 가공까지 담당합니다. BleProfileActivity를 상속하는 액티비티는 BleProfileManager 상속 객체를 static으로 생성하여, 화면이 회전하거나 파괴될 때도 연결 상태를 유지할 수 있지만, 백그라운드 상태에서 프로세스가 종료되면 연결이 유지되지 않는 단점이 있기때문에, 최종 어플리케이션에 적합하지 않은 모델입니다. 이것은 위 GitHub 링크의 앱 설명서에도 나오는 내용입니다.


BleProfileServiceReadyActivity는 피어와 연결된 상태에서 액티비티가 생성될 때마다 BleProfileService와 바인드됩니다. BleProfileService는 백그라운드에서 BleProfileManager 객체를 유지하며, BleProfileManager에서 전송된 데이터 가공하여 BleProfileServiceReadyActivity로 방송합니다. BleProfileServiceReadyActivity는 가공된 데이터에 대한 시각화만 담당합니다. 그리고 피어와 연결이 끊기거나, 액티비티가 파괴될 때마다 BleProfileService와 언바인드됩니다

바인드되는 이유는 BleProfileServiceReadyActivity가 사용자에게 입력받은 내용대로 블루투스 연결/데이터 전송을 제어하기 위해 BleProfileService의 LocalBinder 서브클래스의 메소드를 사용하기 위함입니다. BleProfileServiceReadyActivity를 사용할 땐 BleProfileManager 객체가 BleProfileService에 있기 때문이죠.

반대로 BleProfileService에서 BleProfileServiceReadyActivity로 데이터를 전송할 때는 브로드캐스트 기능이 사용됩니다.


BleManagerCallbacks 객체는 BleProfileManager에서 생성되며, BleProfileActivity, BleProfileService가 구현하여 BleProfileManager의 BleManagerCallbacks 객체에 입력됩니다.


Noise Detector 어플리케이션은 BleProfileServiceReadyActivity, BleProfileService, BleProfileManager를 상속하여 제작된 어플리케이션입니다. 코드를 보면 알 수 있지만, 블루투스 연결 상태 제어에 대한 모든 기능을 BleProfile에서 제공하기 때문에 제가 구현한 부분은 커스텀 서비스 객체를 생성하고, 이 서비스를 이용해 데이터 송수신 정도였습니다.


오히려 어려웠던 부분이 스위치였습니다. Noise Detector ON/OFF 스위치 상태를 바꿀 때마다, 피어와의 통신이 이루어 지고, 안드로이드 화면에 토스트가 표시됩니다. 그런데 사용자가 스위치를 드래그하지 않아도 스위치 상태가 바뀔 때가 있는데, true 상태에서 화면이 회전하거나 백그라운드에서 다시 실행될 때 입니다. 따라서 이 두 동작의 중복을 막을 방법이 필요합니다.

필요 없는 라디오 동작을 막는 것은 nRF51에 구현하는 것이 더 간단하지만, 안드로이드 어플리케이션에서 중복되는 토스트를 막기 위해서 어차피 추가 동작을 지정해야 하기 때문에, 두 동작을 모두 안드로이드 어플리케이션에 구현했습니다. 이를 위해 NDSActivity의 mInteranlRecentBroadcastStatus필드가 사용됩니다.


브로드캐스트 버튼을 누르면 NDSActivity와 바인드되어 있는 NDSService의 내부 클래스인 NDSBinder의 setBroadcast() 메소드를 호출하여 NDSManager 객체에게 NDCP 특성에 쓰기를 요청하고, 결과에 대한 NDCP 특성 Indication을 기다립니다. 그리고 수신될 데이터를 처리하기 위해 NDSBinder setBroadcastStatus() 메소드를 호출하여 BleDataWrappingThread 스래드를 생성/시작합니다.


NDCP 쓰기가 성공적으로 수행되면 200ms마다 ADC 데이터를 저장하고 있는 DNV 특성의 값 속성이 Notification됩니다. BleDataWrappingThread가 이것을 수신하여 NDSActivity의 WaveData 객체에 전달합니다. BleProfile 모듈에서 서비스에서 액티비티로의 통신은 기본적으로 브로드캐스트 방식을 사용하고 있는데, 전 속도향상을 위해 이 부분만 인터페이스 콜백 방식으로 바꿨습니다.


그래픽 모듈의 CriticalLine 포스트에서 설명했지만 NDSActivity는 CriticalLine.ExternalCLValueListener를 구현해서 CriticalLine 값이 변경될 때마다 변경된 값을 즉시 수신합니다. 그리고 NDSActivity는 변경된 값을 NDSBinder의 setCLValue() 메소드를 통해 NDSService의 정적 필드에 저장하게 됩니다. 액티비티가 활성화되어 있다면, NDSService의 CriticalLine 값은 사용되지 않습니다. 하지만 액티비티가 파괴되어 NDSActivity와 NDSService가 언바인드되면, NDSService는 수신된 데이터의 maxValue와 CriticalLine 값을 비교하여 진동을 발생시킵니다.


참고로 BleDataWrappingThread라고 이름지은 이유는 BleDataWrappingThread 스래드에서 루프를 돌며 최대값을 계산하여 WaveData에서 수신된 데이터를 ArrayBundle로 포장(Wrapping)하는 작업의 부담을 줄였기 때문입니다.