Skip to content

Inline Insight Functions

ExoSense Administrators have three options for adding additional Insight modules to an ExoSense application:

  • Third-party Insights from Exchange
  • Create custom inline functions from within ExoSense
  • Building a hosted external web service

Once insight functions have been enabled, end-users with the Asset Management permission can then use these functions in their asset configuration.

This document covers how to create and use custom inline Insight modules and functions from within ExoSense.

Custom Inline Insights

Advanced Topic: Building custom Inline Insights typically is an exercise typically done by a developer with some software background.

Custom Insights can be created and managed within an ExoSense application itself. These 'inline' functions operate natively in the data pipeline and do not require any external services.

Access to Custom Insights are controlled by your ExoSense Tier and management is available by administrative user permission.

Custom Insight Function Management

User Experience

An end user who has the Asset Management permission can use these functions in their pipeline configuration for the asset. They will be provided with a list of modules and functions to choose from including the custom functions. Users are then able to choose matching signals and define any constants.

Example showing a custom function configuration

Creating Modules

Within ExoSense's INSIGHTS tab, you may define new modules for your collections of custom functions.

Create a Insight Module

Creating Functions

Once you have a module, you can create and edit functions, which includes giving it a name, description, type (Transform or Rule), defining Inputs, Outputs, Constants and the actual function logic.

Create a new function

Duplicating

Functions can also be quickly duplicated, which provides a quick place to start to generate a new similar function.

Properties

Inputs, Outputs, and Constants must be defined. These properties inform and guide users who are configuring the specific function for an asset. If a function is mean to operate only on Temperature data, than the input definitions allow you to limit the application to only allow Temperature signals, with the ability to even restrict down to the unit type.

Adding Constants to a function

Adding Inputs to a function

Logic

The custom insights support MathJS or JSON-e expressions. Reference syntax for these languages can be found here:

Math.js

https://mathjs.org/

When using MathJS, the 'Logic' is the contents to be provided within a math.evaluate() function

JSON-e

https://json-e.js.org/

When using JSON-e, the 'Logic' is the JSON-e 'Template'. The Inputs(A,B,C) and Constants are the 'Context' given to the 'Template'

Example of using MathJS

Consideration for functions with more than one input

Functions with more than one input are called each time a new value happens for any signal. When first used, the first signal value that triggers calling the function will likely be in a situation where other signals may not have values yet. In this case, the result is 'undefined', When a function returns undefined it is dropped.

To have a function be able to see which signal input (e.g. A or B), use meta.inlet. See examples below.

Using Inputs in functions

Inputs are available as variables using the tags A, B, C, D, E (up to 5 inputs are supported).

Example Function code with 3 Input Signals that are added

A + B + C
{ "$eval": "A + B + C" }

Using constants in functions

Constants that are defined are available by simply using the name of the defined constant

Handling Outputs in functions

If there is only one output, the result of the function automatically is applied to the output value.

Example Function

If A = 50 then the Output Signal = 150

A + 100
{ "$eval": "A + 100" }

In the case of multiple output signals, to reference the outlets, we use the set_outlet() function. Normally, the results of a function go into the first outlet, but if we set the first outlet to a value, then that is used instead of the function results. Also, the set_outlet() function matches the indexing of the language. So in Math.js the first outlet is 1, whereas in JSON-e the first outlet is 0.

Example with multiple output signals

If A = 50; then Output Signal 1 will be 50 and Output Signal 2 will be 1050

set_outlet(2, A + 1000); set_outlet(1, A)
[
    { "$eval": "set_outlet(1, A + 1000)" },
    { "$eval": "set_outlet(0, A)" }
]

If you don't want to return a value to each output every time, then set some of them to undefined. As long as one outlet has a value, then function will produce output. We can show this with a slightly more complex bit of logic that filters values to one of the two outlets.

Example the shows multiple outputs but filters which output is used

In this case if A = 50; then Output Signal 1 = undefined (not set) and Output Signal 2 = 50

(A > 100)?outlets[1]=A:outlets[2]=A; outlets[1]
{
    "$if": "A > 100",
    "then": [
        { "$eval": "set_outlet(0,A)" },
        { "$eval": "set_outlet(1,null)" }
    ],
    "else": [
        { "$eval": "set_outlet(0,null)" },
        { "$eval": "set_outlet(1,A)" }
    ]
}

Using Output Signal Values in functions

Sometimes you will have logic that builds on prior results. The outlets are available under the variables in order Z, Y, X W, Vwith Z being the first output signal.

Example using the last output signals value (last result)

(isNumeric(Z)?Z:0) + A
{ "$if": "typeof(Z) == 'number'",
  "then": { "$eval": "Z + A" },
  "else": { "$eval": "A" }
}

Functions with non-numerical data

Functions can work with strings, boolean, JSON, and binary data also.

Example using JSON-e to generate a JSON output value

{
  "url": {$eval: "A"},
  "title": {$eval: "B"}
}

You can use the normalize() function in JSON-e, which takes 1 or 2 parameters. First is the string to normalize(), and second is a string of how. The second parameter is one of NFC, NFD, NFKC, NFKD. See https://www.unicode.org/reports/tr15/#Norm_Forms for more information.

Example using Math.js to generate a JSON output value

{
    url: A,
    title: B
}

Using the function meta object

The meta object contains details about the signal data value that triggered the function to run.

Key Description
ts The Unix timestamp in microseconds of the Signal Datapoint's timestamp which can be near real-time or a recorded timestamp from the device origination source which is common for batched data. Example: 1741713742462000
gts The Unix timestamp in microseconds of the the platform's time of processing the value (near real-time).
Example: 1741713114684839
intlet The Signal inlet (e.g. "A", "B", etc) to allow for different logic based on the input signal. Example: "A"

The timestamps in this object can be used to filter out historically recorded and batched data and to compare a timestamp vs the most recent (wall-clock). This allows for check if the data may be out of order which may cause unexpected results for functions that assume ordered data. Note that the output signal values of these functions will use the datapoint's recorded timestamp (gts).

Batched Recorded Data / Out of Order Data in Pipeline

Although the platform supports recording signal data at any historical timestamp , when these signals are connected in the pipeline to real-time event functions (like transforms and rules), it requires reviewing the scenarios that can happen from historical and batched recorded data. If required, handling this type of data scenario in the pipeline should be carefully designed (from edge device source to the pipeline functions to the expected actions) to help eliminate unexpected data results and/or confusing user experiences.

Recommendation

The general recommendation is for edge devices to send data in order (oldest first) when batched recorded data is required (network disconnections, etc). Otherwise try not use recorded data, instead writing data without timestamps and allowing the platform to timestamp the values while allowing data to be in order.

Example using the meta object to ignore value with timestamps that have been recorded at at historical time at least 5 milliseconds in the past.

abs(meta.ts - meta.gts) < 5000 ? A : undefined

Code example to return the items in the meta object.

{"$eval":"meta"}

Result

{
"ts": 1741713114684839,
"gts": 1741713742462000,
"inlet": "A"
}

Using historical data points

Functions provide an array for recent signal values, accessed from the prior variable. Two data points are accessible along with their timestamps in this array - being the two most recent value/timestamps in terms of wall clock (now). Typically the first most recent is the data point that triggered this even to run but there is an exception (see below for more info) The first dimension of the array, denotes the data point (first or second). The second dimension of the array accesses the timestamp (first index) and the value in second index. (Math.js the indexes are 1 for timestamp and 2 for the value. In JSON-e, the indexes will be 0 for timestamp and 1 for the value.)

When using prior, the current value is the most recent "historical" value for the signal, that is, A is equivalent to prior.inlets.A[1,2] (in Math.js). This makes the previous historical datapoint prior.inlets.A[2,2]. There is an exception for this, for data sent using the record function (with a timestamp), this may be written historically and may not be the most recent prior value (based on wall-clock ).

Example finding the difference between the current and previous first inlet

Note: the syntax for Math.js arrays would use prior.inlets.A[1,2] (most recent) & prior.inlets.A[2,2] (next recent) values

delta = A - prior.inlets.A[2,2]

Note: the syntax for JSON-e arrays would use prior.inlets.A[0,1] (most recent) & prior.inlets.A[1,1] (next recent) values

{ "$eval": "A - prior.inlets.A[1,1]" }

Creating Custom Rules

Multi-Inlet Rules are not supported

ExoSense does not support multi-inlet rules throughout the entire application, although there are ways to create them such as in the inline insight interface or building a custom external function.

To create a custom select "Rule" as the Insight Type

Selecting the Rule type

Rules must return an output of type "JSON"

Setting the output type to JSON

This output must return an object that contains a field "level". This level will indicate the state of the rule.

State Value
Normal 0
Info 1
Warning 2
Critical 3
Error 4

This can be returned either directly or the user can select the state to return via "Constants"

An example where the state is returned directly

normal = 0
info = 1
warning = 2
critical = 3
error = 4
{level: (A == 90) ? warning : normal }
{ "$let": {
    normal: 0,
    info: 1,
    warning: 2,
    critical: 3,
    error: 4
  },
  in: { "$if": "A == 90",
    "then": { "$eval": "warning" },
    "else": { "$eval": "normal" },
  }
}

An example where the state is specified in the Rule's configuration

{level: (A == 90) ? level : 0}
{
    "level": {
        "$if": "A == 90",
        "then": { "$eval": "level" },
        "else: 0
    }
}

Adding a Constant to a rule

Testing

The interface also allows in-browser testing of your functions by providing a form to set your input signal values, constants, and last output value (if necessary).

Testing the custom logic

Importing/Exporting

When creating a new function, you have the option to Import a file that will generate the entire function for you. Insight files can be exported from your own custom insights, provided to you by Exosite for reference examples or by third-party or partner. Functions can be exported from the Insights interface.

Examples

Hint

Users can view the source for off the shelf standard functions included in ExoSense and may duplicate and export these to use for starting a new customized function.

Linear Gain

A is the input signal, gain and offset are constants set by user

A * gain + offset
{ "$eval": "A * gain + offset" }

Continuous Accumulation

A is the input signal and Z is the output

A + (isNumeric(Z)?Z:initialValue)
{ "$if": "typeof(Z) == 'number'",
  "then": { "$eval": "A + Z" },
  "else": { "$eval": "initialValue" }
}

Mapping a signal's numeric range to a status

For example 0 to 10 is off, 10 to 40 is on, and greater than 40 is error

(A>0 and A<10)?"off":(A>10 and A<40)?"on":"error"
{ "$switch": {
    "A > 0 && A < 10": "off",
    "A > 10 && A < 40": "on",
    "A <= 0 || A >= 40": "error"
  }
}

Self-Hosted Custom Insights

Custom Self-Hosted Insights can be published in the Exosite IoT Marketplace for private or public use. This interface requires hosting a web-service that supports HTTP requests using Exosite's external Insight schema for handling streaming data requests and responses.

These self-hosted Insights can run anywhere you see fit—in Exosite Murano as a custom solution, Amazon Web Services, Microsoft Azure, Google Cloud Products, or any other hosting platform.

Self-Hosted external insight functions have the same properties and functionality as the custom inline insights, with the noted benefits:

  • Code-base can be hosted and maintained on any infrastructure you want
  • Allows for using external services and platforms for more advanced analytics and functionality
  • Insights can be published in Exosite's IoT Marketplace for use by other Exosite customers.

Warning

As insights are streaming data logic operations, functions must be able to respond quickly.