Troubleshooting
Common issues and solutions when working with the Occupancy Data Engine.
The Occupancy Data Engine is Haltian’s backend processing service that transforms raw sensor events from multiple physical devices into unified, accurate occupancy metrics for a space. Rather than treating each sensor independently, it aggregates data from a device group — a virtual device representing a room, zone, or area — and produces a single authoritative occupancy state.
This is in contrast to single-device occupancy, where one sensor (e.g., a desk PIR sensor) directly reports the occupancy of a space it covers alone.
| Scenario | Approach |
|---|---|
| Single desk with one sensor | Single device — direct sensor reporting |
| Small room with one presence sensor | Single device — direct sensor reporting |
| Large meeting room with entry/exit sensors + motion | Device group |
| Open office zone with multiple sensors | Device group |
| Floor-level occupancy from multiple doorways | Device group |
Device groups are virtual devices that aggregate measurements from multiple physical sensors. Unlike physical devices, they:
A device group is accessible through the deviceGroups query in the Service API. Its member sensors are accessible via the devices relationship, which returns deviceGroupDevices entries — each containing a memberDevice reference to the physical device.
Conference Room A (Device Group)
├── Entryway sensor — reports both entries and exits
└── Occupancy sensor — reports presence/absence
A single Entryway sensor tracks movement in both directions, reporting entry and exit counts from one device. A device group can include any number of Entryway sensors and Occupancy sensors — for example, a large meeting room with two doorways would have two Entryway sensors, while an open office zone might combine several Occupancy sensors with Entryway sensors at each entrance. The Occupancy Data Engine aggregates data from all member sensors and produces a single unified occupancy state for the space.
Open Office Zone (Device Group)
├── Entryway sensor (Main door)
├── Entryway sensor (Side door)
├── Occupancy sensor (Area A)
└── Occupancy sensor (Area B)
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#F6FAFA', 'primaryTextColor': '#143633', 'primaryBorderColor': '#143633', 'lineColor': '#143633', 'secondaryColor': '#C7FDE6', 'tertiaryColor': '#73F9C1', 'clusterBkg': '#ffffff', 'clusterBorder': '#143633', 'edgeLabelBackground': '#ffffff'}}}%%
graph TB
subgraph Input["Sensors"]
A[Entryway sensor<br/>entries + exits]
C[Occupancy sensor<br/>presence events]
end
subgraph DG["Device Group"]
D[Virtual Device<br/>aggregation point]
end
subgraph ODE["Occupancy Data Engine"]
E[Event Correlation]
F[Count Algorithm]
G[Reset Logic]
end
subgraph Output["Output - Occupancy Measurements"]
H[occupantsCount]
I[directionalMovement]
J[occupancyStatus]
end
A --> D
C --> D
D --> E
E --> F
F --> G
G --> H
G --> I
G --> JThe Occupancy Data Engine produces three measurement types, all attributed to the device group:
Current number of people in the space.
Schema Type: measurementOccupantsCount
{
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"measuredAt": "2026-02-05T10:30:00.000Z",
"occupantsCount": 5
}
| Field | Type | Description |
|---|---|---|
measuredAt | ISO 8601 timestamp | When count was calculated |
occupantsCount | Integer | Number of occupants (≥ 0) |
Entry and exit tracking.
Schema Type: measurementDirectionalMovement
{
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"measuredAt": "2026-02-05T10:30:00.000Z",
"entries": 3,
"exits": 1
}
| Field | Type | Description |
|---|---|---|
entries | Integer | Number of entries since last reset |
exits | Integer | Number of exits since last reset |
Boolean occupied/vacant status.
Schema Type: measurementOccupancyStatus
{
"deviceId": "550e8400-e29b-41d4-a716-446655440000",
"measuredAt": "2026-02-05T10:30:00.000Z",
"isOccupied": true
}
| Field | Type | Description |
|---|---|---|
isOccupied | Boolean | true if space is occupied, false if vacant |
Occupancy counts can drift over time due to sensor limitations (e.g., two people entering simultaneously being counted as one). The Occupancy Data Engine supports multiple reset strategies to correct this:
| Type | Description | Use Case |
|---|---|---|
| Scheduled | Reset at specific times (cron expression) | Daily midnight reset |
| Manual | API-triggered reset | On-demand correction |
| Threshold | Reset when count drops to ≤ N | Auto-reset when space empties |
| Timeout | Reset after period of inactivity | Reset after closing hours |
Occupancy Data Engine measurements are accessed through the same APIs as any device measurement:
Since device groups use the same UUID structure as physical devices, no special API handling is required — the same queries and subscriptions work for both single devices and device groups.
# Get member devices of a device group
query DeviceGroupDevices($deviceGroupId: uuid) {
deviceGroupDevices(where: { deviceGroupId: { _eq: $deviceGroupId } }) {
deviceGroupId
deviceId
memberDevice {
id
name
lastSeen
identifiers {
tuid
}
deviceType {
make
model
productName
}
}
}
}
# Get latest occupancy data for a device group
query MeasurementLastOccupantsCount($device: uuid) {
measurementLastOccupantsCount(where: { deviceId: { _eq: $device } }) {
measuredAt
occupantsCount
}
}
query MeasurementLastDirectionalMovement($device: uuid) {
measurementLastDirectionalMovement(where: { deviceId: { _eq: $device } }) {
measuredAt
entries
exits
}
}
query MeasurementLastOccupancyStatus($device: uuid) {
measurementLastOccupancyStatus(where: { deviceId: { _eq: $device } }) {
measuredAt
isOccupied
}
}
Note: Pass the device group’s UUID as the
$deviceparameter — device groups use the same UUID structure as physical devices.
Occupancy Data Engine measurements are published on the same topic structure as physical devices:
haltian-iot/events/{integration-id}/{api-key-id}/measurements/occupantsCount/{device-group-id}
haltian-iot/events/{integration-id}/{api-key-id}/measurements/directionalMovement/{device-group-id}
haltian-iot/events/{integration-id}/{api-key-id}/measurements/occupancyStatus/{device-group-id}
Common issues and solutions when working with the Occupancy Data Engine.