투케이2K

155. (TWOK/UTIL) [Ios/Swift] C_Bluetooth_Advertising_Scan_Module : 블루투스 스캔 및 신호 활성 클래스 본문

투케이2K 유틸파일

155. (TWOK/UTIL) [Ios/Swift] C_Bluetooth_Advertising_Scan_Module : 블루투스 스캔 및 신호 활성 클래스

투케이2K 2025. 1. 12. 10:47

[설 명]

프로그램 : Ios / Swift

설 명 : C_Bluetooth_Advertising_Scan_Module : 블루투스 스캔 및 신호 활성 클래스

 

[소스 코드]

 

import Foundation
import UIKit
// -----------------------------------------
// [블루투스 기능 사용을 위한 import]
import CoreBluetooth
// -----------------------------------------

class C_Bluetooth_Advertising_Scan_Module: NSObject, CBCentralManagerDelegate, CBPeripheralManagerDelegate {



    /**
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * TODO [클래스 설명]
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 1. 블루투스 스캔 및 신호 활성 클래스
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 2. 필요 퍼미션 권한 설정 : 블루투스 사용 권한
     *
     * - Privacy - Bluetooth Always Usage Description
     * - Privacy - Bluetooth Peripheral Usage Description
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 3. 필요 import 호출 선언 :
     *
     * - import CoreBluetooth
     * - import UIKit
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * */





    /**
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * //  TODO [빠른 로직 찾기 : 주석 로직 찾기]
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : observableBluetoothScanList : 블루투스 실시간 목록 스캔 결과 반환
     *
     * >> Android 에서 Advertising 한 Name 정보로 구분 필요
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : observableBluetoothAdvertising : 블루투스 실시간 신호 활성 수행
     *
     * >> Android 에서 블루투스 스캔 시 Name 정보 확인 가능
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : stopBluetoothAdvertising : 블루투스 실시간 신호 활성 종료
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     *
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     *
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * */
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [전역 변수 선언]
    // -----------------------------------------------------------------------------------------
    public static let ACTIVITY_NAME = "C_Bluetooth_Advertising_Scan_Module"
    
    let SEARCH_BLUETOOTH_NAME = "2K-BLUETOOTH" // MARK: [찾으려는 블루투스 이름]

    var CALL_PROC = "" // [블루투스 스캔 및 신호 활성 구분자]

    public static let CALL_SCAN = "CALL_SCAN"
    public static let CALL_ADVERTISING = "CALL_ADVERTISING"
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : observableBluetoothScanList : 블루투스 실시간 목록 스캔 결과 반환
    // -----------------------------------------------------------------------------------------
    // [필요 info plist / import 및 delegate]
    // -----------------------------------------------------------------------------------------
    /*
    // [필요 info plist]
    Privacy - Bluetooth Always Usage Description
    Privacy - Bluetooth Peripheral Usage Description

    // [필요 import 및 delegate]
    import CoreBluetooth / CBCentralManagerDelegate / CBPeripheralManagerDelegate
    */
    // -----------------------------------------------------------------------------------------
    // [소스 코드 사용 방법]
    // -----------------------------------------------------------------------------------------
    /*
    C_Bluetooth_Advertising_Scan_Module().observableBluetoothScanList(){(result) in
             
        S_Log._D_(description: "실시간 블루투스 리스트 스캔 결과", data: ["\(result)"])
        
    }
    */
    // -----------------------------------------------------------------------------------------
    var scanBluetoothList: Array<Dictionary<String, String>> = []
    private let scanBluetoothOperationQueue = OperationQueue() // [블루투스 스캔 작업 큐 정의]

    public static let BLE_SCAN_TIME_OUT = 15.0 // [블루투스 스캔 타임 아웃 시간]
    var scanBluetoothCentralManager: CBCentralManager! // [블루투스 활성 상태 확인 및 실시간 블루투스 신호 스캔]
    
    func observableBluetoothScanList(callback: @escaping (Array<Dictionary<String, String>>) -> ()) {
        S_Log._D_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: observableBluetoothScanList :: 실시간 블루투스 리스트 스캔 실시", data: nil)

        // ---------------------------------------------
        // [CALL 호출 값 지정]
        // ---------------------------------------------
        self.CALL_PROC = C_Bluetooth_Advertising_Scan_Module.CALL_SCAN
        

        // ---------------------------------------------
        // [초기 값 초기화]
        // ---------------------------------------------
        self.scanBluetoothList = []
        
        
        // ---------------------------------------------
        // [작업 큐에 추가]
        // ---------------------------------------------
        self.scanBluetoothOperationQueue.isSuspended = true
        let block = { callback(self.scanBluetoothList) }
        self.scanBluetoothOperationQueue.addOperation(block)
        
        
        // ---------------------------------------------
        // [비동기 작업 수행 실시]
        // ---------------------------------------------
        DispatchQueue.main.async {
         
            // [실시간 블루투스 권한 확인]
            self.scanBluetoothCentralManager = CBCentralManager(delegate: self, queue: nil)
            
        }
        
    }





    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : observableBluetoothAdvertising : 블루투스 실시간 신호 활성 수행
    // -----------------------------------------------------------------------------------------
    // [필요 info plist / import 및 delegate]
    // -----------------------------------------------------------------------------------------
    /*
    // [필요 info plist]
    Privacy - Bluetooth Always Usage Description
    Privacy - Bluetooth Peripheral Usage Description

    // [필요 import 및 delegate]
    import CoreBluetooth / CBCentralManagerDelegate / CBPeripheralManagerDelegate
    */
    // -----------------------------------------------------------------------------------------
    // [소스 코드 사용 방법]
    // -----------------------------------------------------------------------------------------
    /*
    // 랜덤 : 0000ff00-0000-cccc-8000-00805f9b34fb
    // 캐릭터1 : 0000ff01-0000-1000-8000-00805f9b34fb    READ WRITE (디바이스 정보)
    // 캐릭터2 : 0000ff02-0000-1000-8000-00805f9b34fb    디바이스 이름
    // 캐릭터3 : 0000ff03-0000-1000-8000-00805f9b34fb    Notification
     
    let name = "TWOK_BLUETOOTH"
    let uuid = "0000FF01-0000-1000-8000-00805F9B34FB"
     
    C_Bluetooth_Advertising_Scan_Module().observableBluetoothAdvertising(bleName: name, bleUuid:uuid){(result) in
        S_Log._D_(description: "실시간 블루투스 신호 활성 결과", data: ["\(result)"])
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 10) { // [신호 활성 종료 타임 아웃 지정]
            if C_Bluetooth_Advertising_Scan_Module.peripheralManager != nil {
                
                // [블루투스 신호 활성 종료]
                C_Bluetooth_Advertising_Scan_Module().stopBluetoothAdvertising()

            }
        }
    }
    */
    // -----------------------------------------------------------------------------------------
    var advertisingResult = false

    var advertisingName = ""
    var advertisingUuid = ""
    
    private let advertisingBluetoothOperationQueue = OperationQueue() // [블루투스 신호 활성 작업 큐 정의]
    var advertisingBluetoothCentralManager: CBCentralManager! // [블루투스 활성 상태 확인]
    public static var peripheralManager: CBPeripheralManager! // [실시간 블루투스 신호 활성]
    
    func observableBluetoothAdvertising(bleName: String, bleUuid:String, callback: @escaping (Bool) -> ()) {

        S_Log._D_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: observableBluetoothAdvertising :: 실시간 블루투스 신호 활성 실시", data: [
            "BLE_NAME :: \(bleName)",
            "BLE_UUID :: \(bleUuid)"
        ])


        // ---------------------------------------------
        // [CALL 호출 값 지정]
        // ---------------------------------------------
        self.CALL_PROC = C_Bluetooth_Advertising_Scan_Module.CALL_ADVERTISING
        

        // ---------------------------------------------
        // [초기 값 초기화]
        // ---------------------------------------------
        self.advertisingResult = false
        self.advertisingName = "\(bleName)"
        self.advertisingUuid = "\(bleUuid)"
        
        
        // ---------------------------------------------
        // [작업 큐에 추가]
        // ---------------------------------------------
        self.advertisingBluetoothOperationQueue.isSuspended = true
        let block = { callback(self.advertisingResult) }
        self.advertisingBluetoothOperationQueue.addOperation(block)
        
        
        // ---------------------------------------------
        // [비동기 작업 수행 실시]
        // ---------------------------------------------
        DispatchQueue.main.async {
         
            // [실시간 블루투스 권한 확인 수행]
            self.advertisingBluetoothCentralManager = CBCentralManager(delegate: self, queue: nil)
            
        }
        
    }





    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : stopBluetoothAdvertising : 블루투스 실시간 신호 활성 종료
    // -----------------------------------------------------------------------------------------
    // [필요 info plist / import 및 delegate]
    // -----------------------------------------------------------------------------------------
    /*
    // [필요 info plist]
    Privacy - Bluetooth Always Usage Description
    Privacy - Bluetooth Peripheral Usage Description

    // [필요 import 및 delegate]
    import CoreBluetooth / CBCentralManagerDelegate / CBPeripheralManagerDelegate
    */
    // -----------------------------------------------------------------------------------------
    // [소스 코드 사용 방법]
    // -----------------------------------------------------------------------------------------
    /*
    C_Bluetooth_Advertising_Scan_Module().stopBluetoothAdvertising()
    */
    // -----------------------------------------------------------------------------------------

    func stopBluetoothAdvertising() {
        S_Log._D_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: stopBluetoothAdvertising :: 실시간 블루투스 신호 활성 종료 수행", data: nil)

        // ---------------------------------------------
        // [비동기 작업 수행 실시]
        // ---------------------------------------------
        DispatchQueue.main.async {
        
            if C_Bluetooth_Advertising_Scan_Module.peripheralManager != nil {
                
                // [블루투스 신호 활성 종료]
                C_Bluetooth_Advertising_Scan_Module.peripheralManager.stopAdvertising()
                C_Bluetooth_Advertising_Scan_Module.peripheralManager.removeAllServices()
                C_Bluetooth_Advertising_Scan_Module.peripheralManager = nil

            }
            
        }
        
    }

    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [블루투스 권한 부여 상태 응답 확인] : CBCentralManagerDelegate : Delegate
    // -----------------------------------------------------------------------------------------
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        
        switch central.state {

        // ---------------------------------------------------------
        case .unknown:
            S_Log._E_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: 블루투스 권한 :: unknown :: 블루투스 상태 알수 없음", data: ["\(self.CALL_PROC)"])

            if self.CALL_PROC == C_Bluetooth_Advertising_Scan_Module.CALL_SCAN { // [스캔 수행]
                self.scanBluetoothOperationQueue.isSuspended = false // [블루투스 스캔 큐 해제]
            }
            else { // [신호 활성]
                self.advertisingBluetoothOperationQueue.isSuspended = false // [블루투스 신호 활성 큐 해제]
            }
        // ---------------------------------------------------------
        case .resetting:
            S_Log._E_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: 블루투스 권한 :: resetting :: 블루투스 서비스 리셋", data: ["\(self.CALL_PROC)"])
            
            if self.CALL_PROC == C_Bluetooth_Advertising_Scan_Module.CALL_SCAN { // [스캔 수행]
                self.scanBluetoothOperationQueue.isSuspended = false // [블루투스 스캔 큐 해제]
            }
            else { // [신호 활성]
                self.advertisingBluetoothOperationQueue.isSuspended = false // [블루투스 신호 활성 큐 해제]
            }
        // ---------------------------------------------------------
        case .unsupported:
            S_Log._E_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: 블루투스 권한 :: unsupported :: 기기가 블루투스를 지원하지 않습니다", data: ["\(self.CALL_PROC)"])
            
            if self.CALL_PROC == C_Bluetooth_Advertising_Scan_Module.CALL_SCAN { // [스캔 수행]
                self.scanBluetoothOperationQueue.isSuspended = false // [블루투스 스캔 큐 해제]
            }
            else { // [신호 활성]
                self.advertisingBluetoothOperationQueue.isSuspended = false // [블루투스 신호 활성 큐 해제]
            }
        // ---------------------------------------------------------
        case .unauthorized:
            S_Log._E_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: 블루투스 권한 :: unauthorized :: 블루투스 사용 권한 확인 필요", data: ["\(self.CALL_PROC)"])
            
            if self.CALL_PROC == C_Bluetooth_Advertising_Scan_Module.CALL_SCAN { // [스캔 수행]
                self.scanBluetoothOperationQueue.isSuspended = false // [블루투스 스캔 큐 해제]
            }
            else { // [신호 활성]
                self.advertisingBluetoothOperationQueue.isSuspended = false // [블루투스 신호 활성 큐 해제]
            }
            
            // MARK: [앱 블루투스 권한 사용 설정창 이동 및 확인 필요]
        // ---------------------------------------------------------
        case .poweredOff:
            S_Log._E_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: 블루투스 권한 :: poweredOff :: 블루투스 비활성 상태", data: ["\(self.CALL_PROC)"])
            
            if self.CALL_PROC == C_Bluetooth_Advertising_Scan_Module.CALL_SCAN { // [스캔 수행]
                self.scanBluetoothOperationQueue.isSuspended = false // [블루투스 스캔 큐 해제]
            }
            else { // [신호 활성]
                self.advertisingBluetoothOperationQueue.isSuspended = false // [블루투스 신호 활성 큐 해제]
            }
            
            // MARK: [자동으로 시스템에서 비활성 상태 알림 및 팝업창 호출 실시]
        // ---------------------------------------------------------
        case .poweredOn:
            S_Log._W_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: 블루투스 권한 :: poweredOn :: 블루투스 활성 상태", data: ["\(self.CALL_PROC)"])
            
            if self.CALL_PROC == C_Bluetooth_Advertising_Scan_Module.CALL_SCAN { // [스캔 수행]
                
                // ---------------------------------------------------------
                // MARK: [블루투스 실시간 스캔 시작] : centralManager : didDiscover : 함수에서 확인
                // ---------------------------------------------------------
                if self.scanBluetoothCentralManager != nil {
                    //self.scanBluetoothCentralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:false])
                    
                    self.scanBluetoothCentralManager?.stopScan() // [사전 블루투스 스캔 종료]
                    self.scanBluetoothCentralManager?.scanForPeripherals(withServices: nil, options: nil)
                }


                // MARK: [타이머 동작 : 특정 시간 이후 스캔 종료 처리]
                DispatchQueue.main.asyncAfter(deadline: .now() + C_Bluetooth_Advertising_Scan_Module.BLE_SCAN_TIME_OUT) { // [스캔 종료 타임 아웃 지정]
                    if self.scanBluetoothCentralManager != nil {
                        S_Log._D_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: poweredOn :: 블루투스 스캔 타임 아웃 완료", data: nil)
                        
                        // [블루투스 스캔 종료]
                        self.scanBluetoothCentralManager?.stopScan()


                        // [블루투스 스캔 리스트 콜백 반환]
                        self.scanBluetoothOperationQueue.isSuspended = false // [블루투스 스캔 큐 해제]

                    }
                }

            }
            else { // [신호 활성]

                // ---------------------------------------------------------
                // MARK: [블루투스 실시간 신호 활성 시작] : CBPeripheralManager : peripheralManagerDidUpdateState : 함수에서 확인
                // ---------------------------------------------------------
                C_Bluetooth_Advertising_Scan_Module.peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)

            }
        // ---------------------------------------------------------
        @unknown default:
            S_Log._E_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: 블루투스 권한 :: default :: 상태", data: ["\(self.CALL_PROC)"])
            
            if self.CALL_PROC == C_Bluetooth_Advertising_Scan_Module.CALL_SCAN { // [스캔 수행]
                self.scanBluetoothOperationQueue.isSuspended = false // [블루투스 스캔 큐 해제]
            }
            else { // [신호 활성]
                self.advertisingBluetoothOperationQueue.isSuspended = false // [블루투스 신호 활성 큐 해제]
            }
        }
        // ---------------------------------------------------------
    }






    // -----------------------------------------------------------------------------------------
    // MARK: - [블루투스 장치를 찾았을 때 실행되는 이벤트] : CBCentralManagerDelegate : didDiscover
    // -----------------------------------------------------------------------------------------
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
        if peripheral.name == self.SEARCH_BLUETOOTH_NAME{
            print("🔵 :: didDiscover :: 실시간 블루투스 스캔 정보 >> NAME - \(String(peripheral.name ?? "")) / UUID - \(String(peripheral.identifier.uuidString)) / RSSI - \(String(RSSI.intValue))")
        }
        else {
            print("🔴 :: didDiscover :: 실시간 블루투스 스캔 정보 >> NAME - \(String(peripheral.name ?? "")) / UUID - \(String(peripheral.identifier.uuidString)) / RSSI - \(String(RSSI.intValue))")
        }


        // -------------------------------------------------
        // [특정 UUID 를 찾은 경우 : 샘플 코드]
        // -------------------------------------------------
        // if String(peripheral.identifier.uuidString).contains("DB809C5F-9598-16BA-563B-E329FDFA17A3") == true { }
        

        // -------------------------------------------------
        // MARK: [스캔 리턴 리스트에 저장]
        // -------------------------------------------------
        var bleScanDic : Dictionary<String, String> = [String : String]()
        bleScanDic["NAME"] = "\(String(peripheral.name ?? ""))"
        bleScanDic["UUID"] = "\(String(peripheral.identifier.uuidString))"
        //bleScanDic["RSSI"] = "\(String(RSSI.intValue))"

        // MARK: [android 에서 블루투스 advertising 시 NAME 정보는 표시 됨]
        if self.scanBluetoothList != nil
            && self.scanBluetoothList.contains(bleScanDic) == false {
            self.scanBluetoothList.append(bleScanDic) // [배열에 추가]
        }

    }





    // -----------------------------------------------------------------------------------------
    // MARK: - [실시간 블루투스 신호 활성 수행 부분] : CBPeripheralManager : peripheralManagerDidUpdateState
    // -----------------------------------------------------------------------------------------
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {

        // [블루투스 활성 상태 체크 실시 및 블루투스 신호 활성 수행]
        if peripheral.state == .poweredOn {
            
            // MARK: [신호 활성 수행] : [android 에서 블루투스 LE 스캔 시 UUID 는 표시 되지 않고 NAME 정보는 표시 됨]
            peripheral.startAdvertising([
                CBAdvertisementDataLocalNameKey: self.advertisingName,
                CBAdvertisementDataServiceUUIDsKey: [CBUUID(string: self.advertisingUuid)],
            ])

            S_Log._W_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: peripheralManagerDidUpdateState :: startAdvertising :: Success", data: [
                "NAME :: \(self.advertisingName)",
                "UUID :: \(self.advertisingUuid)"
            ])
            
            self.advertisingResult = true // MARK: [리턴 값 변경]
            self.advertisingBluetoothOperationQueue.isSuspended = false // [큐 해제]
            
        }
        else if peripheral.state == .poweredOff {
            S_Log._E_(description: "\(C_Bluetooth_Advertising_Scan_Module.ACTIVITY_NAME) :: peripheralManagerDidUpdateState :: poweredOff", data: nil)
            
            if C_Bluetooth_Advertising_Scan_Module.peripheralManager != nil {
                C_Bluetooth_Advertising_Scan_Module.peripheralManager.stopAdvertising()
            }
            self.advertisingBluetoothOperationQueue.isSuspended = false // [큐 해제]
        }
    }

    
} // [클래스 종료]

 

반응형
Comments