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:typeis the component type (number, text, boolean, enum, button, group)idis 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:
- Number configuration
- Text configuration
- Boolean configuration
- Enum configuration
- Button configuration
- Group configuration
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:
- Number configuration
- Text configuration
- Boolean configuration
- Enum configuration
- Button configuration
- Group configuration
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)
- Receives event info object with:
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 pressdouble_push- Double presstriple_push- Triple presslong_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
Numberfor numeric values with ranges - Use
Booleanfor on/off states - Use
Enumfor multiple choice options - Use
Buttonfor user actions - Use
Textfor 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.
Related Topics
- Shelly API - Core device control
- Timer API - Scheduling and delays
- Virtual Components Service - RPC API documentation