Desk Occupancy Monitoring Using Temperature Sensors

Tracking desk occupancy can be useful for generating an overview of utilization efficiency. For instance, if only 70% of available desks are ever in use in an office environment, cutting back can both reduce costs and introduce space-saving optimizations. Also, for environments where free seating can be chosen by customers, desk occupancy sensing can be used to let people know which desks are available at any given time.

Through body heat radiation, Wireless Temperature Sensors from Disruptive Technologies can be used to detect the presence of people by installing them underneath the desk. This application note presents a proposed algorithm for doing so on continuously streamed data. The following sections explain step-by-step how you can set this up for your own DT Studio project and implement the suggested method using the Python programming language.

Figure 1: Four week downwards trend in occupancy during the summer holidays.

Sensor Placement

For desk occupancy, temperature sensors should be placed underneath the desk, close to where a person is sitting or standing. A good placement is perpendicular to the occupant's keyboard, close to the edge, as shown in figure 2. Only one sensor per desk is necessary. For best performance, the Wireless Temperature Sensor EN12830/330s, sampling at 5.5-minute intervals is recommended. The Wireless Temperature Sensor sampling at 15-minute intervals will also work for this example, but with a lower resolution in time.

To increase the accuracy of the detection algorithm, additional reference temperature sensors should be placed around the office for ambient temperature measurements. Reference temperature sensors should be placed on a wall away from windows or sun, preferably at standing height. It should also be placed away from any heating or cooling sources like air-condition vents, coffee-machines, or wall-mounted electric ovens. It is also assumed that if a reference is used, all sensors in the project are in the same room.

Figure 2: Suggested placement of temperature sensor below a desk.

DT Studio Project Configuration

The implementation is built around using the DT Developer API to interact with a single DT Studio project containing all temperature sensors used in desk occupancy tracking. If not already done, a project needs to be created and configured to enable the API functionality.

Project Authentication

For authenticating the REST API against the DT Studio project, a Service Account key-id, secret, and email must be known, later to be used in the example code. If this concept is unfamiliar to you, read our guide on Creating a Service Account.

Adding temperature sensors to the project

By default, all the temperature sensors in the project are assumed mounted at a desk. The number of sensors used does not have to be configured beforehand and is scaled automatically. The option to move a sensor between projects can be found when selecting a sensor in a DT Studio project, shown in figure 3.

If one wishes to follow the recommendation of using one or more reference temperature sensors, these have to be provided a label in DT Studio in order to identify its type in the code. When selecting a sensor in the project, add a label with the key reference and no value, as shown in figure 3. No label is needed for sensors placed below the desks.

Figure 3: Detailed overview of sensors in the DT Studio project.

Example Code

An example code repository is provided in this application note. It illustrates one way of building a desk occupancy system and is meant to serve as a precursor for further development and implementation. It uses the REST API to interact with the DT Studio project.

Source Access

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

Environment Setup

All code has been written in and tested for Python 3, and while not required, it is recommended to use a virtual environment to avoid conflicts. Required 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 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


If the example code is correctly authenticated to the DT Studio project as described above, running the script will start streaming data from each desk sensor in the project for which occupancy is continuously estimated as new data arrive.


For more advanced usage, such as visualizing the estimates, one or several flags can be provided upon execution.

usage: [-h] [--starttime] [--endtime] [--plot] [--debug]
Desk Occupancy Estimation on Stream and Event History.
optional arguments:
-h, --help show this help message and exit
--starttime Event history UTC starttime [YYYY-MM-DDThh:mm:ssZ].
--endtime Event history UTC endtime [YYYY-MM-DDThh:mm:ssZ].
--plot Plot the estimated desk occupancy.
--debug Visualise algorithm operation.

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.

By providing the --plot argument, a visualization, as shown in figure 4, will be generated. If historical data is included, an interactive plot will be produced after estimating occupancy for the historical data. By closing this plot, the stream will start, and a non-interactive plot will update for each sample that arrives.

Similar to --plot, the --debug argument will also generate a visualization. It shows an overview of thresholds and other values calculated by the algorithm. It is meant to be used mainly for debugging purposes. It does not work for streaming data by default.

Proposed Algorithm

With the aim of quickly and continuously detecting changes in desk occupancy on both streaming and historical data, a simple algorithm based on temperature rate of change (ROC) and dynamic thresholds has been proposed and implemented. The finalized approach was found to strike a good balance between complexity and performance and can detect occupancy with a resolution of fewer than 10 minutes.

Represented as a binary vector in time, the algorithm output is for each desk a binary vector for which a value of 1 represents occupancy. An aggregate percentage of usage for all desks are also included, as shown in figure 4. All intermediate calculations are, however, stored within their related objects and can be experimented with if so desired.

The implementation has been structured such that typical tuning parameters for the algorithm are located in the file ./config/ As no one configuration can work for all data, users are encouraged to experiment with different combinations better suited for their own data.

Figure 4: Three weeks of data for 15 sensors, visualized using the --plot argument.

Flow Overview

Due to the nature of the desk occupancy case, the implementation has been written to scale for an indefinite number of sensors. As it is assumed that one project represents one room of undefined size, all reference data is combined into an average value if available. Also, each desk sensor is assumed independent and is processed as such due to the lack of spatial information.

The architecture is structured such that all new events, either streamed or from history, are passed to a director-class as they arrive. The director, which contains one desk object per known desk sensor in the project, passes on the data to the correct sensor for processing. If the event is labeled as "reference", a single reference class combines the latest information to produce a reference temperature which is used by each desk object when needed.

A desk object, representing one desk and sensor, can be in either of two states. If boolean false, the desk is estimated to be non-occupied, and the algorithm will watch the temperature rate of change (ROC) for signs that someone is arriving. On the contrary, if set to boolean true, the desk is assumed occupied until a fall in temperature larger than a continuously calculated threshold is passed.

Figure 5: Algorithm flow chart, categorized by three main classes.

Removing The Ambient

The temperature data processed by the algorithm will be represented by

x[n]=xdesk[n]xref[n],x[n] = x_{\text{desk}}[n] - x_{\text{ref}}[n],

where xdeskx_{\text{desk}} is the temperature for an individual desk and xrefx_{\text{ref}} the room ambient temperature. If recommendations about including one or several references are ignored however, xrefx_{\text{ref}} simply takes on a value of zero, having the algorithm process the raw temperature data instead.

During testing, subtracting the reference did, under most circumstances, not affect the accuracy much. However, on particularly hot days, the office temperature would oscillate quite heavily in relation to the amount of direct sunlight. Under these circumstances, the inclusion of a reference was found to reduce false alarms significantly.

As the Disruptive temperature sensors provide a steady stream of temperature data at intervals of either 5.5 minutes or 15 minutes, the latest reference temperature is used in the subtraction. If more than one reference sensor exists, an average value of all references is used.

Figure 6: Superposition of raw desk temperature and ambient reference.

Detecting New Occupancy

In order to detect when someone begins using a desk, a dynamic threshold, γr\gamma_r, is combined with the positive temperature rate of change (ROC), rr. While rr could simply be calculated by taking the difference between the two last temperature values, the nature of a Disruptive Temperature Sensor is such that for each time it is touched, an additional event is sent. Therefore, to avoid large non-uniformities in sampling, the normalized ROC is given by

r[n]=60xˉ[n]xˉ[n1]Δt,r[n] = 60 \cdot \frac{\bar{x}[n]-\bar{x}[n-1]}{\Delta t},

where xˉ\bar{x} is a 10 minute average of xx and Δt\Delta t the change in time.

If the latest ROC value is equal to or larger than the latest dynamic threshold value, γr\gamma_r, the desk is assumed occupied. The threshold itself is given by

γr[n]=max{γmax,γr[n1]r[n]α+β}\gamma_r[n] = \text{max}\{\gamma_{\text{max}}, \gamma_r[n-1]-r[n]\cdot \alpha+\beta\}

where the two modifiers αα and ββ are found through experimentation and control the rate at which the threshold increases and decreases respectively, and γmax\gamma_{\text{max}} the maximum value γr\gamma_r can take. Essentially, when rr is large, γr\gamma_r will become smaller, over time increasing the chance that occupancy is determined. If, however, only small noisy values of rr are found, the threshold will increase towards a maximum value and stabilize as shown in figure 7.

While it would be entirely possible to use a hard threshold instead of γγ, it was quickly found that such a method provided numerous false alarms. If a large threshold were used in order to avoid false alarms, it would miss actual occupancy events of smaller magnitude. By implementing the aforementioned dynamic threshold, the threshold is pulled lower by consistent activity and larger values in r[n]r[n], while passing over quick bursts of noise or other artifacts.

Figure 7: Dynamic threshold dropping as ROC activity increases.

Detecting When Someone Leaves

Unlike detecting when someone arrives at a desk, it is not sufficient to only locate the moment at which rr falls back below γrγ_r. When a desk is occupied for some time, the temperature will reach saturation, causing rr to drop regardless of the occupancy state. Additionally, due to the desk itself being heated during occupancy, the temperature decrease is slow enough that during a workday, the temperature does not come back to its initial ambient temperature. This behavior is in line with Newton's Law of Cooling and must be accounted for if a resolution more fine-grained than daily occupancy is to be achieved.

The proposed solution is, however, rather simple. When occupancy is detected, each new sample following detection is included in a running average, μμ, given by

μ=1Kk=0K1x[nk],\mu = \frac{1}{K}\sum_{k=0}^{K-1}x[n-k],

where KK is the number of samples since the occupancy session started. As time goes on and the temperature saturates, μμ will converge towards the saturation value. This behavior is consistent regardless of ROC or final saturation level. Therefore, when someone leaves their desk, μμ acts as a downslope threshold for which the state is changed when passed. The figure below shows the converging nature of the running average towards saturation temperature.

Figure 8: Downslope threshold converging towards temperature during occupancy.

By combining the information for when someone arrives at and leaves a desk, a binary output for a period can be produced. The desk is assumed occupied if the state is 1, and non-occupied if 0, as shown in figure 9.

Figure 9: Binary representation of estimated occupancy for a desk.

Known Issues

  • During testing, it was found that direct sunlight on desks placed at windows did occasionally trigger the occupancy algorithm. This is due to the desk itself being heated quite rapidly, thus also heating the sensor. Mounting the sensor using an additional layer of thick foam-tape somewhat mitigates the problem, but does not entirely solve it.

  • Due to the environment in which the algorithm has been developed, a climate-controlled office during an otherwise cool nordic summer, the implementation might not work as well in environments of higher ambient temperature. Naturally, as the difference between the radiated body heat and room temperature decreases, detecting changes caused by occupancy becomes harder as saturation is reached quicker. This has, however, not been tested for.