투케이2K

927. (Android/Java) [간단 소스] 블루투스 GATT 서버 Open 활성 및 클라이언트 접속 상태 확인 소스 코드 - Bluetooth GATT Server 본문

Android

927. (Android/Java) [간단 소스] 블루투스 GATT 서버 Open 활성 및 클라이언트 접속 상태 확인 소스 코드 - Bluetooth GATT Server

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

[개발 환경 설정]

개발 툴 : AndroidStudio

개발 언어 : Java / Kotlin

 

[소스 코드]

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

- 언어 : Java / Kotlin

- 개발 툴 : AndroidStudio

- 기술 구분 : Bluetooth / GATT / Server

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






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

/**
* // ---------------------------------------------------------------
* 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_Server_Module";

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

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

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

static BluetoothGattServer mServer = null; // [Gatt 서버]

static ArrayList clientMacList = new ArrayList(); // [연결 된 클라이언트 리스트 목록]



// --------------------------------------------------------------
// TODO [클래스 생성 Context 지정 및 싱글톤 패턴 정의]
// --------------------------------------------------------------
public void setContext(Context ctx) {
    mMainCtx = ctx;
}

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

private static class LazyHolder {
    private static final C_Bluetooth_Gatt_Server_Module INSTANCE = new C_Bluetooth_Gatt_Server_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 {
        
        mServer = bluetoothManager.openGattServer(mMainCtx, new BluetoothGattServerCallback() {

            // --------------------------------------------------
            // TODO [원격 장치가 연결되거나 연결 해제되었을 때를 나타내는 콜백입니다]
            // --------------------------------------------------
            @Override
            public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
                super.onConnectionStateChange(device, 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());

                    // [클라이언트 리스트 배열에 추가]
                    if (clientMacList != null &&  clientMacList.contains(device.getAddress()) == false){
                        clientMacList.add(String.valueOf(device.getAddress()));

                        S_Log.w("BLE_GATT", "Add List :: " + clientMacList);
                    }

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

                    // [포함 되어 있는 경우]
                    if (clientMacList != null && clientMacList.contains(device.getAddress()) == true){
                        int idx = clientMacList.indexOf(device.getAddress());
                        if (idx >= 0){
                            clientMacList.remove(idx); // [클라이언트 삭제 수행]
                            S_Log.e("BLE_GATT", "Remove List :: " + clientMacList);
                        }
                    }
                }
            }

            // --------------------------------------------------
            // TODO [원격 클라이언트가 로컬 특성을 읽으려고 요청했습니다.]
            // --------------------------------------------------
            @Override
            public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
                super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
                S_Log.w("BLE_Read", "MAC : " + String.valueOf(device.getAddress()) + " / " + "OFFSET : " + String.valueOf(offset));

                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 [로그 출력 수행]
                S_Log._W_(ACTIVITY_NAME + " :: onCharacteristicReadRequest :: 클라이언트 >> 서버 [읽기] 요청", new String[]{
                        "Device Name :: " + String.valueOf(device.getName()),
                        "Device Mac :: " + String.valueOf(device.getAddress()),
                        "Device UUID :: " + String.valueOf(characteristic.getUuid())
                });


                // TODO [읽기 요청에 대한 응답 반환]

                if (CHARACTERISTIC_UUID.equals(characteristic.getUuid())){ // [CHARACTERISTIC_UUID 가 일치하는 경우]

                    String msg = "Success Read Response - " + C_Util.getFormNowDate("yyy-MM-dd HH:mm:ss SSS E요일");
                    byte resData [] = msg.getBytes(StandardCharsets.UTF_8);

                    S_Log._W_(ACTIVITY_NAME + " :: onCharacteristicReadRequest :: 서버 >> 클라이언트 [읽기] 응답", new String[]{
                            "Device Name :: " + String.valueOf(device.getName()),
                            "Device Mac :: " + String.valueOf(device.getAddress()),
                            "Device UUID :: " + String.valueOf(characteristic.getUuid()),
                            "Server >> Client :: " + String.valueOf(msg)
                    });

                    // TODO [응답 반환] : [BluetoothGatt.GATT_SUCCESS]
                    mServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, resData);
                }
                else {
                    S_Log.e("BLE_Read", "UUID Not Equals (CHARACTERISTIC_UUID = "+CHARACTERISTIC_UUID+" / characteristic.getUuid = "+String.valueOf(characteristic.getUuid()) + ")");
                }
            }

            // --------------------------------------------------
            // TODO [원격 클라이언트가 로컬 특성에 대한 쓰기를 요청했습니다.]
            // --------------------------------------------------
            @Override
            public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
                super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
                S_Log.w("BLE_Write", "MAC : " + String.valueOf(device.getAddress()) + " / " + "OFFSET : " + String.valueOf(offset));

                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 [클라이언트가 setValue 로 보낸 데이터 확인]
                String receivedValue = "";
                try {
                    receivedValue = new String(value, "UTF-8");
                } catch (Exception e) {
                    e.printStackTrace();
                }


                // TODO [로그 출력 수행]
                S_Log._W_(ACTIVITY_NAME + " :: onCharacteristicWriteRequest :: 클라이언트 >> 서버 [쓰기] 요청", new String[]{
                        "Device Name :: " + String.valueOf(device.getName()),
                        "Device Mac :: " + String.valueOf(device.getAddress()),
                        "Device UUID :: " + String.valueOf(characteristic.getUuid()),
                        "Client >> Server :: " + String.valueOf(receivedValue)
                });


                // TODO [쓰기 요청에 대한 응답 반환]
                if (CHARACTERISTIC_UUID.equals(characteristic.getUuid())){ // [CHARACTERISTIC_UUID 가 일치하는 경우]

                    byte resData [] = null;
                    String msg = "";

                    if (C_Util.stringNotNull(receivedValue)){ // [디바이스가 보낸 값이 널이 아닌 경우]
                        msg = receivedValue; // [요청 값 그대로 반환]

                        resData = msg.getBytes(StandardCharsets.UTF_8);
                    }
                    else {
                        msg = "Default"; // [기본 값]

                        resData = msg.getBytes(StandardCharsets.UTF_8);
                    }

                    S_Log._W_(ACTIVITY_NAME + " :: onCharacteristicReadRequest :: 서버 >> 클라이언트 [쓰기] 응답", new String[]{
                            "Device Name :: " + String.valueOf(device.getName()),
                            "Device Mac :: " + String.valueOf(device.getAddress()),
                            "Device UUID :: " + String.valueOf(characteristic.getUuid()),
                            "Server >> Client :: " + String.valueOf(msg)
                    });

                    // TODO [응답 반환] : [BluetoothGatt.GATT_SUCCESS]
                    mServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, resData);
                }
                else {
                    S_Log.e("BLE_Write", "UUID Not Equals (CHARACTERISTIC_UUID = "+CHARACTERISTIC_UUID+" / characteristic.getUuid = "+String.valueOf(characteristic.getUuid()) + ")");
                }

            }
        });


        // --------------------------------------------------
        // TODO [GATT service 추가] : 읽기, 쓰기 허용 설정
        // --------------------------------------------------
        BluetoothGattService service = new BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);
        BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(
                CHARACTERISTIC_UUID,
                BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE
        );
        service.addCharacteristic(characteristic);
        mServer.addService(service);

    }

}

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






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

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

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

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

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