跳至主要内容

End-to-End Scenario

Build a Boolean temperature indicator from a SubNode's analog-current reading. First convert the current to temperature, then combine it with another existing temperature. This walkthrough includes the resource setup, complete API requests and result checks.

The example uses 4 mA = 0 °C, 20 mA = 100 °C, and abnormal = above 80 °C. Exactly 80 °C is not abnormal. Replace the calibration and conditions with those for your application.

Note: JavaScript stages and paired readings

Both stages use JavaScript. B publishes true only when the two readings in its input set are both abnormal; it does not guarantee they were sampled at exactly the same time.

A valid false result means the condition was evaluated and was not met. Missing input or a skipped evaluation does not create a false result.

Prepare the Inputs and Outputs​

Before starting, have:

  • A connected SubNode application already reporting a numeric current stream in mA. Check the hardware conversion if it reports raw ADC counts. Build a SubNode Application covers the application and sensor integration.
  • Another existing numeric Celsius temperature DataStream on the same SubNode device. Its source setup is outside this example.
  • The target Org ID, SubNode Device ID, your deployment's API base URL and an access token authorized for those resources.

1. Discover the two inputs. Use List DataStreams; inspect all pages if necessary:

GET /api/v1/orgs/{orgId}/devices/{deviceId}/data-streams?maxResultCount=100&skipCount=0
Authorization: Bearer YOUR_ACCESS_TOKEN

Match the current and Celsius stream by their definitions and verify their actual units and recent values. A stream name is descriptive; use its returned dataStreamId in the Analyzer bindings.

2. Create the two output destinations. Use Create a DataStream for the converted temperature:

POST /api/v1/orgs/{orgId}/devices/{deviceId}/data-streams
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
"dataStreamName": "temperature_a",
"displayName": "Converted temperature A",
"unitDisplay": "°C",
"dataType": "double",
"recordToDataLake": true
}

Then create the Boolean indicator:

POST /api/v1/orgs/{orgId}/devices/{deviceId}/data-streams
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
"dataStreamName": "both_abnormal",
"displayName": "Both temperatures abnormal",
"dataType": "boolean",
"recordToDataLake": true
}

After each 201 Created, retain the returned dataStreamId. Both destinations must initially have no assigned source. If a name already exists, inspect the existing definition and source before reusing it.

3. Keep this resource map. The following UUIDs are illustrative, not IDs requested by the create operations. Replace them throughout the requests with the actual IDs you found or received:

StreamType / unitIllustrative ID
analog_currentNumeric, mA; existing inputa1111111-1111-4111-8111-111111111111
temperature_bNumeric, °C; existing inputb2222222-2222-4222-8222-222222222222
temperature_adouble, °C; output of A and input to Bd4444444-4444-4444-8444-444444444444
both_abnormalboolean; output of Be5555555-5555-4555-8555-555555555555

Check the result: Two inputs have recent readings, and two separate, unbound output definitions exist on the same device. The independent temperature_tf destination from Transfer Function is not used in this chain.

Create the Conversion Analyzer​

A computes (current - 4) * 100 / 16. This example's script skips non-numeric values and values outside 4–20 mA. It returns the calculated temperature with the original current sample's timestamp.

Send this Create Analyzer request using your actual current and temperature-output IDs:

POST /api/v1/orgs/{orgId}/devices/{deviceId}/analyzers
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
"name": "Analog current to temperature",
"description": "Convert a 4–20 mA input to 0–100 Celsius for the next Analyzer",
"type": "JavaScript",
"inputParameters": [
{
"parameterName": "analog_current",
"dataStreamId": "a1111111-1111-4111-8111-111111111111"
}
],
"config": {
"script": "const current = analog_current.value; if (!Number.isFinite(current) || current < 4 || current > 20) { return null; } return { temperature: (current - 4) * 100 / 16, timestamp: analog_current.timestamp };"
},
"outputs": [
{
"dataStreamId": "d4444444-4444-4444-8444-444444444444",
"valueJsonPath": "$.temperature",
"timeJsonPath": "$.timestamp"
}
]
}

After 201 Created, retain the returned analyzerId as conversionAnalyzerId. Use Get Analyzer to read the saved definition:

GET /api/v1/orgs/{orgId}/devices/{deviceId}/analyzers/{analyzerId}
Authorization: Bearer YOUR_ACCESS_TOKEN

For this check, replace analyzerId with conversionAnalyzerId.

Check the result: A is JavaScript, its input is the current stream, its output is temperature_a, and both paths are saved. It is initially disabled; dataAssemblyPolicy is null because A has one input.

Create the Indicator Analyzer​

B consumes A's actual temperature output ID and the existing second temperature. It evaluates each against > 80, combines the flags with AND, and publishes bothAbnormal to both_abnormal.

The script also returns individual flags, but this request publishes only the combined Boolean. The JavaScript guide explains the readable scripts and output mapping.

POST /api/v1/orgs/{orgId}/devices/{deviceId}/analyzers
Content-Type: application/json
Authorization: Bearer YOUR_ACCESS_TOKEN

{
"name": "Both temperatures abnormal",
"description": "Publish true only when both Celsius readings are above 80",
"type": "JavaScript",
"inputParameters": [
{
"parameterName": "temperature_a",
"dataStreamId": "d4444444-4444-4444-8444-444444444444"
},
{
"parameterName": "temperature_b",
"dataStreamId": "b2222222-2222-4222-8222-222222222222"
}
],
"config": {
"script": "const a = temperature_a.value; const b = temperature_b.value; if (!Number.isFinite(a) || !Number.isFinite(b)) { return null; } const aAbnormal = a > 80; const bAbnormal = b > 80; return { aAbnormal, bAbnormal, bothAbnormal: aAbnormal && bAbnormal, timestamp: Math.max(temperature_a.timestamp, temperature_b.timestamp) };"
},
"outputs": [
{
"dataStreamId": "e5555555-5555-4555-8555-555555555555",
"valueJsonPath": "$.bothAbnormal",
"timeJsonPath": "$.timestamp"
}
],
"dataAssemblyPolicy": {
"queueLimit": 100,
"timeoutSeconds": 5,
"onTimeout": "DropPartial"
}
}

After 201 Created, retain this analyzerId as indicatorAnalyzerId. Read it back with Get Analyzer using that ID.

Check the result: B is disabled, has two input bindings and a Boolean output mapping. Its temperature_a input ID is identical to A's output ID. Its effective collection policy is queueLimit: 100, timeoutSeconds: 5, onTimeout: DropPartial.

This policy waits for a reading from each input. The timeout is measured from the oldest waiting arrival, and another value from the already available source does not restart it. Adjust the policy to your reporting rates using Multi-Input Processing. The default is an example configuration, not a guarantee that every pair of reporting rates fits a five-second wait.

Enable and Verify the Chain​

1. Enable A and B separately. Replace the two Analyzer IDs with the IDs captured from their create responses:

POST /api/v1/orgs/{orgId}/devices/{deviceId}/analyzers/{conversionAnalyzerId}:enable
Authorization: Bearer YOUR_ACCESS_TOKEN
POST /api/v1/orgs/{orgId}/devices/{deviceId}/analyzers/{indicatorAnalyzerId}:enable
Authorization: Bearer YOUR_ACCESS_TOKEN

After 204 No Content, GET each definition and confirm isEnabled: true. Wait for new current and second-temperature readings after both are enabled. A multi-input stage also needs its input collection ready; the enable response alone does not prove a result exists.

2. Query each point in the chain. Use Get Latest Value, substituting each of the four stream IDs from the resource map:

GET /api/v1/orgs/{orgId}/devices/{deviceId}/data-streams/{dataStreamId}/values/latest
Authorization: Bearer YOUR_ACCESS_TOKEN

Read dataObject.value and dataObject.timestamp. Check the current first, the converted temperature next, the existing second temperature, and finally the Boolean output.

For an illustrative corresponding pair:

PointValueSample time
Current input17 mA2026-10-26T08:00:00Z
A's temperature_a81.25 °C2026-10-26T08:00:00Z
Existing temperature_b90 °C2026-10-26T08:00:01Z
B's both_abnormaltrue2026-10-26T08:00:01Z

A preserves its current input's time. B chooses the later of its two input sample times, so the result time is not the time the script finished running. With changing inputs, separate latest queries can refer to different samples; match their timestamps, or use recorded history when available.

Check the result: The conversion follows the calibrated scale, the indicator is a real Boolean, and the values and times correspond to the same evaluated input set.

Check the Conditions and Missing-Data Cases​

Use your controlled source or test setup to generate appropriate readings. These rows are expected calculations, not requests to inject values through the management APIs:

Current inputConverted AExisting BExpected both_abnormal
17 mA81.25 °C90 °Ctrue: both abnormal
16 mA75 °C90 °Cfalse: only B abnormal
17 mA81.25 °C75 °Cfalse: only A abnormal
16 mA75 °C75 °Cfalse: neither abnormal
16.8 mA80 °C90 °Cfalse: A is exactly at the boundary

If a current is invalid for this example, A's script returns null and produces no new temperature for that reading. If B cannot form a complete input set before its DropPartial wait expires, that incomplete wait produces no Boolean output. An older stored Boolean can still be returned as the latest value; inspect its timestamp before treating it as current.

ObservationStart here
No new converted temperatureCurrent reporting, mA units, valid range, A's state and $.temperature / $.timestamp.
A updates but no new Boolean appearsSecond-temperature reporting, B's state and input IDs, policy window, script and paths.
Boolean is falseCompare both actual paired temperatures and the strict > 80 conditions.
A create returns 409Check whether its chosen output already has a source. Reuse the correct existing Analyzer or prepare a separate destination.
B values appear inconsistentCompare matching sample times and the arrival-order behavior; a newer A value does not automatically replace an older queued one.

For changes, disable the affected Analyzer before updating its definition. Review downstream references when changing output IDs, then re-enable and verify each stage. Multilayer Analyzer explains the five-stage and no-cycle rules.