Notice
Recent Posts
Recent Comments
Link
투케이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
// --------------------------------------------------------------------------------------
반응형
'Android' 카테고리의 다른 글
Comments