В предыдущей статье мы рассказали про использование беакон-маяков и технологии BLE в приложениях на React Native c помощью библиотеки React Native BLE. Теперь перейдем к более совершенному подходу для работы с маяками, — разработаем нативный модуль на iOS.

Чем же он лучше? Можно добиться большей точности, обеспечить глубокую интеграцию с iOS и лучшее управление энергопотреблением.

В этой статье мы сравним оба подхода и обсудим, чем же нативное решение лучше для определенных типов приложений. Еще подробно остановимся на технической реализации, настройке маяков и расскажем, зачем они нужны для бизнеса.

В чем разница между BLE и iBeacon?

Bluetooth Low Energy (BLE) и iBeacon работают на одной и той же технологической основе, но предназначены для разных целей и имеют различные области применения.

BLE (Bluetooth Low Energy)

BLE разработан для передачи небольших объемов данных на короткие расстояния с минимальным энергопотреблением. Это идеально подходит для устройств, работающих от батарей, таких как фитнес-браслеты, датчики окружающей среды, умные часы и другие устройства Интернета вещей (IoT).

Основное преимущество BLE — это его способность работать в течение длительного времени на одном заряде батареи благодаря низкому энергопотреблению.

Благодаря BLE можно создавать кучу разных приложений, в которых требуется передача данные между устройствами на коротких расстояниях.

iBeacon

iBeacon — это протокол, разработанный Apple, который использует технологию BLE для предоставления геолокационных услуг в конкретных сценариях. Он позволяет мобильным приложениям определять их близость к маячку, небольшому устройству BLE, которое транслирует уникальный идентификатор.

В отличие от BLE, который отвечает за простой обмен данными между устройствами, iBeacon используют для создания более сложных сценариев взаимодействия. Например, приложение должно реагировать, когда пользователь приближается к определенному месту или покидает его.

Преимущества iBeacon

Протокол iBeacon особенно подходит для приложений, требующих точного и контекстно-зависимого взаимодействия с пользователем внутри помещений.

Благодаря упрощенной настройке геозон, лучшей поддержке фонового режима и повышенной точности определения местоположения. Это делает iBeacon идеальным для целей маркетинга в розничной торговле, внутренней навигации и автоматизации в помещениях.

Упрощенная настройка геозон

iBeacon позволяет легко создавать геозоны вокруг физических маяков. Разработчикам не нужно заботиться о сложной настройке и обслуживании сети GPS внутри помещений. Поскольку не гарантирует такой точности и эффективности. В то же время, iBeacon позволяет легко определять, когда пользователь входит или выходит из заданной геозоны.

Лучшая поддержка фонового режима

iOS позволяет приложению работать в фоновом режиме. Оно получит уведомление о пересечении границы геозоны даже, когда не используется. Благодаря этому можно, например, автоматически предлагать купоны или специальные предложения, когда человек приближается к магазину. Или же трекать важные события для логистических и мониторинговых приложений без активного участия пользователя.

Повышенная точность определения местоположения внутри помещений

В условиях ограниченного пространства, таких как торговые центры, музеи, конференц-залы и другие внутренние локации, GPS ненадежен из-за слабого сигнала и отсутствия точности. iBeacon гораздо лучше определяет местоположения пользователей. Это открывает двери для разработки более точных систем внутренней навигации, позволяя, например, направлять посетителей к определенным экспонатам в музее или предлагать товары, находящиеся в непосредственной близости к пользователю в магазине.

Беакон-маячок

Я использовала маячок Holyiot nRF52810. Он выделяется своим ультранизким энергопотреблением, компактным размером и водонепроницаемым корпусом.

С помощью приложения Holyiot-beacon можно легко настроить маячок становятся и адаптировать параметры под конкретные нужды.

Общие характеристики

• Мощность сигнала и дальность действия:

Маячок способен передавать сигнал на расстояние до 50 метров в открытом пространстве с регулируемой мощностью передачи от -40dB до +4dB. По умолчанию мощность установлена на +4dB, что оптимизирует дальность и энергопотребление.

• Длительность жизни батареи:

С учетом емкости батареи CR2032 в 220 mAh и среднего энергопотребления в 49uA при +4dB и интервале передачи в 500ms, маячок обеспечивает работу до 180 дней без замены батареи. Это делает маячок особенно подходящим для задач, требующих долгосрочного размещения без обслуживания.

• Водонепроницаемость:

Класс защиты IP67 делает маячок стойким к воздействию воды и пыли, что расширяет возможности его использования в различных условиях.

• Физические параметры:

Маячок имеет компактные размеры с диаметром 30 мм и толщиной 8.4 мм при весе всего 6.5 г, что делает его легко интегрируемым в любую среду.

Настройка маячка

Загрузка приложения

• Откройте магазин приложений на смартфоне (App Store для iOS или Google Play для Android).• В поиске введите «Holyiot-beacon» и найдите приложение для конфигурации маяков от Holyiot.• Установите приложение на устройство.

Поиск устройства

• Запустите приложение «Holyiot-beacon» на смартфоне.• В приложении выберите тип устройства для поиска: может быть выбран beacon, ibeacon или eddystone в зависимости от потребностей.• Приложение начнет поиск доступных устройств поблизости.

Подключение к маяку

• Когда маяк будет обнаружен, выберите его в списке доступных устройств.• Для подключения к маяку и доступа к дополнительной информации нажмите на кнопку «Connect».• При запросе введите пароль для подключения. По умолчанию пароль: х`aa14061112`.

Конфигурация маяка

Конфигурация параметров позволяет точно настроить работу маяка под конкретные требования приложения или сервиса, обеспечивая нужный баланс между видимостью, энергопотреблением и точностью позиционирования.

После успешного подключения вы сможете просмотреть детальную информацию о маяке и доступные для настройки параметры. В разделе настроек выберите параметры, которые хотите изменить.

Конфигурация маяка включает в себя настройку различных параметров, каждый из которых играет важную роль в функционировании маяка и его взаимодействии с приложениями и устройствами. Вот основные параметры, которые обычно настраиваются при конфигурации маяка:

UUID (Универсальный уникальный идентификатор)UUID — это 128-битный идентификатор, который используется для обозначения определенной группы или категории маяков в большом пространстве. Он позволяет мобильному приложению отличать маяки вашей организации.

Major и MinorMajor и Minor — это числовые значения, используемые для идентификации подгруппы внутри группы маяков с одинаковым UUID. Major обычно определяет более крупную подгруппу, а Minor — более мелкую. Эти параметры позволяют создавать дополнительную иерархию или структурирование внутри вашей системы маяков, упрощая управление и целенаправленное взаимодействие в приложении.

TX Power (Мощность передачи)TX Power отражает мощность сигнала, с которой маяк передает свои данные. Этот параметр напрямую влияет на дальность действия маяка и потребление энергии. Настройка оптимальной мощности передачи позволяет сбалансировать видимость маяка и длительность его работы от батареи.

ADV_interval (Интервал рекламы)ADV_interval — это время между последовательными передачами рекламных пакетов Bluetooth от маяка. Интервал влияет на то, насколько часто маяк «объявляет» о себе, что влияет на энергопотребление и скорость реакции приложений на появление или исчезновение маяка из зоны действия. Уменьшение интервала увеличивает шансы на быстрое обнаружение маяка, но также увеличивает энергопотребление.

Внесите необходимые изменения и сохраните настройки. Некоторые изменения могут потребовать повторного подключения к маяку или перезагрузки маяка.

Реализация нативного модуля в React Native для iOS

Когда мы разрабатывали приложение на React Native, искали решение для работы с маяками iBeacon, но не нашли такого, которое бы полностью соответствовало нашим требованиям. Поэтому решили разработать собственный нативный модуль для iOS, чтобы максимально использовать возможности этой платформы для взаимодействия с маяками.

Создание нативного модуля

Сперва создаем нативный модуль для iOS, который интегрируем с React Native. Для этого пишем код на Swift или Objective-C, который будет вызван из JavaScript. Этот процесс включает в себя объявление методов, доступных для вызова из React Native, и настройку событийной модели для обмена данными между нативной и JavaScript частями приложения.

Подробные инструкции по созданию и интеграции нативных модулей в проект React Native, включая примеры кода и наилучшие практики, вы найдете в официальной документации React Native.

Настройка необходимых разрешений

После создания нативного модуля следующий этап — настройка необходимых разрешений и возможностей в вашем проекте Xcode, чтобы обеспечить его корректную работу с маяками и уведомлениями.

Откройте настройки проекта в Xcode и перейдите в раздел «Signing & Capabilities".

Здесь необходимо добавить два ключевых capability: «Background Modes" и "Push Notifications".

• Активация «Location updates» и «Uses Bluetooth LE accessories» в разделе «Background Modes» позволяет приложению получать данные о местоположении и работать с Bluetooth-аксессуарами, даже когда оно находится в фоновом режиме.

• Включение «Push Notifications» необходимо, чтобы приложение могло отправлять пользователю локальные уведомления при входе или выходе из зоны действия маяка.

Для корректной работы с маяками и Bluetooth в iOS, важно добавить в файл Info.plist проекта необходимые строки разрешений. Эти строки информируют пользователя о том, как приложение собирается использовать данные или функции устройства.

Вот ключевые разрешения, которые обычно требуются:

Privacy - Location Always and When In Use Usage Description

(NSLocationAlwaysAndWhenInUseUsageDescription): Требуется описание, почему приложению нужен доступ к данным о местоположении пользователя в любое время, включая фоновый режим.

Например, «Это приложение использует ваше местоположение для определения близости к интересующим вас маякам, даже когда приложение находится в фоновом режиме».

Privacy - Location When In Use Usage Description

(`NSLocationWhenInUseUsageDescription`): Необходимо указать причину, по которой приложению требуется доступ к информации о местоположении, когда оно используется.

Пример описания: «Приложению требуется доступ к вашему местоположению, чтобы уведомлять вас о маяках поблизости, когда вы используете приложение».

Privacy - Bluetooth Always Usage Description

(`NSBluetoothAlwaysUsageDescription`): Поскольку модуль использует Bluetooth для определения маяков, необходимо объяснить, зачем приложению постоянный доступ к Bluetooth.

Пример: «Доступ к Bluetooth используется для обнаружения маяков вокруг вас, чтобы предоставить соответствующую информацию и уведомления».

Privacy - Bluetooth Peripheral Usage Description

(`NSBluetoothPeripheralUsageDescription`): Это объяснение нужно, если ваше приложение собирается выступать в роли Bluetooth-периферии.

Например: «Это приложение использует Bluetooth для связи с маяками и другими устройствами в вашей окрестности».

Реализация

BeaconManager.swift

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"
    }
  }
}

BeaconManagerBridge.m

#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

App.js

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;

Работа с маяками в фоновом режиме

Методы `didEnterRegion` и `didExitRegion` являются частью протокола `CLLocationManagerDelegate` и вызываются, когда устройство входит в зону действия маяка или выходит из неё соответственно. Эти методы позволяют приложениям реагировать на изменения местоположения пользователя даже в фоновом режиме.

Ограничения и особенности

⭕ Ограничения iOS на фоновую работу

Несмотря на то, что iOS позволяет приложениям мониторить маяки в фоновом режиме, существуют определенные ограничения, связанные с сохранением заряда батареи и производительностью устройства.

Apple может ограничивать частоту обновлений местоположения или временно приостанавливать фоновую активность приложения для оптимизации работы системы. Также iOS предоставляет ограниченное время для выполнения необходимых действий в ответ на это событие. Это время может варьироваться, но обычно составляет несколько секунд. За это время приложение должно успеть выполнить все необходимые операции.

⭕ Ограничения на отправку уведомлений

Хотя методы позволяют реагировать на вход и выход из регионов в фоне, отправка уведомлений пользователю также подчиняется строгим правилам iOS. Приложение должно получить соответствующее разрешение от пользователя на отправку уведомлений.

⭕ Задержки реагирования

В зависимости от настроек системы и условий окружающей среды, могут наблюдаться задержки между фактическим входом или выходом из региона и моментом вызова соответствующих методов. Это обусловлено как особенностями работы Bluetooth, так и алгоритмами оптимизации энергопотребления iOS.

⭕ Требования к точности и конфигурации маяка

Для оптимальной работы в фоновом режиме, важно обеспечить правильную конфигурацию маяков и настроить приложение на работу с необходимой точностью определения местоположения.

Работа с маяками в фоне значительно расширяет функциональные возможности приложений, позволяя создавать продвинутые решения для локационных сервисов, навигации внутри зданий, автоматизированного взаимодействия с пользователем и многого другого. Однако при разработке таких приложений важно учитывать упомянутые выше особенности и ограничения, чтобы обеспечить наилучшую производительность и пользовательский опыт.

Если вас заинтересовала эта тема, и вы хотите разобраться, как можно применять маяки в жизни, читайте нашу статью "Технология BLE для бизнеса"

Комментарии (1)


  1. gen1lee
    05.11.2024 18:47

    А зачем выкладывать туториал для старых модулей под Bridge, когда в последних версиях RN его уже нет?