User-defined scripts enable dynamic, programmatic control over REST API requests and responses in the REST API Client module. These JavaScript-based scripts provide flexibility beyond static configuration, allowing you to build adaptive integrations that respond to changing conditions.
User defined scripts can be used in the following scenarios:
To create a dynamic path using JavaScript functions (such as Date) and/or parameters from tags. For example, building a path like /api/data?date=2024-01-15&sensor=TEMP01, where the date and sensor values change based on tag values.
To create custom headers dynamically based on JavaScript functions and/or parameters. For example, generating authentication tokens, adding timestamps, or including custom tracking information.
To create the request body programmatically when using POST, PATCH, or PUT methods. This is essential when the body structure needs to change based on tag values or requires complex transformations.
To parse responses from the server into tag data objects. This allows you to extract specific fields from complex JSON/XML responses, handle nested structures, and map server data to your tag namespace.
Moment
Moment is a JavaScript library that simplifies date parsing and formatting, as well as manipulating dates (such as adding a specific duration to a date). The REST API Client version also includes Moment Timezone, which is used to parse dates in specific time zones and to manipulate the time zone of a moment object.
Moment can be accessed by directly invoking the moment() constructor, while Moment Timezone is accessed using moment.tz()
Documentation about Moment can be found in Moment and Moment Timezone.
The following examples demonstrate typical scenarios where Moment is useful:
// Format current date/time for API path
const formattedDate = moment().format('YYYY-MM-DD');
$.output = `/api/data?date=${formattedDate}`;
// Subtract 1 hour from current time
const oneHourAgo = moment().subtract(1, 'hours').toISOString();
$.output = `/api/readings?since=${oneHourAgo}`;
// Parse a specific date and format it
const parsedDate = moment('2024-01-15 14:30:00').format('YYYYMMDD');
$.output = `/api/archive/${parsedDate}`;
// Work with specific timezones
const nyTime = moment.tz('America/New_York').format('YYYY-MM-DD HH:mm:ss');
$.logger.info('New York time: %s', nyTime);
// Calculate duration between dates
const start = moment('2024-01-01');
const end = moment('2024-01-15');
const daysDiff = end.diff(start, 'days');
$.logger.info('Days difference: %d', daysDiff);Tips:
When working with REST APIs that expect specific date formats, use Moment's .format() method to match the required format exactly. For ISO 8601 format (common in REST APIs), use .toISOString().
Buffer
Buffer is a Node.js class that is used to create and manipulate binary arrays. This class can be used when generating or parsing a binary body. Documentation about the Buffer class can be found at Node.js Buffer API.
Buffers are primarily used when working with binary data:
// Create a buffer from a string
const buf = Buffer.from('Hello World', 'utf8');
$.output = buf;
// Create a buffer from hexadecimal string
const hexBuf = Buffer.from('48656c6c6f', 'hex');
$.logger.info('Decoded: %s', hexBuf.toString('utf8')); // Outputs: Hello
// Read binary response from $.input.body (when response is binary)
if (Buffer.isBuffer($.input.body)) {
const hexString = $.input.body.toString('hex');
$.logger.info('Received hex data: %s', hexString);
}
// Create a buffer with specific byte values
const customBuf = Buffer.from([0x48, 0x65, 0x6C, 0x6C, 0x6F]);
$.output = customBuf; // Outputs: Hellosprintf
Sprintf is a function used to format any string given a format pattern containing placeholders and several variables that will substitute the placeholders. The format is similar to that of the C's sprintf function, since placeholders are declared using the special character %, followed by a letter denoting the type of the variable that will substitute the placeholder.
Sprintf is particularly useful for building formatted strings from tag values, constructing complex paths, or creating log messages.
The following examples are valid place-holders for different data types:
Data Type | Placeholder | Description |
|---|---|---|
Integer | %d or %i | Formats as a signed integer. If a decimal number is provided, the decimal part will be truncated. |
String | %s | Formats as a string. |
Binary | %b | Formats as a binary representation. |
JSON | %j | Formats as a JSON string. Useful for logging complex objects. |
ASCII Character | %c | Converts a decimal value to its ASCII character representation. |
Scientific Notation | %e | Formats as a number in scientific notation (e.g., 1.23e3). Use %.xe to specify decimal places (e.g., %.2e). |
Floating Point | %f | Formats as a floating-point number. Use %.xf to specify decimal places (e.g., %.2f for 2 decimals). |
Fixed Point | %g | Formats as a number with fixed precision. Use %.xg to specify precision. |
Octal | %o | Formats as an octal number. |
Unsigned Integer | %u | Formats as an unsigned integer. If a decimal number is provided, the decimal part will be truncated. |
Hexadecimal (lowercase) | %x | Formats as a hexadecimal number with lowercase letters. |
Hexadecimal (uppercase) | %X | Formats as a hexadecimal number with uppercase letters. |
Node Buffer | %r | Formats a Node.js Buffer object as a hexadecimal string. |
If the integer and the unsigned integer format type receive a decimal number, they will truncate the decimal part.
All formats that admit decimal numbers, such as floating point or scientific notation, can be configured to specify the required number of decimals to display by using the format %.xY, where x is the number of decimals, and Y is the format used (f, for floating point; e, for exponential, etc). For example, to show a floating-point number with 2 decimals, the following format must be used: %.2f. See below for examples of the different format options in the following script:
$.sprintf("Numbers are: %d and %i", 10, 15.7)
//Numbers are: 10 and 15
$.sprintf("String is: %s”, “Hello world!");
//String is: Hello world!
$.sprintf("The binary representation of 5 is: %b", 5);
// The binary representation of 5 is: 101
$.sprintf("The ASCII character with decimal value 48 is %c", 48);
// The ASCII character with decimal value 48 is 0
$.sprintf("1000 in scientific notation is: %e", 1000);
//1000 in scientific notation is: 1e3
$.sprintf("1234 in scientific notation and 2 decimals is: %.2e", 1234);
//1234 in scientific notation and 2 decimals is: 1.23e3
$.sprintf("12.34 in floating point notation is: %f", 12.34)
//12.34 in floating point notation is: 12.34
$.sprintf("12.3456 in fixed point notation is: %.3g", 12.3456);
//12.3456 in fixed point notation is: 12.3
$.sprintf("12 in octal is: %o", 12);
//12 in octal is: 14
$.sprintf("-10 in unsigned integer format is: %u", -10)
//-10 in unsigned integer format is: 4294967286
$.sprintf("30 in hexadecimal is: %x", 30);
//10 in hexadecimal is: 1e
$.sprintf("30 in uppercase hexadecimal is: %X", 30);
//30 in uppercase hexadecimal is: 1E
const buf = Buffer.from("Hello world!");
$.sprintf("The buffer is: %r", buf);
//The buffer is: <48 65 6c 6c 6f 20 77 6f 72 6c 64 21>;
//---------------------------------------------------------------
// Build API path with sensor ID and formatted temperature
const sensorId = 'TEMP-001';
const temperature = 25.678;
$.output = $.sprintf('/api/sensors/%s/temperature/%.2f', sensorId, temperature);
// Result: /api/sensors/TEMP-001/temperature/25.68
// Create authorization header with token
const token = $.parameter.AuthToken.value;
$.output = $.sprintf('Bearer %s', token);
// Format log message with multiple parameters
const requestCount = $.local.requestCount || 0;
const duration = 1234.567;
$.logger.info('Request #%d completed in %.3f ms', requestCount, duration);
// Build query string with multiple parameters
const startTime = moment().subtract(1, 'hour').unix();
const endTime = moment().unix();
const deviceId = $.parameter.DeviceId.value;
$.output = $.sprintf('/api/data?device=%s&start=%d&end=%d', deviceId, startTime, endTime);
// Format decimal numbers with specific precision
const value = 3.14159265;
$.logger.info('Pi with 2 decimals: %.2f', value); // Pi with 2 decimals: 3.14
$.logger.info('Pi with 4 decimals: %.4f', value); // Pi with 4 decimals: 3.1416$.logger
The $.logger object is used to log messages to the module log, which can be used for both debugging and informative purposes. The log file can be found at N3uron/log/RestApiClientInstaceName/. It is shared by both the module’s internal execution and any messages written by the users. The logger has five different logging levels:
Method | Description |
|---|---|
$.logger.error() | Logs an error-level message. Used for critical issues that require immediate attention, such as failed API calls, invalid responses, or exceptions. |
$.logger.warn() | Logs a warning-level message. Used for potentially problematic situations that should be reviewed, such as unexpected but manageable conditions, deprecated usage, or performance concerns. |
$.logger.info() | Logs an informational message. Used for general operational information and status updates, such as successful request completions or configuration changes. |
$.logger.debug() | Logs a debug-level message. Used for detailed diagnostic information during development and troubleshooting, such as variable values, intermediate calculations, or processing steps. |
$.logger.trace() | Logs a trace-level message. Used for very detailed diagnostic information, typically for in-depth analysis of script execution flow and fine-grained debugging. |
Each logging function accepts the following parameters:
Parameter | Description |
|---|---|
message (String) | Format string using sprintf formatting. This string can contain placeholders that will be replaced by the arguments. |
arguments (Any) | Optional arguments that will replace the placeholders in the format string. Multiple arguments can be provided. |
The following examples demonstrate effective logging practices:
// Log request details at info level
$.logger.info('Sending request to path: %s', $.output);
// Log parameter values for debugging
$.logger.debug('Parameters: DeviceId=%s, Timestamp=%d',
$.parameter.DeviceId.value, $.parameter.Timestamp.ts);
// Log entire objects as JSON for inspection
$.logger.debug('Response headers: %j', $.input.headers);
// Log errors with context
if ($.input.statusCode !== 200) {
$.logger.error('Request failed with status code %d: %s',
$.input.statusCode, $.input.body);
}
// Log warnings for unexpected conditions
if ($.parameter.Temperature.quality < 192) {
$.logger.warn('Temperature parameter has bad quality: %d',
$.parameter.Temperature.quality);
}
// Use different log levels appropriately
$.logger.trace('Entering response parser script');
$.logger.debug('Processing %d records from response', $.input.body.records.length);
$.logger.info('Successfully parsed response, writing %d tags', $.output.length);
$.logger.warn('Response time exceeded threshold: %.2f ms', responseTime);
$.logger.error('Failed to parse JSON response: %s', error.message);$.parameter
Parameters in requests can be used by accessing the $.parameter object. All of these parameters are tag events and, as such, have a value, a quality, and a timestamp. Parameters use the following configuration:
.png)
Property | Description |
|---|---|
tag | Contains the tag path. |
value | Contains the current value of the tag. The type depends on the tag type and can be a number, string, Boolean, or null. Example: $.parameter.Start.value |
quality | Contains the quality indicator of the tag value as a number in the range 0-255. Example: $.parameter.Start.quality |
ts | Contains the timestamp of the tag value as the number of milliseconds elapsed since January 1, 1970 (Unix Epoch). Example: $.parameter.Start.ts |
The following examples show how to effectively use parameters:
// Use parameter value in path construction
const deviceId = $.parameter.DeviceId.value;
const startTime = $.parameter.StartTime.value;
$.output = `/api/devices/${deviceId}/data?start=${startTime}`;
// Check parameter quality before using
if ($.parameter.Temperature.quality >= 192) {
// Quality is good, use the value
const temp = $.parameter.Temperature.value;
$.output = { temperature: temp, unit: 'celsius' };
} else {
$.logger.warn('Temperature has bad quality, using default value');
$.output = { temperature: 20, unit: 'celsius' };
}
// Use parameter timestamp in calculations
const lastReadingTime = $.parameter.LastReading.ts;
const currentTime = Date.now();
const secondsSinceLastReading = (currentTime - lastReadingTime) / 1000;
$.logger.info('Time since last reading: %.1f seconds', secondsSinceLastReading);
// Build complex request body from multiple parameters
$.output = {
sensor_id: $.parameter.SensorId.value,
temperature: $.parameter.Temperature.value,
humidity: $.parameter.Humidity.value,
timestamp: moment().toISOString(),
quality: {
temperature: $.parameter.Temperature.quality,
humidity: $.parameter.Humidity.quality
}
};
// Use parameters in header generation
const apiKey = $.parameter.ApiKey.value;
const sessionId = $.parameter.SessionId.value;
$.output = $.sprintf('Bearer %s;session=%s', apiKey, sessionId);
// ============================================================================
// CUSTOM PATH: Read record offset from parameter
// ============================================================================
// Parameter name: RecordOffset (type: number)
// Purpose: Track which records have been processed
const offset = $.parameter.RecordOffset.value || 0;
$.output = `/api/events?offset=${offset}&limit=50`;
$.logger.info('Fetching 50 records starting from offset: %d', offset);
// ============================================================================
// RESPONSE PARSER: Update RecordOffset for next batch
// ============================================================================
if ($.input.statusCode !== 200) {
$.output = [];
} else {
const data = $.input.body;
// Process events
$.output = data.events.map(event => ({
tag: `/Events/${event.id}`,
value: event.description,
quality: 192,
ts: event.timestamp
}));
// Update offset for next request
const recordsProcessed = data.events.length;
if (recordsProcessed > 0) {
const newOffset = $.parameter.RecordOffset.value + recordsProcessed;
$.output.push({
tag: $.parameter.RecordOffset.tag, // Increment offset
value: newOffset,
quality: 192
});
$.logger.info('Processed %d records, new offset: %d', recordsProcessed, newOffset);
}
}Iterating Parameters
Since $.parameter is a JavaScript object, it can be iterated to obtain all parameters programmatically. This is useful when the number of parameters is dynamic or when you need to process all parameters in a generic way:
// Iterate through all parameters
for (const [param, value] of Object.entries($.parameter)) {
$.logger.info("Parameter %s is %j", param, value);
}
// Iterate through the Array of parameters
for (const item of $.parameter) {
// 'item' is now the object: { "tag": "...", "value": ... }
// You can access properties directly
$.logger.info("Tag: %s, Value: %s", item.tag, item.value);
// If you need to check quality (like in your screenshot)
if (item.quality >= 192) {
// Do something with high quality data
}
}
// Build dynamic query string from all parameters
const queryParams = [];
for (const [name, param] of Object.entries($.parameter)) {
if (param.quality >= 192) { // Only include good quality parameters
queryParams.push(`${name}=${encodeURIComponent(param.value)}`);
}
}
$.output = `/api/data?${queryParams.join('&')}`;
// Check quality of all parameters
let allGoodQuality = true;
for (const [name, param] of Object.entries($.parameter)) {
if (param.quality < 192) {
$.logger.warn('Parameter %s has bad quality: %d', name, param.quality);
allGoodQuality = false;
}
}
if (!allGoodQuality) {
$.logger.error('One or more parameters have bad quality, aborting request');
// Optionally throw error or set output to null
}$.local
$.local is an empty object that can be used to store any user variables that persist between request executions, as well as any variables that are shared between different scripts belonging to the same request.
The following examples show how to effectively use $.local variables:
// Initialize counter on first execution
if ($.local.requestCount === undefined) {
$.local.requestCount = 0;
}
$.local.requestCount++;
$.logger.info('Request count: %d', $.local.requestCount);
// Store timestamp of last successful request
if ($.input && $.input.statusCode === 200) {
$.local.lastSuccessTime = Date.now();
$.logger.info('Last successful request at: %s',
moment($.local.lastSuccessTime).format('YYYY-MM-DD HH:mm:ss'));
}
// Share data between path and body scripts
// In path script:
// Compute device ID from parameters once
$.local.deviceId = `${$.parameter.Plant.value}_${$.parameter.Line.value}_${$.parameter.Machine.value}`;
$.output = `/api/devices/${$.local.deviceId}/status`;
// In body script:
$.output = {
device_id: $.local.deviceId, // Reuse computed device ID
status: $.parameter.Status.value,
timestamp: Date.now()
};
// Implement simple rate limiting
const now = Date.now();
if ($.local.lastRequestTime && (now - $.local.lastRequestTime) < 1000) {
$.logger.warn('Request throttled - less than 1 second since last request');
// Optionally skip this request
}
$.local.lastRequestTime = now;
// Cache parsed sensor list (expensive computation)
if ($.local.sensorMap === undefined) {
// Parse a large sensor configuration (expensive operation)
$.local.sensorMap = parseLargeSensorConfig($.input.body);
$.logger.info('Sensor map cached (%d sensors)', Object.keys($.local.sensorMap).length);
}
// Use cached map for fast lookup
const sensor = $.local.sensorMap[$.parameter.SensorId.value];
$.output = sensor ? sensor.endpoint : '/default';
Note:
$.local data persists for the lifetime of the module. If the module is restarted, all $.local data is reset. Do not use $.local for critical data that must survive restarts. For long-term storage, utilize tags configured with persistence.
$.input
This variable contains the input given to the custom script, if applicable. The input type depends on the function of the custom script, and may be null if the script has no input. The current scripts with access to $.input are:
Response parser: The input is an object containing three keys:
Status code: Contains the status code that the server responded with. The most common status code is 200 – OK.
Headers: Contains the headers sent with the response by the server. The keys for these objects are the header name, and the values are the header value.
Body: Contains the body of the request. The body type varies depending on the serializer used. If the serialization is binary, the type will be a Buffer. If the serialization is text, it will be plain text, and finally, if the serialization is JSON or XML, the result will be a JavaScript object obtained by deserializing each respective format. More information about the resulting objects can be found in the appendix.
Property | Description |
|---|---|
$.input.statusCode | Contains the HTTP status code returned by the server. The most common status code is 200 – OK. Other common codes include 201 (Created), 400 (Bad Request), 401 (Unauthorized), 404 (Not Found), and 500 (Internal Server Error). |
$.input.headers | Contains the headers sent with the response by the server. The keys for these objects are the header name, and the values are the header value. |
$.input.body | Contains the response body. The type varies depending on the serialization setting: |
Response Parser Practical Examples:
// ============================================================================
// EXAMPLE 1: CHECK STATUS CODE AND HANDLE ERRORS (JSON SENSORS)
// ============================================================================
// Endpoint: http://127.0.0.1:8080/json/sensors
// Purpose: Process JSON sensor data with proper error handling
// Response: {"sensors": [{"id": "S1", "temperature": 23.4, "status": "ok", "timestamp": "..."}]}
// ============================================================================
// Check status code and handle errors
if ($.input.statusCode !== 200) {
$.logger.error('Request failed with status %d', $.input.statusCode);
$.output = []; // Return empty array on error
} else {
// Process response only if status is 200
// Parse JSON response (body is already an object)
const data = $.input.body;
$.output = data.sensors.map(sensor => ({
tag: `/Plant/Sensors/${sensor.id}`,
value: sensor.temperature,
quality: sensor.status === 'ok' ? 192 : 64,
ts: new Date(sensor.timestamp).getTime()
}));
$.logger.info('Processed %d sensors successfully', $.output.length);
}
// ============================================================================
// EXAMPLE 2: HANDLE TEXT/CSV RESPONSE
// ============================================================================
// Endpoint: http://127.0.0.1:8080/text/csv
// Purpose: Parse CSV formatted text responses
// Response: "id,value,timestamp\nMTR01,123.4,2025-01-15T10:30:00Z\n..."
// ============================================================================
// Handle text response
if ($.input.statusCode !== 200) {
$.logger.error('Request failed with status %d', $.input.statusCode);
$.output = [];
} else {
if (typeof $.input.body === 'string') {
// Parse CSV or custom format
const lines = $.input.body.split('\n');
$.output = lines.slice(1).map(line => { // Skip header
if (line.trim() === '') {
return null; // Return null for empty lines (will be filtered out)
}
const [id, value, timestamp] = line.split(',');
return { // 'return' is allowed inside function callbacks
tag: `/Data/${id.trim()}`,
value: parseFloat(value),
ts: new Date(timestamp).getTime()
};
}).filter(item => item !== null); // Remove null entries
$.logger.info('Parsed %d CSV records', $.output.length);
}
}
// ============================================================================
// EXAMPLE 3: CHECK RESPONSE HEADERS (CONTENT-TYPE VALIDATION)
// ============================================================================
// Endpoint: http://127.0.0.1:8080/json/sensors
// Purpose: Validate response headers before processing
// Demonstrates: Header inspection and content-type validation
// ============================================================================
// Check response headers
if ($.input.statusCode !== 200) {
$.logger.error('Request failed with status %d', $.input.statusCode);
$.output = [];
} else {
const contentType = $.input.headers['content-type'];
if (!contentType || !contentType.includes('application/json')) {
$.logger.warn('Unexpected content type: %s', contentType);
}
// Process response normally
const data = $.input.body;
$.output = data.sensors.map(sensor => ({
tag: `/Plant/Sensors/${sensor.id}`,
value: sensor.temperature,
quality: 192
}));
}
// ============================================================================
// EXAMPLE 4: PROCESS NESTED MACHINE/EQUIPMENT DATA
// ============================================================================
// Endpoint: http://127.0.0.1:8080/json/machine
// Purpose: Extract data from nested JSON objects (common in equipment APIs)
// Response: {
// "machine_id": "PRESS_01",
// "status": "running",
// "metrics": {"cycle_count": 1523, "efficiency": 98.4},
// "sensors": {"temperature": 65.3, "pressure": 120.5}
// }
// Demonstrates: Flattening nested objects into individual tags
// ============================================================================
// Process nested machine data
if ($.input.statusCode !== 200) {
$.logger.error('Request failed with status %d', $.input.statusCode);
$.output = [];
} else {
const data = $.input.body;
const machineId = data.machine_id;
const timestamp = Date.now();
// Create tag updates array
$.output = [];
// Add machine status tag
$.output.push({
tag: `/Production/${machineId}/Status`,
value: data.status,
quality: 192,
ts: timestamp
});
// Add all metrics as separate tags
for (const [key, value] of Object.entries(data.metrics)) {
$.output.push({
tag: `/Production/${machineId}/Metrics/${key}`,
value: value,
quality: 192,
ts: timestamp
});
}
// Add all sensor readings as separate tags
for (const [key, value] of Object.entries(data.sensors)) {
$.output.push({
tag: `/Production/${machineId}/Sensors/${key}`,
value: value,
quality: 192,
ts: timestamp
});
}
$.logger.info('Processed machine %s: %d tags created', machineId, $.output.length);
}
// ============================================================================
// EXAMPLE 5: HANDLE BINARY RESPONSE
// ============================================================================
// Endpoint: http://127.0.0.1:8080/bin/value
// Purpose: Parse binary protocol data (floatLE + uint32LE)
// Response: Binary buffer containing 4-byte float + 4-byte unsigned integer
// Binary format: [float (4 bytes)][uint32 timestamp (4 bytes)]
// ============================================================================
// Handle binary response
if ($.input.statusCode !== 200) {
$.logger.error('Request failed with status %d', $.input.statusCode);
$.output = [];
} else {
if (Buffer.isBuffer($.input.body)) {
// Parse binary protocol
const value = $.input.body.readFloatLE(0);
const timestamp = $.input.body.readUInt32LE(4);
$.output = [{
tag: '/BinaryData/Value',
value: value,
quality: 192,
ts: timestamp * 1000 // Convert to milliseconds
}];
$.logger.info('Parsed binary data: value=%f, timestamp=%d', value, timestamp);
} else {
$.logger.error('Expected Buffer but got %s', typeof $.input.body);
$.output = [];
}
}
// ============================================================================
// EXAMPLE 6: VALIDATE RESPONSE STRUCTURE
// ============================================================================
// Endpoint: http://127.0.0.1:8080/json/readings
// Purpose: Validate API response structure before processing
// Response: {"readings": [{"tag_path": "/Plant/Line1/Speed", "value": 123.0}]}
// Demonstrates: Defensive programming with structure validation
// ============================================================================
// Validate response structure
if ($.input.statusCode !== 200) {
$.logger.error('Request failed with status %d', $.input.statusCode);
$.output = [];
} else {
const data = $.input.body;
if (!data.readings || !Array.isArray(data.readings)) {
$.logger.error('Invalid response structure: missing readings array');
$.output = [];
} else {
$.output = data.readings.map(reading => ({
tag: reading.tag_path,
value: reading.value,
quality: 192,
ts: Date.now()
}));
$.logger.info('Validated and processed %d readings', $.output.length);
}
}
// ============================================================================
// EXAMPLE 7: USING HELPER FUNCTIONS WITH RETURN STATEMENTS
// ============================================================================
// Endpoint: http://127.0.0.1:8080/json/sensors
// Purpose: Demonstrate proper use of 'return' inside helper functions
// Demonstrates: Helper functions for cleaner code organization
// ============================================================================
// Helper function to map sensor status to quality code
// 'return' is allowed inside functions like this
function getQualityCode(status) {
if (status === 'ok') {
return 192; // Good quality
}
if (status === 'warn') {
return 64; // Bad quality
}
return 0; // Unknown quality
}
// Helper function to create tag path
function createTagPath(sensorId) {
return `/Plant/Area01/Sensor_${sensorId}`;
}
if ($.input.statusCode !== 200) {
$.logger.error('Request failed with status %d', $.input.statusCode);
$.output = [];
} else {
const data = $.input.body;
// Use helper functions for cleaner code
$.output = data.sensors.map(sensor => {
return { // 'return' is also allowed in map callbacks
tag: createTagPath(sensor.id),
value: sensor.temperature,
quality: getQualityCode(sensor.status),
ts: new Date(sensor.timestamp).getTime()
};
});
$.logger.info('Processed %d sensors using helper functions', $.output.length);
}$.output
The $.output variable is an object used to set the script output in user-defined scripts. The structure of this object depends on where the script is being used within the REST API Client module. Using an incorrect structure will cause an exception to be logged and the request to be aborted.
The following table describes the required structure for the $.output object in each script context:
Script Context | Data Type | Description |
|---|---|---|
Custom Path | String | When creating a path using a script, $.output must be set to a string representing the path. |
Custom Header | String | When a script is used to generate a custom header value, $.output must be set to a string. |
Custom Body Serializer (JSON/XML) | Object | When creating a custom request body with JSON or XML serialization, $.output must be set to an object (which is then converted automatically to a JSON or XML string). |
Custom Body Serializer (Text) | String | When using text serialization for the request body, $.output must be set to a string object. |
Custom Body Serializer (Binary) | Buffer | When using binary serialization for the request body, $.output must be set to a Buffer object. |
Custom Response Parser | Array | When a custom response parser is used to parse the data received from the server, the $.output must be an array of tag data objects that contain, at the very minimum, a tag property and a value property, and optionally, a quality and a timestamp (using the UNIX Epoch with milliseconds format). |
Tag data objects
Whenever data is received from a request to N3uron, the data must be parsed into a format that is compatible with N3uron tags, which is an array of tag data objects. The format of a tag data object differs slightly when the data is saved to a source tag, as opposed to when it is written to a tag. The tag data formats are as follows:
TAG_ADDRESS: This value is used to associate the given tag event with a tag in the N3uron tag model. Values can either be a tag path or an alias (providing that an alias is defined in the tag configuration).
TAG_VALUE: This is the value that will be saved to the specified tag. It can be a number, a string, or a Boolean value. If the destination tag is a different type, the value will be converted to the correct type, if possible.
TAG_QUALITY: This property is optional when the tag is a REST API Client source tag and will be ignored if the tag has a different source (since this will make it a tag write). If defined, this field specifies the quality of this tag event. The property type is a number between 0 and 255. Qualities with values in the 0-64 interval are considered bad, 64-127 are uncertain, and values between 192 and 255 are good. If this value is omitted when acting as the source, it will automatically be set to 192 (GOOD_NON_SPECIFIC).
TAG_TIMESTAMP: This property is optional when the tag is a REST API Client source tag and ignored if the tag has a different source (since this will make it a tag write). If defined, this field sets the timestamp of this tag event. The property type is a number, and the value must be the number of milliseconds elapsed since 1970 (UNIX Epoch with milliseconds). For easy date parsing, Date and/or moment is recommended. If this value is omitted when acting as a source, the timestamp will automatically be set to the current time (using the Date.now() function).
{
tag: TAG_ADDRESS,
value: TAG_VALUE,
quality: TAG_QUALITY,
ts: TAG_TIMESTAMP
}