# Custom devices

Canonical page: https://docs.ovok.com/native-sdk/guide/components/bt-management/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 [#define-a-device]

~~~tsx
import { MeasurementTypeKey } from "@ovok/core/next";
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 [#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. By default (`when: "every-frame"`) it goes out after connection and after each processed frame, which polls a device that answers one request with one reading. `when: "connect"` sends it once after connection, for a request answered by a stream of frames, such as the Glucose Profile's "report all stored records".
- Optional `preferredMtu` requests an MTU from 23 through 517 after connection.
- Optional `oneReadingPerConnection` limits output for that custom definition.
- An optional `createCommandSequencer` (second argument to `defineCustomDevice`) replaces `services[].write` with a `CommandSequencer` built for each connection from the device id and the manager's `recordCursorStorage`. Its `initialCommands` may resolve later, so the first command can depend on stored state, as the Glucose Profile's request for the records after the newest one read does.
- An optional `decorateReading` (second argument to `defineCustomDevice`) adds metadata such as the record's own timestamp to each validated reading. Returning `null` withholds the reading, for a frame that decodes but is not a patient measurement, such as a glucose control-solution test.

`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 [#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](/native-sdk/guide/components/bt-management/types/) and the [Bluetooth guide](/native-sdk/guide/modules/bt-management).
