본문 바로가기

개발 코딩 정보 공유/개발후기

BLE(beacon), Objc-swift, mdm 프로젝트 진행

소개

안녕하세요. 블루스웨터 소프트입니다.

프로젝트를 진행하며 느낀점과 기술적인 부분을 정리 하고 있습니다.

P사의 프로젝트를 진행하다보면 어려운 점은 다름아닌 보안 입니다.

외부로 통하는 경로가 막혀있기 때문에 필요한 lib 등 오픈소스 활용도 어렵습니다.

신청을 통해 필요한 경로를 오픈해주기는 하지만 오래걸리고 까다롭기도 합니다.

개발하는 입장에서는 굉장히 불편한 일이지만 보안이 철저한것은 어찌됬건 환영할일 입니다 😂(울면서 웃는 나)

 

그동안 하드웨어와 앱단의 ble 연동을 원하는 프로젝트들이 있었습니다. 

이번 경우도 마찬가지로 ble를 통해 앱과 디바이스(혹은 특정 단말장치)를

연동하여 특정 기기를 필터링 하길 원하였습니다. 특별히 어려운 점은 없어 보였습니다만...😅 

함정이 존재했습니다  (이후 글에서 설명)

 

블루투스 저전력 프로토콜(Bluetooth Low Energy)은 2010년 블루투스 4.0(Bluetooth Smart) 이후부터 채택되어 사용되기 시작습니다. ble 의 큰 특징은 이름에서 알수 있듯이 아주 작은양의 배터리 만으로도 수년간 동작할수 있는 특징이 있습니다. 속도와 데이터의양이 크지 않아 작은 데이터를 주고받는 가전제품, 센서 통신 등에 활용됩니다. 

 

해당 프로젝트에서는 현장에서 사용하는 Ble 기기 (beacon) 들을 필터링 하여(제조업체별) 기기들의 상태 및 모니터링 하는데 목적이 있습니다. 처음에 단순히 스캐닝만 하고 적당히 표시해주면 될것이라 생각했습니다. 문제는 해당 기기의 정확한 스펙이나 정보가 부족하여 일일히 테스트 해보며 코드를 구현하게 되었습니다. 

 

필수 권한

따로 lib를 쓸 필요가 없기에 CoreBluetooth 를 통해 작업 하였습니다. CoreBluetooth를 사용하기 위해서는 권한관련을 추가해주셔야 합니다. iOS 13 이상에서는 NSBluetoothAlwaysUsageDescription iOS 12 이하 버전에서는 NSBluetoothPeripheralUsageDescription를 추가 해야 합니다. (필수)

 

잘 모르면 둘다 추가해보자.

 

 

BLE 사전 지식

관련을 작업하기 위해서는 선행 지식이 필요합니다. 중앙장치인 Central 주변기기인 Peripheral 아래의 이미지를 보면 아시겠지만 주변기기는 데이터를 송신하고 중앙에서 받고 처리하는 형태 입니다. 기존의 서버 클라이언트와 비교해서 개념을 이해하면 더 쉬울듯 합니다.

 

기존의 클래식 블루투스와 다른 점은 페어링 형태로 특정 타겟과 연동하는 방식이 아닌 ad(광고) 형태로 끊임없이? 데이터를 발송한다는데 있습니다. 이는 ble 방식의 특징으로 발송된 ad 데이터를 중앙기기에서 받아서 처리 하게 되는 방식입니다. 실제로 이전에 했던 프로젝트를 예를 들면 특정 ble 기기 주변에 근접한 중앙기기들에게 쿠폰을 발송, 유저에게 사용을 유도 한다던가 마케팅 적으로 활용할수 있습니다.

개발시 특정 테스트기기를 두고 구현을 진행했습니다. 

 

BLE 테스트 구현

var centralManager = CBCentralManager(delegate: self, queue: nil)
var timer: Timer!

@IBAction func onClickBleScan(_ sender: Any) {//버튼을 두고 테스트 스캔을 시작 합니다.

	if(centralManager?.state == .poweredOn){//특정 상태값 확인
    	showLoading()
    
		self.centralManager?.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey:false])
		self.timer = Timer.scheduledTimer(timeInterval: 5,
                                              target: self,
                                              selector: #selector(self.stopBleScan),
                                              userInfo: nil,
                                              repeats: false)

     }
}

 

타이머를 둔 이유는 5초 동안 스캔후 특정 finish 동작을 수행하고 싶어서였습니다. 

@objc func stopBleScan(){
    if(centralManager!.isScanning){ //스캐닝 상태 파악
        centralManager!.stopScan() //스캔 중단
    }
    
    if let _ = timer {
        timer.invalidate()
        timer = nil
    }

    hideLoading()

}

 

extension BleViewController: CBCentralManagerDelegate {
	func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .unknown:
            print("===블루투스 상태 알수 없음===")
        case .resetting:
            print("===블루투스 서비스 리셋===")
        case .unsupported:
            print("===기기가 블루투스를 지원하지 않습니다===")
        case .unauthorized:
            print("===블루투스 사용 권한 확인 필요===")
        case .poweredOff:
            print("===블루투스 비활성 상태===")
        case .poweredOn:
            print("===블루투스 활성 상태====")
        @unknown default:
            print("===default===")
        }
    }

	func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
             print("==== UUID : \(peripheral.identifier.uuidString) RSSI : \(RSSI.intValue) name: \(peripheral.name) ====")
             print("=== advertisementData : \(advertisementData) ===")
	}


}

 

블루투스 상태를 확인하고 스캔되는대로 데이터를 확인하여 필요한 작업을 요청할 생각이었습니다. 실제로 블루투스 스캔을 진행하면 주변에 엄청나게 많은 기기들의 신호가 잡히는 걸 볼수 있습니다. 필요한 기기들만 리스팅 되도록 하는게 필수적이었습니다. 보통의 경우 UUID를 키로 삼아서 해당 기기를 컨트롤 하면 되겠다고 생각하기 십상입니다. 주의 해야 할 점은 ios는 특정 블루투스 기기의 실제 uuid를 보여주지 않는데 있습니다. 실제로 스캔해보면 계속 바뀌는 값을 확인할수 있습니다. 기기의 고유한 비콘 id 값을 확인하려면 특별한 방법이 필요합니다.  페어링을 하거나(해당없음) 제조사의 모델값을 전달 받아서 활용하는 방법 입니다. 표준값이 존재합니다만 해당 기기 스펙마다 조금씩 다를수 있기 때문에 연동하고자 하는 기기의 정보를 제대로 파악 해야 합니다.  

 

해당 기기가 어떤 업체의 beacon 기기인지를 사전에 확인하는 것이 중요하게습니다.

Ibeacon, eddystone 등 업체도 다양하고 조금씩 특징이 다릅니다. UUID 를 검색해도 의미가 없기에 고유값으로 활용할 beacon Id 값을 찾아야 했습니다. 그냥 보이지는 않고 내부의 manufacturer data 를 규격 대로 해석해야 볼수 있습니다. 

예를 들어 … 0059 355 asdfb234234 sdf fffff 0000 이런식의 코드를 비트연산으로 디코딩 하여야 확인할수 있습니다. 

앞 자리는 회사 id, 그다음은 beacon id , voltage, 등등 어떤 정보인지는 기기마다 조금씩 다릅니다.

 

let advData = AdvertisementData.init(advertisementData)
        var beaconName = advData.localName ?? ""
        if let mData = advData.manufacturerData{
        	 //corp Id
            let manufactureID = UInt16(mData[0]) + UInt16(mData[1]) << 8
            var mId = String(format: "%04X", manufactureID)
            print(mId) //->ex 000D
        
        	//beacon Id parse
            mData.forEach { mInt in
                beaconId += String(format: "%02X", UInt16(mInt))
            }
            print("=== beaconId: \(beaconId) ===")
        
        }
        
        //...
        
 }
//특정 장치만 리스팅 하기위함.
if(beaconId.contains(filterPrefix)){
	print("=== beaconId : \(beaconId) ===")
}

 

이렇게 긴 과정을 통해 beacon id 값으로 특정 기기만 검색하고 서버로 데이터를 전달할수 있게 되었습니다. 

 

Objc-Swift 혼용

해당 프로젝트는 조금 특이한 구조로 이루어져 있습니다.  Objc로 된 소스와 Swift 가 같이 혼재된 형태 입니다. 더 특이한 것은 AppDelegate 단은 Objective-c, 이후에 화면단은 Swift 로 되어있습니다.🤪 굉장히 정신없는 구조라서 까닥하다가는 뭘하러 가는건지 길을 잃어 버리기 십상입니다. Objective-c 와 Swift 를 같이 쓰기 위해서는 특별한 셋팅이 필요합니다.

 

아래와 같이 Project -> Build Setting -> Swift Compiler -> Objective-C Bridging Header 를 설정해주셔야 합니다.

 

지정한 경로에 해당 브릿지 파일을 만들어 주고 필요한 Objc 클래스를 import 하면 됩니다. 

bridge 파일을 생성한다.

 

#import "AppDelegate.h"
#import "DeviceUtil.h"
#import "NSData+AES128.h"

 

해당 프로젝트의 특이한 점은 AppDelegate 가 Objective-C 로 되어있다는 점 입니다. 시작점이 Objective-C 라는 이야기는 Objective-C 에서 Swift 를 호출하는 방법도 작성해야 한다는 말입니다.😗 필요한 클래스에 @objc 어노테이션을 활용하면 간단합니다.

 

@objc 
class TextUtil: NSObject {
   @objc static let shared = TextUtil()
   
}

 

그리고 swift를 사용할 objc 클래스로 이동하고 import , 그리고 사용하면 됩니다.

#import "Pooo-Swift.h"
[[TextUtil shared] file:@"AppDelegate" msg:@"=== 여기서 이동 시작 ==="];

 

MDM 관련

해당 프로젝트에서는 이미 MDM 관련 기능을 제공하고 있기에 최소한의 사전 지식이 필요했습니다. MDM은 수 많은 기기들을 관리 하는 일종의 보안 솔루션 (보안, 감시, 제한 등) 으로 일반적으로 사용할수 없는 위험한? 기능들을 제공하고 있습니다. 보안이 중요한 업계에서는 이미 많이들 사용하는 솔루션 입니다. MDM의 기조는 사용자를 억압하고 제한하는 것입니다. 때문에 사생활 침해 등의 우려는 덤으로 따라오는 옵션이었습니다. 때문에 업무용 폰과 태블릿을 따로 지급하고 해당 기기에만 MDM 을 설치하는 것이 표준이 되고 있습니다만(이번 프로젝트는 그렇지 않습니다😅) 너무 불편했기 때문에 EMM : Enterprise Mobility Management 이라는 새로운 솔루션이 등장합니다. 좀더 세세하게 특정 앱이나, 서비스만을 통제할수 있고, 사생활 침해 등은 최소화 하는 솔루션입니다. 이번 프로젝트의 경우 위치 정보를 기준으로 카메라를 막고, 스크린샷을 막는등 필요한 제한기능을 사용하고 있었습니다. Apple 기기를 MDM 관리 하기 위해서는 개발시 상당히 긴 시간이 필요한것으로 보였습니다. Apple 에서 제공하는 공식 MDM 서비스는 따로 없고 아래의 서비스 등록한뒤 타사의 MDM 솔루션을 사용 해야 됩니다.

 

애플 등록

  • Apple DEP(Device Enrollment Program)
  • APPLE MDM (ABM(Apple Business Manager)

타사 솔루션

- VMware AirWatch

- IBM MaaS360 

- Microsoft Intune

- Citrix XenMobile 

- Cisco Meraki System Manager 

- Jamf

- Hexnode MDM

- SimpleMDM

 

 

마무리글

사내배포 앱을 제작하는 특수성을 가진 프로젝트로써 많은 보안 과제들을 처리해야하는 업무였습니다. BLE, 엔터프라이즈 앱배포 등 관련 내용을 다시한번 정리하고 MDM 같은 특수성을 가진 연동 처리 또한 새로운 지식을 쌓는 계기가 되었습니다. 정말이지 IT 지식은 끝도 없고 양도 방대함을 다시금 깨닿는 계기 였습니다.😵‍💫

 

 

 

문서참고

https://support.apple.com/ko-kr/guide/deployment/depc0aadd3fe/web

https://www.sharedit.co.kr/posts/8235#

https://stackoverflow.com/questions/12524871/corebluetooth-how-to-get-a-unique-uuid?rq=3

https://www.bluetooth.com/specifications/assigned-numbers/

https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothOverview/CoreBluetoothOverview.html