Skip to main content

onDeviceFound

Optional callback prop on <BTProvider>. Fires once for each matching peripheral that is added to the current manager, even if that peripheral sends multiple advertisements. This is the callback responsible for actually pairing + connecting — the provider hands you a BTManagedDevice, and your handler must call device.connect() to start receiving data.

Signature​

import type {
AcceptedDevice,
BTManagedDevice,
BTManager,
DeviceKeyOf,
} from "@ovok/native";

onDeviceFound?: <T extends readonly AcceptedDevice[]>(
device: BTManagedDevice<DeviceKeyOf<T>>,
manager: BTManager<T>,
) => Promise<void>;

device is a typed wrapper around the underlying react-native-ble-plx peripheral. manager is the BT manager instance that owns scanning — most apps never need it; it's available so multi-device flows can stop scanning while pairing.

What device.connect() does​

public connect = async () => { ... }

It is the only public method on BTManagedDevice your handler needs to call. Internally it:

  1. Opens a GATT connection to the peripheral.
  2. Discovers all services + characteristics.
  3. Subscribes to every characteristic the device exposes (the subscribeToAllCharacteristics step is automatic and private — you do not call it).
  4. Wires up the device's internal BTDataProcessor so incoming bytes route through the parser.
  5. Triggers the first onDeviceStatusChanged → Connected after step 1 succeeds.

If a step fails, the rejection can be caught by the app and the normalized failure is also surfaced through onError.

Usage​

import React, { useCallback } from "react";
import { BleManager } from "react-native-ble-plx";
import { BTProvider, IntegratedDevices } from "@ovok/native";

const bleManager = new BleManager();
const acceptedDevices = [IntegratedDevices.BP2] as const;

function MyApp() {
const handleDeviceFound = useCallback(async (device) => {
// device.deviceData also includes optional manufacturerName and model values
console.log("Pairing with", device.deviceData.name, device.deviceData.sn);

await device.connect();
// Subscription happens inside `connect()`. Do NOT call
// `device.subscribeToAllCharacteristics()` — it is a private internal step.
}, []);

return (
<BTProvider
bleManager={bleManager}
acceptedDevices={acceptedDevices}
onDeviceFound={handleDeviceFound}
>
<YourScreens />
</BTProvider>
);
}

Filtering by device kind​

If you accept multiple device kinds, branch in the handler before connecting (e.g. to surface different pairing UI):

const acceptedDevices = [
IntegratedDevices.BP2,
IntegratedDevices.SPO2,
IntegratedDevices.F4,
] as const;

const handleDeviceFound = useCallback(async (device) => {
switch (device.deviceData.name) {
case IntegratedDevices.BP2:
setPairingUI("bp-cuff");
break;
case IntegratedDevices.SPO2:
setPairingUI("pulse-oximeter");
break;
case IntegratedDevices.F4:
setPairingUI("scale");
break;
}
await device.connect();
}, []);

Refusing a device​

onDeviceFound does not have an opt-out return value. If you want to refuse a peripheral your acceptedDevices filter matched, do not call connect(). The manager keeps the peripheral de-duplicated for the current scan, so it will not repeatedly fire the callback for every advertisement. End/restart the manager or use a new flow if the app wants to reconsider it.

const handleDeviceFound = useCallback(async (device) => {
if (!isAuthorizedSerial(device.deviceData.sn)) {
// Refuse — do not connect.
return;
}
await device.connect();
}, []);

Sequence after connect()​

For a typical BP2 pairing:

  1. onDeviceFound → your handler calls await device.connect().
  2. onDeviceStatusChanged → Connected and a bonded connection event.
  3. (User initiates a reading on the cuff.)
  4. onDeviceStatusChanged → Measuring (with measurementTypeKey).
  5. onResult → parsed measurement.
  6. onDeviceStatusChanged → Connected, with the measurement type when the protocol supplies it.

If pairing fails:

  1. onDeviceFound → your handler calls await device.connect().
  2. onError fires with data.deviceData set and data.error describing the failure.
  3. No Connected status arrives. Scanning continues — the next advertisement re-fires onDeviceFound.

Memoize the handler​

onDeviceFound is the most expensive callback to re-register. Each new prop identity tears down + re-registers the scan subscription, and during teardown an in-flight pairing can be aborted. Wrap with useCallback and pin its dependencies:

const handleDeviceFound = useCallback(async (device) => {
await device.connect();
}, []); // empty deps — body uses only `device`, no closures over state