본문 바로가기
  • 원하는 게 있으면 주문을 말해봐~ 디딩 보딩 디보디보딩🎶
안드로이드

[JAVA] BluetoothGatt로 BLE 사용하기(2) - 데이터 전송

by 딩보 2025. 2. 4.

🟨 전송

데이터를 전송하려면 serviceUUID와 characteristicUUID가 필요하다.

Service, Characteristic

이 둘은 블루투스 기기가 가지고 있는 기능 같은 것이다

그냥 특성(characteristic)을 그룹으로 모아놓은 것이 Service이다

  • Service는 특정 기능이나 데이터를 제공하는 논리적 그룹
  • Characteristic은 특정 기능이나 데이터

 

🔸 BluetoothActivity.java

블루투스를 관리하는 파일에 이렇게 블루투스 전송 함수를 만들어준다.

보내고자 하는 값을 byte 형식으로 변경해서 블루투스 기기(Peripheral)로 전송하게 된다.

// 블루투스 전송 함수
public void sendCommand(String command) {
    try {
        if (bluetoothGatt != null) {
            UUID serviceUUID = UUID.fromString("블루투스 기기의 ServiceUUID");
            UUID cmdCharacteristicUUID = UUID.fromString("블루투스 기기의 CharacteristicUUID");

            BluetoothGattCharacteristic cmdCharacteristic = bluetoothGatt.getService(serviceUUID)
                    .getCharacteristic(cmdCharacteristicUUID);

            if (cmdCharacteristic != null) {
                byte[] commandBytes = command.getBytes();
                cmdCharacteristic.setValue(commandBytes);

                bluetoothGatt.writeCharacteristic(cmdCharacteristic);
            }
        }
    } catch (Exception e) {
        Log.e(TAG, "Data transmission error", e);
        finish();
    }
}

 

 

 

 

만약 본인의 serviceUUID와 CharacteristicUUID가 무엇인지 모른다면 1편에서 만들었던 BluetoothGattCallback을 이용하여 쉽게 알아낼 수 있다.

GattCallback 내부의 onServicesDiscovered 메서드를 아래처럼 작성하면 블루투스 기기(Peripheral)와 연결된 후, 해당 peripheral의 service 목록과 특성 목록을 가져올 수 있다.

private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            Log.i(TAG, "Connected to GATT server.");
            // 연결 성공
            gatt.discoverServices(); // 서비스 발견

            ConnectFragment fragment = new ConnectFragment();
            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.fragment_container, fragment)
                    .commit();

        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            Log.i(TAG, "Disconnected from GATT server.");
            bluetoothGatt.close(); // 연결 종료 시 GATT 객체 닫기
        }
    }

    @Override
		public void onServicesDiscovered(BluetoothGatt gatt, int status) {
		    if (status == BluetoothGatt.GATT_SUCCESS) {
		        Log.i(TAG, "Services discovered.");
		        
		        // 서비스 목록 가져오기
		        List<BluetoothGattService> services = gatt.getServices();
		        
		        for (BluetoothGattService service : services) {
		            // 서비스 UUID 로그 출력
		            Log.i(TAG, "Service UUID: " + service.getUuid().toString());
		            
		            // 특성 목록 가져오기
		            List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
		            
		            for (BluetoothGattCharacteristic characteristic : characteristics) {
		                // 특성 UUID 로그 출력
		                Log.i(TAG, "Characteristic UUID: " + characteristic.getUuid().toString());
		            }
		        }
		    } else {
		        Log.w(TAG, "onServicesDiscovered received: " + status);
		    }
		}
};

 

이렇게 하면 여러 개의 serviceUUID와 Characteristic이 나올텐데 본인이 필요한 UUID를 사용하면 된다. 잘 모르겠다면 목록을 gpt에게 전달하면서 조금 징징거리면 알려준다ㅎㅎㅎ,,,

 

 

🔸 ConnectFragment.java

이제 블루투스 전송을 하고자 하는 파일에서 아래처럼 원하는 값을 전송해주면 완료!

// 블루투스 전송 함수
binding.btnSend.setOnClickListener(v -> {
    bluetoothActivity.sendCommand("보내고 싶은 값");
    Toast.makeText(getActivity(), "send data", Toast.LENGTH_SHORT);
    Log.d(TAG, "send data");
});

 

 

 

🟠 전체 코드

🔸 BluetoothActivity.java

package com.example.gatttest;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;

import com.example.gatttest.adapter.DeviceAdapter;
import com.example.gatttest.databinding.ActivityBluetoothBinding;

import java.util.ArrayList;
import java.util.UUID;

@SuppressWarnings("ALL")
public class BluetoothActivity extends AppCompatActivity {
    private static final String TAG = "BluetoothActivity";

    private ActivityBluetoothBinding binding;
    private BluetoothManager bluetoothManager;
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothLeScanner bluetoothLeScanner;
    private ArrayList<BluetoothDevice> discoveredDevicesList = new ArrayList<>();
    private DeviceAdapter deviceAdapter;
    private BluetoothGatt bluetoothGatt;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityBluetoothBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // RecyclerView 설정
        RecyclerView recyclerView = binding.recyclerView;
        deviceAdapter = new DeviceAdapter(discoveredDevicesList, this::onDeviceClick);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(deviceAdapter);

        initBluetooth();
    }

    private void initBluetooth() {
        bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        bluetoothAdapter = bluetoothManager.getAdapter();

        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
            Toast.makeText(this, "Bluetooth가 비활성화되어 있습니다.", Toast.LENGTH_SHORT).show();
            return;
        }

        bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();

        binding.btnScan.setOnClickListener(v -> {
            discoveredDevicesList.clear(); // 리스트 초기화
            deviceAdapter.notifyDataSetChanged(); // 어댑터에 변경 사항 알림
            scanDevice();
        });
    }

    private void scanDevice() {
        long SCAN_PERIOD = 10000; // 스캔 시간
        bluetoothLeScanner.startScan(scanCallback);
        Log.d(TAG, "scan start");

        new Handler().postDelayed(() -> {
            bluetoothLeScanner.stopScan(scanCallback);
            Toast.makeText(this, "스캔 종료", Toast.LENGTH_SHORT).show();
        }, SCAN_PERIOD);
    }

    private final ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            BluetoothDevice device = result.getDevice();
            if (device != null && !discoveredDevicesList.contains(device)) {
                if (device.getName() != null) {
                    discoveredDevicesList.add(device);
                    deviceAdapter.notifyItemInserted(discoveredDevicesList.size() - 1); // 새 장치 추가 시 알림
                    Log.d(TAG, "Discovered device: " + device.getName());
                }
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
            Log.e(TAG, "Scan failed with error code: " + errorCode);
        }
    };

    private void onDeviceClick(BluetoothDevice device) {
        connectToDevice(device);
    }

    private void connectToDevice(BluetoothDevice device) {
        bluetoothGatt = device.connectGatt(this, false, gattCallback);
        Log.d(TAG, "Connecting to device: " + device.getName());
    }

    public void sendCommand(String command) {
        try {
            if (bluetoothGatt != null) {
                UUID serviceUUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e");
                UUID cmdCharacteristicUUID = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e");

                BluetoothGattCharacteristic cmdCharacteristic = bluetoothGatt.getService(serviceUUID)
                        .getCharacteristic(cmdCharacteristicUUID);

                if (cmdCharacteristic != null) {
                    byte[] commandBytes = command.getBytes();
                    cmdCharacteristic.setValue(commandBytes);

                    bluetoothGatt.writeCharacteristic(cmdCharacteristic);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Data transmission error", e);
            finish();
        }
    }

    private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.i(TAG, "Connected to GATT server.");
                // 연결 성공
                gatt.discoverServices(); // 서비스 발견

                ConnectFragment fragment = new ConnectFragment();
                getSupportFragmentManager().beginTransaction()
                        .replace(R.id.fragment_container, fragment)
                        .commit();

            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.i(TAG, "Disconnected from GATT server.");
                bluetoothGatt.close(); // 연결 종료 시 GATT 객체 닫기
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.i(TAG, "Services discovered.");
            } else {
                Log.w(TAG, "onServicesDiscovered received: " + status);
            }
        }
    };
}

 

 

🔸 ConnectFragment.java

package com.example.gatttest;

import android.bluetooth.BluetoothGatt;
import android.os.Bundle;
import androidx.fragment.app.Fragment;

import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.example.gatttest.databinding.FragmentConnectBinding;

public class ConnectFragment extends Fragment {
    private final static String TAG = "ConnectFragment";

    private FragmentConnectBinding binding;
    private BluetoothActivity bluetoothActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentConnectBinding.inflate(inflater, container, false);
        initBluetooth();

        return binding.getRoot();
    }

    private void initBluetooth() {
        bluetoothActivity = ((BluetoothActivity) getActivity());

        binding.btnSend.setOnClickListener(v -> {
            bluetoothActivity.sendCommand("hello world");
            Toast.makeText(getActivity(), "send data", Toast.LENGTH_SHORT);
            Log.d(TAG, "send data");
        });
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }
}