[Android App BLE] #6 BLE 장치 통신 설정

in #kr6 years ago (edited)

이전글 - [Android App BLE] #5 BLE 장치 Connect 구현

다음을 주로 참고하여 작성하였습니다.
Bluetooth Low Energy on Android, Part 1


출처: https://simbeez.com/


BluoothUtils 클래스

이번에 코딩할 부분은 BLE의 Gatt Server와 Client간의 데이터를 주고 받는 부분입니다. 따라서 BLE 관련하여 Helper 함수들이 많이 필요합니다. 예를 들어 Gatt 서버에서 제공하는 Characteristic 중에 우리가 원하는 서비가 있는지 찾고, 비교하는 함수들이 빈번하게 쓰입니다. 이를 위해 BluetoothUtils 클래스를 별도의 파일로 만듭니다. BluetoothUtils에서 필요한 상수들도 별도의 Constants.java 파일로 만듭니다. 이것들은 부수적인 내용이고 분량이 좀 많은 관계로 소스 코드는 내용의 흐름을 위해 후반부에 배치하도록 하겠습니다.

주의할 것은 기존의 MainActivity에 선언된 상수들은 Constants.java로 옮기고, MainActivity에서 Constants 클래스를 import 해야 합니다. 이렇게요.

import static club.etain.blecontrol.Constants.MAC_ADDR;
import static club.etain.blecontrol.Constants.REQUEST_ENABLE_BT;
import static club.etain.blecontrol.Constants.REQUEST_FINE_LOCATION;
import static club.etain.blecontrol.Constants.SCAN_PERIOD;
import static club.etain.blecontrol.Constants.TAG;

public class MainActivity extends AppCompatActivity {
(생략)

서비스 설정

이제 BLE utils 함수들이 준비가 되었으니 Gatt 서버와 데이터를 주고 받을 준비가 됐습니다.

이전글 마지막에 onConnectionStateChange 함수에서 BLE 장치에 연결되었을 때, 단순히 log만 출력했는데, 이제는 장치에서 제공하는 서비스가 뭔지 알아보는 구현을 해보겠습니다. 이를 위해 먼저 서비스를 디스커버 해야 합니다. 아래와 같이 onConnectionStateChange에 코딩합니다.

private class GattClientCallback extends BluetoothGattCallback {
        @Override
        public void onConnectionStateChange( BluetoothGatt _gatt, int _status, int _new_state ) {
            (생략)
            if( _new_state == BluetoothProfile.STATE_CONNECTED ) {
                // set the connection flag
                connected_= true;
                Log.d( TAG, "Connected to the GATT server" );
                _gatt.discoverServices();
            } else if ( _new_state == BluetoothProfile.STATE_DISCONNECTED ) {
                disconnectGattServer();
            }
        }
(생략)

그러면 서비스가 발견되면 이를 처리하는 콜백을 이어서 만들어야 합니다. 추가적으로 데이터를 쓰고, 읽고, 상태 변화가 발생했을 때 처리하는 콜백 함수도 같이 만들도록 합니다.

private class GattClientCallback extends BluetoothGattCallback {
(생략)
        @Override
        public void onServicesDiscovered( BluetoothGatt _gatt, int _status ) {
            super.onServicesDiscovered( _gatt, _status );
            // check if the discovery failed
            if( _status != BluetoothGatt.GATT_SUCCESS ) {
                Log.e( TAG, "Device service discovery failed, status: " + _status );
                return;
            }
            // find discovered characteristics
            List<BluetoothGattCharacteristic> matching_characteristics= BluetoothUtils.findBLECharacteristics( _gatt );
            if( matching_characteristics.isEmpty() ) {
                Log.e( TAG, "Unable to find characteristics" );
                return;
            }
            // log for successful discovery
            Log.d( TAG, "Services discovery is successful" );
        }

        @Override
        public void onCharacteristicChanged( BluetoothGatt _gatt, BluetoothGattCharacteristic _characteristic ) {
            super.onCharacteristicChanged( _gatt, _characteristic );

            Log.d( TAG, "characteristic changed: " + _characteristic.getUuid().toString() );
            readCharacteristic( _characteristic );
        }

        @Override
        public void onCharacteristicWrite( BluetoothGatt _gatt, BluetoothGattCharacteristic _characteristic, int _status ) {
            super.onCharacteristicWrite( _gatt, _characteristic, _status );
            if( _status == BluetoothGatt.GATT_SUCCESS ) {
                Log.d( TAG, "Characteristic written successfully" );
            } else {
                Log.e( TAG, "Characteristic write unsuccessful, status: " + _status) ;
                disconnectGattServer();
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d (TAG, "Characteristic read successfully" );
                readCharacteristic(characteristic);
            } else {
                Log.e( TAG, "Characteristic read unsuccessful, status: " + status);
                // Trying to read from the Time Characteristic? It doesnt have the property or permissions
                // set to allow this. Normally this would be an error and you would want to:
                // disconnectGattServer();
            }
        }

        /*
        Log the value of the characteristic
        @param characteristic
         */
        private void readCharacteristic( BluetoothGattCharacteristic _characteristic ) {
            byte[] msg= _characteristic.getValue();
            Log.d( TAG, "read: " + msg.toString() );
        }

Characteristic 정보 확인

여기서 Characteristic 정보가 잘 찾아지는지 테스트를 해봅니다. 빌드를 하고 앱을 실행시켜 봅니다. 그런데 Log창에 아래와 같이 에러가 발생합니다. app registration이 실패했다고 나옵니다.
image.png

이전에 동작하는 코드로 되돌려서 실행해 봐도 동일한 에러 메시지가 나옵니다. 이럴 때 어떻게 할까요? 네 바로 모든 걸 리셋하는 것입니다!
참고로 git으로 소스 관리를 하면 참 편합니다. 지금과 같이 이전에 제대로 실행되는 코드로 쉽게 돌아가서 테스트를 해볼 수 있으니까요.

안드로이드 스튜디어만 다시 껐다 키니 제대로 동작합니다. 당황하지 않으셨죠? ㅋ
앗! 그런데 제대로 동작하는게 아니었습니다. connection 시도 후 서버에서 응답이 없는 것처럼 나옵니다. 당황스럽네요.

어쩔 수 없이 스마트폰을 리셋합니다! 잠시 기다린 후, 다시 앱을 실행시킵니다.
짜잔~
image.png

모든게 계획대로 잘 동작하는 것을 확인할 수 있습니다. 아마도 BLE가 썩 안정적이지 않는 것 같습니다.

The BLE callbacks have very little tolerance for delay, and stepping through with debugger will cause problems. This is why it’s important to do as little work in the callback as possible.
BLE 콜백에서 지연을 별로 허용하지 않기 때문에, 콜백에서는 가능한 코드를 줄여야 함

뭐 자세한 원인은 검색해봐도 잘 안나옵니다. Gatt 서버가 이미 할당이 되어 있다는 말도 좀 보이긴 합니다.

Constants 클래스 소스 코드

  • 파일명: Constants.java
    기존에 MainActivity에 선언된 constants를 여기로 옮겨 옵니다.
public class Constants {
    // tag for log message
    public static String TAG= "ClientActivity";
    // used to identify adding bluetooth names
    public static int REQUEST_ENABLE_BT= 1;
    // used to request fine location permission
    public static int REQUEST_FINE_LOCATION= 2;

    //// focus v3 service. refer. https://www.foc.us/bluetooth-low-energy-api
    // service and uuid
    public static String SERVICE_STRING = "0000aab0-f845-40fa-995d-658a43feea4c";
    public static UUID UUID_TDCS_SERVICE= UUID.fromString(SERVICE_STRING);
    // command uuid
    public static String CHARACTERISTIC_COMMAND_STRING = "0000AAB1-F845-40FA-995D-658A43FEEA4C";
    public static UUID UUID_CTRL_COMMAND = UUID.fromString( CHARACTERISTIC_COMMAND_STRING );
    // response uuid
    public static String CHARACTERISTIC_RESPONSE_STRING = "0000AAB2-F845-40FA-995D-658A43FEEA4C";
    public static UUID UUID_CTRL_RESPONSE = UUID.fromString( CHARACTERISTIC_COMMAND_STRING );
    // focus MAC address
    public final static String MAC_ADDR= "78:A5:04:58:A7:92";
    // scan period
    public static final long SCAN_PERIOD = 5000;
}

BlutoothUtils 클래스 소스 코드

다음 소스에서 가져왔습니다.
https://github.com/bignerdranch/android-bluetooth-testbed/tree/a/android-ble-part-1
제 코딩 스타일에 맞게 변경하고, 제어할 장치에 맞게 약간 수정했습니다.

  • 파일명: BluetoothUtils.java
public class BluetoothUtils {
    /*
    Find characteristics of BLE
    @param gatt gatt instance
    @return list of found gatt characteristics
     */
    public static List<BluetoothGattCharacteristic> findBLECharacteristics(BluetoothGatt _gatt ) {
        List<BluetoothGattCharacteristic> matching_characteristics = new ArrayList<>();
        List<BluetoothGattService> service_list = _gatt.getServices();
        BluetoothGattService service = findGattService(service_list);
        if (service == null) {
            return matching_characteristics;
        }

        List<BluetoothGattCharacteristic> characteristicList = service.getCharacteristics();
        for (BluetoothGattCharacteristic characteristic : characteristicList) {
            if (isMatchingCharacteristic(characteristic)) {
                matching_characteristics.add(characteristic);
            }
        }

        return matching_characteristics;
    }

    /*
    Find command characteristic of the peripheral device
    @param gatt gatt instance
    @return found characteristic
     */
    @Nullable
    public static BluetoothGattCharacteristic findCommandCharacteristic( BluetoothGatt _gatt ) {
        return findCharacteristic( _gatt, CHARACTERISTIC_COMMAND_STRING );
    }

    /*
    Find response characteristic of the peripheral device
    @param gatt gatt instance
    @return found characteristic
     */
    @Nullable
    public static BluetoothGattCharacteristic findResponseCharacteristic( BluetoothGatt _gatt ) {
        return findCharacteristic( _gatt, CHARACTERISTIC_RESPONSE_STRING );
    }

    /*
    Find the given uuid characteristic
    @param gatt gatt instance
    @param uuid_string uuid to query as string
     */
    @Nullable
    private static BluetoothGattCharacteristic findCharacteristic(BluetoothGatt _gatt, String _uuid_string) {
        List<BluetoothGattService> service_list= _gatt.getServices();
        BluetoothGattService service= BluetoothUtils.findGattService( service_list );
        if( service == null ) {
            return null;
        }

        List<BluetoothGattCharacteristic> characteristicList = service.getCharacteristics();
        for( BluetoothGattCharacteristic characteristic : characteristicList) {
            if( matchCharacteristic( characteristic, _uuid_string ) ) {
                return characteristic;
            }
        }

        return null;
    }

    /*
    Match the given characteristic and a uuid string
    @param characteristic one of found characteristic provided by the server
    @param uuid_string uuid as string to match
    @return true if matched
     */
    private static boolean matchCharacteristic( BluetoothGattCharacteristic _characteristic, String _uuid_string ) {
        if( _characteristic == null ) {
            return false;
        }
        UUID uuid = _characteristic.getUuid();
        return matchUUIDs( uuid.toString(), _uuid_string );
    }

    /*
    Find Gatt service that matches with the server's service
    @param service_list list of services
    @return matched service if found
     */
    @Nullable
    private static BluetoothGattService findGattService(List<BluetoothGattService> _service_list) {
        for (BluetoothGattService service : _service_list) {
            String service_uuid_string = service.getUuid().toString();
            if (matchServiceUUIDString(service_uuid_string)) {
                return service;
            }
        }
        return null;
    }

    /*
    Try to match the given uuid with the service uuid
    @param service_uuid_string service UUID as string
    @return true if service uuid is matched
     */
    private static boolean matchServiceUUIDString(String _service_uuid_string) {
        return matchUUIDs( _service_uuid_string, SERVICE_STRING );
    }

    /*
    Check if there is any matching characteristic
    @param characteristic query characteristic
     */
    private static boolean isMatchingCharacteristic( BluetoothGattCharacteristic _characteristic ) {
        if( _characteristic == null ) {
            return false;
        }
        UUID uuid = _characteristic.getUuid();
        return matchCharacteristicUUID( uuid.toString() );
    }

    /*
    Query the given uuid as string to the provided characteristics by the server
    @param characteristic_uuid_string query uuid as string
    @return true if the matched is found
     */
    private static boolean matchCharacteristicUUID( String _characteristic_uuid_string ) {
        return matchUUIDs( _characteristic_uuid_string, CHARACTERISTIC_COMMAND_STRING, CHARACTERISTIC_RESPONSE_STRING );
    }

    /*
    Try to match a uuid with the given set of uuid
    @param uuid_string uuid to query
    @param matches a set of uuid
    @return true if matched
     */
    private static boolean matchUUIDs( String _uuid_string, String... _matches ) {
        for( String match : _matches ) {
            if( _uuid_string.equalsIgnoreCase(match) ) {
                return true;
            }
        }

        return false;
    }
}
  • @Nullable은 함수의 리턴이 null 일 수 있다는 것을 의미

와 이제 정말 블루투스 통신하기 위핸 거의 모든 준비가 완료된 거 같습니다. 이제 정말 기기에 제어 명령을 보내고 데이터를 읽는 작업만 남았습니다. 90%는 했다고 볼 수 있을거 같네요.

오늘의 실습: 이해가 안가면 그냥 따라해 보세요. 사실 저도 다 이해 못했습니다 ^^; 중요하지 않은 부분은 가볍게 패스하는 마음을 가져보세요.

Sort:  

즐거운 스팀잇 생활하시나요?
무더위야 가라!!!!

Coin Marketplace

STEEM 0.30
TRX 0.12
JST 0.034
BTC 63688.35
ETH 3125.30
USDT 1.00
SBD 3.97