Swift에서 Beacon을 사용하는 방법
실내에서 위치를 파악할 수 있는 방법에는 여러가지가 있습니다. 그 중iBeacon을 사용하는 것은 굉장히 간단합니다.
최근, 프로젝트 개발을 위하여 SwiftUI를 기반으로 iBeacon을 사용하게 되었습니다. iBeacon은 Bluetooth를 기반으로 거리 정보 등을 제공하는 방식으로, 말 그대로 비콘이며, iPhone은 이 Beacon을 감지할 수도, 자신이 그 Beacon이 될 수도 있습니다.
About iBeacon
iBeacon은 Apple에서 발표한 저전력 블루투스(BLE) 기반의 근거리 통신 방식입니다. 이 때 iBeacon은 다음의 값들만 가집니다.
class iBeacon {
var uuid: UUID
var major: Int
var minor: Int
var identifier: Identifier
...
}
iBeacon은 유니크한 값인 UUID를 가지고, Major와 Minor, 그리고 마찬가지로 정보를 전달하는 identifier를 가집니다.
이 때, UUID 값은 해당 iBeacon을 감지하기 위해서 반드시 필요합니다.
이러한 정보를 기반으로 iBeacon을 감지하면, 감지된 정보는 Proximity로 표현되며, 총 4개의 값을 가집니다.
enum Proximity {
case .unknown // == 0
case .far // == 3
case .near // == 2
case .immediate // == 1
}
따라서, 정확한 거리 값을 알기는 어렵습니다. 다만, 각각의 값 별로 “예상되는" 거리는 다음과 같습니다.
far
: 2m ~ 30mnear
: 50cm ~ 2mimmediate
: 0cm ~ 50cm
그럼 이제, iBeacon을 만들고 추적해보겠습니다.
iPhone을 iBeacon으로 만들기
우선, 당신이 iBeacon과 관련된 어플리케이션을 만들고 싶다면, 다음 두 조건 중 하나를 만족해야 합니다.
- UUID를 알고 있는 iBeacon 기기를 보유 중임.
- iPhone 2개를 보유 중임.
만약 둘 중 어디에도 속하지 않는다면, iPhone 보다는 iBeacon을 구매하시기를 추천드립니다. 가격에 50배 정도 차이가 나거든요.
하지만 만약 당신이 iPhone 2개를 보유 중이라면, 다음과 같이 해당 iPhone을 iBeacon으로 만들 수 있습니다.
다만 코드를 작성하고 실행하기 전에, 반드시 Bluetooth와 관련된 권한 확보요청을 info.plist
에 작성하는 것을 잊지 마세요.
import SwiftUI
import CoreBluetooth
import CoreLocation
class BeaconTransmitter: NSObject, ObservableObject, CBPeripheralManagerDelegate {
var peripheralManager: CBPeripheralManager?
var beaconRegion: CLBeaconRegion?
var beaconIdentityConstraint: CLBeaconIdentityConstraint?
override init() {
super.init()
let uuid = UUID(uuidString: "UUID")!
let major: CLBeaconMajorValue = 1000
let minor: CLBeaconMinorValue = 1
let beaconID = " "
beaconIdentityConstraint = CLBeaconIdentityConstraint(uuid: uuid, major: major, minor: minor)
beaconRegion = CLBeaconRegion(beaconIdentityConstraint: beaconIdentityConstraint!, identifier: beaconID)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil)
}
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
if peripheral.state == .poweredOn {
startAdvertising()
} else {
stopAdvertising()
}
}
func startAdvertising() {
guard let beaconRegion = beaconRegion else { return }
let peripheralData = beaconRegion.peripheralData(withMeasuredPower: nil)
peripheralManager?.startAdvertising(((peripheralData as NSDictionary) as! [String: Any]))
}
func stopAdvertising() {
peripheralManager?.stopAdvertising()
}
}
길다고 생각될 수도 있지만, 굉장히 간단한 코드입니다.
이 BeaconTransmitter
는 NSObject
, ObservableObject
, CBPeripheralManagerDelegate
를 준수하는 클래스입니다.
여기서 CBPeripheralManagerDelegate
는 기기의 Bluetooth를 활용하기 위해서 사용되며, 주변 기기와의 통신을 Bluetooth를 통해 진행하기 위해서 사용됩니다.
peripheralManagerDidUpdateState
메소드는 블루투스의 전원을 공급할 수 있는 상황인지 판단하고, 가능하다면 startAdvertising
을 시작하여 iPhone이 iBeacon으로 사용될 수 있도록 합니다.
startAdvertising
메소드는 이미 생성자 단에서 결정된 beaconRegion
정보를 이용하여 iBeacon과 동일하게 BLE 통신을 위한 전파를 뿌리도록 합니다.
이 Class
를 기반으로 iPhone을 iBeacon으로 사용할 수 있고, 이제 이 iBeacon을 인식할 차례입니다.
iPhone에서 iBeacon을 인식하기
이 또한 위의 코드와 같이 매우 간단합니다.
iBeacon을 인식하기 위해서 필요한 요소는 추적하고자 하는 iBeacon의 UUID 값 입니다. 만약 이를 알 수 없다면, iPhone은 iBeacon을 추적할 수 없습니다.
따라서 UUID 값을 잘 메모해두도록 합시다.
해당 iBeacon의 UUID를 확인했다면, 다음과 같이 목표하는 iBeacon을 추적할 수 있습니다.
import Foundation
import CoreLocation
class BeaconManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private var locationManager: CLLocationManager
@Published var detectedBeacons: [CLBeacon] = []
override init() {
self.locationManager = CLLocationManager()
super.init()
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
}
func startScanning() {
let beaconRegion = CLBeaconRegion(uuid: UUID(uuidString: "당신의 UUID")!,
identifier: "동일하지 않아도 인식합니다.")
self.locationManager.startMonitoring(for: beaconRegion)
self.locationManager.startRangingBeacons(satisfying: CLBeaconIdentityConstraint(uuid: beaconRegion.uuid))
}
func stopScanning() {
let beaconRegion = CLBeaconRegion(uuid: UUID(uuidString: "당신의 UUID")!,
identifier: "동일하지 않아도 인식합니다.")
self.locationManager.stopMonitoring(for: beaconRegion)
self.locationManager.stopRangingBeacons(satisfying: CLBeaconIdentityConstraint(uuid: beaconRegion.uuid))
}
func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways || status == .authorizedWhenInUse {
startScanning()
} else {
stopScanning()
}
}
}
여기서 가장 중요한 점 중 하나는 바로, Bluetooth를 사용하지만 CoreBluetooth
를 사용하는 것이 아닌 CoreLocation
을 사용한다는 점입니다.
따라서, info.plist
에서 Location Usage에 대한 권한을 받아오는 것을 잊지 마시기 바랍니다.
Monitoring 과 Ranging
Beacon 사용에 있어 start|stop Monitoring과 start|stop RangingBeacons는 함수 이름으로만 보았을 때 크게 달라보이지 않을 수 있습니다.
다만, 두 메소드는 각각 다른 역할을 담당합니다.
- Monitoring의 경우: Monitoring은 기기가 인지 가능한 범위 안에 Beacon이 들어왔는지 판단합니다. 즉, Beacon이 내뿜고 있는 전파를 인지할 수 있는지 판단합니다.
- Ranging의 경우: Ranging의 경우 Monitoring이 이미 진행된(전파를 인지한) Beacon에 대해서 거리를 측정합니다. 이는
CLBeacon
객체 안의proximity
프로퍼티를 통해 파악할 수 있습니다.
또한, 이는 둘의 매개변수에서도 차이를 볼 수 있습니다.
- BeaconRegion의 경우: BeaconRegion은 단순히 인식이 되었는지 여부를 판단하기 위해서 사용되는 객체입니다. 따라서 해당 기기의 UUID만으로 선언이 가능합니다.
- BeaconIdentityConstraints의 경우: IdentityConstraints의 경우 Beacon의 Identifier, Major, Minor 등의 정보를 가지고 있습니다. 위의 코드에서는 따로 작성하지 않지만, 추적되는 Beacon과 정보를 교환한 뒤에는 Major, Minor 등의 세부적인 정보를 통해서 비콘과 기기 간 거리를 측정하는데에 사용됩니다.
이를 통해, 간단하고 쉬운 여러가지 프로덕트를 만드는데에 도움이 되기를 바랍니다.