DEV Community

HarmonyOS
HarmonyOS

Posted on

Designing a Singleton-Based BleManager for iBeacon Scanning in HarmonyOS

Read the original article:Designing a Singleton-Based BleManager for iBeacon Scanning in HarmonyOS

Requirement Description

The developer requires a reusable and centralized BleManager class in HarmonyOS to handle BLE-based beacon scanning, iBeacon protocol parsing, distance estimation, and UI-independent callbacks all wrapped inside a clean singleton structure.

Background Knowledge

  • BLE beacons advertise structured data packets that include UUID, major, minor, and txPower fields.
  • iBeacon format (Apple’s protocol) is widely used and follows a specific byte structure within the BLE advertisement payload.
  • HarmonyOS BLE scanning is handled via @kit.ConnectivityKit and must be initialized with manufacturer filters (Apple ID 0x004C).
  • RSSI and txPower can be used to approximate the distance between device and beacon.
  • For maintainability, BLE logic should be decoupled from UI and managed via a singleton utility class.

Implementation Steps

  • Create an interface IBeaconData for structured beacon information.
  • Implement BleManager as a singleton class with private constructor and public getInstance() method.
  • Use ble.on('BLEDeviceFind') to handle scan results.
  • Parse raw BLE data into iBeacon format using byte inspection.
  • Allow consumers to register a callback with setOnBeaconFoundCallback().
  • Provide utility method measureDistance() using RSSI and txPower.
  • Wrap all BLE operations (enableBluetooth, startScan, stopScan) with proper error handling and logs.

Code Snippet / Configuration

Core Methods in BleManager

public async startScan(): Promise<void> {
  const state = await bluetooth.getState();
  if (state !== bluetooth.BluetoothState.STATE_ON) {
    hilog.error(0, 'BLE', 'Bluetooth is not enabled.');
    return;
  }

  ble.on('BLEDeviceFind', (results: Array<ble.ScanResult>) => {
    results.forEach((result) => {
      const beacon = this.parseIBeaconData(result.data, result.rssi);
      if (beacon && this.beaconCallback) {
        this.beaconCallback(beacon);
      }
    });
  });

  await ble.startBLEScan([{ manufactureId: 0x004C }], {
    interval: 0,
    dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER,
    matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE,
  });
}
Enter fullscreen mode Exit fullscreen mode

iBeacon Parsing Logic

private parseIBeaconData(buffer: ArrayBuffer, rssi: number): IBeaconData | null {
  const data = new Uint8Array(buffer);
  if (data.length < 30) return null;

  const isIBeacon = data[4] === 0xFF && data[5] === 0x4C && data[6] === 0x00 && data[8] === 0x15;
  if (!isIBeacon) return null;

  const uuid = [...Array(16).keys()]
    .map(i => data[9 + i].toString(16).padStart(2, '0'))
    .join('')
    .replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');

  const major = (data[25] << 8) | data[26];
  const minor = (data[27] << 8) | data[28];
  const txPower = data[29] << 24 >> 24;

  return { uuid, major, minor, txPower, rssi };
}
Enter fullscreen mode Exit fullscreen mode

Distance Estimation Method

measureDistance(rssi: number, txPower: number): number {
  return Math.pow(10, (txPower - rssi) / (10 * 3));
}
Enter fullscreen mode Exit fullscreen mode

Test Results

The BleManager class was tested in real device environments with iBeacon-compatible hardware broadcasting standard payloads.

Scanning was successfully initiated when Bluetooth was enabled, and gracefully aborted with a clear error log when it was not.

When valid iBeacon advertisements were detected, the manager correctly parsed UUID, major, minor, RSSI, and TxPower values, and passed them through the registered callback.

Singleton behavior was also confirmed, as multiple invocations of getInstance() across different parts of the app returned the same initialized manager instance.

All BLE operations ran asynchronously and did not block the UI thread.

Limitations or Considerations

The parseIBeaconData() function supports only iBeacon format; others (Eddystone, AltBeacon) are ignored.

RSSI-based estimation fluctuates in real environments; best used with smoothing/filtering

Since BleManager is UI-agnostic, consuming code must handle UI updates

Be sure to stopScan() on exit or inactivity.

Always validate beacon source and ensure permissions are granted properly.

Related Documents or Links

Written by Arif Emre Ankara

Top comments (0)