openJIIDocs
Extending the Platform

Device support

How a device family's capabilities are declared across the codebase, and the checklist for adding support for a new device.

Every registered device carries a sensor family (its deviceType), chosen once at registration. That family is the single dispatch axis for everything device-kind specific: which local transports the web offers, whether the onboarding configuration can be pushed over a direct connection, which driver speaks to the hardware, and how workbook protocol cells execute against it. Downstream code reads capability registries; it never branches on family names inline. Adding a family therefore means adding registry entries and (when the hardware speaks its own protocol) a driver, not editing consumers.

This page is the internal counterpart to Device Integration, which documents the contract from the device implementer's side.

How a device receives its configuration

The onboarding configuration file is the canonical interface, and it assumes nothing about the device beyond storage: the operator downloads the JSON and loads it however the hardware allows (flashing during provisioning, SD card, a vendor tool). Every family supports this path, and for most it is the only one.

On top of that baseline there is one optional capability, declared per family in the registry:

CapabilityRegistry flagBehavior
Download (always available)noneThe Onboarding tab offers the file; the operator delivers it out of band.
Direct push (SET_CONFIG)supportsStoredConfig: trueThe Onboarding tab additionally offers connect + push over serial or BLE; the file arrives as one SET_CONFIG command frame.

When push is off, the panel explains why with a per-family note: the inline-procedure families (MultispeQ, Ambit, MiniPAR) receive their procedure per measurement and have nothing to store, while Ambyte edge devices load their configuration at provisioning time and expose no onboarding commands over a direct connection. Only the generic family opts into the push today.

A device that wants push support must implement the SET_CONFIG frame documented in Device Integration, persist the delivered config, and treat each delivery as full desired state.

Where family knowledge lives

LayerArtifactDeclares
Databasesensor_family enum in packages/database/src/schema.tsPersisted family values on protocols and devices.
API contractzSensorFamily in packages/api/src/domains/protocol/protocol.schema.tsThe wire enum shared by protocols, devices, and registration.
API contractDEVICE_PROFILES in packages/api/src/domains/device/device-profile.tsCanonical product spelling and display roles. The exhaustive Record makes a missing profile a compile error.
Device toolkitDEVICE_TRANSPORT_SUPPORT in packages/iot/src/core/types.tsTransports, BLE/Bluetooth Classic support, and the stored-config (push) flag per DeviceType.
Device toolkitSENSOR_FAMILIES and packages/iot/src/driver/*Which families are identifiable over a local probe, and the adapter speaking each device-native serial protocol.
Web hostsensorFamilyToDeviceType in apps/web/hooks/iot/device-type-mapping.tsRouting from the API family to the toolkit DeviceType.
Web hostcreateDriver in apps/web/hooks/iot/useIotCommunication/useIotCommunication.tsWhich driver a local connection instantiates per family.
Web hostexecuteProtocolWithDriver in apps/web/hooks/iot/useIotProtocolExecutionHow protocol cells run per family: direct JSON, the SET_CONFIG/RUN/GET_DATA sequence, or refused with a message.
Web hostDOWNLOAD_ONLY_NOTE_KEYS in apps/web/components/iot-devices/device-config-delivery.tsxThe reason copy shown when push is unavailable, resolved through @repo/i18n.

@repo/iot stays dependency-free, so SENSOR_FAMILIES is duplicated by hand from zSensorFamily rather than imported; the two lists differ deliberately when a family has no local identification handshake (Ambyte today).

Two things intentionally do not change per family:

  • The backend onboarding compose is family-agnostic. The family only appears in the returned deviceType field and as the sensorType segment baked into each topicPrefix.
  • The ingest infrastructure needs no change: sensorType is a parameter of the AsyncAPI channel, so topic rules and IoT policies already match any family value.

Current families

FamilyProductLocal transportsConfig pushProtocol cells over a direct connectionDriver
multispeqMultispeQserial (BT Classic on native)noyes, protocol JSON sent directlyMultispeqDriver
ambitAmbitserialnorefused, command cells onlyAmbitDriver
miniparMiniPARserialnoyes, protocol JSON sent directlyMiniParDriver
ambyteAmbyteserialnogeneric compatibility path for protocol testingGenericDeviceDriver
genericnoneserial, BLEyesSET_CONFIG / RUN / GET_DATA sequenceGenericDeviceDriver
mobilenonenonenon/a, the phone runs the app itselfnone

The mobile family is special: phones register themselves silently when someone signs into the mobile app (one Thing per install, Cognito-authenticated, no certificate lifecycle), so it never appears in the manual registration dialog, has no credentials or onboarding surface, and cannot be selected as a protocol family.

Adding a new family

Work through the layers top to bottom; the exhaustive types surface most omissions as compile errors.

  1. Database: add the value to the sensor_family enum and generate a migration.
  2. API contract: add the value to zSensorFamily, then follow the compile error into DEVICE_PROFILES with the product spelling and display roles.
  3. Device toolkit (@repo/iot): add the value to the DeviceType union and give it a DEVICE_TRANSPORT_SUPPORT entry stating its transports and whether it accepts a stored config. If the hardware is identifiable over a local probe and speaks its own protocol, add a driver under packages/iot/src/driver/, list the family in SENSOR_FAMILIES, and extend createConnectorForFamily; otherwise leave it out of SENSOR_FAMILIES and route it through GenericDeviceDriver at the host.
  4. Web host: add the mapping case in sensorFamilyToDeviceType, the createDriver case, and an execution branch in executeProtocolWithDriver when the generic flow does not fit. If push is off and the inline-procedure wording is wrong for the hardware, add a note key to DOWNLOAD_ONLY_NOTE_KEYS with copy in all three locales.
  5. Fixtures and tests: extend the seed if the family should exist in dev data, and update the specs that pin the registries: onboarding.spec.ts in @repo/iot, and the web tests for the mapping, browser support, communication, protocol execution, and config delivery.
  6. Docs: extend Device Integration when the configuration contract gains family-specific behavior.

Capability changes for an existing family follow the same path minus the enum work: flip the registry flag, adjust the host branches the compiler points at, and update the pinned specs.

On this page