Empathic Building server
Warning
The information on this page may be changed without prior notice.
Overview
The Empathic Building system has the following major components:
The ReST based back-end (https://eu-api.empathicbuilding.com), running on Azure
Sensor gateways, pushing data to the back-end
Pusher.com, a third party publish-subscribe service that handles distribution of live updates
Web clients, providing the end-user visual interface to the system by loading data from the ReST back-end and subscribing to live updates from Pusher.com
Authentication
There are two types of user accounts on the Empathic Building back-end:
User accounts
Gateway accounts
User accounts are used with the EB web based client application while gateway accounts are used to send in measurement data. Due to the nature of web applications, user accounts are subject to stricter security than gateway accounts, but they also have access to most API endpoints while gateway accounts can only send measurements and manipulate their own sensors.
Obtaining an access token: user accounts
Access to the API as a regular user is done using time-limited access tokens.
Access tokens are obtained by sending a POST
request to the /v1/login
endpoint.
The request must be of type application/x-www-form-urlencoded
and should have two fields:
email
password
If the login is successful, you will receive a JSON response with the following relevant fields:
access_token
: a token you can use to access the APItoken_type
: the type of the token (to be added to theAuthorization
header)expires_in
: the number of seconds after which the access token is no longer validrefresh_token
: a token you can use to obtain a new access token
Access tokens have a short validity period, so you need to check if the access token is still valid
when submitting an API request. If not, you need to use the refresh token to obtain a new access
token. This is done by sending a POST
request to the /v1/token
endpoint, with just one
field (refresh_token
) which contains the refresh token you obtained from /v1/login
. You
will receive a new refresh and access token pair, as above. The previous refresh token is
invalidated and must not be used again. If an attempt to use a refresh token twice is detected, the
entire chain of refresh and access tokens are then invalidated and you need to start over.
If the refresh token is not refreshed within its validity period, you will have to start over with
/v1/login
.
Note
Avoid using the /v1/login
endpoint when possible as password validation is puts a
much bigger strain on the system than a token refresh.
Obtaining an access token: gateway accounts
A gateway account will have a static, non-expiring access token assigned to it.
If you need to use the account to manage sensors (not usually necessary for a gateway account), you need to obtain the following pieces of information concerning the account
an organization ID
a location ID
Using access token to authenticate against the API
Most API endpoints require authentication using the authorization
HTTP header.
For example, if the access token you received was fb9ba505321b4fe0a2c9f018c6e3f80a
, you need to
add the header authorization: Bearer fb9ba505321b4fe0a2c9f018c6e3f80a
to your HTTP requests.
Browsing the generated API documentation
Generated API documentation is available at https://eu-api.empathicbuilding.com/doc. Note that invoking the endpoints from the API browser will most likely not work. Instead, a third party tool like Postman is recommended.
Subscribing to live data
To get real time notifications on new, changed or deleted items in the database, you will need a client library for the Pusher.com service. You will then need to configure the client settings appropriately:
key:
33d6c4f799c274f7e0bc
(production) or64737761a47148863544
(staging)cluster:
eu
You will not need the app_id
or secret
configuration values. However, you will need to set
https://eu-api.empathicbuilding.com/v1/pusher/auth
as the authentication endpoint (authEndpoint
on
the Javascript client) in order to subscribe to the EB channels (which are all private channels).
If your selected Pusher client does not have the ability to use an authentication endpoint, you can
directly call the authorization endpoint yourself (API docs here), but the details on how to
make this work is beyond the scope of this document.
When you have the Pusher client set up, you can subscribe to:
Organization channels (
private-organization-<ORGANIZATION ID>
)Location channels (
private-location-<LOCATION ID>
)The notifications channel (
notifications
)
Organization channels receive events with the following topic names:
organization-modified (for the organization matching the channel)
organization-deleted (for the organization matching the channel)
location-created
location-modified
location-deleted
user-created
user-modified
user-deleted
Location channels, on the other hand, receive events with these topics:
asset-created
asset-modified
asset-deleted
gateway-created
gateway-modified
gateway-deleted
sensor-created
sensor-modified
sensor-deleted
The notifications channel contains the following topics:
notification-created
notification-modified
notification-deleted
Events are gzip compressed, JSON encoded arrays so in order to decode them, you need to follow these steps:
Decode the base64 encoded data into binary
Decompress the binary data using a gzip library
Decode the decompressed (UTF-8 encoded) text using JSON
Each event contains an array of objects:
A
-created
event contains all non-confidential attributes of the newly created objectA
-modified
event contains the ID of the target object and the changes made to its non-confidential attributesA
-deleted
event contains only the ID of the target object
Sending measurement data
Gateways can send measurement data to the service by posting a JSON encoded array of measurement objects to the add measurements endpoint. Each object must have certain common fields (specified below), plus any extra fields related to the specific measurement type.
When a batch of measurements is received by the server, it is first validated and then sent to a queue for processing. From there, the batch will be picked up by a measurement data processor which will process the measurements using rules specific to the measurement type. If there are no errors, the processed measurements will be appended to the historical data in the database and the sensors’ last communication and last measurement timestamps will be updated along with the last measurement data field. This change will generate a live data event through which the web clients will be informed that a sensor has updated measurement data.
The data processors use the combination of the location_id
, vendor
, vendor_id
and
type
fields to find matching sensors to update. If no match is found with this combination, a
new sensor will be created. Thus, if a sensor is physically moved to a different location, a new
sensor instance will be automatically created. The exception to this rule is location data, where
location tags are found across all locations within the same organization. This allows users to
move between different locations without having to change their associated location tag.
Note
The maximum HTTP request size is 2 MB. Anything larger will result in a 413 error.
Note
Duplicate timestamps are not allowed. Any measurements whose timestamps match previous data will be silently dropped.
Tip
If no new data has been received but a gateway wishes to update the last communication timestamps of sensors, it can send empty sensor status updates for those sensors (with only the mandatory fields filled).
Measurement formats by type
All measurements have certain common fields plus type specific fields.
Timestamps are always represented as milliseconds since the UNIX epoch (1970-01-01 00:00:00 UTC). Percentages are always represented as floating point numbers ranging from 0.0 to 1.0.
The common fields to all measurement types are:
Field |
Data type |
Unit |
Description |
---|---|---|---|
type |
string |
Type of measurement (see below for the full list) |
|
time |
integer |
ms |
Timestamp of measurement |
valid_to |
integer |
ms |
(Optional) Timestamp after which the measurement is considered “expired” (default: 86400000 which equals 24 hours) |
vendor_id |
string |
Vendor specific sensor ID |
|
name |
string |
(Optional) Human-readable name of the sensor |
Temperature
Measurement type: temperature
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
float |
°C |
Temperature |
Humidity
Measurement type: humidity
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
float |
% |
Air humidity (from 0.0 to 1.0) |
CO₂ (Carbon Dioxide)
Measurement type: co2
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
float |
ppm |
Amount of CO₂ particles in the air |
Air pressure
Measurement type: airpressure
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
float |
hPa |
Air pressure |
Occupancy
Measurement type: occupancy
Field |
Data type |
Unit |
Description |
---|---|---|---|
used |
integer |
Number (0 or more) of people (or other things) occupying a space |
|
total |
integer |
Total number of slots |
Noise
Measurement type: noise
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
float |
dB |
Relative loudness of sounds in air |
Location
Measurement type: location
Field |
Data type |
Unit |
Description |
---|---|---|---|
x |
float |
m |
Horizontal offset from the lower left corner of the floor plan |
y |
float |
m |
Vertical offset from the lower left corner of the floor plan |
z |
float |
m |
Altitude from the floor |
lon |
float |
° |
Longitude |
lat |
float |
° |
Latitude |
accuracy |
float |
m |
Maximum possible distance between the estimated location and the actual location |
floor_id |
string |
ID of the related floor asset |
|
group |
string |
One of |
Note
Either (x, y) or (lon, lat) are required. The floor_id
value is also required unless
the coordinates point to a location outside of any building. If only (lon, lat) are
provided, the target building asset must have the north_vector
property set to a
valid value in order for the (x, y) values to be filled in.
Reservation
Measurement type: reservation
Field |
Data type |
Unit |
Description |
---|---|---|---|
events |
array |
Array containing the ongoing (if any) and future reservations |
Each event in the array has the following structure:
Field |
Data type |
Unit |
Description |
---|---|---|---|
start |
integer |
ms |
Start time of the current event |
end |
integer |
ms |
End time of the current event |
subject |
string |
Name (usually in the form of “Lastname Firstname”) of the person who made the current reservation (optional) |
|
user_id |
string |
EB user ID to whom this event belongs (optional) |
|
xo_id |
string |
External Object ID to whom this event belongs (optional) |
An event may optionally contain NoShow integration object data:
Field |
Data type |
Unit |
Description |
---|---|---|---|
confirmed |
boolean |
Whether an event is confirmed to be a no show |
|
reason |
string |
Explanation why an event is or isn’t cancelled |
Satisfaction
Measurement type: satisfaction
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
float |
% |
Satisfaction percentage (from 0.0 to 1.0) |
raw_value |
integer |
Raw survey value from the vendor |
Illuminance
Measurement type: illuminance
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
float |
lux |
Illuminance |
Distance
Measurement type: distance
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
integer |
mm |
Distance |
Stress levels
Measurement type: stress
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
float |
% |
Stress level (from 0.0 to 1.0) |
Total Volatile Organic Compounds
Measurement type: tvoc
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
integer |
ppb |
Total VOC |
Generic events
Measurement type: generic_event
Field |
Data type |
Unit |
Description |
---|---|---|---|
value |
string |
Event name (e.g. |
|
metadata |
mapping |
(optional) JSON-compatible mapping of keys to
values (e.g. |
Counters
Measurement type: counter
Field |
Data type |
Unit |
Description |
---|---|---|---|
increment |
integer |
Value to add to the counter (negative values are also OK;
ignored if |
|
value |
integer |
Total accumulated value (only send when you want to reset the counter to a specific value) |
Sensor status updates
Measurement type: sensorstatus
Field |
Data type |
Unit |
Description |
---|---|---|---|
battery_voltage |
float |
V |
Battery voltage, if the sensor is battery powered |
battery_level |
float |
% |
Battery charge level (from 0.0 to 1.0) |
battery_model |
string |
Battery model name, used to calculate the charge
level from the voltage if it is missing (allowed
values: |
|
link_quality |
float |
% |
Link quality (from 0.0 to 1.0) |
mesh_neighbors |
array[object] |
For mesh devices, the list of neighbors seen by the sensor, along with link qualities to them. |
|
mesh_role |
string |
For mesh devices, the current role of the sensor in
the network ( |
|
mesh_flags |
array[string] |
For mesh devices, the currently configured flags on the sensor device |
|
mesh_sink_address |
string |
For mesh devices, vendor ID of the sink device |
|
mesh_next_hop |
string |
For mesh devices, the vendor ID of the next node on the way to the sink |
All fields are optional here.
For the mesh neighbors array, each object has the following structure:
Field |
Data type |
Unit |
Description |
---|---|---|---|
vendor_id |
string |
Vendor ID of a neighboring device |
|
normalized_rssi |
float |
% |
Link quality with the neighbor (from 0.0 to 1.0) |
Sensor status updates do not cause new measurements to be inserted, but only updates to the
last_status
field of a sensor object.
Practical examples (Python)
The following examples have the following requirements:
To subscribe to live sensor data (via modifications of the last_measurement
attribute) at
location ID 1:
import gzip
import json
from base64 import b64decode
from pprint import pprint
from time import sleep
import requests
from pysher import Pusher
CHANNEL = 'private-location-1'
AUTH_URI = 'https://eu-api.empathicbuilding.com/v1/pusher/auth'
AUTH_HEADERS = {'authorization': 'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'} # replace this
def print_json(data):
uncompressed = gzip.decompress(b64decode(data))
json_data = json.loads(uncompressed)
pprint(json_data)
def connect_handler(data):
# Get the authorization token from the EB server
data = json.loads(data)
form = {'channel_name': CHANNEL, 'socket_id': data['socket_id']}
response = requests.post(AUTH_URI, data=form, headers=AUTH_HEADERS)
response.raise_for_status()
token = response.json()['auth']
# Subscribe to the channel
chan = pusher.subscribe(CHANNEL, auth=token)
# Add an event handler for "sensor-modified" events
chan.bind('sensor-modified', print_json)
pusher = Pusher('33d6c4f799c274f7e0bc', cluster='eu', log_level=None)
pusher.connection.bind('pusher:connection_established', connect_handler)
pusher.connect()
while True:
sleep(1)
To send a new measurement (23.756°C) on behalf of a sensor with vendor ID “ABC-123”:
import requests
AUTH_HEADERS = {'authorization': 'Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'} # replace this
now_milliseconds = int(datetime.now().timestamp() * 1000)
valid_to = now_milliseconds + 15 * 60000 # 15 minutes
data = [
{'vendor_id': 'ABC-123', 'time': now_milliseconds, 'valid_to': valid_to,
'type': 'temperature', 'value': 23.756}
]
response = requests.post('https://eu-api.empathicbuilding.com/v2/measurements', json=data,
headers=AUTH_HEADERS)
response.raise_for_status()