Skip to main content
Version: 1.0

Virtual Components

The Virtual Components API allows scripts to interact with virtual components configured on the device. Virtual components are generic entities (numbers, text, booleans, buttons, etc.) that appear in the device's web interface and can be controlled via the standard RPC API.

Overview

Virtual components provide a way to:

  • Access and control virtual components from scripts
  • Store and manage state persistently
  • Respond to user input from the device UI
  • Integrate with home automation systems
  • Implement custom logic based on virtual component values

Supported Component Types

  • Number - Numeric values with min/max limits
  • Text - String values
  • Enum - Selection from predefined options
  • Boolean - True/false switches
  • Button - Push buttons with various press types
  • Group - Organizational containers

Quick Example

// Get handles to virtual components
let temperature = Virtual.getHandle("number:200");
let status = Virtual.getHandle("text:200");
let alarm = Virtual.getHandle("boolean:200");

// Set values
temperature.setValue(23.5);
status.setValue("Normal");
alarm.setValue(false);

// Read values
console.log("Temperature:", temperature.getValue());

// Configure the temperature component
temperature.setConfig({
name: "Room Temperature",
persisted: true,
min: -40,
max: 85,
default_value: 20,
meta: {
ui: {
unit: "°C",
step: 0.1
}
}
});

// Listen for changes
temperature.on("change", function(event) {
console.log("Temperature changed to", event.value);
if (event.value > 30) {
alarm.setValue(true);
status.setValue("High temperature alert!");
}
});

API Reference

Virtual Global Object

The Virtual object provides access to virtual component functionality.

Virtual.getHandle(key)

Obtains a handle to a virtual component instance.

Virtual.getHandle(key) -> Virtual instance or null

Parameters

  • key (string) - Component identifier in format "type:id" where:
    • type is the component type (number, text, boolean, enum, button, group)
    • id is the numeric component ID (typically 200+)

Return Value

Returns a Virtual instance on success, or null if the component doesn't exist or on error.

Example

let mySwitch = Virtual.getHandle("boolean:200");
let myButton = Virtual.getHandle("button:201");
let myNumber = Virtual.getHandle("number:202");

Virtual Instance Methods

setValue(new_value)

Sets the value of the virtual component. The value is applied asynchronously when the script execution context exits.

instance.setValue(new_value) -> undefined

Note: Not supported for Button and Group components. Attempting to use on these types will raise an exception.

Parameters

  • new_value - The value to set (type depends on component type)

Examples

let temperature = Virtual.getHandle("number:200");
let deviceName = Virtual.getHandle("text:200");
let isEnabled = Virtual.getHandle("boolean:200");

temperature.setValue(25.5);
deviceName.setValue("Living Room Sensor");
isEnabled.setValue(true);

getValue()

Returns the current value of the virtual component.

instance.getValue() -> value or undefined

Note: Not supported for Button and Group components.

Return Value

Returns the component's value, or undefined on error.

Example

let temperature = Virtual.getHandle("number:200");
temperature.setValue(22.5);
console.log("Current temperature:", temperature.getValue()); // 22.5

getStatus()

Returns the status of the virtual component.

instance.getStatus() -> object or undefined

Return Value

Returns the component's status object, or undefined if the component no longer exists. The status format varies by component type.

See the status documentation for each component type:

Example

let sensor = Virtual.getHandle("number:200");
let status = sensor.getStatus();
console.log("Status:", JSON.stringify(status));
// Example output: {"value":42,"source":"script:1","last_update_ts":1753928721}

getConfig()

Returns the configuration of the virtual component.

instance.getConfig() -> object or undefined

Return Value

Returns the component's configuration object, or undefined if the component no longer exists. The configuration format varies by component type.

See the configuration documentation for each component type:

Example

let temp = Virtual.getHandle("number:200");
let config = temp.getConfig();
console.log("Config:", JSON.stringify(config));

setConfig(config_obj)

Updates the configuration of the virtual component.

instance.setConfig(config_obj) -> undefined

Parameters

  • config_obj (object) - Configuration properties to update. The available properties vary by component type.

See the configuration documentation for each component type:

Example

let temperature = Virtual.getHandle("number:200");

temperature.setConfig({
name: "Room Temperature",
persisted: true,
min: -50,
max: 100,
default_value: 20,
meta: {
ui: {
unit: "°C",
step: 0.1
}
}
});

on(event, callback)

Attaches an event handler to the virtual component.

instance.on(event, callback) -> listener_id

Parameters

  • event (string) - Event name to listen for
  • callback (function) - Function to call when event occurs
    • Receives event info object with:
      • source - Source that triggered the event
      • value - New value (for change events)

Return Value

Returns listener ID that can be used with off() to remove the listener.

Supported Events

For Number, Text, Boolean, Enum components:

  • change - Triggered when value changes

For Button components:

  • single_push - Single press
  • double_push - Double press
  • triple_push - Triple press
  • long_push - Long press

Examples

// Value change listener
let temperature = Virtual.getHandle("number:200");
temperature.on("change", function(event) {
console.log("Temperature changed to", event.value);
console.log("Changed by", event.source);
});

// Button press listeners
let button = Virtual.getHandle("button:201");

button.on("single_push", function(event) {
console.log("Button pressed once");
});

button.on("double_push", function(event) {
console.log("Button pressed twice");
});

button.on("long_push", function(event) {
console.log("Button long pressed");
});

off(listener_id)

Removes an event listener.

instance.off(listener_id) -> undefined

Parameters

  • listener_id - The ID returned by on()

Example

let sensor = Virtual.getHandle("number:200");

// Add listener
let listenerId = sensor.on("change", function(event) {
console.log("Value changed to", event.value);
});

// Later, remove the listener
sensor.off(listenerId);

Complete Examples

Temperature Monitor with Alarm

// Virtual components for temperature monitoring
let currentTemp = Virtual.getHandle("number:200");
let tempStatus = Virtual.getHandle("text:201");
let highTempAlarm = Virtual.getHandle("boolean:202");
let resetButton = Virtual.getHandle("button:203");

// Configure temperature sensor
currentTemp.setConfig({
name: "Temperature Sensor",
persisted: true,
min: -50,
max: 150,
default_value: 20,
meta: {
ui: {
unit: "°C",
step: 0.1
}
}
});

// Configure status text
tempStatus.setConfig({
name: "Status",
persisted: false,
default_value: "Normal"
});

// Temperature threshold
const TEMP_THRESHOLD = 35;

// Monitor temperature changes
currentTemp.on("change", function(event) {
console.log("Temperature:", event.value, "°C");

if (event.value > TEMP_THRESHOLD) {
highTempAlarm.setValue(true);
tempStatus.setValue("HIGH TEMPERATURE: " + event.value + "°C");

// Send alert
Shelly.call("HTTP.Request", {
method: "GET",
url: "http://alert.server/high-temp?value=" + event.value
});
} else {
highTempAlarm.setValue(false);
tempStatus.setValue("Normal: " + event.value + "°C");
}
});

// Reset button
resetButton.on("single_push", function() {
console.log("Reset pressed");
highTempAlarm.setValue(false);
tempStatus.setValue("System reset");
});

// Simulate temperature readings (in real use, read from actual sensor)
Timer.set(5000, true, function() {
let mockTemp = 20 + Math.random() * 30;
currentTemp.setValue(mockTemp);
});

console.log("Temperature monitor started");

State Machine with Enum

// Create state machine using enum
let machineState = Virtual.getHandle("enum:200");
let startButton = Virtual.getHandle("button:201");
let stopButton = Virtual.getHandle("button:202");
let statusText = Virtual.getHandle("text:203");

// Configure states
machineState.setConfig({
name: "Machine State",
persisted: true,
options: ["idle", "running", "paused", "error"],
default_value: "idle"
});

// State change handler
machineState.on("change", function(event) {
console.log("State changed to:", event.value);
statusText.setValue("Machine is " + event.value);

// Take action based on state
switch(event.value) {
case "running":
Shelly.call("Switch.Set", {id: 0, on: true});
break;
case "idle":
case "paused":
Shelly.call("Switch.Set", {id: 0, on: false});
break;
case "error":
// Flash LED or send notification
console.log("ERROR STATE - Intervention required");
break;
}
});

// Control buttons
startButton.on("single_push", function() {
let current = machineState.getValue();
if (current === "idle" || current === "paused") {
machineState.setValue("running");
}
});

stopButton.on("single_push", function() {
machineState.setValue("idle");
});

// Error detection (example)
Timer.set(10000, true, function() {
if (machineState.getValue() === "running") {
// Check for error condition
let systemStatus = Shelly.getComponentStatus("sys");
if (systemStatus.ram_free < 10000) {
machineState.setValue("error");
}
}
});

Data Logger with Persistence

// Virtual components for data logging
let sampleCount = Virtual.getHandle("number:200");
let lastSample = Virtual.getHandle("number:201");
let averageValue = Virtual.getHandle("number:202");
let resetButton = Virtual.getHandle("button:203");

// Configure persistent storage
sampleCount.setConfig({ persisted: true, default_value: 0 });
lastSample.setConfig({ persisted: true, unit: "units" });
averageValue.setConfig({ persisted: true, unit: "units" });

// Running totals
let runningTotal = 0;
let count = sampleCount.getValue() || 0;

// If we have previous data, calculate running total
if (count > 0) {
runningTotal = (averageValue.getValue() || 0) * count;
}

// Collect data every minute
Timer.set(60000, true, function() {
// Get sensor reading (example)
let reading = Math.random() * 100; // Replace with actual sensor

// Update statistics
count++;
runningTotal += reading;
let avg = runningTotal / count;

// Update virtual components
sampleCount.setValue(count);
lastSample.setValue(reading);
averageValue.setValue(avg);

console.log("Sample", count, ":", reading, "Avg:", avg);
});

// Reset statistics
resetButton.on("single_push", function() {
console.log("Resetting statistics");
count = 0;
runningTotal = 0;

sampleCount.setValue(0);
lastSample.setValue(0);
averageValue.setValue(0);
});

console.log("Data logger started. Samples:", count);

Best Practices

1. Check Component Existence

let component = Virtual.getHandle("number:200");
if (!component) {
console.log("Virtual component not found!");
return;
}

2. Use Appropriate Component Types

  • Use Number for numeric values with ranges
  • Use Boolean for on/off states
  • Use Enum for multiple choice options
  • Use Button for user actions
  • Use Text for display strings

3. Configure Persistence Appropriately

// Persist important state
stateComponent.setConfig({ persisted: true });

// Don't persist temporary values
tempDisplay.setConfig({ persisted: false });

4. Handle Events Efficiently

// Good - Single handler for multiple actions
button.on("single_push", handleButtonPress);
button.on("double_push", handleButtonPress);

function handleButtonPress(event) {
// Handle different press types
}

// Avoid creating many anonymous functions

5. Clean Up Event Listeners

let listeners = [];

// Track listeners
listeners.push(component.on("change", handler));

// Clean up when done
for (let id of listeners) {
component.off(id);
}

Limitations

  • Not all component types support all methods
  • Values are applied asynchronously
  • Limited number of virtual components per device

Creating Virtual Components

Virtual components must be created through the device's RPC API before scripts can access them:

# Create a virtual number component
curl -X POST -d '{
"method": "Virtual.Add",
"params": {
"type": "number",
"config": {
"name": "Temperature",
"min": -50,
"max": 100
}
}
}' http://<device_ip>/rpc

# Returns: {"id": 200}

Once created, scripts can access them using the returned ID.

  • Shelly API - Core device control
  • Timer API - Scheduling and delays
  • Virtual Components Service - RPC API documentation