Machineboy空
아이폰 2대 간 거리 탐지하기, iBeacon 본문
아이폰 2대를 가지고 A는 iBeacon으로 사용하고, B는 그 비콘을 탐지하는 기기로 사용하고 싶다.
A에는 전파 느낌의 UI를 띄우고, B에는 A와의 실시간 거리 상태를 표시해보겠다.
시도한 방법 1: CoreBlueTooth 사용
튜토리얼
https://www.youtube.com/watch?v=WFl4tnNWXP0
테스트 코드
우선 Info.plist에 Privacy-Bluetooth Always Usage Description 을 추가해 준다.
import SwiftUI
import CoreBluetooth
struct BeaconScanner: View {
@StateObject private var beaconScannerClass = BeaconScannerClass()
var body: some View {
Text("List of scanned devices")
.font(.largeTitle)
.underline(color: .red)
List(beaconScannerClass.discoveredBeacons, id: \.identifier){ beacon in
Text(/*beacon.name ?? */beacon.identifier.uuidString)
}
Text("thisDevice " + beaconScannerClass.getDeviceUUID())
}
}
class BeaconScannerClass: NSObject, ObservableObject, CBCentralManagerDelegate{
var centralManager: CBCentralManager!
@Published var discoveredBeacons: [CBPeripheral] = []
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
startScanning()
}else{
stopScanning()
}
}
func startScanning(){
guard let centralManager = centralManager else {return}
if centralManager.state == .poweredOn {
let uuids: [CBUUID] = []
let options = [CBCentralManagerScanOptionAllowDuplicatesKey: true]
centralManager.scanForPeripherals(withServices: uuids, options: options)
}
}
func stopScanning(){
centralManager.stopScan()
}
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
if !discoveredBeacons.contains(peripheral){
if RSSI.intValue > -40 {
discoveredBeacons.append(peripheral)
}
}
}
func getDeviceUUID() -> String {
print(UIDevice.current.identifierForVendor!.uuidString)
return UIDevice.current.identifierForVendor!.uuidString
}
}
#Preview {
BeaconScanner()
}
결과 :
이 방법으로 기기간 거리를 감지하려면, 블루투스의 신호 세기로 거리를 가늠해야 한다.
이때 사용되는 개념이 RSSI인데, 아래 블로그에 개념이 잘 설명되어 있다.
뭔가 특정 기기를 판별해내는 방법으로는 적절하지 않은듯 하고, beacon을 사용해보고 싶었던 거라 탈락!
* RSSI (Received Signal Strength Indicator)는 블루투스 통신에서 신호의 강도를 측정하는 값입니다. 일반적으로 데시벨 밀리와트(dBm) 단위로 측정되며, 음수 값으로 표시된다. RSSI 값을 이용하여 두 블루투스 장치 간의 거리를 추정할 수 있다. 이는 주로 실내 위치 추적 시스템에서 많이 사용된다. 다만, RSSI 값은 주변 환경(벽, 장애물 등)에 영향을 받기 때문에, 정확한 거리 측정을 위해 보정이 필요하다.
[iOS] CoreBluetooth (2) 키워드, 코드 프로퍼티와 메서드 정리
https://velog.io/@maddie/iOS-CoreBluetooth-1-%EA%B0%9C%EC%9A%94저번엔 CoreBluetooth 이론적인 내용에 대해 다뤘다고 한다면, 오늘은 진짜 써야 되는 실무적인 내용과 알아야 할 키워드를 다뤄보려고 합니다
velog.io
시도한 방법 2: CoreLocation 사용
튜토리얼
내 기기를 비콘으로 만들고, UUID를 설정하는 방법은 아래 블로그,
해당 UUID를 가진 비콘을 감지하고, 테스트를 하기 위한 UI 등은 아래 영상을 참고했다.
[Swift] Swift에서 Beacon을 사용하는 방법
실내에서 위치를 파악할 수 있는 방법에는 여러가지가 있습니다. 그 중iBeacon을 사용하는 것은 굉장히 간단합니다.최근, 프로젝트 개발을 위하여 SwiftUI를 기반으로 iBeacon을 사용하게 되었습니다.
onve.tistory.com
https://www.youtube.com/watch?v=lCNpEaZiKqU
관련 개념
1) UUID 란?
UUID, universally unique Identifier로 각 기기에 부여되는 고유한 디바이스 식별값.
2) iBeacon이 방출하는 정보
- UUID
- major: 128bit
- minor: 16bit unsigned integer
UUID는 Top-level의 정보이고, major, minor는 더 세부적인 값이라고 보면 된다.
UUID가 같아도 major, minor값으로 세부 건물을 분류할 수 있다.
튜토리얼을 참고하니 이런 관계성을 가지는 느낌이다.
UUID | 홈플러스 |
major | 홈플러스_포항점, 홈플러스_서울점 |
minor | 홈플러스_포항점_베스킨라빈스, 홈플러스_포항점_롯데시네마 |
UUID 관련 정보는 아래 티스토리, iBeacon관련 개념은 kodeco 튜토리얼을 참고하였다.
[iOS] UUID와 UDID
디바이스의 고유한 값을 통해 무언가를 식별하기위한 고유한 값이 필요할 수 있다. 이때 사용되는 개념으로 UUID와 UDID가 있다. 1. UDID (Unique Device Identifier) 각 기기에 부여되는 고유한 디바이스 식
hilily.tistory.com
https://www.kodeco.com/632-ibeacon-tutorial-with-ios-and-swift
iBeacon Tutorial with iOS and Swift
Learn how you can find an iBeacon around you, determine its proximity, and send notifications when it moves away from you.
www.kodeco.com
구조
- 비콘용 A 기기에는 BeaconTransmitter을 빌드
- 탐지용 B 기기에서는 BeaconDetector를 빌드
테스트 코드
Beacon용 스크립트
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: "DA1D2EFE-A565-5BD8-B301-6397766AAF26")!
let major: CLBeaconMajorValue = 1000
let minor: CLBeaconMinorValue = 1
let beaconID = "Beacon"
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() {
print("startAdverting")
guard let beaconRegion = beaconRegion else { return }
let peripheralData = beaconRegion.peripheralData(withMeasuredPower: nil)
peripheralManager?.startAdvertising(((peripheralData as NSDictionary) as! [String: Any]))
}
func stopAdvertising() {
peripheralManager?.stopAdvertising()
}
}
struct BeaconView: View {
@ObservedObject var transmitter = BeaconTransmitter()
var body: some View {
ZStack{
WaveEffectView()
.frame(width: 300, height: 300)
Text("비콘 신호 전송중~")
}
}
}
struct WaveEffectView: View {
@State private var animate = false
var body: some View {
ZStack {
ForEach(0..<4) { index in
Circle()
.stroke(lineWidth: 3)
.foregroundColor(Color.blue.opacity(0.4))
.frame(width: animate ? 200 : 50, height: animate ? 200 : 50)
.scaleEffect(animate ? 2.0 : 0.5)
.opacity(animate ? 0 : 1)
.animation(
Animation.easeOut(duration: 2.0)
.repeatForever(autoreverses: false)
.delay(Double(index) * 0.3)
)
}
}
.onAppear {
animate = true
}
}
}
탐지기용 스크립트
- Ranging: Ranging is the process of reading the characteristics of a beacon region, such as signal strength, advertising interval, and measured power.
let beaconRegion = CLBeaconRegion(uuid: uuid, identifier: uuidString)
locationManager.startMonitoring(for: beaconRegion)
locationManager.startRangingBeacons(satisfying: CLBeaconIdentityConstraint(uuid: uuid))
- Monitoring:정확한 정의는 나와있지 않지만, 좀 더 넓은 개념에서 beacon을 탐색하고 그다음 ranging으로 비컨별 세부 정보를 받아내는 프로세스인가? 싶다.
import Combine
import CoreLocation
import SwiftUI
class BeaconDetector: NSObject, ObservableObject, CLLocationManagerDelegate {
var didChange = PassthroughSubject<Void, Never>()
var locationManager: CLLocationManager?
@Published var lastDistance = CLProximity.unknown
override init() {
super.init()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self){
print("isMonitoringAvailable")
if(CLLocationManager.isRangingAvailable()){
print("isRangingAvailable")
startScanning()
}
}
}
}
func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
print("탐지된 비콘의 수는: \(beacons.count)" )
if let beacon = beacons.first {
update(distance: beacon.proximity)
}else{
update(distance: .unknown)
}
}
func startScanning() {
print("startScanning")
// Transmitter에서 설정한 값 그대로 넣어주기!
let uuid = UUID(uuidString: "DA1D2EFE-A565-5BD8-B301-6397766AAF26")!// 테스트 UUID
let constraint = CLBeaconIdentityConstraint(uuid: uuid, major: 1000, minor: 1)
let beaconRegion = CLBeaconRegion(beaconIdentityConstraint: constraint, identifier: "MacBook")
locationManager?.startMonitoring(for: beaconRegion)
locationManager?.startRangingBeacons(satisfying: constraint)
}
func update(distance: CLProximity){
lastDistance = distance
didChange.send(())
}
}
struct BigText: ViewModifier{
func body(content: Content) -> some View {
content
.font(Font.system(size: 72, design: .rounded))
.frame(minWidth: 0,maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
struct ContentView: View {
@ObservedObject var detector = BeaconDetector()
var body: some View {
VStack {
if detector.lastDistance == .immediate {
Text("Right here")
.modifier(BigText())
.background(Color.green)
.edgesIgnoringSafeArea(.all)
}else if detector.lastDistance == .near {
Text("Near")
.modifier(BigText())
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
}else if detector.lastDistance == .far {
Text("far")
.modifier(BigText())
.background(Color.orange)
.edgesIgnoringSafeArea(.all)
}else{
Text("UNKNOWN")
.modifier(BigText())
.background(Color.gray)
.edgesIgnoringSafeArea(.all)
}
}
}
}
#Preview {
ContentView()
}
//ver.2
import Combine
import CoreLocation
import SwiftUI
class BeaconDetector: NSObject, ObservableObject, CLLocationManagerDelegate {
private var locationManager: CLLocationManager
@Published var detectedBeacons: [CLBeacon] = []
@Published var lastDistance: CLProximity = .unknown
override init() {
self.locationManager = CLLocationManager()
super.init()
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
}
func startScanning() {
let beaconRegion = CLBeaconRegion(uuid: UUID(uuidString: "DA1D2EFE-A565-5BD8-B301-6397766AAF26")!,
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) {
if let beacon = beacons.first {
print("비콘 감지 완")
lastDistance = beacon.proximity // 비콘의 거리 정보를 업데이트
} else {
lastDistance = .unknown // 비콘이 감지되지 않으면 거리 정보를 unknown으로 설정
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways || status == .authorizedWhenInUse {
print("권한 인정")
startScanning()
} else {
stopScanning()
}
}
}
struct BigText: ViewModifier{
func body(content: Content) -> some View {
content
.font(Font.system(size: 72, design: .rounded))
.frame(minWidth: 0,maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
struct ContentView: View {
@ObservedObject var detector = BeaconDetector()
var body: some View {
VStack {
if detector.lastDistance == .immediate {
Text("Right here")
.modifier(BigText())
.background(Color.green)
.edgesIgnoringSafeArea(.all)
}else if detector.lastDistance == .near {
Text("Near")
.modifier(BigText())
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
}else if detector.lastDistance == .far {
Text("far")
.modifier(BigText())
.background(Color.orange)
.edgesIgnoringSafeArea(.all)
}else{
Text("UNKNOWN")
.modifier(BigText())
.background(Color.gray)
.edgesIgnoringSafeArea(.all)
}
}
}
}
#Preview {
ContentView()
}
결과 :
enum 형으로 반환되다보니, 정확한 m값을 알 수는 없지만,
near이면 이벤트가 발생하도록 구성하면 될 것 같다.
'언어 > iOS' 카테고리의 다른 글
비콘이 감지되면 버튼을 활성화 시키자! 여러 개의 비컨 감지 로직 (0) | 2024.11.03 |
---|---|
비컨별로 다른 화면 띄우기! (0) | 2024.11.01 |
CoreLocation speed를 이용해 현재 정지해 있는 상태인지 파악해보자! (5) | 2024.10.31 |
iBeacon이란? beacon과 iOS 디바이스 간 거리 감지! (3) | 2024.10.29 |
RealityKit 입문 - 한국에선 불가한 ARGeoAnchor (2) | 2024.10.15 |