Skip to main content
Version: 1.0

Script Development

The script (script.svc.ts) is the runtime logic that runs on your device.

The script synchronizes changes bidirectionally: when hardware reports a change, the script updates the virtual component; when the user interacts with the UI (changing a virtual component), the script writes the change to hardware.

Start with the Manifest to define your device's controls, then use this guide to implement the script logic.

Virtual Component Handles

Get a handle to a virtual component using its role (the key in vc from your manifest):

const stateHandle = Service.getVCHandle('state');
const brightnessHandle = Service.getVCHandle('brightness');

Component Handle Methods

// Get the current value
const currentValue = stateHandle.getValue();

// Set a new value
stateHandle.setValue(true);

// Get manifest configuration
const config = stateHandle.getConfig();

// Listen for changes
stateHandle.on('change', ({ value }) => {
console.log('New value:', value);
});

// For button components, listen for triggers
openHandle.on('single_push', () => {
// Button was pressed
});

Hardware Communication

Firmware extensions talk to hardware through a few channels:

  • UART — Direct serial byte stream to an external microcontroller.
  • TMCU — Numbered datapoints exchanged with an integrated MCU.

UART Example

Direct serial communication with external devices via UART protocol.

Configuration

Get and configure a UART port:

const uart = UART.get(0);  // Port 0
uart.configure({ baud: 115200, mode: '8N1' });

Parameters:

  • port — UART port number (0, 1, etc.)
  • baud — Baud rate (9600, 19200, 38400, 57600, 115200, etc.)
  • mode — Data format:
    • 8N1 — 8 data bits, no parity, 1 stop bit
    • Other modes: 8E1, 8O1, etc.

Writing Data

Send strings to the external device:

uart.write('HELLO\n');
uart.write('STATE:ON\n');
uart.write('DIM:50\n');

Reading Data

Listen for incoming data:

uart.recv((data: string) => {
console.log('Received:', data);
// Parse and handle the data
});

Bidirectional Sync

const uart = UART.get(0);
uart.configure({ baud: 115200, mode: '8N1' });

const stateHandle = Service.getVCHandle('state');

let updatingFromUart = false;

// Device to app
uart.recv((data: string) => {
updatingFromUart = true;
stateHandle.setValue(data === 'CMD_ON');
updatingFromUart = false;
});

// App to device
stateHandle.on('change', (data: any) => {
if (updatingFromUart) return;
uart.write(data.value ? 'CMD_ON' : 'CMD_OFF');
});

See the UART page for more examples (state + brightness, button actions, etc.).

Timers and Intervals

For time-based functionality like elapsed time or periodic updates:

const chargeHandle = Service.getVCHandle('charge');
const durationHandle = Service.getVCHandle('duration');

let chargeStartTime: number | null = null;
let intervalTimer: number | null = null;

chargeHandle.on('change', ({ value }) => {
if (value) {
// Start
durationHandle.setValue(0);
chargeStartTime = Date.now();
intervalTimer = setInterval(() => {
if (chargeStartTime) {
const minutes = Math.floor((Date.now() - chargeStartTime) / 60_000);
durationHandle.setValue(minutes);
}
}, 1000);
} else {
// Stop
chargeStartTime = null;
if (intervalTimer) {
clearInterval(intervalTimer);
intervalTimer = null;
}
}
});

Button Triggers

For button components (which have no stored value), respond to single_push:

const openHandle = Service.getVCHandle('open');
const positionHandle = Service.getVCHandle('position');

openHandle.on('single_push', () => {
// User pressed the button in the app
uart.write('ACTION_EXPAND');
// Optionally update a position component to reflect the action
positionHandle.setValue(100);
});

Next: Web UI Development