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

PropertyDescription
TypeVirtual device (no physical hardware)
IDUUID like physical devices
MeasurementsAggregated from member devices
MQTT TopicSame structure as physical devices
API AccessQuery via GraphQL like physical devices

Occupancy Data Engine (ODE)

The ODE processes raw sensor data and generates occupancy metrics:

Key Measurements

Measurement TypeDescriptionValue Type
occupants_countCurrent number of occupantsInteger
directional_movementEntry/exit direction trackingObject with in/out counts
occupancy_stateRoom occupied statusBoolean or enum
dwell_timeTime spent in zoneDuration 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]
  1. Entry Sensor - Detects people entering the room
  2. Exit Sensor - Detects people leaving the room
  3. Device Group - Aggregates both sensors
  4. ODE - Calculates net occupancy from entry/exit events
  5. Output - Publishes occupants_count and directional_movement

Reset Mechanisms

The ODE supports various reset mechanisms for occupancy counts:

Reset TypeDescription
ScheduledReset count at specific times (e.g., midnight)
ManualAPI-triggered reset
ThresholdReset when count drops below threshold
TimeoutReset after period of no activity

Best Practices

  1. Use device groups for room-level occupancy rather than individual sensors
  2. Subscribe to specific topics rather than wildcards for better performance
  3. Handle count resets in your application logic
  4. Consider time zones when scheduling resets
  5. Monitor sensor health to ensure accurate counts

Next Steps