Welcome to the definitive architectural and engineering reference manual for the Virtuino Cloud Server-Side Scripting Engine. This extensive developer-grade documentation details the deployment of JavaScript programming paradigms to create advanced edge automation, mathematical signal processing, data filtering, and device cross-talk within a multi-tenant enterprise IoT infrastructure.
In standard, legacy Internet of Things (IoT) deployments, intelligence is tightly coupled with physical hardware firmware. When operational logic, calibration curves, or automation profiles require modification, field engineers must flash new binary builds to remote Microcontroller Units (MCUs) or Programmable Logic Controllers (PLCs). This architecture introduces significant maintenance overhead, device downtime, and firmware version fragmentation risks.
The Virtuino Cloud Scripting Engine completely decouples the hardware interface tier from the application logic tier. By intercepting incoming telemetry streams and outgoing downlinks inside a high-performance cloud platform, the engine empowers developers with unparalleled system capabilities:
The Virtuino Cloud Scripting workspace provides an intuitive, high-productivity web interface designed to simplify provisioning, debugging, and deployment workflows. Understanding each structural element within the user environment ensures efficient development:
Trigger / On Event vs. Polling / On Timer) determining exactly when the VM execution pipeline fires.The center of the development console features a high-performance web text editor natively optimized for ECMAScript syntax highlighting, auto-indentation, and bracket pairing evaluation. The editor interfaces directly with the server-side compiler pipeline, providing immediate programmatic syntax parsing during testing phases.
Positioned along the right margin of the interface, this console allows developers to validate code behavior before committing logic to physical field components. It features input fields representing mapped variables where mock data strings or integers can be manually typed. Clicking the Run Test action executes a single deterministic VM loop, printing runtime metrics, object states, and standard console.log() output arrays to the dedicated developer diagnostic console view.
The bridge between raw field network protocols (such as Modbus TCP mapping, HTTP POST endpoints, or Mosquitto MQTT message payloads) and your virtual JavaScript sandbox variables is governed by the IO Mapping (Input/Output Mapping) Table. This layer completely abstracts underlying network configurations, translating hardware addresses into clean, native JavaScript global objects.
| UI Parameter Field | Functional Definition & Operational Behavior |
|---|---|
| JS Variable Name | The exact identifier declared in the script workspace to represent the target data channel (e.g., tempSensor, relayControl). Must follow standard camelCase naming rules. |
| Direction Type (IN / OUT) |
IN: Read-only access direction. Binds live sensor telemetry data coming into the database to the script execution environment.OUT: Write-only access direction. Passes calculated states from the script to output target devices or persistent memory variables.
|
| Device Reference Selector | Links the mapping row to a specific physical hardware entity provisioned within the Virtuino account dashboard (e.g., Boiler_PLC_04). |
| Field Parameter Target | Points directly to the specific register, variable, or database column tracking cell within the chosen device envelope (e.g., holding_register_40001, voltage). |
To avoid data truncation errors and maintain telemetry timestamps in demanding industrial environments, input channels are exposed inside the script runtime as robust composite structures rather than simple, primitive numerical values. Every variable mapped as IN generates a structured sub-property object tree:
| Object Child Property | Data Type | Description & Access Example |
|---|---|---|
.value |
Number / String / Boolean | Retrieves the current value received from the physical hardware asset. Example: let currentTemp = inputs.tempSensor.value; |
.time |
String (ISO 8601 UTC) | Returns the exact network server timestamp when this specific metric was ingested. Example: let lastUpdate = inputs.tempSensor.time; |
.value property will initialize as an empty string (""). Passing unvalidated empty strings to advanced math methods results in runtime NaN exceptions, crashing the sandbox VM cycle. Always check your variables before applying formulas!
Understanding exactly when and how the cloud engine executes a script is vital for designing responsive, reliable systems. Virtuino Cloud provides two core execution modes:
In this framework, the script script engine remains completely idle until a fresh network transaction (e.g., an MQTT Publish packet or an HTTP POST request) arrives at the server for a specific variable defined in your IN mapping table. The moment the value lands in the ingestion pipeline, the VM executes instantaneously.
In this model, the script execution path runs on a deterministic, cyclical schedule (e.g., every 5 seconds, 30 seconds, 15 minutes) configured within the metadata panel. When the timer fires, the engine executes an asynchronous call to the database store, pulls the last recorded entries for all mapped IN variables, updates the inputs object context, and launches the script VM loop.
This section serves as a comprehensive developer reference manual for the native JavaScript (ES6+) commands supported within the cloud sandbox environment. These building blocks can be freely combined to write robust automation scripts.
Avoid using legacy var statements. Always enforce strict memory lifecycle scopes using modern keywords:
// Use const for values that will never be re-assigned during the script loop
const CALIBRATION_FACTOR = 1.414;
const MAX_ALLOWED_PSI = 120.0;
// Use let for variables that need to change states, accumulators, or loop indexes
let currentIterationCount = 0;
let outputRelayState = 0;
Control the execution path of your scripts by evaluating system statuses with relational operators (>, <, ===, !==, &&, ||):
// Evaluation loop with multi-branch options
if (temperature > 80 && safetyValveClosed === true) {
outputRelayState = 1; // High critical state
} else if (temperature > 65 || pressureWarning === 1) {
outputRelayState = 2; // Warning alert state
} else {
outputRelayState = 0; // System normal nominal status
}
The standard JavaScript Math library is fully exposed inside the sandbox environment to handle complex telemetry scaling algorithms without external dependencies:
| Math Expression Syntax | Functional Calculation Output | Developer Context Example |
|---|---|---|
Math.round(x) |
Rounds the parameter to the nearest integer value. | Math.round(23.6); // Returns 24 |
Math.floor(x) |
Truncates decimal values downward to the next integer. | Math.floor(89.9); // Returns 89 |
Math.ceil(x) |
Rounds decimal values upward to the next integer. | Math.ceil(12.1); // Returns 13 |
Math.abs(x) |
Returns the absolute mathematical positive value. | Math.abs(-45.2); // Returns 45.2 |
Math.min(a, b, c...) |
Scans the arguments list and isolates the lowest value. | Math.min(12, 45, 3); // Returns 3 |
Math.max(a, b, c...) |
Scans the arguments list and isolates the highest value. | Math.max(12, 45, 3); // Returns 45 |
Math.pow(base, exp) |
Raises the base value to the specified exponent power. | Math.pow(2, 3); // Returns 8 |
Math.sqrt(x) |
Calculates the mathematical square root of the parameter. | Math.sqrt(16); // Returns 4 |
Arrays are essential for caching data windows or tracking sequences over consecutive execution frames:
// Instantiating arrays and managing element operations
let diagnosticList = [22.4, 25.1, 24.8];
diagnosticList.push(26.3); // Places new item at the end of the array
diagnosticList.unshift(21.2); // Inserts item at index position 0
let lastElement = diagnosticList.pop(); // Removes and extracts the final item
let firstElement = diagnosticList.shift(); // Removes and extracts index 0 item
let elementCount = diagnosticList.length; // Returns array element count
Since the script output data properties can handle complex text strings, JSON operations are essential for saving structured states (like historical queues) into persistent variable channels:
// Converting complex objects to strings and back
const rawJsonString = '{"deviceId":"A45", "status": true, "logs": [12,14]}';
const parsedDataset = JSON.parse(rawJsonString);
let deviceState = parsedDataset.status; // Resolves to true
const mockHistoryArray = [45.2, 44.1, 46.0];
const serializedOutputString = JSON.stringify(mockHistoryArray);
// outputs.persistentStorageField = serializedOutputString;
Virtuino Cloud divides its processing architecture into two logical classifications, based on scope, visibility, and data routing intent:
System scripts are structural automation entities designed to perform global account tasks, background security health checks, system performance optimizations, or cluster-wide notifications. They sit above individual device dashboards and interface with internal system logs, telemetry error diagnostics, or administrative cron triggers.
Standard scripts are application-specific logic containers tightly coupled to user dashboards and specific field installations. They focus on processing mapped IO variable tables, validating physical parameters, executing device cross-talk, and updating custom visualization widgets.
Follow this detailed walkthrough to configure your first script from scratch inside the live Virtuino Cloud workspace environment:
BasementPumpController).Trigger / On Event. For cyclic calculations, select Polling / On Timer.waterLevelSensor. Pick the physical hardware source device node and select the specific data register tracking field.pumpRelayCmd. Set the direction identifier to OUT, select your target relay device asset, and pick its digital command channel register.42.5) into the simulation box input field assigned to waterLevelSensor. Click the Run Test button. Verify that the output results and the debug log console trace conform exactly to your operational guidelines.Is Active and Is Ready status checkboxes to true, then click the Save & Deploy Script action button. The script is now live inside the Virtuino Cloud runtime!Review and deploy these 10 distinct, thoroughly commented automation blueprints. Each example features a dark green background configuration (`#0b2913`) matching your exact environment profile rules.
Intent: Evaluates noisy analog sensor data, drops extreme maximum and minimum spikes, and returns the stable arithmetic average over a sliding historical window.
// Set the maximum element window size for tracking history
const HISTORICAL_BUFFER_LIMIT = 5;
// Extract raw measurement input from mapped IO configuration
const incomingRawRecord = inputs.waterLevelSensor.value;
// Crash protection guard clause: Verify field data availability
if (incomingRawRecord === null || incomingRawRecord === undefined || incomingRawRecord === "") {
console.log("Telemetry packet unallocated or offline. Aborting logic loop.");
return;
}
// Cast string variables to float numbers
const cleanNumericValue = Number(incomingRawRecord);
if (isNaN(cleanNumericValue)) {
console.log("Error: Passed value parameter is not an acceptable number structure.");
return;
}
// Rehydrate historical array structure from output persistent variable memory string
let historicalDataWindow = [];
try {
historicalDataWindow = JSON.parse(outputs.memoryBufferCache || "[]");
} catch (parsingException) {
historicalDataWindow = [];
}
// Place the newest sensor reading at the front of the array index (Position 0)
historicalDataWindow.unshift(cleanNumericValue);
// Truncate old data records that exceed boundary capacity thresholds
if (historicalDataWindow.length > HISTORICAL_BUFFER_LIMIT) {
historicalDataWindow = historicalDataWindow.slice(0, HISTORICAL_BUFFER_LIMIT);
}
// Serialize active array cache state back to storage field
outputs.memoryBufferCache = JSON.stringify(historicalDataWindow);
// Ensure enough array coordinates exist to reject outlier noise spikes
if (historicalDataWindow.length < 3) {
console.log("Populating buffer elements. Ongoing length: " + historicalDataWindow.length);
return;
}
// Clone the array and apply sorting to isolate extreme values
let sortedArrayInstance = [...historicalDataWindow].sort((alpha, beta) => alpha - beta);
sortedArrayInstance.pop(); // Purge absolute peak noise spike
sortedArrayInstance.shift(); // Purge absolute low noise drop
// Sum remaining stable samples
const combinedSum = sortedArrayInstance.reduce((accumulator, currentElement) => accumulator + currentElement, 0);
const calculatedArithmeticMean = combinedSum / sortedArrayInstance.length;
// Output the smoothed value rounded to two decimal points
outputs.smoothedOutputRegister = Math.round(calculatedArithmeticMean * 100) / 100;
console.log("Signal filtering cycle finished smoothly.");
Intent: Verifies incoming data sits inside safe mechanical boundaries before committing changes to tracking registers.
// Define critical hardware operation thresholds
const SAFETY_FLOOR_LIMIT = 5.0;
const SAFETY_CEILING_LIMIT = 95.0;
const rawTelemetryData = inputs.pressureInputSensor.value;
// Validate input existence
if (rawTelemetryData === null || rawTelemetryData === undefined || rawTelemetryData === "") {
console.log("Data stream unreachable. Execution suspended.");
return;
}
const parsedPressureMetric = Number(rawTelemetryData);
if (isNaN(parsedPressureMetric)) {
return;
}
// Evaluate if parameter falls within valid operational tolerances
if (parsedPressureMetric >= SAFETY_FLOOR_LIMIT && parsedPressureMetric <= SAFETY_CEILING_LIMIT) {
outputs.validatedPressureOutput = parsedPressureMetric;
console.log("Data falls within safe tolerances. Logging record.");
} else {
console.log("Alert: Hardware boundary exception encountered. Value dropped!");
return;
}
Intent: Processes a high-resolution signal and generates separate outputs for integer values and two-decimal floats.
const rawHighResInput = inputs.preciseAdcSensor.value;
// Halt execution if data field initializes empty
if (rawHighResInput === null || rawHighResInput === undefined || rawHighResInput === "") {
console.log("Awaiting field parameter assignment updates.");
return;
}
const nativeFloatValue = Number(rawHighResInput);
if (isNaN(nativeFloatValue)) {
console.log("Invalid format parameter detected.");
return;
}
// Map high-resolution values to discrete user interface requirements
outputs.integerDisplayChannel = Math.round(nativeFloatValue);
outputs.precisionFloatDisplayChannel = Math.round(nativeFloatValue * 100) / 100;
console.log("Multi-channel scaling finished successfully.");
Intent: Continuously adds the current sensor input metric to a running historical summation. Ideal for flow meters, pulse counters, or power metrics.
const rawFlowIncrement = inputs.flowSensorPulse.value;
// Validate input metrics
if (rawFlowIncrement === null || rawFlowIncrement === undefined || rawFlowIncrement === "") {
console.log("No pulse or flow increments found.");
return;
}
const incrementalVolume = Number(rawFlowIncrement);
if (isNaN(incrementalVolume)) {
return;
}
// Retrieve past long-term sum total from tracking record column
let historicalAccumulatedSum = outputs.cumulativeTotalStorage;
// Protect against null states during initial system configuration run
if (historicalAccumulatedSum === undefined || historicalAccumulatedSum === null || isNaN(historicalAccumulatedSum)) {
historicalAccumulatedSum = 0.0;
}
// Append incremental volume to long-term database counter
outputs.cumulativeTotalStorage = historicalAccumulatedSum + incrementalVolume;
console.log("Cumulative summation total updated successfully.");
Intent: Monitors operational temperatures and records the absolute historical low value encountered across all device runs.
const rawTemperatureReading = inputs.ambientTempSensor.value;
// Validate sensor metrics
if (rawTemperatureReading === null || rawTemperatureReading === undefined || rawTemperatureReading === "") {
console.log("Temperature line scanning unallocated.");
return;
}
const currentTemperatureValue = Number(rawTemperatureReading);
if (isNaN(currentTemperatureValue)) {
return;
}
// Fetch previous historical minimum value baseline
const recordedHistoricalMinimum = outputs.historicalLowRecord;
// Update record if a new low temperature is detected or if it's the first run
if (recordedHistoricalMinimum === undefined || recordedHistoricalMinimum === null || currentTemperatureValue < recordedHistoricalMinimum) {
outputs.historicalLowRecord = currentTemperatureValue;
console.log("New historical low parameter captured: " + currentTemperatureValue);
} else {
outputs.historicalLowRecord = recordedHistoricalMinimum;
}
const serializedMqttPayload = inputs.gatewayJsonString.value;
// Validate that string attributes exist
if (typeof serializedMqttPayload !== 'string' || serializedMqttPayload.trim() === "") {
console.log("Payload data stream does not match expected JSON string rules.");
return;
}
try {
// Decode the raw string back into a structural JavaScript Object
const nestedDataObject = JSON.parse(serializedMqttPayload);
// Route sub-properties to individual output channels if present
if (nestedDataObject.humidity !== undefined) {
outputs.isolatedHumidityChannel = nestedDataObject.humidity;
}
if (nestedDataObject.temperature !== undefined) {
outputs.isolatedTemperatureChannel = nestedDataObject.temperature;
}
console.log("JSON parsing and data routing tasks finished.");
} catch (jsonParsingException) {
console.log("Fatal Error: Failed to evaluate JSON syntax rules.");
return;
}
Intent: Evaluates status signals from separate physical environments before issuing hardware relay permissions.
const emergencyStopSwitch = inputs.estopStatus.value;
const tankLevelPressure = inputs.pressureHighFlag.value;
// Verify that all dependency attributes are online
if (emergencyStopSwitch === null || emergencyStopSwitch === "" || tankLevelPressure === null || tankLevelPressure === "") {
console.log("Critical safety dependencies are offline. Suspending logic checks.");
return;
}
const estopActiveState = Number(emergencyStopSwitch);
const pressureOverloadState = Number(tankLevelPressure);
// Execute interlocking evaluations using logical operators
if (estopActiveState === 0 && pressureOverloadState === 0) {
outputs.masterSafetyRelayCmd = 1; // Safe condition: Energize line relay
console.log("Safety parameters cleared. Output relay active.");
} else {
outputs.masterSafetyRelayCmd = 0; // Hazard condition: De-energize output instantly
console.log("Hazard alert: Interlock triggered. Disabling line relay.");
}
Intent: Translates raw ADC values via the standard linear calibration formula ($y = ax + b$).
const rawAdcVoltageReading = inputs.rawAdcRegister.value;
// Verify interface availability
if (rawAdcVoltageReading === null || rawAdcVoltageReading === undefined || rawAdcVoltageReading === "") {
console.log("ADC interface frame missing.");
return;
}
const primitiveAdcValue = Number(rawAdcVoltageReading);
if (isNaN(primitiveAdcValue)) {
return;
}
// Convert raw integer profiles to real engineering units (e.g., Temperature Celsius)
// Coefficient scale factor: 1.82, Zero-offset intercept value: 12.40
const engineeringScaledMetric = (primitiveAdcValue * 1.82) + 12.40;
outputs.calibratedCelsiusOutput = Math.round(engineeringScaledMetric * 100) / 100;
console.log("Linear transformation mapping complete.");
Intent: Monitors variance between two matching physical environment sensors and flags maintenance alerts if they drift apart.
const internalZoneSensor = inputs.chamberSensorA.value;
const externalZoneSensor = inputs.chamberSensorB.value;
// Ensure both telemetry lines are reachable
if (internalZoneSensor === null || internalZoneSensor === "" || externalZoneSensor === null || externalZoneSensor === "") {
console.log("Awaiting telemetry sync updates from redundant lines.");
return;
}
const readingValueA = Number(internalZoneSensor);
const readingValueB = Number(externalZoneSensor);
if (isNaN(readingValueA) || isNaN(readingValueB)) {
return;
}
// Calculate absolute difference between the redundant nodes
const variationMagnitude = Math.abs(readingValueA - readingValueB);
// Trigger maintenance alarms if drift variance exceeds 4.5 operational points
if (variationMagnitude > 4.5) {
outputs.sensorDriftAlarmTrigger = 1; // Set alarm state high
console.log("Maintenance alert: Redundant sensors out of sync!");
} else {
outputs.sensorDriftAlarmTrigger = 0; // Clear alarm status
}
outputs.monitoredVarianceTrack = variationMagnitude;
Intent: Triggers a digital command bit using a deadband buffer window to prevent rapid on/off cycling (chattering) at boundary thresholds.
// Define thermostat target limits with deadband buffer rules
const COLD_SETPOINT_LIMIT = 22.0;
const HYSTERESIS_BUFFER_RANGE = 1.5;
const rawChamberTemp = inputs.liveRoomTemperature.value;
if (rawChamberTemp === null || rawChamberTemp === undefined || rawChamberTemp === "") {
console.log("Thermostat tracking loop inactive: Data stream unmapped.");
return;
}
const physicalTemperature = Number(rawChamberTemp);
if (isNaN(physicalTemperature)) {
return;
}
// Fetch current compressor state to evaluate step directions
let ongoingCompressorState = outputs.compressorPowerRelay;
if (ongoingCompressorState === undefined || ongoingCompressorState === null) {
ongoingCompressorState = 0;
}
// Execute thermostat logic tracking with built-in hysteresis deadband values
if (physicalTemperature > (COLD_SETPOINT_LIMIT + HYSTERESIS_BUFFER_RANGE)) {
outputs.compressorPowerRelay = 1; // Engage cooling actuator
console.log("Threshold exceeded: Activating compressor cooling loop.");
} else if (physicalTemperature < (COLD_SETPOINT_LIMIT - HYSTERESIS_BUFFER_RANGE)) {
outputs.compressorPowerRelay = 0; // Disengage cooling actuator
console.log("Target limit met: Deactivating compressor loop.");
} else {
// Maintain current state within deadband range to avoid short-cycling hardware
outputs.compressorPowerRelay = ongoingCompressorState;
}