"The building had four thousand sensors, and not one of them agreed on what to call a room."
A Bewildered AI Agent
Why this section matters
A commercial building is a slow, coupled thermodynamic machine, and its heating, ventilation, and air-conditioning (HVAC) plant is the largest controllable energy load inside it. Every air handler, chilled-water valve, variable-air-volume box, and zone thermostat reports to a building automation system that emits a torrent of telemetry: temperatures, humidities, airflows, valve positions, fan speeds, and setpoints, sampled on schedules you did not pick and named in dialects no two vendors share. Before any model can forecast energy, spot a stuck damper, or infer whether a floor is occupied, you have to understand what this telemetry is, how it is structured, and where its physics hides. This section is about turning a raw building-automation feed into a signal you can actually learn from, and about the semantic-metadata problem that stands between the two.
This section assumes the sampling, timestamping, and synchronization vocabulary of Chapter 3 and the leakage-safe data-engineering discipline of Chapter 5. Where Section 39.1 treated the whole-building electricity meter as one aggregate signal to disaggregate, here we go inside the building to the mechanical system that spends most of that electricity, and we take the HVAC plant's own sensors as our raw material.
The building automation system and its point tree
Almost no building sensor reaches you directly. It reports to a building automation system (BAS), also called a building management system, a hierarchy of controllers that read field devices, run local control loops, and expose the results as named points. The physical equipment forms a tree: a central plant (chillers, boilers, cooling towers) feeds hot and chilled water to air handling units (AHUs), each AHU conditions a stream of air and distributes it through variable-air-volume (VAV) terminal boxes, and each VAV box serves one thermal zone with its own space-temperature sensor and setpoint. A single mid-size office can carry tens of thousands of points across this tree.
The dominant protocol on this network is BACnet, with Modbus common at the equipment level. BACnet models each measurement as an object with a type: analog input (a sensor reading such as supply-air temperature), analog value (a computed or setpoint value), binary input (a status such as fan proof), and binary output (a command such as a damper enable). Two acquisition modes coexist. Periodic polling reads a point on a fixed interval; change-of-value (COV) subscription pushes an update only when a point moves past a reporting increment, much like the report-by-exception deadband of an industrial historian. Long-term storage lives in trend logs, which may be configured per point at different intervals, so two tags on the same AHU can arrive at fifteen-second and fifteen-minute cadences in the same export.
The name is metadata, not measurement
A BAS point arrives as a triple: an opaque identifier, a value, and a timestamp. The identifier (something like AHU3.SAT or VAV-2-14:ZN-T) is a string a controls contractor typed, not a machine-checked label. Nothing guarantees that SAT means supply-air temperature, that its units are Celsius, or that it belongs to the AHU you think. The value stream is trustworthy telemetry; the name attached to it is folklore. Bridging that gap, mapping thousands of cryptic point names onto a consistent physical meaning, is usually the largest single task in a building-analytics project, and it must happen before any model can generalize across buildings.
The metadata problem and semantic schemas
Because point names are unstandardized, the same model trained on Building A collapses on Building B whose contractor abbreviated differently. The field's answer is a semantic metadata schema that describes what each point is and how the equipment connects. Two are widely used: Project Haystack, a tagging convention that attaches marker tags (zone, air, temp, sensor) to each point, and Brick, a formal ontology (an RDF graph) that names entities and the relationships between them (a VAV feeds a zone; an AHU hasPoint a supply-air-temperature sensor). With a Brick model in hand, a query like "every zone-temperature sensor downstream of AHU-3" becomes a graph traversal rather than a fragile string match, and a model can be specified against roles instead of raw names. This graph structure is exactly the input that the graph neural networks of Chapter 54 consume, and it is the backbone of the building digital twins discussed in Chapter 55.
Two vendors, one office tower, one fault detector
An analytics team deployed a stuck-valve fault detector across a 40-floor office tower whose lower floors ran a Siemens BAS and whose upper floors, from a later retrofit, ran a Johnson Controls system. The detector worked perfectly on the floors it was tuned on and produced nonsense above the retrofit line. The reason was purely semantic: the two systems named reheat-valve command, chilled-water valve, and discharge-air temperature with different abbreviations, and one reported valve position as 0 to 100 while the other reported 0 to 1. Rather than retune the model, the team wrote a Brick model for both halves of the building, mapped every point onto the same set of roles and units, and ran the single unchanged detector against the normalized graph. The false alarms vanished, and the same detector later dropped onto a third building by writing only its metadata map.
The physics inside the telemetry
HVAC telemetry is not abstract; it obeys a thermal energy balance you can write down. A single zone behaves, to first order, like a resistor-capacitor circuit: a thermal capacitance \(C\) (the mass of air, furniture, and structure) charged and discharged through a thermal resistance \(R\) to the outdoor air, plus heat injected by the HVAC system and by occupants and equipment.
$$C \, \frac{dT_z}{dt} \;=\; \frac{T_{oa} - T_z}{R} \;+\; \dot{Q}_{hvac} \;+\; \dot{Q}_{int}$$Here \(T_z\) is zone temperature, \(T_{oa}\) is outdoor-air temperature, \(\dot{Q}_{hvac}\) is the (signed) HVAC heating or cooling rate, and \(\dot{Q}_{int}\) is internal gain from people and plug loads. This grey-box RC model matters because you can fit \(R\) and \(C\) from ordinary telemetry, giving you a compact, physically interpretable description of each zone: a large \(RC\) time constant means a sluggish, well-insulated space, and a sudden change in fitted parameters is itself a fault signature. The code below fits the discrete-time version by linear least squares.
import numpy as np
# Telemetry columns (uniformly resampled to dt seconds): zone temp, outdoor
# temp, and HVAC thermal power (positive = heating). Toy synthetic example.
dt = 300.0 # 5-minute trend interval, seconds
rng = np.random.default_rng(0)
n = 2000
Toa = 8 + 6*np.sin(np.arange(n)*2*np.pi/288) # daily outdoor swing, deg C
Qh = 800*(rng.random(n) < 0.4) # reheat cycling on/off, watts
Tz = np.empty(n); Tz[0] = 21.0
R_true, C_true = 0.02, 4.0e5 # deg C/W and J/deg C
for k in range(n-1): # simulate the RC zone
dT = ((Toa[k]-Tz[k])/R_true + Qh[k]) / C_true
Tz[k+1] = Tz[k] + dt*dT + 0.02*rng.standard_normal()
# Fit a, b in Tz[k+1]-Tz[k] = a*(Toa[k]-Tz[k]) + b*Qh[k] by least squares
y = np.diff(Tz)
X = np.column_stack([(Toa[:-1]-Tz[:-1]), Qh[:-1]])
a, b = np.linalg.lstsq(X, y, rcond=None)[0]
C_hat = dt / b # since b = dt / C
R_hat = dt / (a * C_hat) # since a = dt / (R C)
print(f"C_hat={C_hat:.3e} J/K R_hat={R_hat:.4f} K/W tau={R_hat*C_hat/3600:.2f} h")
The regression recovers \(R\), \(C\), and the time constant \(\tau = RC\) directly from three telemetry channels. Because the model is linear in its two lumped parameters, ordinary least squares suffices, and the fitted \(\tau\) tells you how many minutes of history a downstream forecaster actually needs. When you later want the zone's state tracked online rather than its parameters fit offline, this same RC structure becomes the process model for a Kalman filter from Chapter 9.
Let a BACnet client handle COV and discovery
Writing a raw BACnet stack to discover devices, read object lists, subscribe to change-of-value, and reassemble segmented responses is hundreds of lines of protocol handling. The BAC0 Python library (built on bacpypes) reduces a live COV subscription to a handful of lines:
import BAC0
bacnet = BAC0.connect() # discovers devices on the network
ahu = BAC0.device('10.0.3.12', 3056, bacnet) # bind one controller
ahu['SAT'].subscribe_cov(callback=on_update) # push updates past the increment
BAC0, replacing manual device discovery, object-list reads, and segmentation handling with three calls.The library handles who-is/i-am discovery, object enumeration, unit metadata, COV lifetime renewal, and segmentation. You supply an address and a point name; it removes the entire transport layer, roughly a 200-to-3 line reduction for a monitoring client.
From telemetry to inference: occupancy and coupling
Once points are normalized and their physics understood, the telemetry becomes a rich substrate for perception. Carbon-dioxide concentration in a zone rises with the number of people breathing in it, so a CO2 trend, combined with the airflow the VAV box reports, is a soft occupancy sensor without a camera, a privacy-friendly signal we revisit under smart spaces. Supply and return-air temperature differences across an AHU reveal how hard the coil is working. Simultaneous swings across many zones sharing one AHU expose the coupling in the equipment tree, which is why the building graph, not the isolated point, is the right unit of analysis. All of this depends on trustworthy alignment: because trend logs arrive at mixed intervals, you must resample every point onto a common causal grid with last-value-at-or-before semantics before any cross-point feature is valid, the same leakage-safe rule stressed in Chapter 5.
Exercise: fit and stress the zone model
Using the RC fit above: (1) resample a zone-temperature, outdoor-temperature, and reheat-power trend triple onto a uniform 5-minute grid with zero-order hold and confirm the fit is stable to the interval you choose. (2) Split the record into daytime and nighttime segments and fit each separately; explain why the effective \(R\) can differ when windows or blinds change. (3) Introduce a step change in the true \(R\) halfway through a synthetic record (simulating a stuck-open outdoor-air damper) and show that a sliding-window fit of \(\tau\) detects the fault as a parameter shift. Write two sentences on why a parameter change is a more physical fault signal than a raw temperature threshold.
Self-check
- Why can a model trained on one building's BAS points fail on an identical building next door, and what artifact (not model) fixes it?
- What is the difference between BACnet polling and change-of-value subscription, and how does COV resemble a historian deadband?
- In the RC zone model, what does the time constant \(\tau = RC\) tell you about how much history a downstream forecaster needs, and what would a sudden change in fitted \(R\) suggest physically?
What's Next
In Section 39.3, we step outside the mechanical room to the sensors that watch the environment itself: air, water, and soil quality monitoring. Those instruments share the building world's low sampling rates and metadata headaches, but add calibration drift and cross-sensitivity as first-class concerns, and they push the same alignment and semantic-labeling discipline out into networks of outdoor, distributed nodes.