투케이2K

160. (TWOK/UTIL) [Android/Java] C_Bluetooth_Spp_Client_Module : 블루투스 SPP 통신 클라이언트 모듈 클래스 본문

투케이2K 유틸파일

160. (TWOK/UTIL) [Android/Java] C_Bluetooth_Spp_Client_Module : 블루투스 SPP 통신 클라이언트 모듈 클래스

투케이2K 2025. 2. 23. 13:02

[설 명]

프로그램 : Android / Java

설 명 : C_Bluetooth_Spp_Server_Module : 블루투스 SPP 통신 서버 모듈 클래스

 

[소스 코드]

package com.example.javaproject.C_Module;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;

import androidx.core.app.ActivityCompat;

import com.example.javaproject.C_Util;
import com.example.javaproject.S_Log;

import org.json.JSONObject;

import java.io.DataInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.UUID;

public class C_Bluetooth_Spp_Client_Module {



    /**
     * // --------------------------------------------------------------------------------------
     * TODO [클래스 설명]
     * // --------------------------------------------------------------------------------------
     * 1. [설명] : 블루투스 SPP 통신 사용 모듈
     * // --------------------------------------------------------------------------------------
     * 2. 필요 퍼미션 : 필요 퍼미션 권한 : 위치 및 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"/>
     * // --------------------------------------------------------------------------------------
     * */





    /**
     * // --------------------------------------------------------------------------------------
     * TODO [빠른 로직 찾기 : 주석 로직 찾기]
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     *
     * // --------------------------------------------------------------------------------------
     */





    // ------------------------------------------------------------------------------------------
    // TODO [사용 방법]
    // ------------------------------------------------------------------------------------------
    /*
    try {

        new Thread(){
				@Override
				public void run(){
					try {

                        // -------------------------------------------------
                        // TODO [1]. 소켓 통신 연결을 위한 Remote 기기 Mac 주소 지정
                        // -------------------------------------------------
                        // {address=38:30:F9:FA:86:8C, name=1996124, alias=1996124, state=12, type=3}
                        // -------------------------------------------------
                        C_Bluetooth_Spp_Client_Module.DEVICE_ADDRESS = "38:30:F9:FA:86:8C";


                        // --------------------------------------
                        // TODO [2]. [소켓 모듈 클래스 인스턴스 초기화]
                        // --------------------------------------
                        C_Bluetooth_Spp_Client_Module c_socket_module = C_Bluetooth_Spp_Client_Module.getInstance();
                        c_socket_module.setContext(A_Intro.this);


                        // --------------------------------------
                        // TODO [3]. [소켓 연결 수행]
                        // --------------------------------------
                        if(c_socket_module.connectHost(C_Bluetooth_Spp_Client_Module.DEVICE_ADDRESS) == true) { // TODO [소켓 연결 상태 확인]


                            // --------------------------------------
                            // TODO [4]. [기기 등록 요청 수행]
                            // --------------------------------------
                            HashMap<String, Object> deviceRegMap = c_socket_module.request_Device_Reg();

                            S_Log._W_("소켓 통신 :: 디바이스 등록 요청 수행 결과 확인", new String[]{
                                    "SUCCESS :: " + String.valueOf(deviceRegMap.get(c_socket_module.RETURN_SUCCESS_FLAG))
                            });


                            // --------------------------------------
                            // TODO [5]. [소켓 연결 종료]
                            // --------------------------------------
                            c_socket_module.closeSocket();

                        }
                        else {
                            S_Log._E_("C_Bluetooth_Spp_Client_Module :: connectHost :: 소켓 연결 실패", null);
                        }

					}
					catch (Exception e) {
						e.printStackTrace();
					}
				}
			}.start();

    }
    catch (Exception e) {
        e.printStackTrace();
    }
     */
    // ------------------------------------------------------------------------------------------





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

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

    public static String DEVICE_ADDRESS = "00:00:00:00:00:00"; // TODO [Connection Device Bluetooth Mac]

    private static final UUID UUID_ARRAY[] = {
            UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"), // TODO [Client : SmartPhone / Server : SmartPhone]
            UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") // TODO [Client : SmartPhone / Server : Device]
    };

    private BluetoothAdapter bluetoothAdapter; // [블루투스 어댑터]
    private BluetoothSocket bluetoothSocket; // [블루투스 소켓]

    private OutputStream mOutputStream; // [스트림]
    private PrintWriter mPrintWriter; // [스트림]
    private InputStream mInputStream; // [스트림]
    private DataInputStream mDataDataInputStream; // [스트림]

    public static final String RETURN_SUCCESS_FLAG = "RETURN_SUCCESS_FLAG"; // [리턴 데이터 반환 키 값]
    public static final String RETURN_ERROR_MESSAGE = "RETURN_ERROR_MESSAGE"; // [리턴 데이터 반환 키 값]




    // ------------------------------------------------------------------------------------------
    // TODO [콘텍스트 지정]
    // ------------------------------------------------------------------------------------------
    public void setContext(Context ctx) {
        mMainCtx = ctx;
    }





    // ------------------------------------------------------------------------------------------
    // TODO [인스턴스 생성]
    // ------------------------------------------------------------------------------------------
    public static C_Bluetooth_Spp_Client_Module getInstance() { return C_Bluetooth_Spp_Client_Module.LazyHolder.INSTANCE; }
    private static class LazyHolder {
        private static final C_Bluetooth_Spp_Client_Module INSTANCE = new C_Bluetooth_Spp_Client_Module();
    }





    // ------------------------------------------------------------------------------------------
    // TODO [디바이스 호스트 연결 실시]
    // ------------------------------------------------------------------------------------------
    /**
     * @param deviceAddress : 연결한 Remote 디바이스 Mac 주소
     * @return : 정상적으로 페어링 및 연결이 완료 된 경우 true , 아니면 false
     *
     * 참고 사항 정리 :
     * 1. 사전 페어링이 되어 있지 않은 경우는 먼저, 페어링 요청 진행 후 >> Remote 쪽에서 수락 시 연결 상태 return 값 확인 가능
     * 2. 사전 페어링이 되어 있는 경우, 즉시 connect 연결 시도 및 return 반환 수행
     * */
    // ------------------------------------------------------------------------------------------
    public synchronized boolean connectHost(String deviceAddress) {

        // [변수 선언]
        boolean isConnection = false;
        String M_LOG = "";

        // [로직 처리 수행]
        try {

            if (mMainCtx != null){

                // [블루투스 지원 가능 기기 및 활성 상태 확인]
                bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
                if (bluetoothAdapter == null) {
                    M_LOG = "[Error] :: 블루투스 기능 지원 여부 확인 필요 (Bluetooth is not supported on this device)";
                }
                else {

                    if (bluetoothAdapter.isEnabled() == false){
                        M_LOG = "[Error] :: 블루투스 기능 활성 상태 확인 필요 (Bluetooth isEnabled False)";
                    }
                    else {

                        // [기능 사용에 필요한 권한 부여 상태 확인]
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ // TODO [안드로이드 12 이상]

                            if (ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
                                    || ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED

                                    || ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED
                                    || ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED
                                    || ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.BLUETOOTH_ADVERTISE) != PackageManager.PERMISSION_GRANTED){

                                M_LOG = "[Error] :: 안드로이드 12 이상 - 블루투스 기능 사용 퍼미션 권한 확인 필요 (Permission Not Granted)";
                            }
                        }
                        else { // TODO [안드로이드 12 미만]
                            if (ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
                                    || ActivityCompat.checkSelfPermission(mMainCtx, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){

                                M_LOG = "[Error] :: 안드로이드 12 미만 - 블루투스 기능 사용 퍼미션 권한 확인 필요 (Permission Not Granted)";
                            }
                        }


                        // TODO [에러 발생한 적이 없는 경우 : 상위에서 방어 로직 통과한 경우]
                        if (C_Util.stringNotNull(M_LOG) == false){

                            // [소켓 연결 수행]
                            if(this.bluetoothSocket != null) { // TODO [소켓이 널이 아닌 경우]
                                M_LOG = "[Error] :: 블루투스 소켓 연결 된 상태 (Socket Not Null)";
                            }
                            else { // TODO [소켓이 널인 경우]
                                S_Log.w("BLE_SPP", "CONNECT_MAC :: " + String.valueOf(deviceAddress));

                                if (C_Util.stringNotNull(deviceAddress) == true
                                        && String.valueOf(deviceAddress).contains(":") == true){ // [Mac 이 널이 아닌 경우]

                                    // TODO [원격 장치 가져오기] : Ex - {address=38:30:F9:FA:86:8C, name=1996124, alias=1996124, state=12, type=3}
                                    BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress);
                                    S_Log.w("BLE_SPP", "CONNECT_ING :: " + String.valueOf(device.getName()) + " / " + String.valueOf(device.getAddress()));


                                    // TODO [BluetoothSocket 생성]
                                    bluetoothSocket = device.createRfcommSocketToServiceRecord(UUID_ARRAY[0]);


                                    // TODO [연결 시도]
                                    bluetoothSocket.connect();
                                    S_Log.w("BLE_SPP", "CONNECT_SUCCESS :: Connected to the device :: " + String.valueOf(deviceAddress));


                                    // TODO [입출력 스트림 열기]
                                    this.mOutputStream = this.bluetoothSocket.getOutputStream(); // [읽기]
                                    this.mPrintWriter = new PrintWriter(mOutputStream);

                                    this.mInputStream = this.bluetoothSocket.getInputStream(); // [쓰기]
                                    this.mDataDataInputStream = new DataInputStream(mInputStream);

                                    M_LOG = "\n[Success] :: 블루투스 소켓 생성 및 호스트 연결 완료\n";
                                    M_LOG += "\n[Input Device Mac] :: " + String.valueOf(deviceAddress) + "\n";
                                    M_LOG += "\n[Remote Device Name] :: " + String.valueOf(device.getName()) + "\n";
                                    M_LOG += "\n[Remote Device Mac] :: " + String.valueOf(device.getAddress()) + "\n";
                                    M_LOG += "\n[Remote Device Type] :: " + String.valueOf(device.getType()) + "\n";
                                    M_LOG += "\n[Remote Device Uuids] :: " + String.valueOf(device.getUuids()) + "\n";
                                    M_LOG += "\n[Remote Device Bond State] :: " + String.valueOf(device.getBondState()) + "\n";

                                    // TODO [연결 완료 리턴 변수 삽입]
                                    isConnection = true;
                                }
                                else {
                                    M_LOG = "[Error] :: 연결할 디바이스 맥 정보 확인 필요 (deviceAddress Is Null)";
                                }
                            }
                        }

                    }
                }

            }
            else {
                M_LOG = "[Error] :: mMainCtx Is Null";
            }

        }
        catch (Exception e){
            e.printStackTrace();

            // [로그 삽입]
            M_LOG = "[Exception] :: " + String.valueOf(e.getMessage());

            // [Exception 발생 시 소켓 연결 종료]
            closeSocket();

        }

        S_Log._W_(ACTIVITY_NAME + " :: connectHost :: 블루투스 소켓 호스트 연결 수행", new String[]{
                "M_LOG :: " + M_LOG,
                "RETURN :: " + isConnection
        });

        // [리턴 반환 수행]
        return isConnection;
    }





    // ------------------------------------------------------------------------------------------
    // TODO [소켓 연결 종료]
    // ------------------------------------------------------------------------------------------
    public synchronized void closeSocket() {

        // [변수 선언]
        String M_LOG = "";

        // [로직 처리 수행]
        try {

            // [생성된 소켓이 있는 경우 종료 및 스트림 닫기]
            if(this.bluetoothSocket != null) {

                // [스트림 닫기]
                this.mPrintWriter.close();
                this.mDataDataInputStream.close();
                this.mOutputStream.close();
                this.mInputStream.close();

                // [소켓 닫기]
                this.bluetoothSocket.close();
                this.bluetoothSocket = null;

                M_LOG = "[Success] :: bluetoothSocket Closed";
            }
            else {
                M_LOG = "[Error] :: bluetoothSocket Is Null";
            }
        }
        catch (Exception e) {
            e.printStackTrace();

            M_LOG = "[Exception] :: " + String.valueOf(e.getMessage());
            try { this.bluetoothSocket = null; } catch (Exception es){}
        }

        S_Log._E_(ACTIVITY_NAME + " :: closeSocket :: 블루투스 소켓 연결 종료 수행", new String[]{ "M_LOG :: " + String.valueOf(M_LOG) });

    }





    // ------------------------------------------------------------------------------------------
    // TODO [SEARCH FAST] : [기기 등록 요청 실시 >> 응답 결과 확인]
    // ------------------------------------------------------------------------------------------
    public synchronized HashMap request_Device_Reg() {
        S_Log._D_("기기 등록 요청 수행", null);

        /**
         * // ----------------------------------------
         * TODO [REQUEST] : [APP >> DEVICE]
         * // ----------------------------------------
         *
         * {
         *   "reg_name" : "TOWK"
         * }
         *
         * // ----------------------------------------
         * */


        /**
         * // ----------------------------------------
         * TODO [RESPONSE] : [DEVICE >> APP]
         * // ----------------------------------------
         *
         * {
         *   "result" : true
         * }
         *
         * // ----------------------------------------
         * */



        // -----------------------------------------------
        // [리턴 변수 선언]
        // -----------------------------------------------
        HashMap<String, Object> returnData = new HashMap<>();



        // -----------------------------------------------
        // [로직 처리 수행]
        // -----------------------------------------------
        try {

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

            // TODO [1]. [디바이스 등록 요청 실시]
            JSONObject reqJson = new JSONObject();
            reqJson.put("process", "DP01");
            reqJson.put("mode", "create");

            String requestData = reqJson.toString();

            S_Log._D_("[요청] :: [APP] >> [DEVICE] :: 기기 등록 요청 수행", new String[]{
                    "REQUEST :: " + requestData.toString()
            });

            mPrintWriter.write(requestData); // [데이터 전송]
            mPrintWriter.flush();

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



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

            // TODO [2]. [디바이스 정보 응답 확인 실시]
            int len = 0;
            byte[] buffer = new byte[6144];

            if(mInputStream != null){
                len = mInputStream.read(buffer);
            }

            byte[] subBuffer = new byte[len];
            System.arraycopy(buffer, 0, subBuffer,0, len);


            // [byte to string 변환]
            String responseString = new String(subBuffer, "UTF-8");
            responseString = responseString.trim();

            S_Log._D_("[응답] :: [DEVICE] >> [APP] :: 기기 등록 응답 확인", new String[]{
                    "RESPONSE :: " + responseString.toString()
            });


            // [리턴 데이터 삽입]
            returnData.put(RETURN_SUCCESS_FLAG, true);
            returnData.put(RETURN_ERROR_MESSAGE, responseString);

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

        }
        catch (Exception e){
            e.printStackTrace();

            // [에러 발생 데이터 삽입]
            returnData.put(RETURN_SUCCESS_FLAG, false);
            returnData.put(RETURN_ERROR_MESSAGE, String.valueOf(e.getMessage()));
        }


        // [리턴 반환 실시]
        return returnData;
    }


} // TODO [클래스 종료]
 
반응형
Comments