Introduction
The iTwin IoT API provides direct access to device and sensor information, as well as raw and processed observation data that exist in our Cloud application or your Enterprise server.
We use the socket protocol to establish and authenticate all API requests and provide a two-way, real-time data connection between your client and our server.
We have included working code samples in JavaScript and Python (2.x) to demonstrate our API in action. You are not limited to using Javascript or Python in your application and can use any language that supports sockets.
Authentication
Our API uses API keys or IMS JWT Tokens to authenticate socket connections and REST API calls.
You can generate and view your unique API key by doing the following:
- Log in to our Cloud application (app.sensemetrics.com) or your Enterprise server.
- Hover over the settings icon (gear icon) at the top right.
- Select Development from the menu to view or re-generate your API key.
API keys do not expire, however you can re-generate your key if you believe the old one has been compromised.
Also note that your API key will allow you to access only the objects and data that is accessible to you within our Cloud application or your Enterprise server. Our API cannot be used to modify access permissions at this time.
For iTwin documentation and obtaining IMS JWT Tokens please see here: https://developer.bentley.com/apis/overview/authorization
Managing Connections
Opening a Connection
Open and authenticate a new socket connection, listen for messages and events:
var socket,
connected,
authenticating,
nextId = 0,
host = "iiot.bentley.com",
port = 4201,
apiKey = "YourAPIKey";
// Open a new socket connection
socket = new WebSocket("wss://" + host + ":" + port + "/");
// Listen for socket open events
socket.onopen = function(event) {
console.log("Socket opened with " + host + ":" + port);
// Perform socket authentication
authenticating = true;
sendRequest("authenticate", {
"code": apiKey
}, function(result) {
console.log("Authentication successful");
// Save the next request id
nextId = result.nextId;
// Set connection flags
connected = true;
authenticating = false;
});
};
// Listen for socket close events
socket.onclose = function() {
console.log("Socket closed");
// Set connection flags
connected = authenticating = false;
// Clear the socket object and reset request id
socket = undefined;
nextId = 0;
};
// Listen for socket messages
socket.onmessage = function(event) {
onMessage(event.data);
};
import socket, json
from dateutil import parser
host = "iiot.bentley.com"
port = 4200
apiCode = "YourAPIKey"
# Open a new socket connection
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
# Send the authentication request
handshake = json.dumps({
"jsonrpc": "2.0",
"method": "authenticate",
"params": {
"code" : apiCode
},
"id": 0
})
s.send(handshake)
# Parse the response
jsonFrame = decodeOneJsonFrame(s)
response = json.loads(jsonFrame)
print("\r\nHandshake Exchange:\r\n" + " --> " + handshake + "\r\n" + " <-- " + jsonFrame)
# Close the socket connection
s.close()
Our API is built on the JSON-RPC 2.0 specification, a simple and lightweight remote procedure call protocol.
Its general mechanism consists of two peers establishing a data connection. During the lifetime of a connection, peers may invoke methods provided by the other peer. To invoke a remote method, a request is sent. Unless the request is a notification, it must be replied to with a response.
Before using any API calls, you must establish and authenticate a socket connection between your client and our server using your unique API key or JWT Token.
You will need the following to open a connection:
| Parameter | Example | Description |
|---|---|---|
| host | iiot.bentley.com | The URL of the server that you are connecting to. |
| port | 4201 | The socket connection port. Should be 4201 for WebSocket connections and 4200 for all other socket connections. |
| code | A8345HJBWW45939KUIYB | Your unique API key or JWT Token. |
After the connection has been established and authenticated, you can begin sending API requests and receiving responses from the server.
Sending Requests
A generic function for sending requests via a socket:
var callbacks = {}
function sendRequest(request, params, callback) {
// Get the request id to use
var requestId = nextId;
// Send the request message
socket.send(JSON.stringify({
"jsonrpc": "2.0",
"method": request,
"id": requestId,
"params": params
}));
// Save the response callback for later
callbacks[requestId] = callback;
// Increment the id for future requests
nextId++;
return requestId;
};
callbacks = {}
def sendRequest(request, params, callback):
# Get the request id to use
requestId = nextId
# Send the request message
socket.send(json.dumps({
"jsonrpc": "2.0",
"method": request,
"id": requestId,
"params": params
}))
# Save the response callback for later
callbacks[requestId] = callback
# Increment the id for future requests
nextId = nextId + 1
return requestId;
Once a connection has been established and authenticated, you can begin to send requests to our server.
Each request should be in valid JSON format and contain the following properties:
| Property | Example | Description |
|---|---|---|
| jsonrpc | 2.0 | The JSON-RPC protocol version. We use version 2.0. |
| method | getSensors | The name of the API request being sent. |
| id | 5 | The sequential API request id. |
| params | {"sensors": ["sensorId1"]} |
Any required or optional API request parameters. |
The method and params fields will change depending on the API request being performed. Some API requests have required parameters, others have optional parameters.
The numeric id field should be sequentially incremented with each new API request. When you authenticate a new connection, the response will contain a nextId field that should become the starting id for all future requests through that connection.
The responses you will receive from our server will include the original request id, allowing you to correctly route response messages to their request callbacks.
Receiving Responses
A generic function for processing socket responses:
function onMessage(message) {
// Parse the JSON message body
var response = JSON.parse(message);
if ("result" in response) {
// Request succeeded without errors
callbacks[response.id](response.result);
} else if ("error" in response) {
// Request resulted in an error
if ("id" in response) {
// Send error information to original callback for handling
callbacks[response.id](response.error);
} else {
// Log all other socket errors
console.error("Received API error message: ", response.error);
}
}
};
# Gets one JSON frame from the TCP stream
# Extremely slow and not reliable, shown for example only
def decodeOneJsonFrame(socket):
frame = ""
count = 0
while True:
byte = socket.recv(1)
if byte == "{":
count = count + 1
elif byte == "}":
count = count - 1
frame = frame + byte
if count == 0:
return frame;
API responses which you receive back from the server will be in JSON format and will contain a data object with the following properties:
| Property | Example | Description |
|---|---|---|
| id | 5 | The id of the original API request. Can be used for routing responses to their request callbacks. |
| result | {...} |
The data returned by the API response. The contents of this will depend on the original API request. |
| error | {...} |
If the API request resulted in error, this optional field will contain the error code and information. |
Working With Entities
Introduction
Example connection (node) object:
{
"id": "/fn/6C4F6D/node",
"type": "THREAD",
"props": {
"ACCESSIBLE": true,
"DEVICE_GROUP": "/fn/6C4F6D/node",
"GATEWAY": "/fn/6B652F/node",
"LAST_COMM": "2016-10-18T22:26:24.818Z",
"LOCATION": {
"coordinates": [
-53.900861,
30.602073
]
},
"NAME": "North Sector THREAD #1",
"OBSERVATION_COUNT": 16521,
"POWER_MODE": "HIGH_POWER",
"PROJECT_ID": "60F8B30928A37A575ECEE061"
}
}
Example device object:
{
"id": "/fn/6C4F6D/node/dgsimlogger/032432/device",
"type": "DGSI_M_LOGGER",
"props": {
"ACCESSIBLE": true,
"EXTERNAL_PORT": 1,
"LAST_OBSERVATION": {
"timestamp": "2016-06-24T18:01:00.000Z"
},
"LOCATION": {
"coordinates": [
-53.900861,
30.602073,
-17.24
]
},
"MODE": "IPI_BIAXIAL",
"NAME": "North Sector Logger 2",
"NODE_ID": "/fn/6C4F6D/node",
"OBSERVATION_COUNT": 8832,
"SAMPLING_RATE_MAX": 0.0011111111111111111,
"SENSOR_GROUP": "/fn/6C4F6D/node/dgsimlogger/032432/device",
"SIZE": 4,
"ZERO_DATE": "2016-03-22T07:00:00.000Z",
"PROJECT_ID": "60F8B30928A37A575ECEE061"
}
}
Example sensor object:
{
"id": "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
"device": "/fn/6C4F6D/node/dgsimlogger/032432/device",
"type": "TILT_BIAXIAL_DGSI",
"props": {
"ACCESSIBLE": true,
"LAST_OBSERVATION": {
"sensorId": "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
"timestamp": "2016-06-24T18:01:00.000Z",
"values": [
-0.20226,
0.17345,
0.70659
]
},
"LOCATION": {
"coordinates": [
-53.900861,
30.602073,
-17.24
]
},
"MUX_INDEX": 4,
"NAME": "North Sector Tilt 2-2",
"NODE_ID": "/fn/6C4F6D/node",
"OBSERVATION_COUNT": 2208,
"SAMPLING_RATE_MAX": 0.0011111111111111111,
"SIZE": 4,
"ZERO_DATE": "2016-03-22T07:00:00.000Z",
"PROJECT_ID": "60F8B30928A37A575ECEE061"
}
}
An iTwin IoT network consists of a hierarchy of connections (also called nodes), devices and sensors. Together, we often refer to these objects as entities:
| Type | Example | Description |
|---|---|---|
| connection (node) | THREAD | Establishes a connection to a data source or collects data from devices & sensors. |
| device | DGSI M Logger | A physical device that provides connectivity to sensors or a representation of groups of sensors. |
| sensor | Biaxial Tilt | A single physical sensor capable of recording observations. |
All entities always exist in a network in a hierarchical structure:
- Each sensor is nested under a single device.
- Each device is nested under a single connection.
- Each connection is either nested under another connection (only in the case of iTwin IoT THREADs) or represents a top level in the network.
Each entity object will typically include the following properties:
| Property | Example | Description |
|---|---|---|
| id | /fn/6C4F6D/node | The unique id of the entity in the network. Used for retrieving, subscribing to and modifying entities. |
| type | DGSI_M_LOGGER | The type of the connection, device or sensor. |
| props | {...} |
An object containing key/value pairs with configuration parameters for the entity. |
API Requests
The following example illustrates retrieving a sensor object from the server and subscribing to its updates:
var sensor,
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
subscriptionId;
// Retrieve sensor from the server
sendRequest("getSensors", {
"sensors": [sensorId]
}, function(result) {
if (result.length > 0 && result[0].id === sensorId) {
// Sensor retrieved, subscribe to its updates
sensor = result[0];
subscriptionId = sendRequest("subscribeSensors", {
"sensors": [sensorId]
}, function(updates) {
if ("data" in updates) {
// Update the local sensor object
sensor = updates.data;
}
});
} else {
// Sensor could not be retrieved
console.error("Error retrieving sensor", sensorId, ":", result);
}
});
nextId = nextId + 1
sensor = {}
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor"
# Retrieve sensor object from server
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "getSensors",
"id": nextId,
"params": {
"sensors" : [sensorId]
}
}))
# Decode server response
response = json.loads(decodeOneJsonFrame(s))
firstSensorId = response["result"][0]["id"]
if firstSensorId == sensorId:
# Save sensor object locally
sensor = response["result"][0]
# Subscribe to sensor updates
nextId = nextId + 1
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "subscribeSensors",
"id": nextId,
"params": {
"sensors" : [sensorId]
}
}))
# Handle incoming updates asynchronously
while True:
# Decode response
response = json.loads(decodeOneJsonFrame(s))
# Store the incoming observations
if "data" in response["result"]:
sensor = response["result"]["data"]
Several API requests are available to retrieve, subscribe to, modify, and create entities:
| Type | Example | Description |
|---|---|---|
| retrieval | getNodes getDevices getSensors |
Retrieves all connection, device or sensor objects in their current state that match your query parameters. |
| subscriptions | subscribeNodes subscribeDevices subscribeSensors |
Establishes a subscription to any changes in the state of specified connections, devices or sensors. You will receive a new response message when any of the configuration parameters change. Returned subscriptionId can be used to later unsubscribe from updates. |
| subscription terminations | unsubscribeNodes unsubscribeDevices unsubscribeSensors |
Unsubscribe from updates using the subscriptionId returned by the subscription API request. |
| modification | updateNode updateDevice updateSensor |
Overwrite existing configuration parameters of a single connection, device or sensor. |
| creation | createNode createDevice createSensor |
Creates node, device or sensor in the iTwin IoT platform. If the node, device or sensor already exists, this will return an error. See the entity object example above. |
Creating Entities
Creates a connection (node), device or sensor in the iTwin IoT platform. If the connection, device or sensor already exists, an error will be returned.
Create Node
Example createNode request/response:
{
"jsonrpc": "2.0",
"method": "createNode",
"id": "2",
"params": {
"node": "/fn/02DDXX/node",
"type": "THREAD",
"props": {
"NAME": "Sample thread",
"SERIAL_NO": "02DDXX",
"VERSION": "v4_6"
}
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"id": "/fn/02DDXX/node",
"type": "THREAD",
"props": {
"NAME": "Sample thread",
"NODE_ID": "/fn/02DDXX/node",
"ACCESSIBLE": true
}
}
}
| Property | Example | Description |
|---|---|---|
| method | createNode | The name of method. |
| id | 2 | The sequential API request id. |
| params.node | /fn/ |
The connection id to be created, the example format should be followed. |
| params.type | THREAD | The connection type. |
| params.props.SERIAL_NO | 022DDXX | Serial number of the connection. |
| params.props.VERSION | v4_6 | Version of the client software. Required field. |
Create Device
Example createDevice request/response:
{
"jsonrpc": "2.0",
"method": "createDevice",
"id": "3",
"params": {
"device": "/fn/02DDC5/node/demo/032432/device",
"type": "DEMO",
"props": {
"NAME": "Demo device",
"NODE_ID": "/fn/02DDC5/node"
}
}
}
{
"jsonrpc": "2.0",
"id": 3
"result": {
"id": "/fn/02DDC5/node/demo/032432/device",
"type": "DEMO",
"props": {
"NAME": "Demo device",
"NODE_ID": "/fn/02DDC5/node",
"ACCESSIBLE": true
}
}
}
| Property | Example | Description |
|---|---|---|
| method | createDevice | The name of method. |
| id | 3 | The sequential API request id. |
| params.device | /fn/02DDC5/node/demo/032432/device/ | The device id to be created, the example format should be followed. |
| params.type | DEMO | The device type. |
| params.props.NODE_ID | 022DDXX | Connection id to which this device should be attached. Required field. |
| params.props.NAME | Demo device | Name of the device. |
Create Sensor
Example createSensor request/response:
{
"jsonrpc": "2.0",
"method": "createSensor",
"id": "4",
"params": {
"sensor": "/instantel/3E216E/node/demo_sensor/UM9169/device/demo-1/sensor",
"observationType": "DEMO_SENSOR",
"type": "DEMO_SENSOR",
"props": {
"NAME": "Demo Sensor"
}
}
}
{
"jsonrpc": "2.0",
"id": 4
"result": {
"type": "DEMO_SENSOR",
"device": "/instantel/3E216E/node/demo_sensor/UM9169/device",
"id": "/instantel/3E216E/node/demo_sensor/UM9169/device/demo-1/sensor",
"props": {
"ACCESSIBLE": true,
"SERIAL_NO": "UM9169",
"NODE_ID": "/instantel/3E216E/node",
"NAME": "Demo Sensor"
}
}
}
| Property | Example | Description |
|---|---|---|
| method | createSensor | The name of method. |
| id | 4 | The sequential API request id. |
| params.sensor | /instantel/3E216E/node/demo_sensor/UM9169/device/demo-1/sensor/ | The sensor id to be created, the example format should be followed. |
| params.type | DEMO_SENSOR | Observation/Sensor type. |
| params.observationType | DEMO_SENSOR | Observation/Sensor type. |
| params.props.NAME | Demo Sensor | Name of the sensor. |
Working with Projects
Introduction
Project Management allows users to efficiently manage multiple projects from a single user account. Instead of users owning connection/devices/sensors, these assets are now owned by the project.
This means in order to retrieve assets from a specific Project you must provide the appropriate projectId in the API request.
See here for more information about Projects.
REST Requests
In general, our REST API endpoints to retrieve assets (i.e. nodes/devices/sensors) utilize an optional projectId query parameter. Whenever a projectId is not provided, these endpoints will assume the organization’s Default Project is being requested. Example endpoints include:
| Http Method | Endpoint | Description |
|---|---|---|
| GET | /nodes/all?projectIds={projectId} | Return all nodes for the given project |
| GET | /devices/all?projectIds={projectId} | Return all devices for the given project |
| GET | /sensors/all?projectIds={projectId} | Return all sensors for the given project |
| GET | /documents?projectIds={projectId} | Return all documents for the given project |
| GET | /profiles?projectIds={projectId} | Return all profiles for the given project |
However, when making requests on projects themselves, projectIds will be required as a part of the url resource path. Examples endpoints include:
| Http Method | Endpoint | Description |
|---|---|---|
| GET | /projects/{projectId} | Return a project |
| DELETE | /projects/{projectId} | Delete a project |
| GET | /projects/{projcetId}/dataUsage | Returns data usage for a project |
Websocket Requests
Example getNodes request/response:
{
"jsonrpc":"2.0",
"method":"getNodes",
"id":"1",
"params": {
"nodes":["/fn/6C954F/node"],
"projectIds":["60D90DF0B2BD096E970C2824"]
}
}
{
"jsonrpc":"2.0",
"id": 1,
"result": [{
"type": "DYNAMIC",
"id": "/fn/6C954F/node",
"props": {
"PROJECT_ID": "60D90DF0B2BD096E970C2824",
"ACCESSIBLE": false,
"MODEL": "v11",
"NODE_ID": "/fn/6C954F/node",
"PORT_PROXY": 28957,
"POWER_MODE": "HIGH_POWER",
"LAST_MODIFIED_BY": "support",
"LAST_MODIFIED_DATE": "2021-09-01T17:37:14.582Z",
"LOCATION": {"type":"Point","coordinates":[null,null,null]},
"SERIAL_NO": "6C954F",
"NAME": "Thread 6C954F",
"LAST_COMM": "2021-09-16T18:00:58.460Z",
"INTEGRATION_ID": "THREAD_SDE",
"CREATION_DATE": "2021-09-01T17:32:57.556Z",
"VERSION": "v8_1-SNAPSHOT",
"DEVICE_GROUP": "/fn/6C954F/node",
"OBSERVATION_COUNT": 2519,
"TYPE": "XC"
},
"customProps": {}
}]
}
Our Websocket API endpoints also require projectIds, albeit in the form of an additional param.projectIds array (optional and falls back to organization’s Default Project). The projectIds can also be supplied with an iTwin asset ID provided you are using a JWT token from IMS.
Project FAQ
Q: Can I supply multiple projectIds to retrieve assets that span across multiple projects?
A: Currently only a single projectId is supported per request. Implementing support for multiple projectIds is currently on our road map.
Q: How to pull data from multiple projects since the PM release?
A: Currently only a single projectId is supported per request. Implementing support for multiple projectIds is currently on our road map.
Q: How do I know which Project my asset is in?
A: Every asset now contains a PROJECT_ID property which specifies which project the asset is managed/owned by.
Q: Where can I find a Project’s projectId?
A. ProjectIds can be found on the sensemetrics Platform under Project Settings see here for more details
Working with Observations
Introduction
Example data series:
{
"2016-12-15T23:38:07.000Z": 77.53769759239933,
"2016-12-16T07:13:07.000Z": 77.79861178632439,
"2016-12-29T01:26:04.000Z": 77.68848770690741
}
Each sensor in the network is capable of recording observations that are stored on the server.
Observations are recorded and stored as raw numerical values in the sensor’s native metrics and units. The raw observations can often be converted to various derivative metric & unit combinations and retrieved via the API.
Each retrieved data point will be an object with the key representing the observation time stamp and the value representing the observation value:
| Property | Example | Description |
|---|---|---|
| key | 2016-12-15T23:38:07.000Z | The time stamp of the data point, in ISO date format. |
| value | 77.9759239933 | The observation value in the metric & unit specified by the API request parameters. |
If your API request found multiple observations that match your query parameters, they will be returned as a single object with the keys representing the timestamps of the observations.
Metrics
The following example illustrates retrieving metrics and units for a specific sensor:
var allMetrics = [],
allUnits = {},
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor";
// Retrieve metrics for this sensor
sendRequest("getMetrics", {
"sensors": [sensorId]
}, function(result) {
if (!("code" in result) && sensorId in result) {
// Save all metrics
allMetrics = result[sensorId];
var firstMetric = allMetrics[0].id;
// Retrieve units for first available metric
sendRequest("getUnits", {
"metrics": [firstMetric]
}, function(result) {
// Save the retrieved units
allUnits[firstMetric] = result[firstMetric];
});
} else {
console.warn('Error retrieving metrics:', result);
}
});
nextId = nextId + 1
allMetrics = []
allUnits = {}
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor"
# Retrieve metrics for this sensor
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "getMetrics",
"id": nextId,
"params": {
"sensors" : [sensorId]
}
}))
# Decode server response and save the metrics
response = json.loads(decodeOneJsonFrame(s))
allMetrics = response["result"][sensorId]
firstMetric = allMetrics[0]["id"]
# Retrieve the units for the first metric
nextId = nextId + 1
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "getUnits",
"id": nextId,
"params": {
"metrics" : [firstMetric]
}
}))
# Decode server response and save the units
response = json.loads(decodeOneJsonFrame(s))
allUnits[firstMetric] = response["result"][firstMetric]
Example metric object:
{
"defaultUnit": "m",
"displayName": "∆ X",
"id": "dx",
"name": "displacement X",
"notation": "∆x",
"params": [
{
"id": "dt",
"mandatory": false
}, {
"id": "START_DATE",
"mandatory": false
}, {
"id": "END",
"mandatory": false
}, {
"id": "ZERO_DATE",
"mandatory": false
}
]
}
Example unit object:
{
"id": "m",
"name": "meter",
"notation": "m"
}
Before requesting observation data for a sensor, you must know the metrics and units applicable to that sensor.
All sensors have a set of raw metrics & units that are used to record raw observations output by the sensor. Many sensors also have derivative metrics & units that can be calculated from the sensor’s raw observations.
You can retrieve the metrics and units for each sensor using the following API requests:
| Request | Params | Description |
|---|---|---|
| getRawMetrics | “sensors” [array] “types” [array] |
Retrieves available raw metrics for an array of sensor id’s or sensor types. You will typically use the getMetrics API request described below in favor of this one. |
| getMetrics | “sensors” [array] | Retrieves all available metrics for an array of sensor id’s. |
| getUnits | “metrics” [array] | Retrieves all available units for an array of metric id’s. |
Metric Parameters
Example metric parameters specifying reference date and sensor azimuth:
{
"AZIMUTH_XY": {
"value": 90
},
"ZERO_DATE": {
"value": "2017-03-01T22:00:00.000Z"
}
}
As mentioned above, derivative metrics & units are calculated from the sensor’s raw observation readings.
Some derivative metrics have required or optional parameters that can be used to fine-tune how these metrics are calculated. These parameters will be included in the params property within each metric object retrieved via the getRawMetrics or getMetrics API requests.
Each calculation parameter will be an object with the following properties:
| Property | Example | Description |
|---|---|---|
| id | ZERO_DATE | The unique id of the metric parameter. |
| mandatory | false | Whether this metric parameter is required for the metric to be calculated correctly. |
Retrieving Data
The following example illustrates retrieving data for a specific sensor:
var sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
metricId = "dx",
unitId = "m";
// Create a local data object
var series = {
id: sensorId,
metric: metricId,
data: {}
};
// Create a set of parameters for data retrieval
var retrievalParams = {
"sensor": sensorId,
"metric": metricId,
"unit": unitId,
"params": {
"ZERO_DATE": {
"value": "2017-03-01T22:00:00.000Z"
}
},
"startDate": "2017-01-01T23:17:10.305Z",
"endDate": "2017-01-20T23:17:10.305Z"
};
// Retrieve data from the server
sendRequest("getData", retrievalParams, function(result) {
if ("requestStatus" in result && result.requestStatus === "finished") {
// Data retrieval finished
} else if ("data" in result) {
// Save each data point locally
for (var date in result.data) {
if (result.data.hasOwnProperty(date)) {
series.data[date] = result.data[date];
}
}
} else if ("code" in result) {
console.error("Error retrieving data with params", params, ":", result);
}
});
nextId = nextId + 1
metricId = "dx"
unitId = "m"
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor"
# Create a local data object
series = {
"id": sensorId,
"metric": metricId,
"data": {}
}
# Create a set of parameters for data retrieval
retrievalParams = {
"sensor": sensorId,
"metric": metricId,
"unit": unitId,
"params": {
"ZERO_DATE": {
"value": "2017-03-01T22:00:00.000Z"
}
},
"startDate": "2017-01-01T23:17:10.305Z",
"endDate": "2017-01-20T23:17:10.305Z"
};
# Retrieve data from server
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "getData",
"id": nextId,
"params": retrievalParams
}))
# Handle incoming observations asynchronously
while True:
# Decode response
response = json.loads(decodeOneJsonFrame(s))
# Store the incoming observations
if "data" in response["result"]:
for date in response["result"]["data"]:
series["data"][date] = response["result"]["data"][date];
# Close the socket when all observations are received
if "requestStatus" in response["result"] and response["result"]["requestStatus"] == "finished":
s.close()
break
To retrieve observation data, you must always specify a sensor id, a metric id and a unit id. In addition, you can optionally include the following parameters with your API request:
| Parameter | Example | Description |
|---|---|---|
| startDate | 2017-01-01T23:17:10.305Z | The start date for the data query, in ISO date format. If omitted, the data returned will start with the first observation. |
| endDate | 2017-01-20T23:17:10.305Z | The end date for the data query, in ISO date format. If omitted, the data returned will end with the most recent observation. |
| params | {dt: {value: 604800}} |
Any mandatory or optional metric parameters, as described above. Metric parameter id’s should be used as keys of this object. Each metric parameter must have a value property and an optional unit property. |
| limit | {function: "last", params: {size: 1}} {function: "first", params: {size: 1}} |
A configuration object that limits the number of observations returned. We currently only support retrieving a single first or last value. |
Once you compile a list of data query parameters, you can retrieve the actual observations or statistical information about them using the following API requests:
| Request | Params | Description |
|---|---|---|
| getData | described above | Retrieves a series of observations for a specific sensor that match the data query parameters. |
| getObservations | described above | Retrieves a series of observations for a specific sensor that match the data query parameters in the sensor’s raw metrics & units. Not recommended in most cases. |
| getObservationsSummary | described above | Retrieves statistical information about a series of observations. Currently returns the first and last observations that match the data query parameters. |
See Below for our end to end example
Let’s assume we have acquired the sensorId of /fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor from the Sensor configuration page. If we want to obtain the full data model of our sensor we can execute the following getSensors request
from websocket import create_connection
ws = create_connection("wss://iiot.bentley.com:4201")
json = r'{"jsonrpc":"2.0","method":"authenticate","id":"0","params":{"code":"<REPLACE_WITH_YOUR_API_CODE_OR_JWT_TOKEN>"}}'
print(json)
ws.send(json)
result = ws.recv()
print("authenticate response: " + result)
json = '''{
"jsonrpc":"2.0",
"method":"getSensors",
"id":1,
"params":{
"sensors": ["/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor"],
"projectIds":[
"60F8B30928A37A575ECEE061"
]
}
}'''
print(json)
ws.send(json)
result = ws.recv()
print("response: " + result)
NOTE: Because sensor: /fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor is in our default Project the projectIds param was not required.
Our request will result in a response that looks something like the response below. Make note of the PROJECT_ID field it will be needed later
{
"jsonrpc":"2.0",
"result":[
{
"type": "DYNAMIC",
"device": "/fn/xxxxx/node/dynamic/xxxxx/device",
"id": "/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor",
"props": {
"MODEL": "RUGGED_TROLL_200",
"NAME": "My Cool Rugged troll",
"NODE_ID": "/fn/xxxxx/node",
"PROJECT_ID": "60F8B30928A37A575ECEE061"
}
}
],
"id":1
}
Next we can run the following code to find or metric/units of interest.
from websocket import create_connection
ws = create_connection("wss://iiot.bentley.com:4201")
json = r'{"jsonrpc":"2.0","method":"authenticate","id":"0","params":{"code":"<REPLACE_WITH_YOUR_API_CODE_OR_JWT_TOKEN>"}}'
print(json)
ws.send(json)
result = ws.recv()
print("authenticate response: " + result)
json = '''{
"jsonrpc":"2.0",
"method":"getMetrics",
"id":1,
"params":{
"sensors": ["/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor"]
}
}'''
print(json)
ws.send(json)
result = ws.recv()
print("response: " + result)
Which will result in a response that looks something like
{
"jsonrpc":"2.0",
"result":{
"/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor":[
{
"id":"dT",
"name":"delta temperature",
"displayName":"∆ Temperature",
"defaultUnit":"C",
"notation":"∆T",
"hidden":false,
"params":[
{
"id":"dt",
"mandatory":false
},
{
"id":"START_DATE",
"mandatory":false
},
{
"id":"ZERO_DATE",
"mandatory":false
}
]
},
{
"id":"T",
"name":"temperature",
"displayName":"Temperature",
"defaultUnit":"C",
"notation":"T",
"hidden":false,
"params":[
]
},
{
"id":"P_raw",
"name":"raw pressure",
"displayName":"Raw Pressure",
"defaultUnit":"Pa",
"notation":"P_raw",
"hidden":false,
"params":[
]
}
]
},
"id":1
}
Let’s assume we are interested in raw pressure which has a metricId of P_raw and default unit of Pa (if a different unit is desired, you can run the getUnits API request to view a list of the available units for your desired metric). We now have our metric, unit, and PROJECT_ID (captured from the getSensors response above) we are now ready to make our getData call.
import json
from datetime import datetime
from websocket import create_connection
now = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
print('Using timestamp: ' + now)
ws = create_connection("wss://iiot.bentley.com:4201")
jsonRequest = r'{"jsonrpc":"2.0","method":"authenticate","id":"0","params":{"code":"<REPLACE_WITH_YOUR_API_CODE_OR_JWT_TOKEN>"}}'
print(jsonRequest)
ws.send(jsonRequest)
result = ws.recv()
print("authenticate response: " + result)
jsonRequest = '''{
"jsonrpc":"2.0",
"method":"getData",
"id":1,
"params":{
"sensor":"/fn/xxxxx/node/dynamic/xxxxx/device/rugged_troll_insitu_sde/sensor",
"metric":"P_raw",
"unit":"Pa",
"params":{
},
"startDate":"2021-06-24T10:56:11.832Z",
"endDate":"2021-07-22T00:38:05.477Z",
"projectIds":[
"60F8B30928A37A575ECEE061"
]
}
}'''
print(jsonRequest)
ws.send(jsonRequest)
start = datetime.utcnow()
while True:
result = json.loads(ws.recv())
print(result)
if "requestStatus" in result["result"] and result["result"]["requestStatus"] == "finished":
ws.close()
break
done = datetime.utcnow()
delta = done - start
print delta.total_seconds() * 1000
Subscribing to New Data
The following example illustrates subscribing to a sensor’s new observations:
var sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor",
metricId = "dx",
unitId = "m";
// Create a local data object
var series = {
id: sensorId,
metric: metricId,
data: {}
};
// Create a set of parameters for a data subscription
var subscriptionParams = {
"sensors": [sensorId],
"metric": metricId,
"unit": unitId,
"params": {
"ZERO_DATE": {
"value": "2017-03-01T22:00:00.000Z"
}
}
};
// Subscribe to new sensor observations
var subscriptionId = sendRequest("subscribeData", subscriptionParams, function(result) {
if ("data" in result) {
// Save each data point locally
for (var date in result.data) {
if (result.data.hasOwnProperty(date)) {
series.data[date] = result.data[date];
}
}
}
});
nextId = nextId + 1
metricId = "dx"
unitId = "m"
sensorId = "/fn/6C4F6D/node/dgsimlogger/032432/device/tilt4/sensor"
# Create a local data object
series = {
"id": sensorId,
"metric": metricId,
"data": {}
}
# Create a set of parameters for data subscription
subscriptionParams = {
"sensors": [sensorId],
"metric": metricId,
"unit": unitId,
"params": {
"ZERO_DATE": {
"value": "2017-03-01T22:00:00.000Z"
}
}
};
# Subscribe to new sensor observations
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "subscribeData",
"id": nextId,
"params": subscriptionParams
}))
# Handle incoming updates asynchronously
while True:
# Decode response
response = json.loads(decodeOneJsonFrame(s))
# Store the incoming observations
if "data" in response["result"]:
for date in response["result"]["data"]:
series["data"][date] = response["result"]["data"][date];
In addition to retrieving observation data, you can subscribe to a sensor’s data stream to receive all newly recorded observations.
The data subscription API request can be performed with most of the same parameters as the observation retrieval request, so any newly recorded observations will be passed to you in the metrics & units specified (instead of the raw metrics & units they are recorded in).
You can subscribe and unsubscribe from a sensor’s observation stream using the following API requests:
| Request | Params | Description |
|---|---|---|
| subscribeData | “sensors” [array] “metric” [string] “unit” [string] “params” [object] |
Establishes a subscription to any new sensor observations. You will receive a new response message when a new observation is recorded in the metrics & units specified by the API request. Returned subscriptionId can be used to later unsubscribe from updates. |
| unsubscribeData | “id” [integer] | Unsubscribe from new observation updates using the subscriptionId returned by the subscription API request. |
Uploading Data
The following example illustrates how to upload new observations for a single sensor:
var uploadDataParams = {
"data": [
{
"sensorId": "/trimble4d/nodeId/node/deviceId/device/sensorId/sensor",
"timestamp": "2018-06-04T19:35:35.400Z",
"values": {
"e": 100.0,
"e_adjusted": 101.0,
"n": 100.0,
"n_adjusted": 101.0,
"h": 100.0,
"h_adjusted": 101.0,
"ha": 45.0,
"za": 60.0,
"sd": 100.0,
"sd_corrected": 101.0,
"T": 23.0,
"P": 101325
}
}
]
};
sendRequest("uploadData", uploadDataParams, function(result) {
console.log("uploadData result: ", result);
});
nextId = nextId + 1
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "uploadData",
"id": nextId,
"params": {
"data": [
{
"sensorId": "/trimble4d/nodeId/node/deviceId/device/sensorId/sensor",
"timestamp": "2018-06-04T19:35:35.400Z",
"values": {
"e": 100.0,
"e_adjusted": 101.0,
"n": 100.0,
"n_adjusted": 101.0,
"h": 100.0,
"h_adjusted": 101.0,
"ha": 45.0,
"za": 60.0,
"sd": 100.0,
"sd_corrected": 101.0,
"T": 23.0,
"P": 101325
}
}
]
}
}))
response = json.loads(decodeOneJsonFrame(s))
print("\r\n" + response["result"])
Example response object:
{
"jsonrpc": "2.0",
"result": {
"successfullyProcessedObs": 1
},
"id": 4
}
In addition to retrieving observation data, you can upload new observation data for a particular sensor given it’s unique sensorId.
In order to upload new observation data for a sensor in the iTwin IoT platform, that sensor must already exist in the system. See the createSensor API call described above if you need to create a new sensor first.
The values specified in the data object should have a key-value pair for each raw metric defined for this type of sensor. You can obtain the raw metric keys for a given sensor using the getRawMetrics request described above.
The response has a key successfullyProcessedObs that reflects the actual number of observations that were inserted. There is an example of this response object on the right.
| Request | Params | Description |
|---|---|---|
| uploadData | “data” [array] |
This is an array of objects that represent a sensorId, timestamp, and the associated metrics and their values. See example object on the right. |
data Object Description (all fields are mandatory):
| Parameter | Example | Description |
|---|---|---|
| sensorId | /trimble4d/nodeId/node/deviceId/device/sensorId/sensor | The sensorId that represents the sensor you are uploading observation data for. This sensor must already exist in the system. |
| timestamp | 2018-06-04T23:17:10.305Z | The timestamp for this observation, in ISO date format. |
| values | {"e": 100.0, "n": 200.0, "h": 300.0, ...} |
The key is the metric name, and the value is the value for that metric for this observation. |
Utilities
Retrieve help information for all available API requests:
sendRequest("help", {}, function(result) {
console.log("Available API requests:", result);
});
nextId = nextId + 1
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "help",
"id": nextId
}))
response = json.loads(decodeOneJsonFrame(s))
print("\r\n" + response["result"])
Keep a connection alive with periodic time requests:
setInterval(function() {
// Check if a connection host has been set
if (host !== undefined) {
// Check if a connection is already open
if (socket === undefined) {
// If no connection, establish a new one
} else {
// Make sure we are no actively authenticating
if (!authenticating) {
// Refresh the connection by sending a "time" request
sendRequest("time", {}, function() {
connected = true;
});
}
}
} else {
// Set connection flags
connected = authenticating = false;
}
}, 5000);
nextId = nextId + 1
s.send(json.dumps({
"jsonrpc": "2.0",
"method": "time",
"id": nextId
}))
response = json.loads(decodeOneJsonFrame(s))
print("\r\nThe server time is " + parser.parse(response["result"]["time"]).strftime('%c'))
There are three utility API requests available for your convenience:
| Request | Parameters | Description |
|---|---|---|
| authenticate | “code” [string] | Authenticates a socket connection using your unique API key or JWT Token. This must be the first API request after opening a new connection. |
| help | None | Retrieves information on all available API requests and their parameters. |
| time | None | Retrieves the current time from the server. |
iTwin IoT Platform REST API
The iTwin IoT platform REST API is a standard REST API available to all clients. The full API suite is available here.
Getting Started
To begin using the REST API you will first need to obtain your iTwin IoT ApiKey or JWT Token
(See the Authentication section above for details). The ApiKey/Token is required on every request, and
must be supplied to the Authorization header.
See the cURL tab for a sample request to get all sensors on the specified Project.
For ApiKey:
curl --location --request GET 'https://iiot.bentley.com/api/sensors/all?pageSize=100&projectIds=60F8B30928A37A575ECEE061' \
--header 'Authorization: ApiKey <apikey>' \
--header 'Content-Type: application/json'
For JWT Token (token emitted for brevity):
curl --location --request GET 'https://iiot.bentley.com/api/sensors/all?pageSize=100&projectIds=026a6db1-68c6-4416-9bd0-1f83e191ec31' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json'
iTwin IoT Edge REST API
The iTwin IoT Edge REST API is a standard REST API available to client applications running on a iTwin IoT Device. All REST API requests and responses use the Content-Type “application/json”.
GET Serial Number
get Serial Number
curl http://localhost/api/serialNumber
Get the serial number of this iTwin IoT THREAD. This is otherwise known as the THREAD Connect Code. It is a 6-character hex string.
GET Time
get Time
curl http://localhost/api/time
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> timeTask = getSampleCall("time", "/time", "");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
timeTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
auto fileStream = std::make_shared<ostream>();
//auto fileStream = std::__1::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
builder.append(U(queryParams));
const string_t &fragment = builder.to_string();
return client.request(methods::GET, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Get the current time on the Device
POST Time
Example Body
{
"time" : "2019-11-19T15:41:30.000Z"
}
post time
curl -X POST http://localhost/api/time -d '{"time":"2019-11-19T15:41:30.000Z"}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> timeTask = getSampleCall("time", "/time", "");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
timeTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
auto fileStream = std::make_shared<ostream>();
//auto fileStream = std::__1::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
builder.append(U(queryParams));
const string_t &fragment = builder.to_string();
return client.request(methods::GET, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Set the Device’s time to the provided date time.
Model
| Key | Type | Description |
|---|---|---|
| time | ISO 8601 Date Time | Date Time to set on Device |
GET Time Accurate
Check if the clock synchronization process between the on-board RTC and OS clock has finished
curl http://localhost/api/timeAccurate
Check if the clock synchronization process between the on-board RTC and OS clock has finished. Returns ‘true’ if it is complete, 'false’ otherwise.
GET Input Voltage
get input voltage
curl http://localhost/api/chargeInputVoltage
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> chargeInputVoltageTask = getSampleCall("chargeInputVoltage", "/chargeInputVoltage", "");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
chargeInputVoltageTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
auto fileStream = std::make_shared<ostream>();
//auto fileStream = std::__1::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
builder.append(U(queryParams));
const string_t &fragment = builder.to_string();
return client.request(methods::GET, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Response
{
"value":14.5288
}
Get the Device’s input voltage in volts
GET Input Current
get input current
curl http://localhost/api/chargeInputCurrent
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> chargeInputCurrentTask = getSampleCall("chargeInputCurrent", "/chargeInputCurrent", "");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
chargeInputCurrentTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
auto fileStream = std::make_shared<ostream>();
//auto fileStream = std::__1::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
builder.append(U(queryParams));
const string_t &fragment = builder.to_string();
return client.request(methods::GET, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Response
{
"value":0.0225
}
Get the Device’s input current in amps
GET Battery Voltage
get battery voltage
curl http://localhost/api/batteryVoltage
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> batteryVoltageTask = getSampleCall("batteryVoltage", "/batteryVoltage", "");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
batteryVoltageTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
auto fileStream = std::make_shared<ostream>();
//auto fileStream = std::__1::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
builder.append(U(queryParams));
const string_t &fragment = builder.to_string();
return client.request(methods::GET, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Response
{
"value":13.7456
}
Get the Device’s battery voltage in volts
GET Battery State of Charge
Reports the battery percentage 0-100%
curl http://localhost/api/stateOfCharge
Reports the battery percentage 0-100%
GET Battery Saver
Get the currently configured battery saver percentages
curl -X GET http://localhost/api/batterySaver
Response
{
"value": false,
"low": 0,
"high": 100
}
Get the currently configured battery saver percentages
PUT Battery Saver
Example Body
{
"value" : true,
"low": 10,
"high": 100
}
Set the charge controller to stop charging at 80% charge and shut off at 20%
curl -X PUT http://localhost/api/batterySaver -d '{ "value": true, "low": 20, "high": 80}'
Sets the charge controller to stop charging at a desired high %charge and shut off at a desired low %charge. Low must be between 0-40 inclusive. High must be between 60-100 inclusive.
If 'value’ is set to true and no low/high value is given, it will set it to low 10 high 100.
Both low/high must be in the request payload or both must be absent from the request payload. You cannot set only 1 of the 2 low/high values.
Model
| Key | Type | Description |
|---|---|---|
| value | boolean | true to enable battery saver, false to disable battery saver |
| low | int | %charge to shutoff. Must be between 0-40 inclusive. |
| high | int | %charge to stop charging. Must be between 60-100 inclusive. |
GET Device Port Voltage and Current
Get the voltage and current for a given device port. Valid ports are 1 & 2 & 3 valid options are 0 (voltage) & 1 (current)
curl 'http://localhost/api/deviceAdcRead?port=1&option=0'
Get the voltage and current for a given device port. Valid ports are 1 & 2 & 3 valid options are 0 (voltage) & 1 (current)
POST Device Voltage Output
Example Body
{
"portNumber" : 1,
"value" : 1
}
set device voltage on the specified device port
curl -X POST http://localhost/api/deviceVoltage -d '{ "portNumber": 1, "value": 1}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> postDeviceVoltage(const char *fileName, const char *urlPath);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> deviceVoltageTask = postDeviceVoltage("deviceVoltage", "/deviceVoltage");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
deviceVoltageTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> postDeviceVoltage(const char *fileName, const char *urlPath) {
auto fileStream = std::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
const string_t &fragment = builder.to_string();
web::json::value json_v ;
json_v["portNumber"] = web::json::value::number(1);
json_v["value"] = web::json::value::number(1);
return client.request(methods::POST, U(urlPath), json_v);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Set the voltage (0 = off, 1= 12v, 2 = 15v) on the specified device port (1, 2, 3)
Model
| Key | Type | Description |
|---|---|---|
| portNumber | integer | The port number which the power will be set on. valid values are 1 and 2 and 3 |
| value | integer | The voltage to set on the specified port; valid values are 0 (off), 1 (12v), and 2 (15v) |
POST Device Sleep
Example Body
{
"duration" : 10000
}
set sleep duration
curl -X POST http://localhost/api/sleep -d '{"duration":10000}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> postSleep(const char *fileName, const char *urlPath);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> sleepTask = postSleep("sleep", "/sleep");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
sleep.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> postSleep(const char *fileName, const char *urlPath) {
auto fileStream = std::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
const string_t &fragment = builder.to_string();
web::json::value json_v ;
json_v["duration"] = web::json::value::number(10000);
return client.request(methods::POST, U(urlPath), json_v);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Put the Device in low power mode for the specified duration of time in milliseconds
Model
| Key | Type | Description |
|---|---|---|
| duration | integer | The amount of time in milliseconds to put the Device in low power mode. |
GET Device Temperature
get Device temperature celsius
curl http://localhost/api/tempRead
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> tempReadTask = getSampleCall("tempRead", "/tempRead", "");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
tempReadTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
auto fileStream = std::make_shared<ostream>();
//auto fileStream = std::__1::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
builder.append(U(queryParams));
const string_t &fragment = builder.to_string();
return client.request(methods::GET, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Response
{
"value":36.75
}
Get the Device’s temperature in celsius
GET Atmospheric Pressure
get atmospheric pressure
curl http://localhost/api/pressRead
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> pressReadTask = getSampleCall("pressRead", "/pressRead", "");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
pressReadTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
auto fileStream = std::make_shared<ostream>();
//auto fileStream = std::__1::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
builder.append(U(queryParams));
const string_t &fragment = builder.to_string();
return client.request(methods::GET, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Response
{
"value":101788.50996
}
Get atmospheric pressure surrounding the Device in daPa (decapascal). Multiply by 10 to get pascals.
For V11 and older THREADs the unit returned is hPa (hectopascal).
GET Sensor current
get sensor current
curl -X GET 'http://localhost/api/deviceAdcRead420?port=1'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> deviceAdcRead420Task = getSampleCall("deviceAdcRead420", "/deviceAdcRead420", "?port=1");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
deviceAdcRead420Task.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> getSampleCall(const char *fileName, const char *urlPath, const char *queryParams) {
auto fileStream = std::make_shared<ostream>();
//auto fileStream = std::__1::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
builder.append(U(queryParams));
const string_t &fragment = builder.to_string();
return client.request(methods::GET, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Response
{
"value":-0.00429
}
Get the current from the specified port on the Device in amps. This will temporarily configure the port for 4-20mA support, take a reading, and then set the port back to default
Query params
| Key | Type | Description |
|---|---|---|
| port | integer | The port number to read from. Valid values are 1 and 2 and 3 |
POST Device Port Mode
Example Body
{
"portNumber" : 1,
"content" : "RS232"
}
set device interface protocol on the specified device port
curl -X POST http://localhost/api/deviceRsSel -d '{ "portNumber": 1, "content": "RS232"}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> postDeviceRsSel(const char *fileName, const char *urlPath);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> deviceRsSelTask = postDeviceRsSel("deviceRsSel", "/deviceRsSel");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
deviceRsSelTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> postDeviceRsSel(const char *fileName, const char *urlPath) {
auto fileStream = std::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
const string_t &fragment = builder.to_string();
web::json::value json_v ;
json_v["portNumber"] = web::json::value::number(1);
json_v["content"] = web::json::value::string("RS232");
return client.request(methods::POST, U(urlPath), json_v);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Set the interface mode (RS232, RS485, ANALOG, USB, OFF) on the specified device port (1, 2, 3). Note USB is only supported on port 1.
Model
| Key | Type | Description |
|---|---|---|
| portNumber | integer | The port number which to set the specified interface type. |
| content | string | The interface protocol; valid values are RS232, RS485, ANALOG, USB, and OFF |
POST LED Flash
Flash the LED
curl -X POST http://localhost/api/observation
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> postNoBodySampleCall(const char *fileName, const char *urlPath);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> observationTask = postNoBodySampleCall("observation", "/observation");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
observationTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> postNoBodySampleCall(const char *fileName, const char *urlPath) {
//auto fileStream = std::__1::make_shared<ostream>();
auto fileStream = std::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
const string_t &fragment = builder.to_string();
return client.request(methods::POST, fragment);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Flash the Device LED.
GET Device Port Properties
Get Device configuration on the specified port (1, 2, 3)
curl -X GET 'http://localhost/api/configure?port=1'
Get Device configuration on the specified port (1, 2, 3)
Model
| Key | Type | Description |
|---|---|---|
| port | integer | The port number which to get the Device Port properties. |
PUT Device Port Properties
Example Body
{
"COMM_TYPE" : "RS232",
"BAUD_RATE" : 9600,
"DELIMITERS_HEX" : ["0D0A"],
"PORT" : 1234,
"HOST" : "127.0.0.1"
}
Set configuration properties on the specified port (1, 2, 3)
curl -X PUT 'http://localhost/api/configure?port=1' -H 'Content-Type: application/json' -d '{ "COMM_TYPE": "USB", "PORT": 1212, "HOST": "192.168.254.3"}'
Set configuration properties on the specified port (1, 2, 3). 1 to N number of configuration properties can be set with a single call.
Here is a table of Device Port properties that can be set.
| Key | Type | Description | Examples |
|---|---|---|---|
| COMM_TYPE | string | Serial communication interface type. | RS232, RS485, USB |
| BAUD_RATE | integer | Baud rate of the connection. | 230400, 115200, 57600, 38400, 19200, 9600, 4800, 1200 |
| DATA_BITS | integer | Data bits. | 8, 7, 6, 5 |
| PARITY_BIT | string | Parity bit. | NONE, ODD, EVEN, MARK, SPACE |
| DELIMITERS_HEX | string | End of message delimiter marker. | 0D0A, 0D |
| PORT | string | Only used in USB for RNDIS driver. | 1212 |
| HOST | string | Only used in USB for RNDIS driver. | 192.168.254.3 |
DELETE Device Port Properties
Remove device configuration properties on the specified port (1, 2, 3)
curl -X DELETE 'http://localhost/api/configure?port=1' -d '{"resources" : ["DELIMITERS_HEX"] }'
curl -X DELETE 'http://localhost/api/configure?port=1' -d '{"resources" : ["HOST", "PORT"] }'
Remove device configuration properties on the specified port (1, 2, 3)
GET Interface Priority
get Interface Priority
curl http://localhost/api/interfacePriority
Response
{
"content":"eth0"
}
Returns the network interface that is currently the preferred route.
PUT Cellular Interface Prioritized
Set cellular to be the preferred network interface to use if available
curl -X PUT 'http://localhost/api/interfacePriority/cellular'
Set cellular to be the preferred network interface to use if available. This sets ppp0 to be used as the default network route.
PUT Ethernet Interface Prioritized
Set ethernet to be the preferred network interface to use if available
curl -X PUT 'http://localhost/api/interfacePriority/ethernet'
Set ethernet to be the preferred network interface to use if available. This sets eth0 to be used as the default network route.
POST Message
Example Body
{
"portNumber" : 1,
"message" : "hello world"
}
send a message on the specified device port
curl -X POST http://localhost/api/message -d '{ "portNumber": "1", "message": "hello world"}'
#include <cpprest/http_client.h>
#include <cpprest/filestream.h>
pplx::task<void> postMsg(const char *fileName, const char *urlPath);
using namespace utility; // Common utilities like string conversions
using namespace web; // Common features like URIs.
using namespace web::http; // Common HTTP functionality
using namespace web::http::client; // HTTP client features
using namespace concurrency::streams; // Asynchronous streams
int main(int argc, char *argv[]) {
pplx::task<void> messageTask = msg("message", "/message");
// Wait for all the outstanding I/O to complete and handle any exceptions
try {
messageTask.wait();
}
catch (const std::exception &e) {
printf("Error exception:%s\n", e.what());
}
return 0;
}
pplx::task<void> postMsg(const char *fileName, const char *urlPath) {
auto fileStream = std::make_shared<ostream>();
// Open stream to output file.
pplx::task<void> requestTask = fstream::open_ostream(U(fileName)).then([=](ostream outFile) {
*fileStream = outFile;
// Create http_client to send the request.
http_client client(U("http://localhost/api/"));
// Build request URI and start the request.
uri_builder builder(U(urlPath));
const string_t &fragment = builder.to_string();
web::json::value json_v ;
json_v["portNumber"] = web::json::value::number(1);
json_v["content"] = web::json::value::string("hello world");
return client.request(methods::POST, U(urlPath), json_v);
})
// Handle response headers arriving.
.then([=](http_response response) {
printf("Received response status code:%u from:%s\n", response.status_code(), U(urlPath));
// Write response body into the file.
return response.body().read_to_end(fileStream->streambuf());
})
// Close the file stream.
.then([=](size_t) {
return fileStream->close();
});
return requestTask;
}
Send a message on the specified device port (1, 2, 3)
Model
| Key | Type | Description |
|---|---|---|
| portNumber | integer | The device port to send the message. |
| message | string | The message to send. |
GET Application Version
Get application version.
curl -X GET 'http://localhost/api/version'
Get application version.
GET Firmware Version
Get firmware version.
curl -X GET 'http://localhost/api/firmware'
Get firmware version.
GET Hardware Version
Get hardware version.
curl -X GET 'http://localhost/api/hardware'
Get hardware version.
POST Update
Update application version.
# update to the latest cloud version
curl -X POST 'http://localhost/api/update'
# update to a specific cloud version
curl -X POST 'http://localhost/api/update?version=7.18.0'
# update from a local file on the THREAD filesystem using the full absolute local path
curl -X POST 'http://localhost/api/update?file=/tmp/snoopy-1.2.3.jar'
Update application to latest version or supply optional query parameter version or file to update to a
specific version (e.g. ?version=7.18.0 or ?file=/tmp/snoopy-1.2.3.jar)
POST LED (Accessible)
Update the color of the LED on the exterior of the THREAD to either green (true) or red (false)
# set to red
curl -X POST http://localhost/api/accessible -d '{ "value": "false"}'
# set to green
curl -X POST http://localhost/api/accessible -d '{ "value": "true"}'
Update the color of the LED on the exterior of the THREAD to either green (true) or red (false)
It should be noted that the cloud connection must be set to false to take advantage of this. If the cloud connection is still enabled (true), then the health of that connection overrides this setting.
This setting is ephemeral and will not survive across a reboot or factory reset. After reboot the LED will be red
GET Cloud Connection
Show the status of the cloud connection enabled (true) or disabled (false)
curl http://localhost/api/cloudConnection
Show the status of the cloud connection enabled (true) or disabled (false)
PUT Cloud Connection
Enable or disable connection to iTwin IoT cloud
# disable connection
curl -X PUT http://localhost/api/cloudConnection -d '{ "value": "false"}'
# enable connection
curl -X PUT http://localhost/api/cloudConnection -d '{ "value": "true"}'
Enable or disable connection to iTwin IoT cloud. This will prevent the THREAD from connecting to iTwin IoT cloud. For use by customers who have their own cloud. This will make the exterior LED red which can be adjusted using the LED endpoint above.
This setting is persistent and will remain across reboots but not factory resets
GET Device Connection
Show the status of the device connection enabled (true) or disabled (false)
curl 'http://localhost/api/deviceConnection?port=2'
Show the status of the device connection enabled (true) or disabled (false)
PUT Device Connection
Enable or disable connection to a device plugged into the given physical port
# disable connection
curl -X PUT 'http://localhost/api/deviceConnection?port=3' -H 'Content-Type: application/json' -d '{ "value": false}'
# enable connection
curl -X PUT 'http://localhost/api/deviceConnection?port=3' -H 'Content-Type: application/json' -d '{ "value": true}'
Enable or disable connection to a device plugged into the given physical port. This will open the connection for the device on the given port using the defined properties on the device.
iTwin IoT Edge WebSocket API
The iTwin IoT Edge WebSocket API uses WebSockets to allow for streaming bi-directional data. This is useful for “listening” for data as it comes in from a device as well as sending data directly to the device.
Sending and Receiving Data
Example WebSocket client
from websocket import create_connection
# open the WebSocket connection to device on port 1
ws = create_connection("ws://localhost/ws/port/1")
# send over a message to the device directly
ws.send("hello")
# listen for data from the device in an infinite loop and print out
while True:
result = ws.recv()
print(result)
# close WebSocket connection when done
ws.close()
Any data that comes in from the device will be sent to the websocket client. Any data that the websocket client sends in over the connection will be sent to the device directly.
Here are the WebSocket URIs any standard WebSocket client can connect to in order to send and receive data.
| URI | Description |
|---|---|
| ws://localhost/ws/port/1 | device connected to THREAD port 1 |
| ws://localhost/ws/port/2 | device connected to THREAD port 2 |
| ws://localhost/ws/port/3 | device connected to THREAD port 3 |
Sensor Data Service
Introduction
The sensor data service offers REST APIs to third-party sensor manufacturers/data sources to integrate their sensors with iTwin IoT platform.
This document focuses on importing generic sensors into iTwin IoT and concentrates on the persona, where the sensor manufacturers will be able to push their data into sensor data service.
Entities Hierarchy
The first step to visualizing data on the platform is to add a connection to a project, then attach subsequent devices and sensors. Your network is broken up into three distinct levels of entities: Connections, Devices, and Sensors.
A connection is simply a connection to your data source. It aggregates the data from all associated devices and sensors and sends the information to the platform. They can be either hardware (cell/radio) or software (API and FTP) connections.
The device can then read the array of associated sensors and store their data, or it can house the sensor datasets being transmitted from the file transfer connection.
A sensor will physically collect the data from the field, and they also represent the data aggregation and storage node in the app.

Prerequisites
To create a Bentley account if you don’t already have one, click here and complete the registration process.
Use the following steps to integrate your sensors with the iTwin IoT platform.
- Create an application in iTwin Platform
- Obtaining Authorization Token
- Register an asset in iTwin IoT
Create an Application in iTwin Platform
You can register an application on the iTwin Platform to use the sensor data service. Selecting the Administration and Digital Twin Management API associations during registration will add the scopes to the allowed scopes field. Verify that the scopes listed below have been added, drop any other scopes that are unnecessary, choose “Service” as the application type, and save. You will see the client ID and client secret.
Required scopes: sensor-data:modify sensor-data:read users:read itwins:read itwins:modify


Obtaining Authorization Token
You can use the generated client id, client secret and scopes to obtain access token.
POST https://ims.bentley.com/connect/token
curl --location 'https://ims.bentley.com/connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=<Your client id> ' \
--data-urlencode 'client_secret=<Your client secret> ' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=sensor-data:modify sensor-data:read users:read itwins:read itwins:modify'
fetch('https://ims.bentley.com/connect/token', {
method: 'POST',
body: new URLSearchParams({
'client_id': '<Your client id> ',
'client_secret': '<Your client secret> ',
'grant_type': 'client_credentials',
'scope': 'sensor-data:modify sensor-data:read users:read itwins:read itwins:modify'
})
});
import requests
data = {
'client_id': '<Your client id> ',
'client_secret': '<Your client secret> ',
'grant_type': 'client_credentials',
'scope': 'sensor-data:modify sensor-data:read users:read itwins:read itwins:modify',
}
response = requests.post('https://ims.bentley.com/connect/token', data=data)
Register an Asset in iTwin IoT
In order to create an asset you must have at least one of the following roles assigned in User Management: Account Administrator, Co-Administrator, or CONNECT Services Administrator. Then you can register an asset in iTwin IoT and make sure to enable Allow external team members.
Add the user that got created, while registering an application in iTwin Platform
(example@apps.imsoidc.bentley.com) into the create asset with IoT Creator role.


Create Entites
There are two methods by which you can create connection, device(s), and sensor(s):
- Simple approach: Allows you to create connection, device(s), and sensor(s) all at once
- Advanced approach: Allows you to create connection before adding device(s) & sensor(s)
Create Connection, Device(s), and Sensor(s)
You can use this API to create new connection, devices and sensors all together.
If none of the provided device template/sensor template meet the requirements, you can use IMPORT_DEVICE_SDE for device and GENERIC_SENSOR_SDE for sensor.
POST https://iiot.bentley.com/api/integrations/integrate?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537
curl --location 'https://iiot.bentley.com/api/integrations/integrate?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Your access token>' \
--data '{
"integration": {
"changeState": "new",
"devices": [
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "IMPORT_DEVICE_SDE",
"NAME": "Device-1"
},
"sensors": [
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"NAME": "Sensor-1",
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
}
}
},
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"NAME": "Sensor-2",
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
}
}
}
]
}
]
}
}
'
fetch('https://iiot.bentley.com/api/integrations/integrate?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <Your access token>'
},
body: JSON.stringify({
"integration": {
"changeState": "new",
"devices": [
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "IMPORT_DEVICE_SDE",
"NAME": "Device-1"
},
"sensors": [
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"NAME": "Sensor-1",
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
}
}
},
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"NAME": "Sensor-2",
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
}
}
}
]
}
]
}
})
});
import requests
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer <Your access token>',
}
params = {
'projectIds': '6ee68656-ce20-452c-a4e6-b6b3a3b55537',
}
json_data = {
"integration": {
"changeState": "new",
"devices": [
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "IMPORT_DEVICE_SDE",
"NAME": "Device-1"
},
"sensors": [
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"NAME": "Sensor-1",
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
}
}
},
{
"changeState": "new",
"props": {
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"NAME": "Sensor-2",
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
}
}
}
]
}
]
}
}
response = requests.post('https://iiot.bentley.com/api/integrations/integrate', params=params, headers=headers, json=json_data)
Example response (200 OK)
{
"integration": {
"node": {
"props": {
"SERIAL_NO": "B6627B",
"CREATION_DATE": "2024-03-05T10:27:22.553+00:00",
"INTEGRATION_ID": "NODE_API_SDE",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
"NAME": "Data Source API Import B6627B"
},
"id": "/api/B6627B/node",
"type": "DYNAMIC"
},
"devices": [
{
"device": {
"props": {
"SIZE": 2,
"CREATION_DATE": "2024-03-05T10:27:22.585+00:00",
"NODE_ID": "/api/B6627B/node",
"INTEGRATION_ID": "IMPORT_DEVICE_SDE",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
"NAME": "Device-1"
},
"id": "/api/B6627B/node/dynamic/BED858/device",
"type": "DYNAMIC"
},
"sensors": [
{
"props": {
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"CREATION_DATE": "2024-03-05T10:27:22.615+00:00",
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
},
"NODE_ID": "/api/B6627B/node",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
"NAME": "Sensor-1"
},
"id": "/api/B6627B/node/dynamic/BED858/device/B2D116/sensor",
"observationType": "DYNAMIC",
"type": "DYNAMIC",
"device": "/api/B6627B/node/dynamic/BED858/device"
},
{
"props": {
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"CREATION_DATE": "2024-03-05T10:27:22.626+00:00",
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
},
"NODE_ID": "/api/B6627B/node",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
"NAME": "Sensor-2"
},
"id": "/api/B6627B/node/dynamic/BED858/device/5AD71D/sensor",
"observationType": "DYNAMIC",
"type": "DYNAMIC",
"device": "/api/B6627B/node/dynamic/BED858/device"
}
]
}
]
}
}
Example response (400 Bad Request)
{
"error": {
"code": "InvalidProcessIntegrationRequest",
"message": "Minimum request parameters not provided, new Devices and Sensors must have a valid `props.INTEGRATION_ID` set."
}
}
Request parameters
| Name | Required? | Description |
|---|---|---|
| projectIds | Yes | iTwin IoT asset id |
Request definitions
| Name | Type | Required? | Description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| integration[changeState] | string | No | To create a new connection (Although this property is not necessary, failing to provide will result in a 422 error) | |||||||||
| devices[].changeState/ sensors[].changeState | string | No | To create devices and sensors set changeState to “new”; to update set to “update” to remove set to “remove”. | |||||||||
| INTEGRATION_ID | string | Yes | Device id or sensor id obtained from the devices types /sensor types endpoint. | |||||||||
| NAME | string | No | Device or sensor name | |||||||||
| LOCATION | object | No | X, Y & Z coordinates | |||||||||
| UNKNOWN_UNITS | object | No | Unknow units | |||||||||
| UNKNOWN_METRICS | object | No | Arbitrary metrics | |||||||||
| KNOWN_UNITS | object | No | Known units | |||||||||
| KNOWN_METRICS | object | No | Known metrics | |||||||||
| SERIAL_NO | string | No | Sensor serial no | |||||||||
| NOTES | string | No | Additional information of devices and sensors | |||||||||
| refId | string | No | Unique entity id can be used to update or delete devices or sensors. |
Generic sensors
If you have sensors that you would like to import into iTwin IoT, but the sensors don’t match any of the existing iTwin IoT Sensor templates. In this scenario GENERIC_SENSOR_SDE can be set to sensors props.INTEGRATION_ID property.
Generic sensors MUST have their metrics and units defined on the sensor during the time of creation. Most scenarios which opt to use a generic sensor use free form custom string for both metrics and unit.
Example of unknown metric and unknown unit
{
"changeState": "new",
"refId": "{{$guid}}",
"props": {
"NAME": "Unknown metrics and units",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"UNKNOWN_UNITS": {
"0": "abcUnit"
},
"UNKNOWN_METRICS": {
"0": "abc"
}
}
}
Example known metric and known unit:
Other scenarios might use both known metrics and known units however the ordering of metrics list slightly differs from a defined sensor template this might look like the sample below
{
"changeState": "new",
"refId": "{{$guid}}",
"props": {
"NAME": "Known metrics and units",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"KNOWN_UNITS": {
"0": "Hz",
"1": "mm"
},
"KNOWN_METRICS": {
"0": "f",
"1" : "cum_distance"
}
}
}
Example unknown metric and known unit: Final scenarios might use known units with unknown metrics.
{
"changeState": "new",
"refId": "{{$guid}}",
"props": {
"NAME": "Known metrics and units",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"KNOWN_UNITS": {
"0": "Hz",
"1": "mm"
},
"UNKNOWN_METRICS": {
"0": "abc",
"1" : "def"
}
}
}
Upload Sensor Observations
To upload readings into specified sensor you require sensor id, timestamp at observation recorded and an object values containing required supported metrics with a key-value. You can find the required metrics along with units using Metrics and Units.
POST https://iiot.bentley.com/api/data/upload
curl --location 'https://iiot.bentley.com/api/data/upload' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Your access token> ' \
--data '{
"observations": [
{
"sensorId": "/api/B08851/node/dynamic/AB10E2/device/D76619/sensor",
"timestamp": "2024-02-20T04:35:23.653Z",
"values": {
"Alignment_Left_SD": 0.23,
"Alignment_Right_SD": 0.84
}
},
]
}'
fetch('https://iiot.bentley.com/api/data/upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <Your access token> '
},
body: '{"observations":[{"sensorId":"/api/B08851/node/dynamic/AB10E2/device/D76619/sensor","timestamp":"2024-02-20T04:35:23.653Z","values":{"Alignment_Left_SD":0.23,"Alignment_Right_SD":0.84}},]}'
});
import requests
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer <Your access token> ',
}
data = '{"observations":[{"sensorId":"/api/B08851/node/dynamic/AB10E2/device/D76619/sensor","timestamp":"2024-02-20T04:35:23.653Z","values":{"Alignment_Left_SD":0.23,"Alignment_Right_SD":0.84}},]}'
response = requests.post('https://iiot.bentley.com/api/data/upload', headers=headers, data=data)
Example Response (202 Accepted): This response indicates the observation was successfully uploaded.
{
"status": "ACCEPTED"
}
Example Response (400 Bad Request): This response indicates that request lacks valid payload (request body).
{
"errors": [
{
"message": "must not be empty",
"path": "observations"
}
]
}
Request definitions
| Name | Type | Required? | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| sensorId | string | Yes | Sensor id | ||||||||
| timestamp | string | Yes | The date and time of the recorded observation in ISO date format | ||||||||
| values | object | Yes | Object containing all required metrics with corresponding value |
Advanced Connection Creation
To create a new connection via iTwin IoT open previously created asset and click on Connectivity tab and then on +Connection.

Choose Data Source option, enter Serial Number which is a unique id of underlying datasource system you want to associate a connection (This may go by different names across integrators; for instance, Urban io refers to it as Location Id) and click on Next button.

Enter connection URL and Authorization Token to form initial platform to platform connection. Input latitude, longitude, elevation and notes if needed and click on Apply button.

Advanced Device(s), and Sensor(s) Creation
Create new device(s), and sensor(s) under a connection.
The connection id that you generated in iTwin IoT is required. It is also necessary to have the device and sensor INTEGRATION_ID, which is obtained from the device template and sensor template APIs.
POST https://iiot.bentley.com/api/integrations/integrate
curl --location 'https://iiot.bentley.com/api/integrations/integrate?projectIds=<Your asset id>' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <Your access token>' \
--data '
{
"integration": {
"nodeId": "/api/B08851/node",
"devices": [
{
"changeState": "new",
"refId": "{{$guid}}",
"props": {
"INTEGRATION_ID": "IMPORT_DEVICE_SDE",
"NAME": "Test Device",
"LOCATION": {
"type": "Point",
"coordinates": [
174.7805678,
-41.27834066,
20
]
},
"NOTES":"Demo device"
},
"sensors": [
{
"changeState": "new",
"refId": "{{$guid}}",
"props": {
"NAME": "Test Sensor",
"SERIAL_NO":"1234",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"LOCATION": {
"type": "Point",
"coordinates": [
174.7805678,
-41.25834066,
65
]
},
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
},
"NOTES":"Generic sensor"
}
},
]
},
]
}
}
'
fetch('https://iiot.bentley.com/api/integrations/integrate?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer '
},
body: '{"integration":{"nodeId":"/api/B08851/node","devices":[{"changeState":"new","refId":"/api/13DC2F/node-test-device","props":{"INTEGRATION_ID":"IMPORT_DEVICE_SDE","NAME":"TestDevice","LOCATION":{"type":"Point","coordinates":[174.7805678,-41.27834066,20]},"NOTES":"Demodevice"},"sensors":[{"changeState":"new","refId":"/api/13DC2F/node-test-sensor","props":{"NAME":"TestSensor","SERIAL_NO":"1234","INTEGRATION_ID":"GENERIC_SENSOR_SDE","LOCATION":{"type":"Point","coordinates":[174.7805678,-41.25834066,65]},"UNKNOWN_UNITS":{"0":"millimeter","1":"millimeter"},"UNKNOWN_METRICS":{"0":"Alignment_Left_SD","1":"Alignment_Right_SD"},"NOTES":"Genericsensor"}}]}]}}'
});
import requests
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ',
}
params = {
'projectIds': '6ee68656-ce20-452c-a4e6-b6b3a3b55537',
}
data = '{"integration":{"nodeId":"/api/B08851/node","devices":[{"changeState":"new","refId":"/api/13DC2F/node-test-device","props":{"INTEGRATION_ID":"IMPORT_DEVICE_SDE","NAME":"TestDevice","LOCATION":{"type":"Point","coordinates":[174.7805678,-41.27834066,20]},"NOTES":"Demodevice"},"sensors":[{"changeState":"new","refId":"/api/13DC2F/node-test-sensor","props":{"NAME":"TestSensor","SERIAL_NO":"1234","INTEGRATION_ID":"GENERIC_SENSOR_SDE","LOCATION":{"type":"Point","coordinates":[174.7805678,-41.25834066,65]},"UNKNOWN_UNITS":{"0":"millimeter","1":"millimeter"},"UNKNOWN_METRICS":{"0":"Alignment_Left_SD","1":"Alignment_Right_SD"},"NOTES":"Genericsensor"}}]}]}}'
response = requests.post('https://iiot.bentley.com/api/integrations/integrate', params=params, headers=headers, data=data)
Request parameters
| Name | Required? | Description |
|---|---|---|
| projectIds | Yes | iTwin IoT asset id |
Request definitions
| Name | Type | Required? | Description | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| nodeId | string | Yes | connection id | |||||||||
| changeState | string | No | To create an entity set changeState to “new”; to update set to “update” to remove set to “remove”. | |||||||||
| INTEGRATION_ID | string | Yes | Device id or sensor id obtained from the devices types /sensor types endpoint. | |||||||||
| NAME | string | No | Device or sensor name | |||||||||
| LOCATION | object | No | X, Y & Z coordinates | |||||||||
| UNKNOWN_UNITS | object | No | Unknow units | |||||||||
| UNKNOWN_METRICS | object | No | Arbitrary metrics | |||||||||
| KNOWN_UNITS | object | No | Known units | |||||||||
| KNOWN_METRICS | object | No | Known metrics | |||||||||
| SERIAL_NO | string | No | Sensor serial no | |||||||||
| NOTES | string | No | Additional information of devices and sensors | |||||||||
| refId | string | No | Unique entity id can be used to update or delete devices or sensors. |
Example Response (200 Created):
{
"integration": {
"node": {
"props": {
"LOCATION": {
"type": "Point",
"coordinates": [
-60.0,
90.0,
123.0
]
},
"AUTH_TOKEN": "test auth",
"SERIAL_NO": "12345",
"CREATION_DATE": "2024-02-20T12:50:57.069+00:00",
"INTEGRATION_ID": "NODE_API_SDE",
"URL": "test url",
"NAME": "test node",
"NOTES": "test notes",
"EXTERNAL_ID": "12345",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA"
},
"id": "/api/B08851/node",
"type": "DYNAMIC"
},
"devices": [
{
"device": {
"props": {
"LOCATION": {
"type": "Point",
"coordinates": [
174.7805678,
-41.27834066,
20.0
]
},
"SERIAL_NO": "/api/13DC2F/node-test-device",
"SIZE": 1,
"CREATION_DATE": "2024-02-20T12:51:16.418+00:00",
"NODE_ID": "/api/B08851/node",
"INTEGRATION_ID": "IMPORT_DEVICE_SDE",
"NAME": "Test Device",
"NOTES": "Demo device",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA"
},
"id": "/api/B08851/node/dynamic/6B7514/device",
"type": "DYNAMIC"
},
"sensors": [
{
"props": {
"LOCATION": {
"type": "Point",
"coordinates": [
174.7805678,
-41.25834066,
65.0
]
},
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"NOTES": "Generic sensor",
"SERIAL_NO": "/api/13DC2F/node-test-sensor",
"CREATION_DATE": "2024-02-20T12:51:16.450+00:00",
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
},
"NODE_ID": "/api/B08851/node",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
"NAME": "Test Sensor"
},
"id": "/api/B08851/node/dynamic/6B7514/device/B059A6/sensor",
"observationType": "DYNAMIC",
"type": "DYNAMIC",
"device": "/api/B08851/node/dynamic/6B7514/device"
}
]
}
]
}
}
Example Response (401 Unauthorized): This response indicates that request lacks valid authentication credentials. Access token might not been provided, issued by the wrong issuer, does not have required scopes or request headers were malformed.
{
"errors": [
{
"message": "Token has expired on Tue Feb 20 04:12:01 PST 2024",
"path": "Authorization"
}
]
}
Example Response (402 Unprocessable Entity ): Invalid request review the request body. The following error messages displayed if required properties (nodeId or INTEGRATION_ID) missing.
{
"error": {
"code": "InvalidProcessIntegrationRequest",
"message": "Cannot process integration ensure the provided nodeId is valid."
}
}
{
"error": {
"code": "InvalidProcessIntegrationRequest",
"message": "Minimum request parameters not provided, new Devices and Sensors must have a valid `props.INTEGRATION_ID` set."
}
}
Choose Device Template
You can see the list of supported Device templates and choose the suitable device template with the help of this REST API.
GET https://iiot.bentley.com/api/devices/types?excludeLegacy=true
curl --location 'https://iiot.bentley.com/api/devices/types?excludeLegacy=true' \
--header 'Authorization: Bearer <Your access token>'
fetch('https://iiot.bentley.com/api/devices/types?excludeLegacy=true', {
headers: {
'Authorization': 'Bearer <Your access token>'
}
});
import requests
headers = {
'Authorization': 'Bearer <Your access token>',
}
params = {
'excludeLegacy': 'true',
}
response = requests.get('https://iiot.bentley.com/api/devices/types', params=params, headers=headers)
In the response you can see the id field of the device template. Which will be needed to create a new device.
Example Response:
{
"deviceTypes": [
{
"id": "IMPORT_DEVICE_SDE",
"name": "Import Device",
"icon": "smicon-device-import",
"iconUnicode": "",
"manufacturer": "sensemetrics",
"minThreadVersion": "1.0",
"editOnly": false,
"isDynamic": true
}
]
}
Choose Sensor Template
You can see the list of supported Sensor templates and choose the suitable sensor template with the help of this REST API.
GET https://iiot.bentley.com/api/sensors/types?excludeLegacy=true
curl --location 'https://iiot.bentley.com/api/sensors/types?excludeLegacy=true' \
--header 'Authorization: Bearer <Your access token>'
In the response you can see the id field of the sensor template. Which will be needed to create a new sensor.
fetch('https://iiot.bentley.com/api/sensors/types?excludeLegacy=true', {
headers: {
'Authorization': 'Bearer <Your access token>'
}
});
import requests
headers = {
'Authorization': 'Bearer <Your access token>',
}
params = {
'excludeLegacy': 'true',
}
response = requests.get('https://iiot.bentley.com/api/sensors/types', params=params, headers=headers)
Example Response:
{
" sensorTypes": [
{
"id": "GENERIC_SENSOR_SDE",
"name": "GENERIC_SENSOR_SDE",
"icon": "smicon-circle",
"iconUnicode": "",
"metricsAndUnits": [
{
"metric": "GENERIC",
"id": "GENERIC",
"displayName": "generic",
"defaultUnit": "GENERIC",
"units": [
{
"unit": "GENERIC",
"id": "GENERIC"
}
]
}
],
"isDynamic": true
},
]
}
Metrics and Units
To view the catalog of all standard/known metrics and units use this REST request. For example, the temperature sensor above TEMPERATURE_SDE Stores readings for a single metric Temperature which has a metric Id of T the supported units for this metric are Celsius © kelvin (K) and Fahrenheit (F).
GET https://iiot.bentley.com/api/metrics/types?excludeLegacy=true
curl --location 'https://iiot.bentley.com/api/metrics/types?excludeLegacy=true' \
--header 'Authorization: Bearer <Your access token>'
fetch('https://iiot.bentley.com/api/metrics/types?excludeLegacy=true', {
headers: {
'Authorization': 'Bearer <Your access token>'
}
});
import requests
headers = {
'Authorization': 'Bearer <Your access token>',
}
params = {
'excludeLegacy': 'true',
}
response = requests.get('https://iiot.bentley.com/api/metrics/types', params=params, headers=headers)
Example Response:
{
"standard": true,
"metrics": [
{
"metric": "T",
"id": "T",
"displayName": "Temperature",
"defaultUnit": "C",
"units": [
{
"unit": "C",
"id": "C",
"displayName": "celsius",
"notation": "°C"
},
{
"unit": "K",
"id": "K",
"displayName": "kelvin",
"notation": "°K"
},
{
"unit": "F",
"id": "F",
"displayName": "fahrenheit",
"notation": "°F"
}
]
},
]
}
Obtain Required Metrics and Supported Units For a Sensor
POST https://iiot.bentley.com/api/sensors/metrics
curl --location 'https://iiot.bentley.com/api/sensors/metrics?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537&filters=raw' \
--header 'Authorization: Bearer <Your access token> ' \
--header 'Content-Type: application/json' \
--data '{
"ids": [
"/api/B08851/node/dynamic/AB10E2/device/D76619/sensor"
]
}'
fetch('https://iiot.bentley.com/api/sensors/metrics?projectIds=6ee68656-ce20-452c-a4e6-b6b3a3b55537', {
method: 'POST',
headers: {
'Authorization': 'Bearer <Your access token> ',
'Content-Type': 'application/json'
},
body: JSON.stringify({
'ids': [
'/api/B08851/node/dynamic/AB10E2/device/D76619/sensor'
]
})
});
import requests
headers = {
'Authorization': 'Bearer <Your access token> ',
'Content-Type': 'application/json',
}
params = {
'projectIds': '6ee68656-ce20-452c-a4e6-b6b3a3b55537'
}
json_data = {
'ids': [
'/api/B08851/node/dynamic/AB10E2/device/D76619/sensor',
],
}
response = requests.post('https://iiot.bentley.com/api/sensors/metrics', params=params, headers=headers, json=json_data)
Request parameters
| Name | Required? | Description |
|---|---|---|
| projectIds | Yes | iTwin IoT asset id |
According to the response, the Observations values object for sensor /api/5C2AAF/node/dynamic/9D1284/device/95A76E/sensor should have two metricIds Alignment_Left_SD and Alignment_Right_SD.
Example Response (200 Ok)
{
"metricsBySensor": {
"/api/13DC2F/node/dynamic/AB10E2/device/D76619/sensor": [
{
"defaultUnit": "millimeter",
"hidden": false,
"displayName": "Alignment_Left_SD",
"notation": "Alignment_Left_SD",
"name": "Alignment_Left_SD",
"id": "Alignment_Left_SD",
"units": [
{
"notation": "millimeter",
"name": "millimeter",
"id": "millimeter"
}
]
},
{
"defaultUnit": "millimeter",
"hidden": false,
"displayName": "Alignment_Right_SD",
"notation": "Alignment_Right_SD",
"name": "Alignment_Right_SD",
"id": "Alignment_Right_SD",
"units": [
{
"notation": "millimeter",
"name": "millimeter",
"id": "millimeter"
}
]
}
]
}
}
Get Integrations
This API can be used to retrieve information like all devices and sensors of given connection id.
POST https://iiot.bentley.com/api/integrations//api/13DC2F/node
curl --location 'https://iiot.bentley.com/api/integrations//api/13DC2F/node' \
--header 'Authorization: Bearer <your access_token>' \
--header 'Content-Type: application/json' \
--data ''
fetch('https://iiot.bentley.com/api/integrations//api/13DC2F/node', {
method: 'POST',
headers: {
'Authorization': 'Bearer <your access_token>',
'Content-Type': 'application/json'
},
body: ''
});
import requests
headers = {
'Authorization': 'Bearer <your access_token>',
'Content-Type': 'application/json',
}
response = requests.post('https://iiot.bentley.com/api/integrations//api/13DC2F/node', headers=headers)
Example Response (200 Ok)
{
"integration": {
"node": {
"props": {
"LOCATION": {
"type": "Point",
"coordinates": [
null,
null,
null
]
},
"LAST_MODIFIED_BY": "ajinkya.gholape@bentley.com",
"SERIAL_NO": "13DC2F",
"OBSERVATION_COUNT": 7,
"CREATION_DATE": "2024-02-19T12:50:48.705+00:00",
"LAST_MODIFIED_DATE": "2024-02-19T12:52:41.085+00:00",
"INTEGRATION_ID": "NODE_API_SDE",
"NAME": "UI-13DC2F",
"NOTES": "Created through UI",
"EXTERNAL_ID": "1234",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA"
},
"id": "/api/13DC2F/node",
"type": "DYNAMIC"
},
"devices": [
{
"device": {
"props": {
"LOCATION": {
"type": "Point",
"coordinates": [
174.7805678,
-41.27834066,
20.0
]
},
"SERIAL_NO": "/api/13DC2F/node-test-device",
"SIZE": 1,
"CREATION_DATE": "2024-02-20T12:47:40.179+00:00",
"NODE_ID": "/api/13DC2F/node",
"INTEGRATION_ID": "IMPORT_DEVICE_SDE",
"NAME": "Test Device",
"NOTES": "Demo device",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA"
},
"id": "/api/13DC2F/node/dynamic/62D7C7/device",
"type": "DYNAMIC"
},
"sensors": [
{
"props": {
"LOCATION": {
"type": "Point",
"coordinates": [
-75.68640693450183,
40.06649152407075,
119.52999339815064
]
},
"NOTES": "Generic sensor",
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"SERIAL_NO": "/api/13DC2F/node-test-sensor",
"CREATION_DATE": "2024-02-20T12:47:40.206+00:00",
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
},
"NODE_ID": "/api/13DC2F/node",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
"NAME": "Test Sensor"
},
"id": "/api/13DC2F/node/dynamic/62D7C7/device/F55FE5/sensor",
"observationType": "DYNAMIC",
"type": "DYNAMIC",
"device": "/api/13DC2F/node/dynamic/62D7C7/device"
}
]
},
{
"device": {
"props": {
"LOCATION": {
"type": "Point",
"coordinates": [
174.7805678,
-41.27834066,
20.0
]
},
"SERIAL_NO": "/api/13DC2F/node-device-51",
"SIZE": 1,
"CREATION_DATE": "2024-02-20T12:45:13.203+00:00",
"NODE_ID": "/api/13DC2F/node",
"INTEGRATION_ID": "IMPORT_DEVICE_SDE",
"NAME": "Device-51",
"NOTES": "Demo device",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA"
},
"id": "/api/13DC2F/node/dynamic/DB01AD/device",
"type": "DYNAMIC"
},
"sensors": [
{
"props": {
"LOCATION": {
"type": "Point",
"coordinates": [
-75.69059769203692,
40.064844285778456,
111.5061861896762
]
},
"NOTES": "Generic sensor",
"UNKNOWN_UNITS": {
"0": "millimeter",
"1": "millimeter"
},
"SERIAL_NO": "/api/13DC2F/node-sensor-51",
"CREATION_DATE": "2024-02-20T12:45:13.229+00:00",
"UNKNOWN_METRICS": {
"0": "Alignment_Left_SD",
"1": "Alignment_Right_SD"
},
"NODE_ID": "/api/13DC2F/node",
"INTEGRATION_ID": "GENERIC_SENSOR_SDE",
"PROJECT_ID": "65CB51CE8E199448BB4D4BDA",
"NAME": "Sensor-51"
},
"id": "/api/13DC2F/node/dynamic/DB01AD/device/1FFCBB/sensor",
"observationType": "DYNAMIC",
"type": "DYNAMIC",
"device": "/api/13DC2F/node/dynamic/DB01AD/device"
}
]
}
]
}
}