Model your data

Give your readings structure. A profile says what a sensor measures, in what units, and within what range — so bad data is caught at the door and good data is ready to chart.

Profiles describe your sensors

A SensorProfile is a versioned, typed schema for a channel: each field has a type, unit, optional range, and whether it's required or writable. A DeviceProfile describes a whole device type — which components it carries (each bound to a SensorProfile) and what's configurable.

Profiles are versioned major.minor and are immutable for a given version: an additive change bumps the minor version, a breaking change bumps the major. They're installed as data — typically by a Pack — rather than edited ad hoc, so a device type means the same thing everywhere. Each profile can also be exported as a DTDL-style JSON-LD interface.

Readings are typed and validated

Every Reading is a timestamped value stored in a wide, time-series table — one of four typed columns is filled depending on the channel's value type:

  • DOUBLE — floating-point measurements
  • BIGINT — integer counts
  • BOOLEAN — on/off, open/closed
  • TEXT — short string states

Readings are stored in a TimescaleDB hypertable, partitioned by time and organization. Structured, multi-field readings are validated against their SensorProfile before they're stored — required fields, types, array sizes, and numeric ranges are all checked. Anything that fails is parked in quarantine for review instead of being silently dropped.

In the app

Browse profiles under Sensor profiles (/{slug}/sensor-profiles) and Device profiles (/{slug}/device-profiles). Readings are charted on each device's detail page.

In the API

  • GET /api/v1/sensor-profiles — list or look up a specific version
  • GET /api/v1/device-profiles — list or look up a specific version
  • GET /api/schemas/sensor-profile/{id} — export a profile as DTDL JSON-LD
  • GET /api/v1/readings/sensor/{sensorId} — raw readings; /window for a relative range
  • GET /api/v1/readings/sensor/{sensorId}/aggregated — time-bucketed averages/min/max/count

See Visualize for how the aggregation endpoints keep large time ranges fast.

Was this page helpful?