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:

  1. email

  2. 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 API

  • token_type: the type of the token (to be added to the Authorization header)

  • expires_in: the number of seconds after which the access token is no longer valid

  • refresh_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

  1. an organization ID

  2. 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) or 64737761a47148863544 (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:

  1. Decode the base64 encoded data into binary

  2. Decompress the binary data using a gzip library

  3. 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 object

  • A -modified event contains the ID of the target object and the changes made to its non-confidential attributes

  • A -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 employee, external, visitor, maintenance, cleaning, asset

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. WASH_MACHINE_REQUEST)

metadata

mapping

(optional) JSON-compatible mapping of keys to values (e.g. {"field1": "value"}

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 has been specified)

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: cr2032, cr17505)

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 (sink, headnode, subnode)

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()