투케이2K

926. (Android/Java) [간단 소스] 블루투스 GATT 클라이언트 , Remote 원격 서버 연결 소스 코드 - Bluetooth GATT Client 본문

Android

926. (Android/Java) [간단 소스] 블루투스 GATT 클라이언트 , Remote 원격 서버 연결 소스 코드 - Bluetooth GATT Client

투케이2K 2025. 1. 2. 19:01

[개발 환경 설정]

개발 툴 : AndroidStudio

개발 언어 : Java / Kotlin

 

[소스 코드]

// --------------------------------------------------------------------------------------
[개발 및 테스트 환경]
// --------------------------------------------------------------------------------------

- 언어 : Java / Kotlin

- 개발 툴 : AndroidStudio

- 기술 구분 : Bluetooth / GATT / Client

// --------------------------------------------------------------------------------------






// --------------------------------------------------------------------------------------
[사전) 필요 권한 설정 관련]
// --------------------------------------------------------------------------------------

/**
* // ---------------------------------------------------------------
* 1. 필요 퍼미션 : 필요 퍼미션 권한 : 위치 및 GPS 권한 , 블루투스 권한 - SCAN , ADVERTISE , CONNECT
*
* // TODO [공통]
* <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
* <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
*
* <uses-permission android:name="android.permission.BLUETOOTH"/>
* <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
* <uses-feature android:name="android.hardware.bluetooth_le" />
*
* // TODO [안드로이드 12 이상 : S 버전]
* <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
* <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
* <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
* // ---------------------------------------------------------------
* 2. 참고 :
*
* - 서버 및 클라이언트 GATT 서비스 설정 시 UUID 값이 일치해야합니다
* - 클라이언트가 Read 요청 시 서버는 응답 확인 현재 날짜 및 시간 값을 반환합니다
* - 클라이언트가 Write 요청 시 setValue 로 지정 된 값 그대로 반환합니다
* - 클라이언트는 원격 서버와 연결 완료 후 실시간 Notify 알림을 받을 수 있게 설정합니다
* // ---------------------------------------------------------------
* */

// --------------------------------------------------------------------------------------






// --------------------------------------------------------------------------------------
[소스 코드]
// --------------------------------------------------------------------------------------

// --------------------------------------------------------------
// TODO [전역 변수 선언]
// --------------------------------------------------------------
private static String ACTIVITY_NAME = "C_Bluetooth_Gatt_Client_Module";

private static Context mMainCtx; // [컨텍스트]

// TODO [UUIDs for the GATT service and characteristic] : 서버 및 클라이언트 UUID 값이 일치해야합니다
private static final UUID SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805F9B34FB");
private static final UUID CHARACTERISTIC_UUID = UUID.fromString("00002A19-0000-1000-8000-00805F9B34FB");

private static final UUID DESCRIPTION_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB");

private static BluetoothAdapter bluetoothAdapter; // [블루투스 어댑터]

static BluetoothGatt bluetoothGatt = null; // [Gatt 연결 클라이언트]

static ArrayList serverMacList = new ArrayList(); // [연결 된 서버 리스트 목록]

private static String DEVICE_MAC = "38:30:F9:FA:86:8C"; // [GATT 연결 서버 기기 Mac 정보]



// --------------------------------------------------------------
// TODO [클래스 생성 Context 지정 및 싱글톤 패턴 정의]
// --------------------------------------------------------------

public void setContext(Context ctx) {
    mMainCtx = ctx;
}

public static C_Bluetooth_Gatt_Client_Module getInstance() {
    return C_Bluetooth_Gatt_Client_Module.LazyHolder.INSTANCE;
}

private static class LazyHolder {
    private static final C_Bluetooth_Gatt_Client_Module INSTANCE = new C_Bluetooth_Gatt_Client_Module();
}



// --------------------------------------------------------------
// TODO [블루투스 어댑터 지정 및 GATT 연결 소스 코드]
// --------------------------------------------------------------

// [블루투스 지원 가능 기기 및 활성 상태 확인]
BluetoothManager bluetoothManager = (BluetoothManager) mMainCtx.getSystemService(BLUETOOTH_SERVICE);

bluetoothAdapter = bluetoothManager.getAdapter();

if (bluetoothAdapter == null) {
    
    String connectErrorMessage = "[Error] :: 블루투스 기능 지원 여부 확인 필요 (Bluetooth is not supported on this device)";
    return;

} else {

    if (bluetoothAdapter.isEnabled() == false) {

        String connectErrorMessage = "[Error] :: 블루투스 기능 활성 상태 확인 필요 (Bluetooth isEnabled False)";
        return;

    } else {
        
        // -------------------------------------------------------
        // [Bluetooth Gatt Connect 수행]
        // -------------------------------------------------------
        // TODO [원격 연결 된 디바이스 지정]
        BluetoothDevice device = bluetoothAdapter.getRemoteDevice(DEVICE_MAC);

        bluetoothGatt = device.connectGatt(mMainCtx, false, new BluetoothGattCallback() {

            // --------------------------------------------------
            // TODO [GATT 클라이언트가 원격 GATT 서버에 연결되거나 연결 해제되는 시점을 나타내는 콜백입니다.]
            // --------------------------------------------------
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                super.onConnectionStateChange(gatt, status, newState);
                if (mMainCtx != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // TODO [안드로이드 12 이상]
                    if (ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                        return;
                    }
                }

                S_Log._W_(ACTIVITY_NAME + " :: onConnectionStateChange :: 원격 장치 연결 및 해제 상태 체크 콜백 동작", new String[]{
                        "Device Name :: " + String.valueOf(device.getName()),
                        "Device Mac :: " + String.valueOf(device.getAddress()),
                        "Device Status :: " + String.valueOf(newState)
                });

                if (newState == BluetoothAdapter.STATE_CONNECTED) {
                    S_Log.w("BLE_GATT", "Device connected :: " + device.getAddress());

                    
                    // TODO [주변 서비스 장치 찾기]
                    gatt.discoverServices();

                } else if (newState == BluetoothAdapter.STATE_DISCONNECTED) {
                    S_Log.e("BLE_GATT", "Device disconnected :: " + device.getAddress());

                }
            }

            // --------------------------------------------------
            // TODO [원격 장치의 원격 서비스, 특성 및 설명자 목록이 업데이트되었을 때, 즉 새로운 서비스가 검색되었을 때 호출되는 콜백입니다.]
            // --------------------------------------------------
            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                super.onServicesDiscovered(gatt, status);

                if (status == BluetoothGatt.GATT_SUCCESS) {
                    BluetoothGattService service = gatt.getService(SERVICE_UUID);

                    if (service != null) {
                        BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHARACTERISTIC_UUID);

                        if (characteristic != null) {
                            if (mMainCtx != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // TODO [안드로이드 12 이상]
                                if (ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                                    return;
                                }
                            }

                            // --------------------------------------------------
                            // TODO [setCharacteristicNotification 설정]
                            // --------------------------------------------------
                            // TODO 서버로부터 실시간 notify 알림 수신 설정
                            // --------------------------------------------------
                            S_Log._W_(ACTIVITY_NAME + " :: onServicesDiscovered :: 새로운 서비스 검색 :: Notification 원격 알림 수신 설정", new String[]{ "status :: " + String.valueOf(status) });

                            gatt.setCharacteristicNotification(characteristic, true);

                            BluetoothGattDescriptor bluetoothGattDescriptor = characteristic.getDescriptor(DESCRIPTION_UUID);
                            if (bluetoothGattDescriptor != null){
                                try {
                                    bluetoothGattDescriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                                    bluetoothGatt.writeDescriptor(bluetoothGattDescriptor);
                                }
                                catch (Exception e){
                                    e.printStackTrace();
                                }
                            }
                        }
                        else {
                            S_Log._E_(ACTIVITY_NAME + " :: onServicesDiscovered :: 새로운 서비스 검색 :: Characteristic Is Null", new String[]{ "status :: " + String.valueOf(status) });
                        }

                    } else {
                        S_Log._E_(ACTIVITY_NAME + " :: onServicesDiscovered :: 새로운 서비스 검색 :: Service Is Null", new String[]{ "status :: " + String.valueOf(status) });
                    }

                } else {
                    S_Log._E_(ACTIVITY_NAME + " :: onServicesDiscovered :: 새로운 서비스 검색 :: Failed (GATT_SUCCESS False)", new String[]{ "status :: " + String.valueOf(status) });
                }
            }

            // --------------------------------------------------
            // TODO [특성 읽기 작업의 결과를 보고하는 콜백입니다.]
            // --------------------------------------------------

            @Override
            public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte []value, int status) {
                super.onCharacteristicRead(gatt, characteristic, value, status);
                S_Log.w("BLE_Read", "MAC : " + String.valueOf(device.getAddress()) + " / " + "STATUS : " + String.valueOf(status));

                if (mMainCtx != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // TODO [안드로이드 12 이상]
                    if (ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                        return;
                    }
                }


                // TODO [서버가 보낸 데이터 확인]
                String resData = "";
                try {
                    resData = new String(characteristic.getValue(), "UTF-8");
                }
                catch (Exception e){
                    e.printStackTrace();
                }


                // TODO [읽기 요청에 대한 응답 로그 출력]
                S_Log._W_(ACTIVITY_NAME + " :: onCharacteristicRead :: 서버 >> 클라이언트 [읽기] 응답 확인", new String[]{
                        "Status :: " + String.valueOf(status),
                        "Device Name :: " + String.valueOf(device.getName()),
                        "Device Mac :: " + String.valueOf(device.getAddress()),
                        "Device UUID :: " + String.valueOf(characteristic.getUuid()),
                        "Server >> Device :: " + String.valueOf(resData)
                });


                // TODO [Status 성공, 실패 로직 분기 처리]
                if (status == BluetoothGatt.GATT_SUCCESS) {

                }
                else {

                }
            }

            // --------------------------------------------------
            // TODO [원격 특성 알림의 결과로 콜백이 트리거되었습니다.]
            // --------------------------------------------------
            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte []value) {
                super.onCharacteristicChanged(gatt, characteristic, value);
                S_Log.w("BLE_Changed", "MAC : " + String.valueOf(device.getAddress()));

                if (mMainCtx != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // TODO [안드로이드 12 이상]
                    if (ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                        return;
                    }
                }


                // TODO [서버가 보낸 데이터 확인]
                String resData = "";
                try {
                    resData = new String(characteristic.getValue(), "UTF-8");
                }
                catch (Exception e){
                    e.printStackTrace();
                }


                // TODO [알림 응답 로그 출력]
                S_Log._W_(ACTIVITY_NAME + " :: onCharacteristicRead :: 서버 >> 클라이언트 [알림] 응답", new String[]{
                        "Device Name :: " + String.valueOf(device.getName()),
                        "Device Mac :: " + String.valueOf(device.getAddress()),
                        "Device UUID :: " + String.valueOf(characteristic.getUuid()),
                        "Server >> Device :: " + String.valueOf(resData)
                });
            }

            // --------------------------------------------------
            // TODO [특성 쓰기 작업의 결과를 나타내는 콜백입니다.]
            // --------------------------------------------------
            @Override
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                super.onCharacteristicWrite(gatt, characteristic, status);
                S_Log.w("BLE_Write", "MAC : " + String.valueOf(device.getAddress()) + " / " + "STATUS : " + String.valueOf(status));

                if (mMainCtx != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { // TODO [안드로이드 12 이상]
                    if (ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                        return;
                    }
                }


                // TODO [서버가 보낸 데이터 확인]
                String resData = "";
                try {
                    resData = new String(characteristic.getValue(), "UTF-8");
                }
                catch (Exception e){
                    e.printStackTrace();
                }


                // TODO [읽기 요청에 대한 응답 로그 출력]
                S_Log._W_(ACTIVITY_NAME + " :: onCharacteristicWrite :: 서버 >> 클라이언트 [쓰기] 응답 확인", new String[]{
                        "Status :: " + String.valueOf(status),
                        "Device Name :: " + String.valueOf(device.getName()),
                        "Device Mac :: " + String.valueOf(device.getAddress()),
                        "Device UUID :: " + String.valueOf(characteristic.getUuid()),
                        "Server >> Client :: " + String.valueOf(resData)
                });


                // TODO [Status 성공, 실패 로직 분기 처리]
                if (status == BluetoothGatt.GATT_SUCCESS) {

                }
                else {

                }

            }
        });

    }
}

// --------------------------------------------------------------------------------------






// --------------------------------------------------------------------------------------
[참고 사이트]
// --------------------------------------------------------------------------------------

https://developer.android.com/reference/android/bluetooth/le/ScanCallback

https://blog.naver.com/kkh0977/223704480147

https://blog.naver.com/kkh0977/223704485666

// --------------------------------------------------------------------------------------
 
반응형
Comments