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.ConnectivityKitand must be initialized with manufacturer filters (Apple ID0x004C). - 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
IBeaconDatafor structured beacon information. - Implement
BleManageras a singleton class with private constructor and publicgetInstance()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,
});
}
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 };
}
Distance Estimation Method
measureDistance(rssi: number, txPower: number): number {
return Math.pow(10, (txPower - rssi) / (10 * 3));
}
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
- HarmonyOS Connectivity Kit
- iBeacon Protocol Structure
Top comments (0)