Skip to main content

Device Management Services

A comprehensive set of services for managing Bluetooth Low Energy medical device operations including scanning, connection management, device creation, storage, and event handling.

Overview​

The Device Management Services layer provides the core infrastructure for BLE medical device operations. These services handle device lifecycle management, data processing, error recovery, and persistent storage with a robust, event-driven architecture.

Canonical surface note. Application code should consume BLE device events through the five BTProvider callbacks (onDeviceFound, onError, onResult, onDeviceStatusChanged, onAccessPermissionChanged). The services described below are the internal implementation that powers those callbacks — they are documented here for reference and for advanced use cases where the provider surface is not sufficient. Do not duplicate logic that the provider already gives you.

Core Services​

ScanManager​

Singleton service for managing BLE device scanning with intelligent filtering and automatic reconnection.

Features​

  • Intelligent Scanning: Automatic device filtering and recognition
  • Auto-Reload: Configurable scanning intervals to prevent battery drain
  • Queue Management: Prevents multiple simultaneous connections
  • Error Recovery: Automatic retry with exponential backoff
  • Permission Handling: Bluetooth and location permission management

Usage​

import { ScanManager } from '@ovok/native';

const scanManager = ScanManager.instance(bleManager);

// Start scanning
scanManager.startScan();

// In application code prefer BTProvider's onDeviceFound / onError callbacks
// rather than subscribing to the internal scan stream directly. ScanManager
// is shown here only for advanced/diagnostic usage.

// Auto-reload every 20 minutes
scanManager.turnOnAutoReloadScan();

// Stop scanning
scanManager.stopScan();

Configuration​

// Internal configuration
const integratedDevicesMap = new Map([
[/F4/i, IntegratedDevices.F4],
[/BP2/i, IntegratedDevices.BP2],
[/Oxyfit/i, IntegratedDevices.SPO2],
[/AOJ-20A/i, IntegratedDevices.AOJ20A],
[/OxySmart/i, IntegratedDevices.PC60WF],
[/TAIDOC TD4216/i, IntegratedDevices.TAIDOC_GLUCOSE],
[/GlucoCheck/i, IntegratedDevices.GLUCOCHECK],
[/TEMP/i, IntegratedDevices.ANDESFITTEMP],
[/FORA IR20/i, IntegratedDevices.FORATEMP],
[/two/i, IntegratedDevices.COSINUSS],
[/BC01/i, IntegratedDevices.BC01],
[/FORA W550/i, IntegratedDevices.FORA_SCALE],
[/GBS-2012-B/i, IntegratedDevices.RPM_SCALE],
[/TAIDOC TD1107/i, IntegratedDevices.CLEVERTEMP],
[/cmed/i, IntegratedDevices.CMED],
[/PULMO80B/i, IntegratedDevices.SP80B],
[/Le S5/i, IntegratedDevices.S5],
[/alivecor/i, IntegratedDevices.ALIVE_COR],
[/BGM/i, IntegratedDevices.BGM]
]);

DeviceCreator​

Factory service for creating device instances based on discovered BLE devices.

Features​

  • Factory Pattern: Creates appropriate device instances based on device type
  • Device Validation: Ensures devices are supported and properly configured
  • Error Handling: Throws meaningful errors for unsupported devices

Usage​

import { DeviceCreator } from '@ovok/native';

// Create device instance from BLE device
try {
const device = DeviceCreator.createDevice(bleDevice);
console.log('Created device:', device.deviceData);
} catch (error) {
console.error('Device not supported:', error.message);
}

Implementation​

export class DeviceCreator {
static createDevice(device: Device): IBaseDevice {
switch (true) {
case device.name?.includes(IntegratedDevices.F4):
return new DeviceF4(device);
case device.name?.includes(IntegratedDevices.BP2):
return new DeviceBp2(device);
case device.name?.includes(IntegratedDevices.SPO2):
return new DeviceOxyFit(device);
case device.name?.includes(IntegratedDevices.AOJ20A):
return new DeviceAOJ20A(device);
case device.name?.includes(IntegratedDevices.PC60WF):
return new DevicePC60WF(device);
case device.name?.includes(IntegratedDevices.TAIDOC_GLUCOSE):
return new DeviceTaidoc(device);
case device.name?.includes(IntegratedDevices.GLUCOCHECK):
return new DeviceGlucoCheck(device);
case device.name?.includes(IntegratedDevices.ANDESFITTEMP):
return new DeviceAndesfitTemp(device);
case device.name?.includes(IntegratedDevices.FORATEMP):
return new DeviceFORATemp(device);
case device.name?.includes(IntegratedDevices.COSINUSS):
return new DeviceCosinussTemp(device);
case device.name?.includes(IntegratedDevices.BC01):
return new DeviceBC01(device);
case device.name?.includes(IntegratedDevices.FORA_SCALE):
return new DeviceForaScale(device);
case device.name?.includes(IntegratedDevices.RPM_SCALE):
return new DeviceTeleRPM(device);
case device.name?.includes(IntegratedDevices.CLEVERTEMP):
return new DeviceCleverTemp(device);
case device.name?.includes(IntegratedDevices.CMED):
return new DeviceCosinussTemp(device);
case device.name?.includes(IntegratedDevices.SP80B):
return new SP80BDevice(device);
case device.name?.includes(IntegratedDevices.S5):
return new DeviceS5(device);
case device.name?.includes(IntegratedDevices.BGM):
return new DeviceBGM(device);
default:
throw new Error(`Device "${device.name}" is not supported`);
}
}
}

DeviceManager​

Static service for managing connected device states and preventing connection conflicts.

Features​

  • Connection Tracking: Maintains map of connected devices
  • Conflict Prevention: Ensures only one connection per device type
  • State Management: Centralized device connection state

Usage​

import { DeviceManager } from '@ovok/native';

// Check if device is connected
const isConnected = DeviceManager.connectedDevices.has(IntegratedDevices.F4);

// Get connected device ID
const deviceId = DeviceManager.connectedDevices.get(IntegratedDevices.F4);

// Register new connection (handled internally)
DeviceManager.connectedDevices.set(IntegratedDevices.F4, "device-id");

// Remove connection (handled internally)
DeviceManager.connectedDevices.delete(IntegratedDevices.F4);

DeviceStorageService​

Service for persistent storage of device pairing information and user preferences.

Features​

  • Persistent Storage: Saves device pairing across app sessions
  • Device Preferences: Stores user preferences for specific devices
  • Secure Storage: Uses secure storage mechanisms for sensitive data

Usage​

import { DeviceStorageService } from '@ovok/native';

// Get saved device ID
const savedDeviceId = await DeviceStorageService.getSavedDeviceId(
IntegratedDevices.F4
);

// Save device pairing
await DeviceStorageService.saveDeviceInStorage(
IntegratedDevices.F4,
"device-id-123"
);

// Get all saved devices
const savedDevices = await DeviceStorageService.getSavedDevicesFromStorage();

DeviceEventManager​

Centralized event management system for device communication and state changes. BTProvider subscribes to this manager and exposes its events to application code through its five callbacks — most apps should consume that provider surface rather than subscribe here directly.

Features​

  • Event Types: Device status, measurements (results), and errors
  • Subscription Management: Easy subscribe/unsubscribe pattern
  • Type Safety: Strongly typed event parameters
  • Performance: Efficient event emission and handling

Mapping to BTProvider callbacks​

Internal eventBTProvider callbackPayload shape
device status changeonDeviceStatusChanged{ deviceData, status, measurementTypeKey? }
measurement / result dataonResult{ deviceData, data }
device erroronError{ error, deviceData? }
device discoveredonDeviceFound(device, manager) — call await device.connect() to attach
permission grant changedonAccessPermissionChangedgranted: boolean

No separate streaming-data callback. The canonical SDK does not emit a distinct "stream data" event. Real-time samples and one-shot measurement results both arrive through onResult — distinguish them by inspecting the measurementTypeKey / payload on data rather than by a separate event name.

import { BTProvider, DeviceStatus } from '@ovok/native';

<BTProvider
bleManager={bleManager}
acceptedDevices={acceptedDevices}
onDeviceFound={async (device, _manager) => {
// connect() handles characteristic subscription internally
await device.connect();
}}
onDeviceStatusChanged={({ deviceData, status, measurementTypeKey }) => {
switch (status) {
case DeviceStatus.Connected:
console.log(`${deviceData.name} connected`);
break;
case DeviceStatus.Measuring:
console.log(`${deviceData.name} measuring ${measurementTypeKey}`);
break;
case DeviceStatus.Disconnected:
console.log(`${deviceData.name} disconnected`);
break;
case DeviceStatus.LowBattery:
console.log(`${deviceData.name} low battery`);
break;
}
}}
onResult={({ deviceData, data }) => {
console.log('Measurement / stream sample from', deviceData.name, data);
}}
onError={({ error, deviceData }) => {
console.error(`Device error${deviceData ? ` for ${deviceData.name}` : ''}:`, error);
}}
onAccessPermissionChanged={(granted) => {
console.log('BLE permission granted:', granted);
}}
>
{children}
</BTProvider>

Event Types​

// Device Status Events — canonical SDK enum (4 members)
export enum DeviceStatus {
Connected = "Connected",
Disconnected = "Disconnected",
Measuring = "Measuring",
LowBattery = "LowBattery"
}

The internal DeviceEventManager emits a small set of event constants used to fan out the provider callbacks above. There is no public ON_STREAM_DATA event — measurement samples (including streaming samples) are surfaced via onResult. Treat any reference to onStreamData, onScanError, onDeviceErrors, onMeasurementUpdated, or onDeviceStatusUpdated in older docs as out-of-date: those names are not part of the SDK surface.

Data Processing Services​

MessageCollector​

Service for collecting and assembling fragmented BLE messages.

Features​

  • Message Assembly: Combines fragmented BLE characteristics data
  • Length Validation: Validates message completeness
  • Timeout Handling: Automatic reset for incomplete messages
  • Protocol Support: Supports various device communication protocols

Usage​

import { MessageCollector } from '@ovok/native';

const msgController = new MessageCollector();

// Collect message parts
msgController.collectMessage(
messageFromDevice,
(fullMessage) => {
// Process complete message
console.log('Complete message:', fullMessage);
const parsedData = DataParser.parse(fullMessage, parsingCase);
},
characteristic.parsingModel.messageCountParsingDefinition
);

SP80BMessageCollector​

Specialized message collector for SP80B spirometer with timeout handling.

Features​

  • Timeout Management: Configurable timeout for message collection
  • Spirometer Protocol: Specialized for SP80B device communication
  • Data Validation: Validates spirometry measurement data

Usage​

import { SP80BMessageCollector } from '@ovok/native';

const sp80bMsgController = new SP80BMessageCollector(20000); // 20 second timeout

sp80bMsgController.collectMessage(
messageFromDevice,
(fullMessage) => {
const spirometryData = DataParser.parse(fullMessage, parsingCase);
// Process spirometry measurement
}
);

DataParser​

Service for parsing raw BLE data into structured measurement objects.

Features​

  • Protocol Parsing: Supports multiple device communication protocols
  • Data Conversion: Converts hex data to meaningful values
  • Type Safety: Returns strongly typed measurement objects
  • Custom Parsers: Supports device-specific parsing functions

Usage​

import { DataParser } from '@ovok/native';

// Parse raw device data
const measurement = DataParser.parse(hexMessage, parsingCase);

// Access parsed values
console.log('Measurement type:', measurement.measurementTypeKey);
console.log('Values:', measurement);

// Example for blood pressure
if (measurement.measurementTypeKey === MeasurementTypeKey.bloodPressure) {
console.log(`BP: ${measurement.systolic}/${measurement.diastolic}`);
console.log(`HR: ${measurement.heartRate}`);
}

Utility Services​

MillisecondTimer​

High-precision timer for measurement timing and device operation monitoring.

Features​

  • Millisecond Precision: Accurate timing for medical measurements
  • Start/Stop Control: Easy timer lifecycle management
  • Elapsed Time Tracking: Real-time elapsed time calculation

Usage​

import { MillisecondTimer } from '@ovok/native';

const timer = new MillisecondTimer();

// Start timing
timer.startTimer();

// Get elapsed time
console.log('Elapsed:', timer.elapsedTime, 'ms');

// Stop timing
timer.stopTimer();

Converter​

Utility service for data format conversion and mathematical operations.

Features​

  • Data Conversion: Hex/binary/base64 conversions
  • Mathematical Operations: Two's complement, checksum calculations
  • Medical Calculations: BMI, body composition, spirometry values
  • Validation: Data validation and error checking

Usage​

import { Converter } from '@ovok/native';

// Convert hex to base64
const base64Data = Converter.hexToBase64("48656c6c6f");

// Convert base64 to hex array
const hexArray = Converter.base64ToHex(base64Data);

// Calculate spirometer values
const fvcValue = Converter.calculateSpirometerValue("1234");

// Body composition calculations
const fatPercentage = Converter.getFatPercentage(70, 175, 30, 1, 500);
const waterPercentage = Converter.getWaterPercentage(70, 175, 30, 1, 500);

// Data validation
const checksum = Converter.checkSum(dataArray);

Service Integration Patterns​

Complete Device Integration (via BTProvider)​

import {
BTProvider,
DeviceStorageService,
DeviceStatus,
} from '@ovok/native';
import type { IBaseDevice, DeviceData, Measurement, MeasurementTypeKey } from '@ovok/native';

function DeviceIntegrationProvider({
bleManager,
acceptedDevices,
children,
}: {
bleManager: BleManager;
acceptedDevices: AcceptedDevices;
children: React.ReactNode;
}) {
const connectedDevices = React.useRef(new Map<string, IBaseDevice>());

const handleDeviceFound = React.useCallback(
async (device: IBaseDevice, _manager: unknown) => {
try {
// Persist pairing the first time we see this device
const savedId = await DeviceStorageService.getSavedDeviceId(device.deviceName);
if (!savedId) {
await DeviceStorageService.saveDeviceInStorage(
device.deviceName,
device.deviceId,
);
}

// connect() auto-subscribes to characteristics internally —
// do NOT call subscribeToAllCharacteristics() (private method).
await device.connect();
connectedDevices.current.set(device.deviceName, device);
} catch (error) {
console.error('Device connection failed:', error);
}
},
[],
);

const handleDeviceStatusChanged = React.useCallback(
({
deviceData,
status,
measurementTypeKey,
}: {
deviceData: DeviceData;
status: DeviceStatus;
measurementTypeKey?: MeasurementTypeKey;
}) => {
switch (status) {
case DeviceStatus.Connected:
console.log(`${deviceData.name} connected and ready`);
break;
case DeviceStatus.Measuring:
console.log(`${deviceData.name} measuring ${measurementTypeKey}`);
break;
case DeviceStatus.Disconnected:
console.log(`${deviceData.name} disconnected`);
connectedDevices.current.delete(deviceData.name);
break;
case DeviceStatus.LowBattery:
console.log(`${deviceData.name} has low battery`);
break;
}
},
[],
);

const handleResult = React.useCallback(
({ deviceData, data }: { deviceData: DeviceData; data: Measurement }) => {
const enriched = {
...data,
deviceInfo: {
name: deviceData.name,
serialNumber: deviceData.sn,
model: deviceData.model?.deviceName,
},
timestamp: new Date().toISOString(),
};
// saveMeasurement(enriched);
},
[],
);

const handleError = React.useCallback(
async ({ error, deviceData }: { error: string; deviceData?: DeviceData }) => {
console.error(`Device error${deviceData ? ` for ${deviceData.name}` : ''}:`, error);

// Simple reconnect example
if (deviceData && typeof error === 'string' && error.includes('Connection')) {
const instance = connectedDevices.current.get(deviceData.name);
if (instance) {
try {
await instance.connect();
} catch (reconnectError) {
console.error('Reconnection failed:', reconnectError);
}
}
}
},
[],
);

const handleAccessPermissionChanged = React.useCallback((granted: boolean) => {
console.log('BLE permission granted:', granted);
}, []);

return (
<BTProvider
bleManager={bleManager}
acceptedDevices={acceptedDevices}
onDeviceFound={handleDeviceFound}
onDeviceStatusChanged={handleDeviceStatusChanged}
onResult={handleResult}
onError={handleError}
onAccessPermissionChanged={handleAccessPermissionChanged}
>
{children}
</BTProvider>
);
}

Error Handling​

Scan Error Recovery​

const scanErrorActionMap = new Map([
[102, handleBluetoothOff], // Bluetooth is off
[101, handleLocationPermission], // Location permission denied
[100, handleReConnect], // Connection issue
[600, handleScanTimeout] // Scan timeout
]);

async function handleBluetoothOff(bleManager: BleManager) {
await bleManager.enable();
}

async function handleLocationPermission() {
// Request location permissions
const permissions = await requestMultiple([
PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION,
PERMISSIONS.IOS.LOCATION_WHEN_IN_USE
]);
return permissions;
}

async function handleReConnect(bleManager: BleManager) {
return new Promise<void>((resolve, reject) => {
const subscription = bleManager.onStateChange(async (state) => {
if (state === "PoweredOn") {
subscription.remove();
resolve();
} else {
await createDelay(5);
await bleManager.enable();
}
});

setTimeout(() => {
subscription.remove();
reject(new Error("Bluetooth state change timeout"));
}, 10000);
});
}

Performance Optimization​

Queue Management​

export class ScanDeviceQueue {
private queuedDevices = new Map<IntegratedDevices, Map<DeviceId, NodeJS.Timeout>>();
private timeout: number;

addTimerForDevice(device: IBaseDevice): NodeJS.Timeout {
const deviceType = device.deviceName;
const deviceId = device.deviceId;

if (!this.queuedDevices.has(deviceType)) {
this.queuedDevices.set(deviceType, new Map());
}

const timer = setTimeout(() => {
this.onTimerWorks(device);
}, this.timeout);

this.queuedDevices.get(deviceType)?.set(deviceId, timer);
return timer;
}

removeAllTimersForDevice(deviceName: IntegratedDevices): void {
const deviceTimers = this.queuedDevices.get(deviceName);
if (deviceTimers) {
deviceTimers.forEach(timer => clearTimeout(timer));
deviceTimers.clear();
}
}
}

Dependencies​

  • react-native-ble-plx: BLE communication
  • react-native-permissions: Permission management
  • @ovok/core: Core measurement types
  • react-native: Core platform functionality