Working with Occupancy Data
How to access device groups and occupancy engine data from Haltian IoT
This guide explains how to work with Haltian IoT’s occupancy data, including device groups and the Occupancy Data Engine (ODE).
Overview
Haltian IoT provides occupancy intelligence through:
- Device Groups - Virtual devices that aggregate data from multiple physical sensors
- Occupancy Data Engine (ODE) - Backend service that processes sensor data into occupancy metrics
Device Groups
Device groups act as virtual devices that combine measurements from multiple physical sensors. They’re commonly used for:
- Room occupancy - Combining multiple presence sensors in a conference room
- Zone monitoring - Aggregating sensors across a floor or building zone
- Entryway counting - Calculating net occupancy from entry/exit sensors
Device Group Characteristics
| Property | Description |
|---|---|
| Type | Virtual device (no physical hardware) |
| ID | UUID like physical devices |
| Measurements | Aggregated from member devices |
| MQTT Topic | Same structure as physical devices |
| API Access | Query via GraphQL like physical devices |
Occupancy Data Engine (ODE)
The ODE processes raw sensor data and generates occupancy metrics:
Key Measurements
| Measurement Type | Description | Value Type |
|---|---|---|
occupants_count | Current number of occupants | Integer |
directional_movement | Entry/exit direction tracking | Object with in/out counts |
occupancy_state | Room occupied status | Boolean or enum |
dwell_time | Time spent in zone | Duration in seconds |
Accessing Occupancy Data via MQTT
Device groups publish to MQTT topics just like physical devices:
haltian-iot/events/{integration}/{apikey}/measurements/occupants_count/{device-group-id}
Python Example: Subscribe to Occupancy Data
#!/usr/bin/env python3
"""
Subscribe to occupancy data from Haltian IoT device groups.
"""
import ssl
import json
import paho.mqtt.client as mqtt
# Configuration
MQTT_HOST = "mqtt.eu.haltian.io"
MQTT_PORT = 8883
API_KEY_ID = "your-api-key-id"
API_KEY_TOKEN = "your-api-key-token"
INTEGRATION_ID = "your-integration-id"
# Subscribe to occupancy-related measurements
OCCUPANCY_TOPICS = [
f"haltian-iot/events/{INTEGRATION_ID}/{API_KEY_ID}/measurements/occupants_count/#",
f"haltian-iot/events/{INTEGRATION_ID}/{API_KEY_ID}/measurements/directional_movement/#",
f"haltian-iot/events/{INTEGRATION_ID}/{API_KEY_ID}/measurements/occupancy_state/#",
]
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("Connected to MQTT broker")
for topic in OCCUPANCY_TOPICS:
client.subscribe(topic)
print(f"Subscribed to: {topic}")
def on_message(client, userdata, msg):
topic_parts = msg.topic.split("/")
measurement_type = topic_parts[5]
device_id = topic_parts[6]
payload = json.loads(msg.payload.decode("utf-8"))
print(f"\n[{measurement_type}] Device: {device_id}")
print(f" Timestamp: {payload['measured_at']}")
print(f" Value: {json.dumps(payload['value'], indent=4)}")
def main():
client = mqtt.Client(protocol=mqtt.MQTTv311)
client.username_pw_set(API_KEY_ID, API_KEY_TOKEN)
client.tls_set(cert_reqs=ssl.CERT_NONE)
client.tls_insecure_set(True)
client.on_connect = on_connect
client.on_message = on_message
client.connect(MQTT_HOST, MQTT_PORT, keepalive=60)
try:
client.loop_forever()
except KeyboardInterrupt:
client.disconnect()
if __name__ == "__main__":
main()
Querying Device Groups via GraphQL
Get Device Group Details
query GetDeviceGroup($id: ID!) {
device(id: $id) {
id
name
deviceModel {
name
type # Will be "VIRTUAL" for device groups
}
# Member devices (for device groups)
childDevices {
edges {
node {
id
tuid
name
}
}
}
}
}
Get Occupancy Measurements
query GetOccupancyMeasurements($deviceId: ID!, $from: DateTime!, $to: DateTime!) {
device(id: $deviceId) {
measurements(
filter: {
types: ["occupants_count", "directional_movement"]
from: $from
to: $to
}
) {
edges {
node {
type
measuredAt
value
}
}
}
}
}
Occupancy Data Payload Examples
occupants_count
{
"measured_at": "2025-01-28T14:30:00.000Z",
"value": 5
}
directional_movement
{
"measured_at": "2025-01-28T14:30:00.000Z",
"value": {
"in": 3,
"out": 1,
"net": 2
}
}
occupancy_state
{
"measured_at": "2025-01-28T14:30:00.000Z",
"value": {
"occupied": true,
"confidence": 0.95
}
}
Entryway Scenario
A common use case is tracking room occupancy with entry/exit sensors:
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#73f9c1','primaryTextColor':'#143633','primaryBorderColor':'#143633','lineColor':'#143633'}}}%%
graph LR
A[Entry Sensor] --> C[Device Group]
B[Exit Sensor] --> C
C --> D[ODE Processing]
D --> E[occupants_count]
D --> F[directional_movement]- Entry Sensor - Detects people entering the room
- Exit Sensor - Detects people leaving the room
- Device Group - Aggregates both sensors
- ODE - Calculates net occupancy from entry/exit events
- Output - Publishes
occupants_countanddirectional_movement
Reset Mechanisms
The ODE supports various reset mechanisms for occupancy counts:
| Reset Type | Description |
|---|---|
| Scheduled | Reset count at specific times (e.g., midnight) |
| Manual | API-triggered reset |
| Threshold | Reset when count drops below threshold |
| Timeout | Reset after period of no activity |
Best Practices
- Use device groups for room-level occupancy rather than individual sensors
- Subscribe to specific topics rather than wildcards for better performance
- Handle count resets in your application logic
- Consider time zones when scheduling resets
- Monitor sensor health to ensure accurate counts
Next Steps
- Stream API Reference - Complete MQTT documentation
- Measurement Types - All measurement types
- Position Data Best Practices - Working with location data