Most devices output data via points - a point represents a particular output at a particular time, which could be as simple as "Yes, I'm on" or "No, I'm off" for a device like a light. For something more complex like a thermostat, you'll have a much wider range of possible points. Ambient temperature in the room, what temperature is set, is the thermostat set to heat or set to cool - all possible points.
To get to point data, you can either start with a thing - we already know the id for a VAV (Variable Air Volume HVAC system), so we'll pull the list of points using it for our filter:
RequestLive ResponseCopy1 2 3 4 5 6 7 8 9 10 11
{ things(filter: {id: {eq: "THGJjbQG52zKvVYif43R7SCSu"}}) { id name points { id name type } } }
or you can find points in a place, like starting with a building:
RequestLive ResponseCopy1 2 3 4 5 6 7 8 9 10 11
{ buildings(filter: {id: {eq: "BLDG5o26DguWKu5T9nRvSYn5Em"}}) { name id points { id name exactType } } }
You can also filter for a specific type of point:
RequestLive ResponseCopy1 2 3 4 5 6 7 8 9 10 11
{ buildings(filter: {id: {eq: "BLDG5o26DguWKu5T9nRvSYn5Em"}}) { name id points(filter: {type: {eq: "Supply_Air_Temperature_Sensor"}}) { id name exactType } } }
A point by itself can be useful - knowing if a security camera was on at a particular moment, or if a door was locked - but in a lot of cases you're going to want to know point data across a a window of time. This is called time series, and there are couple of ways you can work with them.
In this example, we're retrieving the series data from a Supply_Air_Temperature_Sensor for the VAV we looked up earlier, restricted to a specific window of time. The response will contain float values that represent what the temperature was set to every 15 minutes within that specified hour. Note that 15 minute interval is not defined by the API, rather the device itself will determine how often to store the value.
Request ResponseCopy1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
{ things(filter: {id: {eq: "THGJjbQG52zKvVYif43R7SCSu"}}) { id name points(filter: {id: {eq: "PNTXK13hHH6RUK4sp2csBqSyv"}}) { id name type series(startTime: "2024-07-13T17:00:00.000", endTime: "2024-07-13T20:00:00.000") { timestamp value { float64Value } } } } }
If you're polling for the most recent value on a regular basis, you don't really need a date range - just the latest result. There's a filter option for that as well:
RequestLive ResponseCopy1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
{ things(filter: {id: {eq: "THGJjbQG52zKvVYif43R7SCSu"}}) { id name points(filter: {id: {eq: "PNTXK13hHH6RUK4sp2csBqSyv"}}) { id name type series(latest: true) { timestamp value { float64Value } } } } }
The second option is to use aggregation, which takes point data and performs functions on it, like finding the average, sum, min, max or count. You'll need to define the period as well, which accepts DAY, HOUR and MINUTE as values. In this example, we'll look for the average hourly temperature recorded by an air temperature sensor:
Request ResponseCopy1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
{ things(filter: {id: {eq: "THGJjbQG52zKvVYif43R7SCSu"}}) { id name points(filter: {id: {eq: "PNTXK13hHH6RUK4sp2csBqSyv"}}) { id name type aggregation( startTime: "2024-07-13T17:00:00.000" endTime: "2024-07-14T17:00:00.000" period: HOUR ) { timestamp avg } } } }
You can use this kind of data to determine when the AC might be struggling to keep up - maybe there are more people in the space than normal, maybe the time of day is a factor, or maybe the AC itself needs servicing; all useful data when you're trying to keep people comfortable, or trying to save on energy costs.
For some points, the result data is a numeric value that doesn't have enough context on its own to make sense - like a 0 or a 1 for an Occupancy Sensor, vs something like 75 for a Temperature Setpoint. For these types of points, we include a stateTexts field. This field provides the missing context; here's an example:
Request ResponseCopy1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
{ things(filter: {id: {eq: "THGD5NLUsAndK3EAcAuJwABcd"}}) { name exactType points { id name exactType stateTexts series(latest: true) { timestamp value { float64Value } } } } }
In the above example, the stateTexts field includes the values "Unoccupied, Occupied, Inactive, and Unknown" which correspond to the float values "0, 1, 2, 3" in the series data. So in the above example, the float value of "1" means translates into "Occupied".
When you start to retrieve point data, you'll get a numerical value returned. Identifying the unit of measure for that data just looking at it could be difficult. Even with a temperature sensor, is the data in Fahrenheit or Celsius? To help with that, there's a unit field available for each thing that provides those details:
RequestLive ResponseCopy1 2 3 4 5 6 7 8 9 10 11 12
{ things(filter: {id: {eq: "THGJjbQG52zKvVYif43R7SCSu"}}) { name points { name unit { name description } } } }
Note that the ID format, including the number of characters, may vary from those displayed in the samples here. Once a point or a thing has an ID assigned, that ID will never change, however we do not recommend placing any kind of validation on format or character count.
If you're interested in viewing all of the potential units, you can make a call to retrieve the full list as well:
RequestLive ResponseCopy1 2 3 4 5 6
{ units { name description } }
Each point might have a different type of data - integer, boolean and similar - depending on what it's measuring. In order to determine the correct field to use (float64value, int64value, boolValue, etc) you'll often want to retrieve the dataType. This is a field under point, like unit:
RequestLive ResponseCopy1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
{ things(filter: {id: {eq: "THGPcErc7PG4fn737VcnsJkrZ"}}) { id name exactType points { id name datatype series(latest: true) { value { float64Value } timestamp } } } }
Note: when dataType returns a double value, you'll want to use the float64value field when pulling the series data - this will be adjusted to align in a future release.