Using iBeacon technology in React Native

In the previous article, we talked about the use of beacon beacons and BLE technology in React Native applications using the React Native BLE library. Now let's move on to a more advanced approach for working with beacons – let's develop a native module for iOS.

Why is he better? You can achieve greater accuracy, deep integration with iOS, and better power management.

In this article, we will compare both approaches and discuss why a native solution is better for certain types of applications. We will also dwell in detail on the technical implementation, setting up beacons and tell you why they are needed for business.

What is the difference between BLE and iBeacon?

Bluetooth Low Energy (BLE) and iBeacon operate on the same technology basis, but are designed for different purposes and have different applications.

BLE (Bluetooth Low Energy)

BLE is designed to transmit small amounts of data over short distances with minimal power consumption. This is ideal for battery-powered devices such as fitness bands, environmental sensors, smartwatches and other Internet of Things (IoT) devices.

The main advantage of BLE is its ability to operate for a long time on a single battery charge due to its low power consumption.

Thanks to BLE, you can create a bunch of different applications that require data transfer between devices over short distances.

iBeacon

iBeacon is a protocol developed by Apple that uses BLE technology to provide geolocation services in specific scenarios. It allows mobile apps to determine their proximity to a beacon, a small BLE device that broadcasts a unique identifier.

Unlike BLE, which is responsible for simple data exchange between devices, iBeacon is used to create more complex interaction scenarios. For example, the application must respond when a user approaches or leaves a specific location.

Benefits of iBeacon

The iBeacon protocol is particularly suitable for applications that require precise and context-sensitive user interactions indoors.

With simplified geofencing setup, better background support, and improved location accuracy. This makes iBeacon ideal for retail marketing, indoor navigation and indoor automation applications.

Simplified geofencing setup

iBeacon makes it easy to create geofences around physical beacons. Developers don't have to worry about the complex setup and maintenance of an indoor GPS network. Because it does not guarantee such accuracy and efficiency. At the same time, iBeacon makes it easy to detect when a user enters or leaves a given geofence.

Better background support

iOS allows the app to run in the background. It will receive a notification when a geofence boundary is crossed even when not in use. This makes it possible, for example, to automatically offer coupons or special offers when a person approaches a store. Or track important events for logistics and monitoring applications without active user participation.

Improved indoor location accuracy

In confined spaces such as shopping malls, museums, conference rooms and other indoor locations, GPS is unreliable due to weak signal and lack of accuracy. iBeacon is much better at identifying user locations. This opens the door to the development of more precise indoor navigation systems, allowing, for example, to direct visitors to specific exhibits in a museum or suggest products that are in close proximity to the user in a store.

Beacon beacon

I used the Holyiot nRF52810 beacon. It stands out for its ultra-low power consumption, compact size and waterproof design.

Using the Holyiot-beacon application, you can easily configure the beacon and adapt the parameters to specific needs.

General characteristics

• Signal strength and range:

The beacon is capable of transmitting a signal over a distance of up to 50 meters in open space with adjustable transmission power from -40dB to +4dB. By default, the power is set to +4dB, which optimizes range and power consumption.

• Battery life:

Taking into account the CR2032 battery capacity of 220 mAh and average power consumption of 49uA at +4dB and a transmission interval of 500ms, the beacon provides up to 180 days of operation without battery replacement. This makes the beacon particularly suitable for applications that require long-term deployment without maintenance.

• Waterproof:

The IP67 protection class makes the beacon resistant to water and dust, which expands the possibilities of its use in various conditions.

• Physical parameters:

The beacon has compact dimensions with a diameter of 30 mm and a thickness of 8.4 mm while weighing only 6.5 g, making it easily integrated into any environment.

Setting up a beacon

Download the application

• Open the app store on your smartphone (App Store for iOS or Google Play for Android).• Search for “Holyiot-beacon” and look for the beacon configuration app from Holyiot.• Install the app on your device.

Search for device

• Launch the “Holyiot-beacon” app on your smartphone.• In the app, select the device type to search: beacon, ibeacon or eddystone can be selected depending on your needs.• The app will start searching for available devices nearby.

Connecting to a beacon

• When the tracker is detected, select it from the list of available devices.• To connect to the tracker and access additional information, click on the “Connect” button.• When prompted, enter the password to connect. Default password: x`aa14061112`.

Beacon configuration

Parameter configuration allows you to fine-tune the beacon's operation to the specific requirements of the application or service, providing the desired balance between visibility, power consumption and positioning accuracy.

After a successful connection, you will be able to view detailed information about the tracker and the parameters available for configuration. In the settings section, select the settings you want to change.

Beacon configuration involves setting various parameters, each of which plays an important role in how the beacon functions and interacts with applications and devices. Here are the main parameters that are usually configured when configuring a beacon:

UUID (Universal Unique Identifier)A UUID is a 128-bit identifier that is used to identify a specific group or category of beacons in a large space. It allows the mobile app to differentiate your organization's beacons.

Major and MinorMajor and Minor are numeric values ​​used to identify a subgroup within a group of beacons with the same UUID. Major usually defines a larger subgroup, and Minor usually defines a smaller one. These options allow you to create additional hierarchy or structuring within your beacon system, making it easier to manage and target interactions within your application.

TX PowerTX Power reflects the signal strength with which the beacon transmits its data. This parameter directly affects the beacon's range and energy consumption. Setting the optimal transmit power allows you to balance the visibility of the beacon and the duration of its battery life.

ADV_interval (Advertising interval)ADV_interval is the time between successive transmissions of Bluetooth advertising packets from the beacon. The interval affects how often the beacon “announces” itself, which affects power consumption and how quickly applications respond to the beacon appearing or disappearing from range. Decreasing the interval increases the chances of quickly detecting the beacon, but also increases power consumption.

Make the necessary changes and save the settings. Some changes may require reconnecting to the beacon or rebooting the beacon.

Implementing a native module in React Native for iOS

When we were developing an application in React Native, we were looking for a solution for working with iBeacons, but we did not find one that fully met our requirements. Therefore, we decided to develop our own native module for iOS in order to make maximum use of the capabilities of this platform for interacting with beacons.

Creating a native module

First, we create a native module for iOS, which we integrate with React Native. To do this, we write code in Swift or Objective-C, which will be called from JavaScript. This process involves declaring methods that can be called from React Native and setting up an event model for exchanging data between the native and JavaScript parts of the application.

For detailed instructions on how to create and integrate native modules into a React Native project, including code examples and best practices, see the official React Native documentation.

Setting up required permissions

After creating a native module, the next step is to configure the necessary permissions and capabilities in your Xcode project to ensure it works correctly with beacons and notifications.

Open your project settings in Xcode and go to the “Signing & Capabilities” section.

Here you need to add two key capabilities: “Background Modes” and “Push Notifications”.

• Activating “Location updates” and “Uses Bluetooth LE accessories” in the “Background Modes” section allows the application to receive location data and work with Bluetooth accessories even when it is in the background.

• Enabling “Push Notifications” is necessary so that the application can send local notifications to the user when entering or leaving the beacon’s coverage area.

To work correctly with beacons and Bluetooth in iOS, it is important to add the necessary permission lines to the project's Info.plist file. These lines inform the user how the application intends to use the device's data or functionality.

Here are the key permissions that are typically required:

Privacy – Location Always and When In Use Usage Description

(NSLocationAlwaysAndWhenInUseUsageDescription): Requires a description of why the application needs access to the user's location data at any time, including in the background.

For example, “This app uses your location to determine proximity to beacons of interest to you, even when the app is in the background.”».

Privacy – Location When In Use Usage Description

(`NSLocationWhenInUseUsageDescription`): You must specify the reason why the application needs access to location information when it is in use.

Example description: “The app requires access to your location in order to notify you of nearby beacons when you use the app».

Privacy – Bluetooth Always Usage Description

(`NSBluetoothAlwaysUsageDescription`): Since the module uses Bluetooth to detect beacons, it is necessary to explain why the application needs constant access to Bluetooth.

Example: “Bluetooth access is used to detect beacons around you to provide relevant information and notifications».

Privacy – Bluetooth Peripheral Usage Description

(`NSBluetoothPeripheralUsageDescription`): This explanation is needed if your application is going to act as a Bluetooth peripheral.

For example: “This app uses Bluetooth to communicate with beacons and other devices in your area».

Implementation

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;

Working with beacons in the background

The `didEnterRegion` and `didExitRegion` methods are part of the `CLLocationManagerDelegate` protocol and are called when a device enters or leaves the beacon's range, respectively. These methods allow apps to respond to user location changes even in the background.

Limitations and Features

⭕ iOS restrictions on background work

Although iOS allows apps to monitor beacons in the background, there are certain limitations related to battery conservation and device performance.

Apple may limit the frequency of location updates or temporarily pause background app activity to optimize system performance. iOS also provides a limited time to perform the necessary actions in response to this event. This time may vary, but is usually a few seconds. During this time, the application must have time to complete all necessary operations.

⭕ Restrictions on sending notifications

While the methods allow you to respond to regions entering and leaving the background, sending notifications to the user is also subject to strict iOS rules. The application must obtain appropriate permission from the user to send notifications.

⭕ Response delays

Depending on system settings and environmental conditions, there may be delays between actually entering or leaving a region and when the appropriate methods are called. This is due to both the operating features of Bluetooth and iOS power consumption optimization algorithms.

⭕ Requirements for the accuracy and configuration of the beacon

For optimal performance in the background, it is important to ensure the correct configuration of beacons and configure the application to work with the required location accuracy.

Working with beacons in the background significantly expands the functionality of applications, allowing you to create advanced solutions for location services, navigation inside buildings, automated user interaction and much more. However, when developing such applications, it is important to consider the features and limitations mentioned above to ensure the best performance and user experience.

If you are interested in this topic and want to understand how beacons can be used in life, read our article “BLE Technology for Business”

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *