DT Developer Docs
REST APIDT StudioStatus Page
  • Getting Started
  • Overview
  • Concepts
    • Devices
    • Events
    • Topics
      • Temperature Measurement Interval
      • Motion Sensor Activity Timer
  • Data Connectors
    • Introduction to Data Connectors
    • Creating a Data Connector
    • Configuring a Data Connector
    • Receiving Events
    • Best Practices
    • Example Integrations
      • Heroku
      • Google Cloud Functions
      • AWS Lambda
      • Azure HTTP Triggers
      • IBM Cloud Actions
    • Development Guides
      • Local Development with ngrok
  • REST API
  • Introduction to REST API
  • Explore Our Endpoints
    • with cURL
    • with Python API
    • with Postman
  • Authentication
    • OAuth2
    • Basic Auth
  • Error Codes
  • Emulator API
  • Examples
    • Pagination
    • Streaming Events
    • Touch to Identify
    • Refreshing Access Token
  • Reference
  • Status Page
  • Service Accounts
    • Introduction to Service Accounts
    • Creating a Service Account
    • Managing Access Rights
    • Permissions
    • Organizational Structures
  • Other
    • Application Notes
      • Generating a Room Temperature Heatmap
      • Modeling Fridge Content Temperatures
      • Outlier Detection on Multiple Temperature Sensors
      • Simple Temperature Forecasting for Substation Transformers
      • Sensor Data Insight with Power BI and Azure
      • Third-Party Sensor Data in DT Cloud
    • Frequently Asked Question
Powered by GitBook
On this page
  • Introduction
  • DT Studio Project Configuration
  • Project Authentication
  • Adding sensors to the project
  • Sensor Placement
  • Example Code
  • Source Access
  • Environment Setup
  • Defining the Rooms Layout
  • Usage
  • Implementation Details
  • Layout Construction
  • Euclidean Distance Map Population
  • Door State
  • Animated Result
  • Future Improvements
  • References

Was this helpful?

  1. Other
  2. Application Notes

Generating a Room Temperature Heatmap

Last updated 1 year ago

Was this helpful?

Introduction

Continuously logging temperature data in an office- or industry environment can provide a range of benefits. Be it optimizing energy usage by minimizing heat loss or tracking individual equipment temperatures, this information could be useful to gain better insight. Due to their small size, robustness, and long battery life, can easily be employed to collect temperature data streams for almost any environment.

In this application note, a method of combining several DT temperature- and door-proximity sensors to generate a temperature heatmap is proposed. By using room layout features, the heatmap gradients are calculated by inverse distance weighting (IDW) [1]. The distances are found by euclidean path-finding [2], where traversal can not occur through walls, only around corners and through doors. The result is a heatmap where rooms are separated by the walls between them but allows for temperature gradients to expand through doors and open spaces like the wave of a droplet.

DT Studio Project Configuration

The implementation is built around using the developer API to interact with a single DT Studio project containing all sensors for which the heatmap is generated. If not already done, a project needs to be created and configured to enable API functionalities.

Project Authentication

Adding sensors to the project

Any number and type of sensors can be included in the project. Only those included in the room layout file, which will be discussed later, are fetched by the API. The use of labels is not necessary for this implementation. The option to move sensors from one project to another can be found when viewing a particular sensor in DT Studio.

Sensor Placement

For the heatmap to produce an output consistent with the ambient temperature in the room independent of external sources, some thought should be put into sensor placement. During testing, it was found that a relatively large temperature difference could be seen depending on the height at which the sensor was placed, which was expected as warm air rises. The following recommendations should, therefore, be considered when placing the sensors.

  • Avoid placing sensors in direct sunlight.

  • Avoid placing sensors near air-conditioning, radiators, or other heat sources. This includes people, which are comparable to a 100-watt heat-source.

  • Placement height should be somewhat consistent between sensors. Approximately chest-level height was found to provide the most representative results.

Example Code

Source Access

Environment Setup

All code has been written in and tested for Python 3. While not required, it is recommended to use a virtual environment to avoid conflicts. Additional dependencies can be installed using pip and the provided requirements text file.

pip3 install -r requirements.txt 

Using the details found during the project authentication section, edit the following lines in sensor_stream.py to authenticate the API with your DT Studio project.

USERNAME   = "SERVICE_ACCOUNT_KEY"    # this is the key
PASSWORD   = "SERVICE_ACCOUNT_SECRET" # this is the secret
PROJECT_ID = "PROJECT_ID"             # this is the project id

Defining the Rooms Layout

In order to provide the layout of your environment to the example code project, the JSON file format is used and should be provided as an input argument. If no layout is provided, the project will default to a sample layout located in ./config/sample_layout.json. Please see "Layout Construction" under the Implementation section for details about setting up your environment layout. It is recommended that the sample layout structure is understood before designing your own.

Usage

If the example code is correctly authenticated to the DT Studio project as described above, running the script sensor_stream.py will generate and show the sample layout.

python3 sensor_stream.py 

If a custom layout file is provided, the script will start streaming data from all initialized sensors in the layout for which a heatmap is continuously updated as new data arrive. For more advanced usage, such as setting a historical time frame to pull data from, provide any of the following extra arguments and values.

usage: sensor_stream.py [-h] [--layout] [--starttime] [--endtime] [--timestep]
                        [--plot] [--debug] [--read]

Heatmap generation on Stream and Event History.

optional arguments:
  -h, --help    show this help message and exit
  --layout      Path to json layout file.
  --starttime   Event history UTC starttime [YYYY-MM-DDTHH:MM:SSZ].
  --endtime     Event history UTC endtime [YYYY-MM-DDTHH:MM:SSZ].
  --timestep    Heatmap update period.
  --plot        Plot the estimated desk occupancy.
  --debug       Disables multithreading for debug visualization.
  --read        Import cached distance maps. 

The arguments --starttime and --endtime should be of the format YYYY-MM-DDThh:mm:ssZ, where YYYY is the year, MM the month, and DD the day. Likewise, hh, mm, and ss are the hour, minutes, and seconds respectively. Notice the separator, T, and Z, which must be included. It should also be noted that the time is given in UTC. Local timezone corrections should, therefore, be made accordingly.

With a default value of 1 hour, the --timestep argument represents the number of seconds between each time the heatmap is updated when looking at historical data. During a stream, the heatmap will update every new event regardless.

Implementation Details

With the aim of generating a heatmap that continuously updates as new temperature data arrives in the stream, the implementation is written such that any processing-intensive task is done during initialization. By generating a Euclidean distance map for each sensor covering the entire room layout, the inverse distance weighted (IDW) temperature value at any point can quickly be calculated using the maps as lookup tables. Therefore, to update the heatmap, only a single scan of the grid is required. This results in a total delay of only a few seconds from sensor sampling heartbeat to heatmap update. The maps are written to temporary files and can be loaded on successive runs.

Layout Construction

To illustrate how custom room layouts are generated, the provided sample layout will be used as an example. It consists of 2 rooms, a door connecting them, and a few sensors with some initial temperature. The constructed sample layout is shown in figure 4, where sensors are represented by 'x', and objects of interest (OOF) by a circle. An OOF is simply a temperature value that is displayed without affecting the heatmap itself.

The following lines show the JSON format used to generate the room layout shown in figure 4. Each room in the room list is defined by a name, a list of corners, and the sensors that exist in the room. Observe how the corners are listed in a clockwise manner. Following this template, the layout can be expanded to an undefined number of rooms and sensors.

{
    "rooms": [
        {
            "name": "room0",
            "corners": [
                {"x": 0.0, "y": 0.0},
                {"x": 0.0, "y": 5.0},
                {"x":10.0, "y": 5.0},
                {"x":10.0, "y": 0.0}
            ],
            "sensors": [
                {"x": 0.2, "y": 2.5, "sensor_id": "xxxxx", "t0": 23},
                {"x": 5.0, "y": 4.8, "sensor_id": "xxxxx", "t0": 22},
                {"x": 8.0, "y": 0.2, "sensor_id": "xxxxx", "t0": 24}
            ]
        },
        {
            "name": "room1",
            "corners": [
                {"x":10.0, "y": 0.0},
                {"x":10.0, "y":8.0},
                {"x":15.0, "y":8.0},
                {"x":15.0, "y": 0.0}
            ],
            "sensors": [
                {"x":12.5, "y": 7.5, "sensor_id": "xxxxx", "t0": 22}
            ]
        }
    ],
    "doors": [
        {
            "p1": {"x":10.0, "y": 3.0}, 
            "p2": {"x":10.0, "y": 4.0}, 
            "room1": "room0", 
            "room2": "room1", 
            "sensor_id": "xxxxx",
            "closed": false
        }
    ],
    "oofs": [
        {"x": 17.5, "y": 8.3, "sensor_id": "xxxxx"}
    ]
}

Considerations when designing your layout:

  • Sensors must not be placed exactly on walls. This will create a line-of-sight issue when calculating the euclidean distance map. Sensors should therefore be slightly offset towards the room center. Any positive non-zero value will suffice.

  • The corners of a room must be provided in chronological order, as two adjacent corners define a single wall.

  • Two adjacent rooms should only share a single door. While a single room can have doors leading into multiple rooms, only a single door should be used between any one pair of rooms. This is to avoid generating an exponential amount of distance maps.

Euclidean Distance Map Population

In order to produce a heatmap in which each sensor's contribution is weighted by their distance, the distance from each sensor to every point in the grid needs to be known. This would be rather simple if walls were not to be considered. However, in order to create a more believable heatmap, the distance from a sensor to any point in any room can only be calculated as a path through doors, and not walls, as is the case with airflow.

For every sensor, a distance map representing the shortest Euclidean distance to every point in the grid is created where the euclidean distance is given by

def find_shortest_paths(current, path, path_length):
    # append path with currently active point
    path.append(current)

    # stop if we've been here before on a shorter path
    if path_length > length of current.shortest_path:
        return path

    # copy path to current point to save it
    current.shortest_path = path

    # find candidate corners for path expansion
    candidates = find(doors and convex corners in line of sight of current)

    # recursively iterate candidates
    for c in candidates:
        # calculate distance from current to candidate
        new_distance = euclidean_distance(current.x, current.y, c.x, c.y)

        # next recursive step
        path = find_shortest_paths(c, path, length of path + new_distance)
            
        # remove current step from path
        path.pop()
            
    return path

Essentially, for each recursive step starting at the sensor position, all convex corners and doors in line of sight are defined as candidates for the next step. Here, the line of sight is defined as a point to which a direct line can be stretched without intersecting a wall. Concave corners are ignored as they will never represent the shortest path. As they are visited, each point subsequently saves the shortest path to it, as shown in figure 6, used in the next step to populate the euclidean distance map.

For each step in every recursive path found previously, the euclidean distance to all points in the grid within its line of sight is calculated. If the new distance plus the total length traveled by the path is shorter than the existing value, it is updated. The result is a grid in which each point represents the shortest possible distance from the initial sensor location, shown in figure 6, where an increase in brightness represents a longer distance traveled. Notice how the distance gradient does not increase through walls but instead enters a room only through a doorway as expected.

For every new temperature value that is received in the stream, the heatmap is recalculated. This is very quick as the distance from every sensor to every point is known from the previous steps. Therefore, the heatmap grid is scanned once, where for each grid point, the value is set to the inverse distance weighted (IDW) temperature of all sensors. The IDW is given by

Door State

Due to calculating the euclidean distance map earlier, information regarding which doors the path pass through is known. Therefore, to determine whether or not a sensor should be involved in the IDW estimate for a given grid point during heatmap update, only a simple boolean check for the doors in each path has to be done with a negligible execution time impact. Figure 7 shows how the temperature gradient changes when a door between a cold and warm room opens (green) and closes (red).

Animated Result

The following animation represents the same day, as shown in figure 1, but includes the temperature variations for the whole day. A second in the animation represents 2 hours of real-time. Notice how doors open and close, and how the temperature spikes in the morning, before being brought back down to normal by the air-conditioning system kicking in.

Future Improvements

  • Currently, walls can only be defined as a straight line between two points. Spline interpolation could be implemented to solve this should it be necessary.

  • Doorways are currently implemented so that the temperature gradient only passes through the exact middle. This approximation reduces the recursive depth when finding the shortest path while having a negligible impact on the resulting heatmap. However, if a doorway is several meters wide, this would be noticeable and should be fixed.

References

For authenticating the developer API against your DT Studio project, a Service Account key-id, secret, and email must be known, later to be used in the example code. If you're unfamiliar with the concept you can read our .

An example code repository is provided in this application note. It illustrates one way of building a heatmap from multiple sensor streams and could serve as a precursor for further development and implementation. It uses our to interface with the DT Studio project and constructs a virtual representation of the environment for which details are provided by the user in a room layout JSON input argument.

The example code source is publicly hosted on the official Disruptive Technologies GitHub account under the MIT license. It can be found by following .

Sensors can be initialized with a temperature t0t0t0. This value is only used if the script does not connect to the API, such as when using the sample layout. Then, the heatmap will be generated using said initial values instead. These can, however, be omitted and are meant for debugging purposes. The same goes for initializing doors with a closed boolean.

d=∑i=1n(qi−pi)2,d = \sqrt{\sum_{i=1}^{n}(q_i-p_i)^2},d=i=1∑n​(qi​−pi​)2​,

where qiq_iqi​ and pip_ipi​ are the iii-th coordinate values of two points in an nnn-dimensional space. The following code snippet represents the recursive function implemented to do a breadth-first search for the shortest path from a sensor to any door- and convex corner [3].

d=∑i=1nzidip/∑i=1n1dip,d = \sum_{i=1}^{n}{\frac{z_i}{d_i^p}}/\sum_{i=1}^{n}{\frac{1}{d_i^p}},d=i=1∑n​dip​zi​​/i=1∑n​dip​1​,

where nnn is the number of sensors, ziz_izi​ the value of sensor iii, and dipd_i^pdip​ the distance to sensor iii to the power of ppp. It is a simple but flexible spatial interpolation method that produces a smooth gradient between sensors. Other methods, such as kriging statistical modeling or radial basis functions, could also be experimented with to produce the heatmap.

To produce a more natural heatmap, the temperature gradient in a room is only affected by sensors in other rooms - should the door between them be open. By using in the door frame, the heatmap can be automatically updated should doors open and/or close.

Introduction to Service Accounts
REST API
this link
Proximity Sensors
https://en.wikipedia.org/wiki/Inverse_distance_weighting
https://en.wikipedia.org/wiki/Pathfinding
https://en.wikipedia.org/wiki/Breadth-first_search
Disruptive Technologies (DT) Wireless Temperature Sensors
Figure 1: Temperature heatmap on a particularly hot day. Here, temperature sensors are represented by an 'x'. Doors can be in an open (green) or closed (red) state. An animation showing the temperature change during the entire day can be found at the end of this application note.
Figure 2: DT Studio sensor overview menu where, among other things, sensors can be moved to different projects.
Figure 3: Disruptive Technologies Temperature Sensors mounted on a wall.
Figure 4: Sample layout provided with the example code. Here, an 'x' represents a sensor, a green filled line an open door, and a circle an OOF.
Figure 5: Shortest paths (red) from a sensor to all convex corners and doors in layout found through recursively locating all corners within line of sight at any step.
Figure 6: Euclidean distance grid population process where each point in the grid is filled with the shortest distance from the sensor, traveling through a path of convex corners and doors as necessary. Here, as distance increases, the color becomes lighter.
Figure 7: Difference in temperature gradient depending on whether or not the door between the two rooms are open (green) or closed (red).
Figure 8: Animated heatmap for an entire day where the temperatures were particularly hot, showing how the air-conditioning system kicks in to suppress the heat.