Использование технологии iBeacon в React Native
В чем разница между BLE и iBeacon?
BLE (Bluetooth Low Energy)
iBeacon
Преимущества iBeacon
Упрощенная настройка геозон
Лучшая поддержка фонового режима
Повышенная точность определения местоположения внутри помещений
Беакон-маячок
Общие характеристики
Настройка маячка
• В поиске введите «Holyiot-beacon» и найдите приложение для конфигурации маяков от Holyiot.
• Установите приложение на устройство.
• В приложении выберите тип устройства для поиска: может быть выбран beacon, ibeacon или eddystone в зависимости от потребностей.
• Приложение начнет поиск доступных устройств поблизости.
• Для подключения к маяку и доступа к дополнительной информации нажмите на кнопку «Connect».
• При запросе введите пароль для подключения. По умолчанию пароль: х`aa14061112`.
UUID — это 128-битный идентификатор, который используется для обозначения определенной группы или категории маяков в большом пространстве. Он позволяет мобильному приложению отличать маяки вашей организации.
Major и Minor — это числовые значения, используемые для идентификации подгруппы внутри группы маяков с одинаковым UUID. Major обычно определяет более крупную подгруппу, а Minor — более мелкую. Эти параметры позволяют создавать дополнительную иерархию или структурирование внутри вашей системы маяков, упрощая управление и целенаправленное взаимодействие в приложении.
TX Power отражает мощность сигнала, с которой маяк передает свои данные. Этот параметр напрямую влияет на дальность действия маяка и потребление энергии. Настройка оптимальной мощности передачи позволяет сбалансировать видимость маяка и длительность его работы от батареи.
ADV_interval — это время между последовательными передачами рекламных пакетов Bluetooth от маяка. Интервал влияет на то, насколько часто маяк «объявляет» о себе, что влияет на энергопотребление и скорость реакции приложений на появление или исчезновение маяка из зоны действия. Уменьшение интервала увеличивает шансы на быстрое обнаружение маяка, но также увеличивает энергопотребление.
Реализация нативного модуля в React Native для iOS
Создание нативного модуля
Настройка необходимых разрешений
Реализация
import CoreBluetooth
import CoreLocation
import React
import UserNotifications
// Объявляем класс BeaconManager, который реализует интерфейсы для работы с React Native и делегаты для мониторинга маяков и Bluetooth
@objc(BeaconManager)
class BeaconManager: NSObject, RCTBridgeModule, CLLocationManagerDelegate, CBCentralManagerDelegate
{
// Имя модуля для React Native
static func moduleName() -> String {
return "BeaconManager"
}
private var locationManager: CLLocationManager!
private var beaconRegion: CLBeaconRegion!
public var bridge: RCTBridge!
private var centralManager: CBCentralManager!
// Метод для отправки локальных уведомлений
func sendLocalNotification(with message: String) {
let content = UNMutableNotificationContent()
content.title = message // Заголовок уведомления
content.body = "This is a region event" // Текст уведомления
content.sound = .default // Звук уведомления
let request = UNNotificationRequest(
identifier: UUID().uuidString, content: content, trigger: nil) // Создание запроса на уведомление
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil) // Добавление запроса в центр уведомлений
}
// Начать сканирование маяков с заданным UUID
@objc func startScanning(_ uuid: String, config: NSDictionary) {
// Запрос разрешений на отправку уведомлений
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) {
granted, error in
if granted {
print("Notifications allowed")
} else {
print("Notifications not allowed")
}
}
DispatchQueue.main.async {
self.locationManager = CLLocationManager() // Инициализация CLLocationManager
self.locationManager.delegate = self // Установка делегата
self.locationManager.requestAlwaysAuthorization() // Запрос на постоянный доступ к геолокации
// Проверка и установка настроек для сканирования в фоновом режиме
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.pausesLocationUpdatesAutomatically = false
let uuid = UUID(uuidString: uuid)! // Преобразование строки UUID в UUID
let beaconConstraint = CLBeaconIdentityConstraint(uuid: uuid) // Создание ограничения для маяка
self.beaconRegion = CLBeaconRegion(
beaconIdentityConstraint: beaconConstraint, identifier: "BeaconManagerRegion") // Инициализация региона маяка
self.beaconRegion.notifyOnEntry = true // Уведомление при входе в регион
self.beaconRegion.notifyOnExit = true // Уведомление при выходе из региона
self.locationManager.startMonitoring(for: self.beaconRegion) // Начало мониторинга региона
self.locationManager.startRangingBeacons(in: self.beaconRegion) // Начало определения расстояния до маяков в регионе
}
}
// Остановить сканирование маяков
@objc func stopScanning() {
if let beaconRegion = self.beaconRegion {
self.locationManager.stopMonitoring(for: beaconRegion) // Остановка мониторинга региона
self.locationManager.stopRangingBeacons(in: beaconRegion) // Остановка определения расстояния до маяков
self.beaconRegion = nil // Сброс региона маяка
self.locationManager = nil // Сброс CLLocationManager
}
}
// Инициализация менеджера Bluetooth
@objc func initializeBluetoothManager() {
centralManager = CBCentralManager(
delegate: self, queue: nil, options: [CBCentralManagerOptionShowPowerAlertKey: false])
}
// Обработка изменения состояния Bluetooth
func centralManagerDidUpdateState(_ central: CBCentralManager) {
var msg = ""
switch central.state {
case .unknown: msg = "unknown"
case .resetting: msg = "resetting"
case .unsupported: msg = "unsupported"
case .unauthorized: msg = "unauthorized"
case .poweredOff: msg = "poweredOff"
case .poweredOn: msg = "poweredOn"
@unknown default: msg = "unknown"
}
bridge.eventDispatcher().sendAppEvent(withName: "onBluetoothStateChanged", body: ["state": msg]) // Отправка события изменения состояния Bluetooth в React Native
}
// Запрос на постоянный доступ к геолокации
@objc func requestAlwaysAuthorization(
_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock
) {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
let status = CLLocationManager.authorizationStatus()
let statusString = statusToString(status)
resolve(["status": statusString])
}
// Запрос на доступ к геолокации при использовании приложения
@objc func requestWhenInUseAuthorization(
_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock
) {
let locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()
let status = CLLocationManager.authorizationStatus()
let statusString = statusToString(status)
resolve(["status": statusString])
}
// Получение текущего статуса разрешений геолокации
@objc func getAuthorizationStatus(
_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock
) {
let status = CLLocationManager.authorizationStatus()
resolve(statusToString(status))
}
// Обработка событий входа в регион и выхода из региона
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
if let beaconRegion = region as? CLBeaconRegion {
sendLocalNotification(with: "Entered region: \(region.identifier)") // Отправка уведомления о входе в регион
if let bridge = self.bridge {
bridge.eventDispatcher().sendAppEvent(
withName: "onEnterRegion", body: ["region": beaconRegion.identifier]) // Отправка события входа в регион в React Native
}
}
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
if let beaconRegion = region as? CLBeaconRegion {
sendLocalNotification(with: "Exit region: \(region.identifier)") // Отправка уведомления о выходе из региона
if let bridge = self.bridge {
bridge.eventDispatcher().sendAppEvent(
withName: "onExitRegion", body: ["region": beaconRegion.identifier]) // Отправка события выхода из региона в React Native
}
}
}
// Обработка обнаружения маяков в регионе
func locationManager(
_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion
) {
let beaconArray = beacons.map { beacon -> [String: Any] in
return [
"uuid": beacon.uuid.uuidString, // UUID маяка
"major": beacon.major.intValue, // Major значение маяка
"minor": beacon.minor.intValue, // Minor значение маяка
"distance": beacon.accuracy, // Точность расстояния до маяка
"rssi": beacon.rssi, // Мощность сигнала маяка
]
}
if let bridge = bridge {
bridge.eventDispatcher().sendAppEvent(withName: "onBeaconsDetected", body: beaconArray) // Отправка данных об обнаруженных маяках в React Native
}
}
// Обработка изменения разрешений геолокации
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if #available(iOS 14.0, *) {
if manager.authorizationStatus == .authorizedAlways
|| manager.authorizationStatus == .authorizedWhenInUse
{
locationManager.startMonitoring(for: beaconRegion) // Начало мониторинга региона
locationManager.startRangingBeacons(in: beaconRegion) // Начало определения расстояния до маяков
}
} else {
if CLLocationManager.authorizationStatus() == .authorizedAlways
|| CLLocationManager.authorizationStatus() == .authorizedWhenInUse
{
locationManager.startMonitoring(for: beaconRegion)
locationManager.startRangingBeacons(in: beaconRegion)
}
}
}
// Вспомогательный метод для преобразования статуса разрешения геолокации в строку
private func statusToString(_ status: CLAuthorizationStatus) -> String {
switch status {
case .notDetermined: return "notDetermined"
case .restricted: return "restricted"
case .denied: return "denied"
case .authorizedAlways: return "authorizedAlways"
case .authorizedWhenInUse: return "authorizedWhenInUse"
@unknown default: return "unknown"
}
}
}
#import "React/RCTBridgeModule.h"
@interface RCT_EXTERN_MODULE(BeaconManager, NSObject)
RCT_EXTERN_METHOD(startScanning:(NSString *)uuid config:(NSDictionary *)config)
RCT_EXTERN_METHOD(stopScanning)
RCT_EXTERN_METHOD(requestAlwaysAuthorization:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(requestWhenInUseAuthorization:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(getAuthorizationStatus:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(initializeBluetoothManager)
+ (BOOL)requiresMainQueueSetup {
return YES;
}
@end
import React, {useEffect, useState} from 'react';
import {View, NativeModules, Text} from 'react-native';
import {DeviceEventEmitter} from 'react-native';
const {BeaconManager} = NativeModules;
BeaconManager.requestAlwaysAuthorization();
BeaconManager.startScanning('FDA50693-A4E2-4FB1-AFCF-C6EB07647825');
const App = () => {
const [inRegion, setInRegion] = useState(false);
useEffect(() => {
DeviceEventEmitter.addListener('onBeaconsDetected', beacons => {
console.log('onBeaconsDetected', beacons);
});
}, []);
useEffect(() => {
DeviceEventEmitter.addListener('onEnterRegion', beacons => {
setInRegion(true);
});
}, []);
useEffect(() => {
DeviceEventEmitter.addListener('onExitRegion', beacons => {
setInRegion(false);
});
}, []);
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: 32,
backgroundColor: inRegion ? '#62BB46' : '#472F92',
}}>
<Text style={{color: '#fff', fontWeight: 600, fontSize: 16}}>
{inRegion
? 'You are within the range of the beacon'
: 'You are out of range of the beacon'}
</Text>
</View>
);
};
export default App;
Работа с маяками в фоновом режиме
Ограничения и особенности
⭕ Ограничения iOS на фоновую работу
⭕ Ограничения на отправку уведомлений
⭕ Задержки реагирования
⭕ Требования к точности и конфигурации маяка
Если вас заинтересовала эта тема, и вы хотите разобраться, как можно применять маяки в жизни, читайте нашу статью "Технология BLE для бизнеса"