Skip to main content

Custom devices

Use defineCustomDevice when the SDK does not already implement a device protocol and the device can be described by the declarative GATT and frame format. The function validates the declaration and returns a branded CustomDeviceDefinition for BTProvider.acceptedDevices.

Import the builder and types from @ovok/native/bt-management.

Define a device​

import { MeasurementTypeKey } from "@ovok/core";
import {
defineCustomDevice,
IntegratedDevices,
} from "@ovok/native/bt-management";

const acmeScale = defineCustomDevice({
id: "acme-scale",
nameMatch: "ACME Scale",
stuckDataTimeoutMs: 30_000,
services: [
{
uuid: "181d",
monitor: [
{
uuid: "2a9d",
frame: { minLengthBytes: 3 },
caseSelectorBytes: [0],
cases: [
{
selector: "00",
kind: "result",
measurementTypeKey: MeasurementTypeKey.bodyWeight,
fields: [
{
name: "bodyWeight",
byteIndexes: [1, 2],
byteOrder: "little",
encoding: "uint",
unit: "kg",
scale: 0.005,
decimals: 3,
},
],
},
],
},
],
},
],
});

const acceptedDevices = [IntegratedDevices.BP2, acmeScale] as const;

A string nameMatch becomes an exact advertised-name match. A RegExp can match a family of names; do not use the global or sticky flag. When a device does not advertise a usable name, set matchByService: true to match by a declared service UUID.

Pass the resulting definition in acceptedDevices. The custom ID becomes deviceData.name for results and errors. IDs are trimmed, default to ovok-custom-device when omitted, and cannot collide with a built-in device name.

What a declaration describes​

  • services lists GATT service UUIDs and one or more monitored characteristics. Every service needs at least one monitor.
  • frame describes a frame header, minimum length, optional length field, and optional checksum. The SDK does not scan forward for a header; field offsets are measured from the beginning of the frame.
  • caseSelectorBytes selects a case using the hex values at those byte positions.
  • A result case declares a supported measurement type and fields. Each field must match the SDK measurement catalog's field name and canonical unit, and its byte indexes must fit within the frame.
  • stream and idle cases update device state without emitting a measurement.
  • repeatedRecords decodes fixed-width records separately from a history frame.
  • additionalMeasurements can emit multiple measurement types from a single result frame.
  • An optional services[].write sends one static hex command after connection and after each processed frame.
  • Optional preferredMtu requests an MTU from 23 through 517 after connection.
  • Optional oneReadingPerConnection limits output for that custom definition.

defineCustomDevice checks IDs, UUIDs, timeouts, frame bounds, measurement fields, and other declaration constraints at registration. Invalid declarations throw with the declaration path; the device is not accepted as a runnable definition.

Know when the declarative format is not enough​

A device that needs a handshake, challenge-response, or command sequence cannot be represented by the ordinary static-write declaration. The SDK also supports a dedicated LEPU/Viatom protocol declaration through lepu, which owns its framing and command sequence. For a protocol outside the declarative forms, an SDK driver is required.

See the public custom device types and the Bluetooth guide.