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
BTProvidercallbacks (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 event | BTProvider callback | Payload shape |
|---|---|---|
| device status change | onDeviceStatusChanged | { deviceData, status, measurementTypeKey? } |
| measurement / result data | onResult | { deviceData, data } |
| device error | onError | { error, deviceData? } |
| device discovered | onDeviceFound | (device, manager) — call await device.connect() to attach |
| permission grant changed | onAccessPermissionChanged | granted: 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 themeasurementTypeKey/ payload ondatarather than by a separate event name.
Usage (via BTProvider — recommended)
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
DeviceEventManageremits a small set of event constants used to fan out the provider callbacks above. There is no publicON_STREAM_DATAevent — measurement samples (including streaming samples) are surfaced viaonResult. Treat any reference toonStreamData,onScanError,onDeviceErrors,onMeasurementUpdated, oronDeviceStatusUpdatedin 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 communicationreact-native-permissions: Permission management@ovok/core: Core measurement typesreact-native: Core platform functionality
Related Documentation
BTProvider: React provider componentDevice Types: Medical device configurationsTypes: TypeScript type definitions