Skip to main content

Smart buildings — BMS & energy

The problem. A commercial building's management system runs hundreds of points — zone temperatures, CO₂, air-handler status, sub-metered energy. Facilities teams want two things at once: a live comfort view that flags a stuffy or overheating zone before the complaints come in, and an energy picture that shows where the kilowatt- hours actually go so they can cut waste.

This scenario pairs live comfort monitoring with energy aggregation on the same building model.

Set up demo data

New workspace? Run this once (Python) to create a zone's CO₂ series, create the subscription before we listen (with a stuffy spell), and an energy series for the roll-up. Safe to re-run.

import datahub_sdk, numpy as np, pandas as pd

client = datahub_sdk.DataHubClient.from_env()

client.timeseries.create([datahub_sdk.TimeSeries(external_id="zone_l8_co2_ppm", name="L8 open-plan CO2", unit="ppm", value_type="float")])
client.subscriptions.create([datahub_sdk.Subscription(
external_id="tower_a_comfort", name="Tower A comfort", timeseries=["zone_l8_co2_ppm"])])
idx = pd.date_range(end=pd.Timestamp.now(tz="UTC"), periods=60, freq="1min")
co2 = np.full(60, 600.0); co2[-10:] = 1180.0 # out of comfort band
client.timeseries.insert_from_lists(timestamps=idx, values=co2, ts="zone_l8_co2_ppm")

client.timeseries.create([datahub_sdk.TimeSeries(external_id="tower_a_l8_energy_kwh", name="Tower A L8 energy", unit="kwh", value_type="float")])
idx2 = pd.date_range(end=pd.Timestamp.now(tz="UTC"), periods=30, freq="1d")
client.timeseries.insert_from_lists(timestamps=idx2, values=np.random.uniform(200, 400, 30), ts="tower_a_l8_energy_kwh")

1. Watch comfort live

Subscribe to the zone comfort series and react as readings land — raise a comfort_alert when a zone drifts out of band. See Consume live data.

import ai.intellistream.datahub.sdk.subscriptions.SubscriptionMessage;

try (var stream = client.subscriptions().listen(List.of("tower_a_comfort"))
.stream((SubscriptionMessage msg) -> { // auto-acks after each message
if (outsideComfort(msg.payload())) { // temp or CO2 out of band
EventModel alert = new EventModel();
alert.setExternalId("comfort_alert_l8_" + System.currentTimeMillis());
alert.setType("comfort_alert");
alert.setStatus("open");
alert.setMetadata(Map.of("zone", "zone_l8_open_plan", "co2_ppm", "1180"));
alert.setEventTime(ZonedDateTime.now());
client.events().create(List.of(alert));
}
})) {
awaitShutdown(); // your app lifecycle; closing the stream ends delivery
}

2. See where the energy goes

Sub-meters report per floor and system. Roll consumption up to daily totals per meter to rank the biggest users and spot overnight waste. See Query & aggregate.

var filter = new RetrieveFilter();
filter.setExternalId("tower_a_l8_energy_kwh");
filter.setStart(ZonedDateTime.now().minusDays(30));
filter.setEnd(ZonedDateTime.now());
filter.setAggregates(List.of("sum"));
filter.setGranularity("1d");

var request = new DataRetriever<RetrieveFilter>();
request.setItems(List.of(filter));

client.timeseries().retrieve(request).getItems().get(0).getDatapoints()
.forEach(p -> chartDailyKwh(p.getTimestamp(), p.getValue()));
Correlate comfort and equipment

When several zones go uncomfortable together, walk the graph from each to the air handler they share — the same alarm correlation trick finds the one unit behind the complaints.

See the result

The stuffy zone trips the loop before the complaints come in:

comfort_alert_l8_… → open (CO₂ 1180 ppm, above the comfort band)

See also