====== EMSCOPE WEBSOCKET PROTOCOL DOCUMENTATION ======
This page documents the WebSocket protocol for EMScope **software ≥ 4.1**.
For software version 4.0, the protocol is identical except that measurement data
was always sent as JSON (see section 7).
The Emscope software consists in a //WebSocket// server listening on //port 8010// of the machine and a //Web Server// listening on the standard HTTP //port 80// that serves the front end html and related files.
To configure, parametrize the measurements and manage the standards, a //JSON// style command set is used through a //WebSocket// connection.
===== - First connection =====
Upon connection, the server does not send any data automatically. The client must first send a //session_UUID// message to activate itself. See section 8 for details on session locking.
The server responds to this with device info:
{
"SN": "123456789",
"MAC": "00:11:22:33:44:55",
"SFP_SN": "SFP123456",
"sw_version": "4.1",
"measurement_uncertainty": "0.5 dB",
"num_points": 8192
}
After that, the client must send //trace_type// to enable measurement data streaming. See section 6 for a full initial configuration example.
===== - Default values =====
The default values for measurements are:
* Channel: LG
* Detector: PK
* Tracer: Clear/write
* Units: dbuv
* RWB: 9
* Subranges: 10
* Margin: 10
* Active: true
* Mode: circuit
* input_attenuator: auto
* reference_level: 100 (dbuV)
* average: 10
===== - Commands and responses =====
The following parameters can be sent to configure the device:
^ Name ^ Parameter ^ Type ^ Default ^
| Channel | measure_channel | "lg", "ng", "cm", "dm" (RX2) / "l1", "l2", "l3", "n" (RX4) | “lg” |
| Detector type | detector_type | “pk”, “qp”, “av” | “pk” |
| Trace | trace_type | “clearwrite”, “maxhold”, “minhold”, “freeze”, "average" | “clearwrite” |
| RBW | rbw | "200","9","120" (CISPR), "1", "10" (MIL), "200_9", "1_10" (dual band) | "9" |
| Average (for trace average) | average | integer (10 - 20) | 10 |
| Mode | measure_mode | “circuit”, “modal” | “circuit” |
| Reference level| reference_level | integer | 100 |
| Input attenuator| input_attenuator | {0..78} (integer), “auto” | "auto" |
| Amplitude units| amp_units | “dbm”, “dbmv”, “dbuv”, “watts”, “volts” | "dbuv" |
| Serial number | SN | string | |
| Sweep time | sweep_time | {1..15} (float) | 1 |
| External loss attenuator | external_loss | External loss name (string) | none |
===== - Changing RBW =====
After setting the RBW we shouldn't send any commands until the device it sends back the RBW value.
This is so because a hot-change of firmware, that typically takes around 3.5 seconds, is required.
To set a new RBW, send a **rbw** field with the string corresponding to the RBW:
{
"rbw": "9", "threephase": false
}
RBW table:
^ Value ^ RBW ^ Frequency ^ Standard ^
| 200 | 200 Hz | 9 kHz - 150 kHz | CISPR 16-1-1 |
| 9 | 9 kHz | 150 kHz - 30 MHz | CISPR 16-1-1 |
| 120 | 120 kHz | 30 MHz - 110 MHz | CISPR 16-1-1 |
| 1 | 1 kHz | 10 kHz - 150 kHz | MIL-STD-461 |
| 10 | 10 kHz | 150 kHz - 30 MHz | MIL-STD-461 |
| 200_9 | 200 Hz / 9 kHz | 9 kHz - 30 MHz | Dual-band |
| 1_10 | 1 kHz / 10 kHz | 10 kHz - 30 MHz | Dual-band |
threephase (boolean, default false) — set to true for three-phase (RX4) instruments and EMI_3P. Must be sent together with rbw in the same message. Sending rbw without threephase defaults to false.
===== - Additional configuration fields =====
The following fields can be sent as individual JSON messages at any time after
the RBW has been set.
==== display_range ====
Restricts the frequency window of the data sent by the server to a sub-range
of the active band.
Send:
{ "display_range": [from_hz, to_hz] }
Both values are absolute frequencies in Hz and must lie within the band of the
currently active RBW.
Example — receive only the 150 kHz – 30 MHz portion of the 9 kHz band:
{ "display_range": [150000, 30000000] }
To restore the full band, send the full frequency limits of the active band.
==== visible ====
Controls whether the trace is shown on the instrument's front panel display.
Does not affect data transmission to WebSocket clients.
Send:
{ "visible": true }
{ "visible": false }
==== freeze (trace_type) ====
In addition to the trace types listed in section 3, the value "freeze" is also
accepted:
{ "trace_type": "freeze" }
The instrument continues sweeping internally but stops updating the displayed
trace. Sending a different trace_type unfreezes it.
===== - Initial configuration =====
A typical initial configuration parameters example would be:
{
"detector_type": "pk",
"measure_channel": "lg",
"trace_type": "clearwrite",
"amp_units": "dbmv",
"rbw": "9",
"reference_level": 70,
"input_attenuator": "auto",
"sweep_time": "1"
}
===== 7 - Receiving measurements =====
To start receiving measurements, send at least **trace_type**. From that point
the server streams data continuously after each sweep completes.
Since **SW 4.1**, measurement data uses a Binary WebSocket frame.
The **frequency axis** is sent as a separate JSON message and is cached — it is
not repeated in every sweep frame.
==== 7.1 - Frequency axis (JSON) ====
Sent by the server once when the RBW changes or the display range is updated.
All subsequent sweep frames share this cached axis until the next such change.
{ "freq": [9000.0, 18000.0, 27000.0, ...], "rbw": "9" }
* ''freq'' — array of absolute frequencies in Hz, one per measurement bin.
* ''rbw'' — the active RBW string, echoed for reference.
==== 7.2 - Binary amplitude frame (EMI mode, SW ≥ 4.1) ====
One binary WebSocket frame is sent per sweep. The layout is:
Offset Size Field
------ ------- ----------------------------------------
0 1 byte flags
1 3 bytes padding (ignored)
4 N×4 B float32 amplitude values (little-endian)
**flags** byte:
* ''flags & 0x01'' → **overload**: the ADC saturated during this sweep. Reduce ''reference_level'' or increase ''input_attenuator''.
* ''flags & 0x02'' → **repetition**: a quasi-peak repeat sweep was triggered (200 Hz and 9 kHz bands only).
The number of float32 values (N) matches the length of the last received ''freq''
array. Each value corresponds to the amplitude at ''freq[i]'', in the units
configured via ''amp_units''.
==== 7.4 - Input attenuator echo ====
When ''input_attenuator'' is set to ''"auto"'', the server sends the resolved
hardware attenuation as a separate JSON message after each configuration change:
{ "input_attenuator": 30 }
This message is independent of the sweep data frame. It is not embedded in
binary frames.
===== - Locking the device to the current session =====
Sending //session_UUID// is required — the server will not activate the client or send any data until it receives this message. It also serves as a session lock: if another client already holds the lock with a different UUID, the server closes the connection with close code 4003. Use the same UUID for subsequent reconnections to the same session.
{
"session_UUID": "12345qwerty"
}
The server responds immediately with device info (SN, MAC, SFP_SN). Once this exchange is complete, the client is active and can send configuration messages.
===== - Standards (or Limits) =====
==== - Getting the standards list ====
Send the field **get_standards** with a value of "true":
{
"get_standards": true
}
It will return a **standards** object with an array of standards with the following format:
"Standard name": [ { "rbw": value, "data":
[ From frequency (Mhz), To frequency (Mhz), From Quasi-peak (dBuV), To Quasi-peak (dBuV), Average from Quasi-peak (dBuV), Average to Quasi-peak (dBuV) ],
...
}
]
For example:
{
"standards": [
{
"Standard One": { "rbw": "9", "data": [
[1, 2, 3, 4, 5, 6],
[7, 8, 9, 10, 11, 12],
...
]
}
},
{
"Standard Two": { "rbw": "200", "data": [
[21, 22, 23, 24, 25, 26],
[27, 28, 29, 210, 211, 212],
...
]
}
},
...
]
}
==== - Creating a standard ====
To create a standard, send a **name** field with the name of the new standard, a **modify** field set to "false" and a **values** array in the same format we described before when receiving the standards list.
{
"name":"New standard",
"modify":false,
"standard_rbw": "10",
"values":[
["1","2","3","4","5","6"],
["11","12","13","14","15","16"],
...
]
}
==== - Editing a standard ====
Send the same values as creating it, but with the field //modify// set to //true// and an additional field, //original_name//, just in case we wanted to rename the original //name//, i.e:
{
"original_name": "New standard",
"name":"Modified standard",
"modify":true,
"standard_rbw": "1",
"values":[
["21","22","23","24","25","26"],
["11","12","13","14","15","16"],
...
]
}
==== - Deleting a standard ====
Simply send a **delete_standard** with the name of the standard, i.e:
{
"delete_standard": "Standard One"
}
==== - Resetting standards to factory defaults ====
Simply send a **reset_standards** with a value of "true":
{
"reset_standards": true
}
===== - Sessions (or Presets) =====
==== - Getting the sessions list ====
Send the field **get_sessions** with a value of "true":
{
"get_sessions": true
}
It will return a **sessions** object with an array of sessions with the following format:
[
"Session name": [BLOB DATA],
...
]
For example:
{
"sessions": [
{
"Session One": "{\"PROGRAM_VERSION\":1.25,\"id_counter\":2,\"current_center_freq_units\":\"mhz\",\"current_start_freq_units\":\"mhz\",\"current_stop_freq_units\":\"mhz\",\"current_span_units\":\"mhz\",\"current_center_frequency\":15000000,\"current_start_frequency\":0,\"current_stop_frequency\":30000000,\"current_span\":30000000,\"measure_mode\":\"circuit\",\"current_display_scale\":0,\"aDetectors\":[{\"type\":\"normal\",\"measure_channel\":\"lg\",\"trace_type\":\"clearwrite\",\"detector_type\":\"pk\",\"amp_units\":\"dbuv\",\"color\":\"red\",\"hidden\":false,\"standard\":null,\"id\":0,\"sweep_time\":1},{\"type\":\"normal\",\"measure_channel\":\"lg\",\"trace_type\":\"clearwrite\",\"detector_type\":\"pk\",\"amp_units\":\"dbuv\",\"color\":\"red\",\"hidden\":false,\"standard\":null,\"id\":1}],\"reference_level\":100,\"reference_level_volts\":0.1,\"amplitude_step\":10,\"aCurrentFreq\":[150000,30000000],\"aCurrentSpan\":[0,100],\"aMarkers\":[[],[]],\"aMMarkers\":[],\"aStandards\":{\"CISPR 22 CLASS A\":[[\"0.15\",\"0.5\",\"79\",\"79\",\"66\",\"66\"],[\"0.5\",\"30\",\"73\",\"73\",\"60\",\"60\"]],\"CISPR 22 CLASS B\":[[\"0.15\",\"0.5\",\"66\",\"56\",\"56\",\"46\"],[\"0.5\",\"5\",\"56\",\"56\",\"46\",\"46\"],[\"5\",\"30\",\"60\",\"60\",\"50\",\"50\"]]},\"aStandardsQuasiPeak\":{},\"aStandardsAverage\":{},\"active_tab\":1,\"ZERO_SPAN_MODE\":false,\"amp_units\":\"dbuv\",\"rbw\":\"9\",\"input_attenuator\":\"auto\",\"sweep_time\":1,\"current_color_theme\":\"dark\"}",
...
]
},
{
"Session Two": "......",
...
]
},
...
]
}
==== - Creating a session ====
To create a session, send a **session_name** field with the name of the new session, a **modify** field set to "false" and a **session_values** array in the same format we described before when receiving the sessions list.
{
"session_name":"New session",
"modify":false,
"session_values": ".....",
...
]
}
==== - Editing a session ====
Send the same values as creating it, but with the field //modify// set to //true// and an additional field, //original_session//, just in case we wanted to rename the original //name//, i.e:
{
"original_session": "New session",
"session_name":"Modified session",
"modify":true,
"values": "......",
...
]
}
==== - Deleting a session ====
Simply send a **delete_session** with the name of the session, i.e:
{
"delete_session":"Session One"
}
==== - Deleting all sessions ====
Simply send a **reset_sessions** with a value of "true":
{
"reset_sessions": true
}
===== - External loss attenuators =====
==== - Getting the external loss attenuators list ====
Send the field **get_external_losses** with a value of "true":
{
"get_external_losses": true
}
It will return a **external_losses** object with an array of external loss attenuators with the following format:
"External loss attenuator name": [ [ Frequency (Mhz), Line (dB), Neutral (dB) ], [ Frequency (Mhz), Line (dB), Neutral (dB) ], ... ]
For example:
{
"external_losses": [
{
"External loss attenuator 1": [ [ 1, 2, 3 ], [ 2, 3, 4 ], ... ]
},
{
"External loss attenuator 2": [ [ 10, 20, 30 ], [ 20, 30, 40 ], ... ]
},
...
]
}
==== - Creating an external loss attenuator ====
To create an external loss attenuator, send an **external_loss_name** field with the name of the new external loss attenuator, a **modify** field set to "false" and a **values** array in the same format we described before when receiving the external loss attenuators list.
{
"external_loss_name":"New External loss attenuator",
"modify":false,
"values":[
["1","2","3"],
["10","20","30"],
...
]
}
==== - Editing an external loss attenuator ====
Send the same values as creating it, but with the field //modify// set to //true// and an additional field, //original_external_loss_name//, just in case we wanted to rename the original //external_loss_name//, i.e:
{
"original_external_loss_name": "New External loss attenuator",
"external_loss_name":"Modified External loss attenuator",
"modify":true,
"values":[
["11","22","33"],
["21","22","23"],
...
]
}
==== - Deleting an external loss attenuator ====
Simply send a **delete_external_loss** with the name of the external loss attenuator, i.e:
{
"delete_external_loss": "External loss attenuator 1"
}
===== - Report lists =====
To generate a report list, we must send 3 values: //standard// (the standard name string), //subranges// (number of subranges, an integer) and //margin// (the margin, an integer), i.e:
{
"standard": "One standard",
"subranges": 10,
"margin": 10
}
The answer to the request will be a boolean //frase// and a //report// object with an array of arrays in the following order:
[
Marker (integer),
Frequency in Mhz (float),
Peak level in dBuV (float),
Quasi-Peak level in dBuV (float),
Quasi-Peak limit in dBuV (float),
Distance to Quasi-Peak limit [dB] (float),
Average level in dBuv (float),
Average limit in dBuV (float),
Distance to Average limit [dB] (float),
Channel (string),
Compliance (PASS/FAIL)
]
The //frase// value is **true** if at least one of the reported emissions is within margin dB of its applicable limit (i.e. the result is close to the pass/fail boundary). **false** otherwise.
For example:
{
"report": [
[ 1, 5, 116.964216, 116.418941, 119.000000, 10.000000, 116.480995, 109.000000, 10.000000, "L", "PASS" ],
[ 2, 6, 313.963336, 133.418211, 1123440000, 111220000, 334.440995, 239.020000, 30.000000, "N", "FAIL" ],
...
],
"frase": true
}
===== - Keepalive =====
The server sends periodic keepalive messages to detect disconnected clients:
{ "ping": true }
The client MUST reply immediately with:
{ "pong": true }
If the client does not respond within the timeout, the server will close the
connection. Clients that use the official Python API (EmscopeConnection) have
this handled automatically.
===== - Miscellaneous =====
==== - Getting the activated licenses ====
Send the field **get_licenses** with a value of "true":
{
"get_licenses": true
}
As a response you will get a **licenses** object with an array with the name of each activated licenses:
{"licenses": [ "emi", "osc" ]}
==== - Getting the current PCB and FPGA temperature ====
Send the field **get_temps** with a value of "true":
{
"get_temps": true
}
As a response you will get a **temperatures** object with an array with the celsius degrees. The first value is for the PCB, the second one for the FPGA:
{"temperatures": [ 45.12345, 50.12345 ]}