Query Language
RDF Primer
Brick models are graphs consisting of nodes and directed edges. These graphs are usually represented using the RDF data model, which represents a directed, labeled graph as a set of triples. A triple consists of a subject (node), a predicate (directed edge) and an object (node). Each triple represents 2 nodes (the subject and object) connected with a directed edge (the predicate). We will interact with the graph by way of these triples.
Triples are usually stored in a self-contained text file in the Turtle format (file ending .ttl
).
The nodes in the Brick graph represent the sensors, actuators, equipment, subsystems, location and logical groupings in the building. Edges represent the relationships between these. There are only a few types of relationships that Brick captures. Here’s a brief recap of some common ones:
- Subcomponents:
bf:hasPart
/bf:isPartOf
. Used for mechanical composition, relating HVAC zones with physical spaces - Identifying flows:
bf:feeds
/bf:isFedBy
. Used for flows of water, electricity, air, etc. - Sensing + actuation:
bf:hasPoint
/bf:isPointOf
. Used for associating sensor, setpoints, statuses, commands, meters, etc with related equipment, locations, etc - Location:
bf:hasLocation
/bf:isLocationOf
. Identifies the physical location of something - Instantiation:
rdf:type
Recall that in the RDF data model, all nodes are either Literals (strings) or URIs. All URIs have a namespace: for example in the URI https://brickschema.org/schema/1.0.1/Brick#VAV
, the namespace is https://brickschema.org/schema/1.0.1/Brick#
and the “node” is VAV
. For convenience, we will abbreviate the URI so we can write brick:VAV
instead of https://brickschema.org/schema/1.0.1/Brick#VAV
.
Here are the abbreviations we will use:
Namespace | Usage | Abbreviation |
---|---|---|
https://brickschema.org/schema/1.0.1/Brick# |
Brick classes | brick: |
https://brickschema.org/schema/1.0.1/BrickFrame# |
Brick relationships | bf: |
http://www.w3.org/1999/02/22-rdf-syntax-ns# |
class instantiation | rdf: |
http://www.w3.org/2000/01/rdf-schema# |
subclassing | rdfs: |
There’s a lot in those namespaces, but don’t worry! It will feel managable soon enough, and HodDB includes tools to make it easier to find the URIs you need.
Example Building
Our example building contains the following components:
- one floor (
floor_1
) - one room on the floor (
room_1
) - one HVAC zone, containing the one room (
hvaczone_1
) - one zone temperature sensor in the room (
ztemp_1
) - one VAV supplying the HVAC zone (
vav_1
) - one AHU supplying the VAV (
ahu_1
)
The Brick model of these relationships will be the triples representing the following graph
This graph would be defined by this set of triples:
mybuilding:ahu_1 rdf:type brick:AHU
mybuilding:room_1 rdf:type brick:Room
mybuilding:ztemp_1 rdf:type brick:Zone_Temperature_Sensor
mybuilding:floor_1 rdf:type brick:Floor
mybuilding:hvaczone_1 rdf:type brick:HVAC_Zone
mybuilding:vav_1 rdf:type brick:VAV
mybuilding:ahu_1 bf:feeds mybuilding:vav_1
mybuilding:room_1 bf:isPartOf mybuilding:floor_1
mybuilding:room_1 bf:isPartOf mybuilding:hvaczone_1
mybuilding:ztemp_1 bf:isPointOf mybuilding:vav_1
mybuilding:vav_1 bf:feeds mybuilding:hvaczone_1
Note that we are using a new namespace to “store” the names of the entities that are actually in our building.
SPARQL
SPARQL is a query language for RDF, but common Brick usage only uses a subset of its features.
One way of thinking about SPARQL is treating a query like pattern matching over the graph (i.e. finding graph isomorphisms). A SPARQL query consists of a WHERE
clause containing the patterns of triples we want to match, and a SELECT
clause identifying which parts of those triples we want to return.
Variables
SPARQL variables are indicated by a ?
prefix (e.g. ?vav
). Variables take the place of subjects, predicates and objects in the terms of our query.
Consider a Brick model that lists a bunch of VAVs
mybuilding:vav_1 rdf:type brick:VAV
mybuilding:vav_2 rdf:type brick:VAV
mybuilding:vav_3 rdf:type brick:VAV
mybuilding:vav_4 rdf:type brick:VAV
mybuilding:vav_5 rdf:type brick:VAV
Finding all VAVs in the building (e.g. mybuilding:vav_1
) is equivalent to finding all nodes that have an rdf:type
edge to the brick:VAV
node.
Put another way, we want to find the subject of all triples in the Graph that have rdf:type
as the predicate and brick:VAV
as the object.
Expressed as a triple, this is:
?vav rdf:type brick:VAV
And the returned results would be mybuilding:vav_1
, mybuilding:vav_2
, etc.
Variables can be used more than once in a query, and a term can contain more than one variable.
Variables in the SELECT
clause determine the “rows” that get returned as the result of a query.
Basic Query Construction
To start, let’s list all of the VAVs in the building. To do this, we want to find all nodes that have a rdf:type
edge (which indicates instantiation) to the brick:VAV
node which represents the Brick VAV class. In the corresponding triple, we will put rdf:type
in the “predicate” slot and brick:VAV
in the “object” slot. In the subject slot, we will place a variable that will be populated when the query executes:
1 | ?vav rdf:type brick:VAV
|
This corresponds to finding instances of the following subgraph:
Listing Neighbors
A natural question when interacting with a new Brick model is what kind of information is associated with a particular VAV. We can express this as a SPARQL query by seeing which triples exist that have a VAV as the subject.
1 | ex:VAV_RM-1100D ?pred ?obj
|
This corresponds to finding instances of the following subgraph:
Listing Types
The above queries work well if we know the exact classes instantiated in our Brick model. If we don’t have this information and want to discover it, we can leverage Brick’s class hierarchy.
A common use case is finding which kinds of temperature sensors exist in the Brick model. The basic form of this uses both the rdf:type
relationship (instantiation) and the rdfs:subClassOf
relationship. By default, mentioning a predicate only traverses a single edge in the graph. We use SPARQL’s *
operator to match “0 or more” instances of an edge to transparently query more than one level of the hierarchy. SPARQL also provides a +
operator to match “1 or more” edges.
This matches instances that are immediate subclasses of brick:Temperature_Sensor
1 | ?sensor rdf:type/rdfs:subClassOf brick:Temperature_Sensor
|
This matches instances of brick:Temperature_Sensor
as well as instances of any subclass of brick:Temperature_Sensor
1 | ?sensor rdf:type/rdfs:subClassOf* brick:Temperature_Sensor
|
This query corresponds to the following subgraph in the case of Zone Air Temperature Sensors
We can now run this query on our real Brick model to see what flavors of Temperature Sensor exist
If we are not interested in the intermediate class name, we can combine the expression of the rdf:type
and rdfs:subClassOf*
predicates using the /
SPARQL operator:
Sample Queries
One of the main benefits of Brick is its ability to represent multiple building subsystems and query across them. We will explore these capabilities of Brick through the implementation of two analytics applications: - Stuck Damper Detection - Simultaneous Heating and Cooling Detection
The point of these explorations is not to implement cutting-edge fault detection/diagnosis algorithms, but rather to illustrate how Brick can make it easier to find the relevant data streams and make an implementation portable across buildings.
Stuck Damper Detection
One method of detecting stuck dampers is to look at the difference between the supply air flow sensor and setpoint for a VAV.
- If the measured air flow is within some delta of the air flow setpoint, then the damper is fine
- Else, it might be broken
First, we write the Brick query(ies) to extract the relevant data streams: namely, the supply air flow sensor and supply air flow setpoint for each VAV in our building. In addition to the names of those points, we also extract from the Brick model some “timeseries identifier” that will point us to where we can obtain the actual data. Here, we use the brick:hasUuid
relationship which points to an RDF literal corresponding to the name of a CSV data file.
Lets progressively build up the query. First, lets begin by identifying all the VAVs in the building:
1 | ?vav rdf:type brick:VAV .
|
We will then identify the supply air flow setpoints and sensors using the rdf:type
relationship, and because they measure an aspect of a VAV, we know they will be related to a VAV using the isPointOf
relationship.
1 2 3 4 | ?setpoint rdf:type brick:Supply_Air_Flow_Setpoint .
?sensor rdf:type brick:Supply_Air_Flow_Sensor .
?setpoint bf:isPointOf brick:VAV .
?sensor bf:isPointOf brick:VAV .
|
Lastly, we pull out the UUIDs for the timeseries
1 2 | ?setpoint brick:hasUuid ?setpoint_uuid .
?sensor brick:hasUuid ?sensor_uuid .
|
All together:
1 2 3 4 5 6 7 8 | SELECT * WHERE {
?setpoint rdf:type brick:Supply_Air_Flow_Setpoint .
?sensor rdf:type brick:Supply_Air_Flow_Sensor .
?setpoint bf:isPointOf brick:VAV .
?sensor bf:isPointOf brick:VAV .
?setpoint brick:hasUuid ?setpoint_uuid .
?sensor brick:hasUuid ?sensor_uuid .
};
|
Simultaneous Heating and Cooling Detection
Properly identifying simultaneous heating and cooling in a building involves traversing the HVAC and spatial hierarchies of the building. We must first find rooms that are contained within more than one HVAC zone and therefore are conditioned by more than one VAV.
From this, we have a few possible avenues. First, we can compare the reheat coil percentages for the VAVs feeding a single zone. Additionally, we can look at the cooling coil percentage for the AHU upstream of each VAV combined with the supply air flow as further evidence.
To begin, we need to find rooms that are in more than one HVAC zone. We follow the same procedure for building up the query: identify the instances of the relevant classes, and then filter these by the required relationships.
The instances we are interested in for this first queries are rooms and HVAC zones:
1 2 | ?room rdf:type brick:Room .
?zone rdf:type brick:HVAC_Zone .
|
We then relate rooms and HVAC zones using the proper isPartOf
relationship:
1 | ?room bf:isPartOf ?zone .
|
For each of the rooms in multiple HVAC zones, we get the reheat coil valve command for the VAVs feeding those zones. Following the rules of composition for the Brick model, we know there is:
- an
isPartOf
relationship from the room to the zone - a
feeds
relationship from the VAV to the zone - a
isPointOf
relationship from the reheat valve command to the VAV
We combine these into the following SPARQL triples:
1 2 3 4 5 6 7 8 | ?zone rdf:type brick:HVAC_Zone .
?vav rdf:type brick:VAV .
?rhc rdf:type brick:Reheat_Valve_Command .
<our room> bf:isPartOf ?zone .
?vav bf:feeds ?zone .
?rhc bf:isPointOf ?vav .
?rhc brick:hasUuid ?rhc_uuid .
|
The last triple gets us the UUID for the timeseries data representing the actual reheat valve command values.
We add to our query the Supply Air Flow sensors so we can tell how much hot/cold air is being blown into a room from the different VAVs
1 2 3 | ?saf rdf:type brick:Supply_Air_Flow_Sensor .
?saf bf:isPointOf ?vav .
?saf brick:hasUuid ?saf_uuid .
|
All together
1 2 3 4 5 6 7 8 9 10 11 12 13 | SELECT * WHERE {
?zone rdf:type brick:HVAC_Zone .
?vav rdf:type brick:VAV .
?rhc rdf:type brick:Reheat_Valve_Command .
<our room> bf:isPartOf ?zone .
?vav bf:feeds ?zone .
?rhc bf:isPointOf ?vav .
?rhc brick:hasUuid ?rhc_uuid .
?saf rdf:type brick:Supply_Air_Flow_Sensor .
?saf bf:isPointOf ?vav .
?saf brick:hasUuid ?saf_uuid .
};
|
Querying Multiple Buildings
As of version 0.5.1, Hod supports loading and querying multiple graphs in parallel. The most helpful use for this is loading multiple buildings into a single HodDB database and querying across them..
Load in buildings and name them using the Buildings
key of the hodconfig.yaml
file (documentation link).
By default, all of the graphs in HodDB are queried. This can be changed (or made explicit) through the use of a FROM
clause, which specifies the graphs to query by name in a space-delimited list.
For example, consider a databse in which we’ve loaded 2 graphs: bldg123
and bldgABC
.
SELECT clause |
Graphs Queried |
---|---|
SELECT * WHERE { ... } |
bldg123 , bldgABC |
SELECT * FROM bldg123 WHERE { ... } |
bldg123 |
SELECT * FROM bldg123 bldgABC WHERE { ... } |
bldg123 , bldgABC |
Data Integration
Brick models contain references to devices and other sources of data.
There are two pieces of information typically associated with each data source: the UUID (timeseries identifier) and a URI (for BOSSWAVE publish/subscribe).
UUIDs and URIs are stored as RDF Literals (strings) in the Brick graph, and are found using the https://brickschema.org/schema/1.0.1/BrickFrame#uuid
/bf:uuid
and https://brickschema.org/schema/1.0.1/BrickFrame#uri
/bf:uri
relationships.
For example, to find all thermostats we can subscribe to, use this query:
1 2 3 4 | SELECT ?tstat ?uri WHERE {
?tstat rdf:type brick:Thermostat .
?tstat bf:uri ?uri
};
|