How to create user-defined functions in Azure Digital Twins
Important
A new version of the Azure Digital Twins service has been released. In light of the new service's expanded capabilities, the original Azure Digital Twins service (described in this documentation set) has been retired.
To view the documentation for the new service, visit the active Azure Digital Twins Documentation.
User-defined functions enable users to configure custom logic to be executed from incoming telemetry messages and spatial graph metadata. Users can also send events to predefined endpoints.
This guide walks through an example demonstrating how to detect and alert on any reading that exceeds a certain temperature from received temperature events.
In the examples below, YOUR_MANAGEMENT_API_URL
refers to the URI of the Digital Twins APIs:
https://YOUR_INSTANCE_NAME.YOUR_LOCATION.azuresmartspaces.net/management/api/v1.0
Name | Replace with |
---|---|
YOUR_INSTANCE_NAME | The name of your Azure Digital Twins instance |
YOUR_LOCATION | The region your instance is hosted on |
Client library reference
Functions available as helper methods in the user-defined functions runtime are listed in the client library reference document.
Create a matcher
Matchers are graph objects that determine what user-defined functions run for a given telemetry message.
Valid matcher condition comparisons:
Equals
NotEquals
Contains
Valid matcher condition targets:
Sensor
SensorDevice
SensorSpace
The following example matcher evaluates to true on any sensor telemetry event with "Temperature"
as its data type value. You can create multiple matchers on a user-defined function by making an authenticated HTTP POST request to:
YOUR_MANAGEMENT_API_URL/matchers
With JSON body:
{
"id": "3626464-f39b-46c0-d9b0c-436aysj55",
"name": "Temperature Matcher",
"spaceId": "YOUR_SPACE_IDENTIFIER",
"conditions": [
{
"id": "ag7gq35cfu3-e15a-4e9c-6437-sj6w68sy44s",
"target": "Sensor",
"path": "$.dataType",
"value": "\"Temperature\"",
"comparison": "Equals"
}
]
}
Value | Replace with |
---|---|
YOUR_SPACE_IDENTIFIER | Which server region your instance is hosted on |
Create a user-defined function
Creating a user-defined function involves making a multipart HTTP request to the Azure Digital Twins Management APIs.
Note
Multipart requests typically require three pieces:
- A Content-Type header:
application/json; charset=utf-8
multipart/form-data; boundary="USER_DEFINED_BOUNDARY"
- A Content-Disposition:
form-data; name="metadata"
- The file content to upload
Content-Type and Content-Disposition will vary depending on use scenario.
Multipart requests can be made programmatically (through C#), through a REST client, or tool such as Postman. REST client tools may have varying levels of support for complex multipart requests. Configuration settings may also vary slightly from tool to tool. Verify which tool is best suited for your needs.
Important
Multipart requests made to the Azure Digital Twins Management APIs typically have two parts:
- Blob metadata (such as an associated MIME type) that's declared by Content-Type and/or Content-Disposition
- Blob contents which include the unstructured contents of a file to be uploaded
Neither of the two parts is required for PATCH requests. Both are required for POST or create operations.
The Occupancy Quickstart source code contains complete C# examples demonstrating how to make multipart requests against the Azure Digital Twins Management APIs.
After the matchers are created, upload the function snippet with the following authenticated multipart HTTP POST request to:
YOUR_MANAGEMENT_API_URL/userdefinedfunctions
Use the following body:
--USER_DEFINED_BOUNDARY
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name="metadata"
{
"spaceId": "YOUR_SPACE_IDENTIFIER",
"name": "User Defined Function",
"description": "The contents of this udf will be executed when matched against incoming telemetry.",
"matchers": ["YOUR_MATCHER_IDENTIFIER"]
}
--USER_DEFINED_BOUNDARY
Content-Disposition: form-data; name="contents"; filename="userDefinedFunction.js"
Content-Type: text/javascript
function process(telemetry, executionContext) {
// Code goes here.
}
--USER_DEFINED_BOUNDARY--
Value | Replace with |
---|---|
USER_DEFINED_BOUNDARY | A multipart content boundary name |
YOUR_SPACE_IDENTIFIER | The space identifier |
YOUR_MATCHER_IDENTIFIER | The ID of the matcher you want to use |
Verify that the headers include:
Content-Type: multipart/form-data; boundary="USER_DEFINED_BOUNDARY"
.Verify that the body is multipart:
- The first part contains the required user-defined function metadata.
- The second part contains the JavaScript compute logic.
In the USER_DEFINED_BOUNDARY section, replace the spaceId (
YOUR_SPACE_IDENTIFIER
) and matchers (YOUR_MATCHER_IDENTIFIER
) values.Verify that the JavaScript user-defined function is supplied as
Content-Type: text/javascript
.
Example functions
Set the sensor telemetry reading directly for the sensor with data type Temperature, which is sensor.DataType
:
function process(telemetry, executionContext) {
// Get sensor metadata
var sensor = getSensorMetadata(telemetry.SensorId);
// Retrieve the sensor value
var parseReading = JSON.parse(telemetry.Message);
// Set the sensor reading as the current value for the sensor.
setSensorValue(telemetry.SensorId, sensor.DataType, parseReading.SensorValue);
}
The telemetry parameter exposes the SensorId and Message attributes, corresponding to a message sent by a sensor. The executionContext parameter exposes the following attributes:
var executionContext = new UdfExecutionContext
{
EnqueuedTime = request.HubEnqueuedTime,
ProcessorReceivedTime = request.ProcessorReceivedTime,
UserDefinedFunctionId = request.UserDefinedFunctionId,
CorrelationId = correlationId.ToString(),
};
In the next example, we log a message if the sensor telemetry reading surpasses a predefined threshold. If your diagnostic settings are enabled on the Azure Digital Twins instance, logs from user-defined functions are also forwarded:
function process(telemetry, executionContext) {
// Retrieve the sensor value
var parseReading = JSON.parse(telemetry.Message);
// Example sensor telemetry reading range is greater than 0.5
if(parseFloat(parseReading.SensorValue) > 0.5) {
log(`Alert: Sensor with ID: ${telemetry.SensorId} detected an anomaly!`);
}
}
The following code triggers a notification if the temperature level rises above the predefined constant:
function process(telemetry, executionContext) {
// Retrieve the sensor value
var parseReading = JSON.parse(telemetry.Message);
// Define threshold
var threshold = 70;
// Trigger notification
if(parseInt(parseReading) > threshold) {
var alert = {
message: 'Temperature reading has surpassed threshold',
sensorId: telemetry.SensorId,
reading: parseReading
};
sendNotification(telemetry.SensorId, "Sensor", JSON.stringify(alert));
}
}
For a more complex user-defined function code sample, read the Occupancy quickstart.
Create a role assignment
Create a role assignment for the user-defined function to run under. If no role assignment exists for the user-defined function, it won't have the proper permissions to interact with the Management API or have access to perform actions on graph objects. Actions that a user-defined function may perform are specified and defined via role-based access control within the Azure Digital Twins Management APIs. For example, user-defined functions can be limited in scope by specifying certain roles or certain access control paths. For more information, read the Role-based access control documentation.
Query the System API for all roles to get the role ID you want to assign to your user-defined function. Do so by making an authenticated HTTP GET request to:
YOUR_MANAGEMENT_API_URL/system/roles
Keep the desired role ID. It will be passed as the JSON body attribute roleId (
YOUR_DESIRED_ROLE_IDENTIFIER
) below.objectId (
YOUR_USER_DEFINED_FUNCTION_ID
) will be the user-defined function ID that was created earlier.Find the value of path (
YOUR_ACCESS_CONTROL_PATH
) by querying your spaces withfullpath
.Copy the returned
spacePaths
value. You'll use that below. Make an authenticated HTTP GET request to:YOUR_MANAGEMENT_API_URL/spaces?name=YOUR_SPACE_NAME&includes=fullpath
Value Replace with YOUR_SPACE_NAME The name of the space you wish to use Paste the returned
spacePaths
value into path to create a user-defined function role assignment by making an authenticated HTTP POST request to:YOUR_MANAGEMENT_API_URL/roleassignments
With JSON body:
{ "roleId": "YOUR_DESIRED_ROLE_IDENTIFIER", "objectId": "YOUR_USER_DEFINED_FUNCTION_ID", "objectIdType": "YOUR_USER_DEFINED_FUNCTION_TYPE_ID", "path": "YOUR_ACCESS_CONTROL_PATH" }
Value Replace with YOUR_DESIRED_ROLE_IDENTIFIER The identifier for the desired role YOUR_USER_DEFINED_FUNCTION_ID The ID for the user-defined function you want to use YOUR_USER_DEFINED_FUNCTION_TYPE_ID The ID specifying the user-defined function type ( UserDefinedFunctionId
)YOUR_ACCESS_CONTROL_PATH The access control path
Tip
Read the article How to create and manage role assignments for more information about user-defined function Management API operations and endpoints.
Send telemetry to be processed
The sensor defined in the spatial intelligence graph sends telemetry. In turn, the telemetry triggers the execution of the user-defined function that was uploaded. The data processor picks up the telemetry. Then, an execution plan is created for the invocation of the user-defined function.
- Retrieve the matchers for the sensor the reading was generated from.
- Depending on what matchers were evaluated successfully, retrieve the associated user-defined functions.
- Execute each user-defined function.
Next steps
Learn how to create Azure Digital Twins endpoints to send events to.
For more details about routing in Azure Digital Twins, read Routing events and messages.
Review the client library reference documentation.