doc/help/Output-Controls.md
Output controls are interactive dashboard widgets that send data back to a connected device. While standard widgets visualize incoming telemetry, output controls transmit commands, setpoints, and parameters from the Serial Studio dashboard, so the dashboard can both read from and write to the device.
Each output control uses a user-defined JavaScript transmit(value) function that converts widget interactions (button clicks, slider drags, text input) into the exact bytes your device expects. That makes output controls protocol-agnostic: the same slider widget can drive a plain-text serial command, a JSON payload, or a binary packet by changing only the transmit function.
Output controls require a Pro license.
flowchart LR
A["User Interaction
(click, drag, type)"] --> B["Widget passes value
to transmit(value)"]
B --> C["JavaScript returns
formatted command"]
C --> D["Serial Studio sends
bytes to device"]
transmit(value) function with the interaction value.String.fromCharCode).Transmission is rate-limited to a minimum of 50 ms between sends, preventing device buffer overflows during continuous interactions like slider drags.
Sends a single command on click.
| Property | Value |
|---|---|
Value passed to transmit() | 1 (integer) |
| Interaction | Single click |
| Use cases | Reset, start/stop, trigger measurement |
Sends a numeric value from a draggable slider.
| Property | Default | Description |
|---|---|---|
| Min Value | 0 | Lower bound of the slider range |
| Max Value | 100 | Upper bound of the slider range |
| Step Size | 1 | Increment between discrete positions |
| Initial Value | 0 | Starting position |
The value passed to transmit() is a number clamped to [Min, Max]. Transmissions occur continuously while dragging, rate-limited to 50 ms intervals.
Binary on/off switch.
| Property | Default | Description |
|---|---|---|
| Initial Value | 0 | Starting state (0 = off, 1 = on) |
Passes 1 to transmit() when switched on, 0 when switched off.
Accepts arbitrary typed input and sends it as a string.
| Property | Value |
|---|---|
Value passed to transmit() | The typed string |
| Interaction | Press Enter or click Send |
| Use cases | AT commands, debug console, custom queries |
Rotary dial for continuous setpoint adjustment. Same numeric properties as Slider (Min, Max, Step, Initial Value) but displayed as a circular dial.
Output controls live inside Output Panel groups. You can also add an Output Panel group first (via the toolbar), then add controls to it. Each Output Panel can hold multiple controls of mixed types, packed automatically into as many columns as fit the available width.
Every output control has a JavaScript transmit(value) function that defines how interactions become device commands. The function is compiled once when the dashboard opens and executed on each interaction.
The function receives a single value parameter and must return a string:
function transmit(value) {
// value is:
// 1 for Button clicks
// 0 or 1 for Toggle state changes
// number for Slider and Knob
// "string" for TextField input
return "CMD " + value + "\r\n";
}
The return value must be a string. For binary protocols, build a byte-string with String.fromCharCode(...) (see the Binary Packet template); returning a plain array of numbers transmits nothing. Payloads are capped at 65536 bytes, and a transmit() call that runs longer than 500 ms is stopped by a watchdog. Both conditions abort the transmission and flash a red border on the control; hover the control to see the error message.
The code editor includes a set of ready-to-use templates (Simple Command, JSON Command, Binary Packet, PWM Control, PID Setpoint, Relay Toggle, AT Command, Modbus Write, CAN Bus Frame, G-Code, GRBL, NMEA, SCPI, SLCAN, and a default starting point). Select one from the template dropdown and customize it for your device.
Sends plain text with a line terminator. Adapts to the widget type automatically.
function transmit(value) {
if (typeof value === "string")
return value + "\r\n";
if (value === 1)
return "ON\r\n";
if (value === 0)
return "OFF\r\n";
return "SET " + value + "\r\n";
}
Sends structured JSON objects. Useful for firmware that parses JSON input.
function transmit(value) {
var obj = {
cmd: "set",
value: value
};
return JSON.stringify(obj) + "\n";
}
Sends framed binary data with STX/ETX delimiters.
function transmit(value) {
var STX = String.fromCharCode(0x02);
var ETX = String.fromCharCode(0x03);
var cmd = String.fromCharCode(0x01);
var val = String.fromCharCode(Math.round(value) & 0xFF);
return STX + cmd + val + ETX;
}
Sends a duty cycle value (0-255) for motor speed, LED brightness, or heater control.
function transmit(value) {
var duty = Math.round(Math.max(0, Math.min(255, value)));
return "PWM " + duty + "\r\n";
}
Sends a floating-point setpoint with 2 decimal places for PID controllers.
function transmit(value) {
return "SP " + Number(value).toFixed(2) + "\r\n";
}
Sends distinct ON/OFF commands for relay or digital output control.
function transmit(value) {
return value ? "RELAY ON\r\n" : "RELAY OFF\r\n";
}
Sends AT-style commands for modems, Bluetooth modules, and WiFi modules.
function transmit(value) {
if (typeof value === "string" && value.length > 0)
return "AT+" + value + "\r\n";
return "AT\r\n";
}
Writes a slider value directly to a Modbus holding register using the built-in helper function.
function transmit(value) {
return modbusWriteRegister(0x0001, value);
}
Sends a numeric value as a CAN frame using the built-in helper function.
function transmit(value) {
return canSendValue(0x100, value, 2);
}
Click the import button in the code editor toolbar to load a .js file from disk. This is useful for sharing transmit functions across projects or version-controlling them separately.
Every output widget's JavaScript engine includes built-in helper functions for Modbus and CAN Bus protocols. These handle binary byte-packing so you don't have to construct raw bytes manually.
modbusWriteRegister(address, value)Writes a 16-bit integer to a single holding register.
| Parameter | Type | Description |
|---|---|---|
address | Number | Register address (0x0000–0xFFFF) |
value | Number | Value to write (rounded to integer, 0–65535) |
function transmit(value) {
return modbusWriteRegister(0x0001, value);
}
modbusWriteCoil(address, on)Writes a coil value (ON = 0xFF00, OFF = 0x0000).
| Parameter | Type | Description |
|---|---|---|
address | Number | Coil address (0x0000–0xFFFF) |
on | Boolean/Number | Truthy = ON, falsy = OFF |
// Toggle widget controlling a relay coil
function transmit(value) {
return modbusWriteCoil(0x0000, value);
}
modbusWriteFloat(address, value)Writes an IEEE-754 32-bit float across two consecutive holding registers (big-endian).
| Parameter | Type | Description |
|---|---|---|
address | Number | Starting register address |
value | Number | Floating-point value |
// Slider writing a temperature setpoint as a 32-bit float
function transmit(value) {
return modbusWriteFloat(0x0010, value);
}
canSendFrame(id, payload)Sends an arbitrary CAN frame with the given identifier and payload.
| Parameter | Type | Description |
|---|---|---|
id | Number | CAN identifier, packed as two bytes (masked to 0x0000–0xFFFF) |
payload | Array or String | Payload bytes as an array of numbers (0–255), or a raw string |
// Button sending a fixed command frame
function transmit(value) {
return canSendFrame(0x200, [0x01, 0x00, 0xFF]);
}
// Slider packing its value into a 3-byte payload
function transmit(value) {
var v = Math.round(value);
return canSendFrame(0x100, [0x01, (v >> 8) & 0xFF, v & 0xFF]);
}
canSendValue(id, value, bytes)Sends a numeric value packed big-endian into a CAN frame.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | Number | Required | CAN identifier |
value | Number | Required | Numeric value (rounded to integer) |
bytes | Number | 2 | Number of payload bytes (1–8) |
// Slider sending a 16-bit value on CAN ID 0x100
function transmit(value) {
return canSendValue(0x100, value, 2);
}
// Knob sending a 32-bit value on CAN ID 0x300
function transmit(value) {
return canSendValue(0x300, value, 4);
}
The helpers return strings that can be concatenated or conditionally selected:
// Write different registers based on a toggle state
function transmit(value) {
if (value)
return modbusWriteRegister(0x0010, 1); // Enable
else
return modbusWriteRegister(0x0010, 0); // Disable
}
// Send a CAN frame with a header byte and the widget value
function transmit(value) {
return canSendFrame(0x150, [0xAA, Math.round(value) & 0xFF]);
}
Output controls are displayed in an Output Panel widget on the dashboard. The panel uses an adaptive layout engine:
In projects with multiple data sources (devices), the target device is determined by the source of the Output Panel group a control belongs to, not by a per-control property. Every control in a group transmits to that group's source. To send to a different device, place the control in an Output Panel group assigned to that source.
Both output controls and Actions send data to connected devices, but they serve different purposes:
| Feature | Output Controls | Actions |
|---|---|---|
| Widget types | 5 (button, slider, toggle, text, knob) | Button only |
| Data formatting | JavaScript transmit() function | Fixed TX Data + EOL |
| Continuous values | Yes (slider, knob) | No |
| Timer/auto-repeat | No | Yes (4 timer modes) |
| Auto-execute on connect | No | Yes |
| License | Pro | Free |
Use Actions for simple fire-and-forget commands, periodic polling, and auto-execute-on-connect sequences. Use Output Controls when you need interactive controls with continuous values, custom data formatting, or a mix of widget types.
Control motor speed with a slider and an emergency stop button.
| Control | Type | Properties |
|---|---|---|
| Speed | Slider | Min: 0, Max: 100, Units: "%" |
| Emergency Stop | Button |
Speed transmit function:
function transmit(value) {
return "SPD " + Math.round(value) + "\r\n";
}
Emergency Stop transmit function:
function transmit(value) {
return "ESTOP\r\n";
}
Toggle 3 relays independently.
| Control | Type | Properties |
|---|---|---|
| Relay 1 | Toggle | ON: "Closed", OFF: "Open" |
| Relay 2 | Toggle | ON: "Closed", OFF: "Open" |
| Relay 3 | Toggle | ON: "Closed", OFF: "Open" |
Each relay uses a customized transmit function with its relay number:
// Relay 1
function transmit(value) {
return value ? "R1 ON\r\n" : "R1 OFF\r\n";
}
Combine a text field for commands with a knob for fine adjustment.
| Control | Type | Properties |
|---|---|---|
| Command | TextField | |
| Offset | Knob | Min: -10, Max: 10, Step: 0.1, Units: "mV" |
Command transmit function:
function transmit(value) {
return "CAL " + value + "\r\n";
}
Offset transmit function:
function transmit(value) {
return "OFFSET " + Number(value).toFixed(1) + "\r\n";
}
Control a PID loop over Modbus by writing setpoint, Kp, and enable/disable to holding registers.
| Control | Type | Properties |
|---|---|---|
| Setpoint | Slider | Min: 0, Max: 500, Step: 0.5, Units: "°C" |
| Kp Gain | Knob | Min: 0, Max: 10, Step: 0.01 |
| Enable | Toggle | ON: "Running", OFF: "Stopped" |
Setpoint transmit function (32-bit float to registers 0x0010–0x0011):
function transmit(value) {
return modbusWriteFloat(0x0010, value);
}
Kp Gain transmit function (32-bit float to registers 0x0012–0x0013):
function transmit(value) {
return modbusWriteFloat(0x0012, value);
}
Enable transmit function (coil at address 0x0000):
function transmit(value) {
return modbusWriteCoil(0x0000, value);
}
Control a motor over CAN Bus with speed setpoint and emergency stop.
| Control | Type | Properties |
|---|---|---|
| Speed | Slider | Min: 0, Max: 10000, Units: "RPM" |
| Direction | Toggle | ON: "Forward", OFF: "Reverse" |
| E-Stop | Button |
Speed transmit function (16-bit value on CAN ID 0x100):
function transmit(value) {
return canSendValue(0x100, value, 2);
}
Direction transmit function (single byte on CAN ID 0x101):
function transmit(value) {
return canSendFrame(0x101, [value ? 0x01 : 0x00]);
}
E-Stop transmit function (fixed command frame on CAN ID 0x1FF):
function transmit(value) {
return canSendFrame(0x1FF, [0xFF, 0x00]);
}
Symptom: Output controls are configured in the Project Editor but do not appear on the dashboard.
Fix: Ensure the device is connected. Output panels only appear on the dashboard while a connection is active. Also verify that the controls are inside an Output Panel group (group type must be "Output").
Symptom: The control is visible and interactive, but the device does not respond.
Fix:
\r\n).Symptom: The device is overwhelmed or the serial buffer overflows while dragging a slider.
Fix: The built-in 50 ms rate limit prevents most flooding, but if your device needs more time between commands, increase the step size to reduce the number of discrete values, or add debouncing logic in your transmit function.
Symptom: The control shows the red text "No transmit function defined" instead of its widget.
Fix: Open the Project Editor and check the transmit function for syntax errors. The function must be a valid JavaScript function named transmit that accepts one parameter and returns a string. This label also appears when the field is left empty. Runtime errors raised during a transmit (watchdog timeout or an oversize payload) abort the send but are not shown on the widget; use the Console view to confirm what was sent.
modbusWriteRegister, canSendFrame, and so on) instead of packing binary bytes by hand. See Protocol Helper Functions above.transmit() in the same script. Variables declared outside transmit() persist across calls.