Skip to main content
Firmware Stable

SXapi / WebSocket

warning

The SXapi / WebSocket is a new interface that is available in SWTools emGUI 4.0 and prerelease versions.

tip

TLDR: The SXapi / WebSocket is a modern, full-duplex replacement for the SXapi / Server (HTTP REST) interface. It lets programs running on your PC or in a web browser talk to a connected siliXcon device over a single, persistent JSON-RPC 2.0 connection, with real-time server push.

The SXapi / WebSocket is provided by emGUI and uses JSON-RPC 2.0 over a WebSocket. It exposes the same device operations as the older SXapi / Server (HTTP REST) — search nodes, read/write variables, execute commands, open dialogs — but over one connection that also carries asynchronous events (task and node state changes).

info

Default endpoint: ws://localhost:29045

A second emGUI running at the same time scans upward from 29045. emGUI opens its web applications with the port in the URL, so they find the right one.

Access​

The server listens on your own PC only. Two things decide whether a client gets through:

  • Allow web applications in emGUI's Servers menu. Unticked, the server still listens and accepts connections, but answers that it is disabled (see Disabled). It is ticked by default; Servers → Remember settings keeps the choice for the next start; --ws-off and --ws-on start emGUI with it unticked or ticked, whatever was remembered.

  • Where a web page comes from. Browsers let any website open a WebSocket to localhost, so emGUI checks the Origin the browser sends when the connection opens:

    OriginAccepted
    none (a program such as wscat, Python or Node)yes
    localhost, 127.0.0.1, ::1, any portyes
    the web applications' site (--web-url, by default this one)yes
    each site given with --ws-origin=URLyes
    null (a page opened from a file)only when --web-url is a file: URL
    any other siteno: the connection is refused, and logged

    The check stops web pages, not programs: a program can send any Origin it likes.

Disabled​

While web applications are not allowed:

  • system.getVersion is still answered, with "enabled": false.
  • Every other request fails with -32004, data: {"reason": "disabled"}.
  • node.* notifications are not sent.

The switch takes effect on open connections at once (a command already running finishes), and every connected client receives system.stateChanged. An older emGUI has no enabled field: read a missing field as enabled.

Why a new API?​

The HTTP REST + SSE server works well for atomic, request/response automation, but it has structural limits that the WebSocket API is designed to solve:

  • One persistent, full-duplex connection. REST opens (or keeps alive) a socket per burst of requests and has no clean way to push data. The WebSocket stays open and carries requests, responses and server-initiated events on the same channel — ideal for high-frequency polling such as siliWatch.
  • First-class real-time events. REST needed a separate long-lived /events SSE endpoint for asynchronous task output. The WebSocket delivers task.stateChanged and node.stateChanged push notifications over the very same connection — no second stream to manage.
  • Standard framing. JSON-RPC 2.0 gives request/response correlation via the id field, structured error objects with numeric codes, and a well-defined notification shape. REST relied on ad-hoc URL query strings and a bespoke result field.
  • Safer node handles. REST returned the device node's raw pointer value as a hex string and accepted it back verbatim. The WebSocket instead hands out opaque integer handles from a per-connection registry, which are validated on every call and never expose internal addresses.
  • Per-connection isolation. Each WebSocket connection runs on its own worker thread with its own handle registry; a blocking I/O operation on one client does not stall the others, and all state is cleaned up on disconnect.
  • Namespaced, versioned protocol. Methods are grouped by scope (system.*, ui.*, node.*, task.*) and the wire protocol carries its own version (protocol) that evolves independently of the application version.

Protocol overview​

All messages are JSON objects with a "jsonrpc":"2.0" field.

Request (client → server):

{ "jsonrpc": "2.0", "id": 1, "method": "node.readVariable", "params": { "handle": 1, "path": "/driver/temp" } }

Response (server → client):

{ "jsonrpc": "2.0", "id": 1, "result": { "result": 0, "value": "40.5640" } }

Error (server → client):

{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Unknown handle: 7" } }

Error with an annotation (server → client, optional data):

{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -100, "message": "Another search is already running", "data": { "reason": "busy", "retry": true } }
}

Notification (server → client, no id):

{ "jsonrpc": "2.0", "method": "task.stateChanged", "params": { "pid": "6", "state": 2 } }
note

The inner result object usually carries a device-level result integer where 0 means success and a negative value is an error code — this is distinct from the JSON-RPC transport-level error object, which is only present on protocol/validation failures.

Error codes​

codeMeaning
-32700Parse error: the message is not a JSON object.
-32600Invalid request: method is missing.
-32601Method not found.
-32602Invalid params, see below.
-32004Web applications are not allowed in emGUI, see Disabled.
otherThe operation failed: the device or emGUI error code, with a readable message.

-32602 is returned for a missing or unknown handle, an unknown mode, a missing value or command, and for stdout together with stream or without a positive timeout.

The data member is advisory: it may be absent, and a client must treat a missing data as "unknown" and never depend on it. When data.retry is true the request may be repeated after a pause; any other reason means the client should stop rather than loop.

Node handles​

node.resolveNode returns an opaque integer handle, which the other node.* methods take. Handles belong to the connection and are not stable:

  • A handle becomes invalid when the node tree is rebuilt (any search other than reclaim), when the tree is discarded, when the node is dropped in emGUI, and when the connection closes.
  • An invalid handle fails with -32602 Unknown handle. Handle numbers are never reused, so an old handle can never address a different node.
  • Resolving the same node again returns the same handle.

Run node.resolveNode again rather than keeping a handle across a search or a reconnect. node.searchFinished and node.treeCleared say when that is needed.

Method namespaces​

PrefixScope
system.*Host/meta operations, no node context required.
ui.*Standalone GUI operations, no node context required.
node.*Per-node operations (require a handle) and node-state events.
task.*Asynchronous task lifecycle notifications (server → client only).

Method reference​

system.getVersion​

Returns version and info about the server.

> {"jsonrpc":"2.0", "id":1, "method":"system.getVersion", "params":{}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0, "protocol":2, "vendor":"siliXcon", "hostapp":"emGUI", "version":"4.0.0", "enabled":true}}

Answered even while web applications are not allowed.

  • enabled — false while web applications are not allowed in emGUI (see Disabled). Absent from an older emGUI, which means enabled.

  • protocol — SXapi WebSocket protocol version. It changes independently of the application version. Version 2 added the stdout capture of node.executeCommand and system.getConnection.

  • version — application (SWTools) version.

system.getConnection​

Returns the interface emGUI reaches the devices through, and whether it is connected. No parameters, no device I/O.

> {"jsonrpc":"2.0", "id":1, "method":"system.getConnection", "params":{}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0, "loaded":true, "connected":true, "config":{"name":"kvaser", "protocol":"...", "options":"...", "local_address":"7", "target_address":"1", "mesh_address":null, "credential":{"user":"admin", "password":true}, "service_token":false}}}
  • loaded — an interface driver is loaded.
  • connected — the interface is connected.
  • config — the interface settings, kept apart from the state:
    • name — interface name, see Communication interfaces.
    • protocol, options — protocol and the interface's option string (its format is in the interface's section of Communication interfaces).
    • local_address, target_address, mesh_address — addresses.
    • credential — user is the login user name; password only says whether a password is set.
    • service_token — whether a service token is set.

An unset string is null. The configuration cannot change while the interface is connected: when connected is true the values are what the connection was opened with, otherwise what the next connect will use. The credential is the exception, the user may change it at any time in emGUI. The password and the service token themselves are never returned.

ui.openDialog​

Show a siliXcon tool with no node context.

ParamTypeNotes
commandstringOptional. Empty ⇒ show emGUI; or "{term}" / "{scope}" followed by optional tool arguments.

node.searchNodes​

Perform a search and wait for the result. Returns the number of nodes found.

ParamTypeNotes
flagsint | string[]Optional. Integer bitmask, or an array of flag names. Default 0x7F.

Flag names accepted in the array form:

NameMeaning
dummyDummy search — ignores all other flags; returns the count of the current tree nodes.
multipleSF_MULTI
sequentialSF_SEQ
heartbeatSF_ENHB
authSF_AUTH
fetchSF_FETCH
pullSF_PULL
recurseSF_RECURSE
reclaimSF_RECLAIM

Default flags are multiple, sequential, heartbeat, auth, fetch, pull, recurse. The default applies when flags is missing; an array is taken as it is, so an empty array searches with no flags. Unknown names in the array are ignored.

> {"jsonrpc":"2.0", "id":1, "method":"node.searchNodes", "params":{"flags":["dummy"]}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":2}}

Only one search runs at a time. A request that arrives while another search is running (a dummy one included, so that it does not report a half-built tree) does not fail: it waits until that search ends and is answered with the resulting node count. A request whose flags the running search does not cover is queued, and its own search runs once the current one is done. Only if the wait times out is busy returned.

On failure the error carries data.reason:

reasonMeaning
busyAnother search is still running (retry: true): repeat after a pause.
abortedThe connection is closing.
notfoundNo node was discovered.
nointerfaceThe interface is not connected, see system.getConnection.
failedAny other failure; the message says more.
warning

The count returned by node.searchNodes is the number of searched nodes and may differ from the total number of nodes. Do not use it as the maximum order for node.resolveNode — instead increase order from 0 until an error is returned.

node.resolveNode​

Retrieve a single node and register a handle for it. Usually only one selector is provided.

ParamTypeNotes
orderintIf multiple nodes match, return the Nth one (0-based).
addressstringNode address, e.g. "1".
namestringNode name, e.g. "SXmsr". May not be unique.

Returns an opaque integer handle (see Node handles), plus ident (name, hwid, swid, sn, uuid, class; a field the node does not report is left out) and, when the node is live, a target object (state, inconsistent, disabled, address; inconsistent and disabled are 0 or 1). No match fails with code -1 Node not found.

warning

Known limitation: node.resolveNode with address returns the first node with a matching address even if that node is disconnected (result 0, no target). After a node stops and a new search runs, a stale node with the same address may shadow the live one. Prefer resolving by order when possible.

node.openDialog​

Open an entry dialog / show a tool for a specific node.

ParamTypeNotes
handleintRequired. From node.resolveNode.
commandstringEntry path (e.g. /driver/supply/voltage) or "{term}" / "{scope}" with optional tool arguments.

A dialog or tool that does not exist fails with Dialog not found; the same applies to ui.openDialog.

node.getVariableInfo​

Retrieve variable meta-data.

ParamTypeNotes
handleintRequired.
pathstringEntry path, e.g. /driver/temp.
> {"jsonrpc":"2.0", "id":1, "method":"node.getVariableInfo", "params":{"handle":1, "path":"/driver/temp"}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0, "class":"state variable", "dimension":1, "inconsistent":false, "isSetableVariable":false, "type":"float"}}

class is always present. type, dimension, isSetableVariable and inconsistent are present only for a variable, not for a directory or a command.

node.readVariable​

Read a variable value.

ParamTypeNotes
handleintRequired.
pathstringEntry path.
modestringOptional I/O mode (see I/O modes). Default "wait".
> {"jsonrpc":"2.0", "id":1, "method":"node.readVariable", "params":{"handle":1, "path":"/driver/temp"}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0, "value":"40.5640"}}

The value is always returned as a string. An unknown mode fails with -32602.

node.writeVariable​

Write a variable value.

ParamTypeNotes
handleintRequired.
pathstringEntry path.
valuestringRequired. Value to write.
modestringOptional I/O mode (see I/O modes). Default "wait".
> {"jsonrpc":"2.0", "id":1, "method":"node.writeVariable", "params":{"handle":1, "path":"/driver/sampling_time", "value":"100"}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0}}

The value is passed as a string, even for a number. An empty value fails with -32602 Missing value, so an empty string cannot be written.

node.executeCommand​

Execute a command on a node, optionally streaming task progress.

ParamTypeNotes
handleintRequired.
commandstringRequired. Command to execute, e.g. "plot".
modestring"wait" (default) or "attempt" (only if no other I/O is pending).
streamboolfalse (default) or true ⇒ push task.stateChanged notifications.
timeoutintMilliseconds; default 3000 for "attempt", otherwise 1000. -1: do not wait for the return value.
argumentsstring[]Optional array of string arguments.
stdoutboolfalse (default) or true ⇒ capture the command's output and return it. Not together with stream.
stdout_limitintByte limit of the capture; default 32768, at most 1048576. A value outside that range means the maximum.

With stream:false the response carries the device result and, when timeout >= 0, the command's return value. With stream:true the server emits task.stateChanged notifications during execution. Any mode other than "attempt" is taken as "wait".

Capturing the output​

> {"jsonrpc":"2.0", "id":1, "method":"node.executeCommand", "params":{"handle":1, "command":"pr", "arguments":["-r","/driver"], "stdout":true}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0, "return":3, "stdout":"List of available params:\n-> sampling_time uint8 : 100 (100), Interval for sending msg from driver API\n\nTotal 3 entries\n", "stdout_truncated":false}}

With stdout:true the response carries stdout (the captured text, possibly empty) and stdout_truncated (true when stdout_limit cut it short). The output is decoded as UTF-8, so binary output is not usefully readable this way.

  • The capture is complete when the response arrives; there is no partial delivery while the command runs.
  • timeout must be positive; -1 fails with -32602. It is an inactivity deadline, restarted by every task update and every completed read: it bounds a stalled command, not one that keeps printing.
  • Reaching stdout_limit stops the copying, not the command: it still runs to the end, so the response is not delayed.
  • emGUI's main thread is blocked while the output is captured. The capture is meant for short listing commands (pr, st, ls, version, msgconf…); a long-running command freezes emGUI for as long as it runs.
  • stdout together with stream:true fails with -32602: the streamed path has no capture yet.

Timeout and failure​

A command abandoned on its timeout fails with code -3 and data.reason: "timeout". The command may still be running on the device: emGUI stopped waiting, it did not cancel anything. A command that fails or times out may already have printed something; with stdout:true that output is in the error's data.stdout and data.stdout_truncated. On a timeout it is a partial capture, not the whole output. As with any data, it may be absent.

note

Known ordering quirk (stream:true): because execution is blocking, the final response ({"result":{"result":0}}) is currently sent after the last task.stateChanged notification. JSON-RPC convention would expect the response to acknowledge submission early, with notifications carrying progress.

Server notifications​

These are pushed by the server without a preceding request (no id).

system.stateChanged​

Fired to every connected client when the operator allows or disallows web applications in emGUI.

{ "jsonrpc": "2.0", "method": "system.stateChanged", "params": { "enabled": false } }
  • enabled — the new state. While false, show the user that web applications are disabled in emGUI (Servers → Allow web applications) rather than retrying.

task.stateChanged​

Fired during a streamed node.executeCommand (stream:true).

{ "jsonrpc": "2.0", "method": "task.stateChanged", "params": { "pid": "6", "state": 2 } }
  • pid — task id (string).
  • state — task state; 0 means finished (then result is included).

node.stateChanged​

Fired when a node for which this connection holds a handle changes state.

{ "jsonrpc": "2.0", "method": "node.stateChanged", "params": { "handle": 4, "state": 1 } }

node.searchFinished​

Fired when a search has finished and the node tree changed. Broadcast to every client, including the one that started the search — so an application can refresh when somebody else searched (another client, or the emGUI user) instead of polling for it.

{
"jsonrpc": "2.0",
"method": "node.searchFinished",
"params": { "count": 3, "flags": 127, "generation": 7 }
}
  • count — nodes in the tree now.
  • flags — what the search ran with, so a client can tell whether the result covers what it would have asked for itself. Same values as node.searchNodes.
  • generation — tree revision; changes whenever nodes are added, dropped, or the tree is rebuilt.

Sent only when the tree actually changed, so an unchanged view is never disturbed; a dummy search never triggers it. It is the coalesced form of node.stateChanged, which fires per node during a search, against a half-built tree — this fires once, when the tree has settled.

Receiving it means every handle held from before is void: re-run node.resolveNode. There is no need to search again — the tree is already up to date, so enumerating is enough and costs no bus I/O.

node.treeCleared​

Fired when the node tree is discarded on its own — by the emGUI user, or by a client. The tree is now empty and every handle is void.

{ "jsonrpc": "2.0", "method": "node.treeCleared", "params": { "generation": 8 } }
  • generation — tree revision after the clear, so it can be ordered against node.searchFinished.

There is no count: the tree is empty by definition. Handle it the same way as node.searchFinished — re-enumerate, do not search.

The clear that every search performs on its way in is not reported here. Announcing it would have clients enumerate the empty tree the search is about to fill; that case is reported once by node.searchFinished when the search settles.

I/O modes​

node.readVariable and node.writeVariable (and partially node.executeCommand) accept a mode that controls how the value is exchanged with the device vs. the emGUI cache:

modeBehaviour
noneCache only — no device I/O (non-blocking).
attemptPerform I/O only if the device is not busy (non-blocking).
scheduleSchedule the I/O; block while a previous operation is pending.
waitFull blocking I/O. Default.

Equivalence with SXapi / Server (HTTP REST)​

Every operation of the older SXapi / Server (HTTP REST) has a direct equivalent in the WebSocket API. The main differences are the transport (persistent WebSocket vs. per-request HTTP), the framing (JSON-RPC 2.0 vs. query string + result JSON), and node handles (opaque integers vs. raw pointer hex strings).

HTTP REST endpointWebSocket methodNotes
GET / (HTML help page)—Replaced by this documentation page.
GET /versionsystem.getVersionWS adds a protocol field.
—system.getConnectionWebSocket only: interface configuration and state.
GET /events (SSE stream)(push notifications)task.stateChanged / node.stateChanged / node.searchFinished / node.treeCleared over the same socket.
GET /show [cmd]ui.openDialog [command]Show emGUI / a tool, no node.
GET /search [flags]node.searchNodes [flags]WS also accepts flags as an array of names.
GET /node [name] [addr] [order]node.resolveNode [order] [address] [name]WS returns an opaque integer handle; REST returned a pointer hex string.
GET /open (handle) (path)node.openDialog (handle) (command)
GET /var (handle) (path)node.getVariableInfo (handle) (path)
GET /get (handle) (path)node.readVariable mode:"wait"Default.
GET /cget …node.readVariable mode:"none"Cache only.
GET /aget …node.readVariable mode:"attempt"
GET /sget …node.readVariable mode:"schedule"
GET /set (handle) (path) (value)node.writeVariable mode:"wait"Default.
GET /cset …node.writeVariable mode:"none"
GET /aset …node.writeVariable mode:"attempt"
GET /sset …node.writeVariable mode:"schedule"
GET /exec (handle) (path) [timeout] [args]node.executeCommand mode:"wait", stream:falseWS can also return the output (stdout:true).
GET /aexec …node.executeCommand mode:"attempt", stream:false
GET /execio (handle) (path) [timeout] [args]node.executeCommand stream:trueProgress via task.stateChanged instead of the /events SSE stream.
GET /aexecio …node.executeCommand mode:"attempt", stream:true
info

The REST /get, /set and /exec families each split their four/two cache-vs-I/O flavours into distinct URLs. The WebSocket API keeps a single method per operation and selects the flavour through the mode parameter, which is easier to discover and extend.

Example session​

Using wscat to talk to the server interactively:

PS> wscat --connect ws://localhost:29045
> {"jsonrpc":"2.0", "id":1, "method":"system.getConnection", "params":{}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0, "loaded":true, "connected":true, "config":{"name":"kvaser", ...}}}
> {"jsonrpc":"2.0", "id":1, "method":"node.searchNodes", "params":{}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":2}}
> {"jsonrpc":"2.0", "id":1, "method":"node.resolveNode", "params":{"order":0}}
< {"jsonrpc":"2.0", "id":1, "result":{"handle":1, "ident":{"class":"0D:SX", "hwid":"esc5-sx1e_62kla1060-A00", "name":"SXmsr", "sn":"702F4P081E4A", "swid":"VECTOR_epeklo_generic v0.6.9 May 10 2023", "uuid":"2037303246345008001E004A"}, "result":0, "target":{"address":"2", "disabled":0, "inconsistent":0, "state":1}}}
> {"jsonrpc":"2.0", "id":1, "method":"node.getVariableInfo", "params":{"handle":1, "path":"/driver/temp"}}
< {"jsonrpc":"2.0", "id":1, "result":{"class":"state variable", "dimension":1, "inconsistent":false, "isSetableVariable":false, "result":0, "type":"float"}}
> {"jsonrpc":"2.0", "id":1, "method":"node.readVariable", "params":{"handle":1, "path":"/driver/temp"}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0, "value":"40.5640"}}
> {"jsonrpc":"2.0", "id":1, "method":"node.executeCommand", "params":{"handle":1, "command":"pr", "arguments":["-r","/driver"], "stdout":true}}
< {"jsonrpc":"2.0", "id":1, "result":{"result":0, "return":3, "stdout":"List of available params:\n...", "stdout_truncated":false}}
tip

This documentation site uses the SXapi / WebSocket to power its interactive tools (for example siliDash).