Skip to main content
Firmware Stable

SXapi / WebSocket

warning

Experimental: The SXapi / WebSocket is a new interface that is still under active development. The protocol and method set may change in future releases. It is available in prerelease SWTools emGUI 4.0.

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

The HTTP REST server (default port 28945) and the WebSocket server (29045) can run at the same time inside emGUI, so existing REST clients keep working while new clients migrate to the WebSocket API.

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" } }

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.

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":1, "vendor":"siliXcon", "hostapp":"emGUI", "version":"4.0.0"}}
  • protocol — SXapi WebSocket protocol version (increments on any breaking change).
  • version — application (SWTools) version.

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.

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, plus ident (name, hwid, swid, sn, uuid, class) and, when the node is live, a target object (state, inconsistent, disabled, address).

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.

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"}}

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"}}

node.writeVariable

Write a variable value.

ParamTypeNotes
handleintRequired.
pathstringEntry path.
valuestringRequired. Value to write.
modestringOptional I/O mode (see I/O modes). Default "wait".

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.
argumentsstring[]Optional array of string arguments.

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.

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).

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.
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:false
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 collapsed 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":"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"}}
tip

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