The server writes and reads strictly-formatted JSON objects delmited by newlines, like:
{ "type": "join", "value": "name-of-the-room" }
or
{ "type": "foo", "value": ["bar", 2, {"baz": "quux"}] }
The wire format must enquote object keys, and have no newlines except to terminate an object. But, this document uses a relaxed notation where object keys are unquoted and newlines are not significant, such as:
{
type: "join",
value: "name-of-the-room"
}
Each object must have at least the two members type
and value
.
The client may send an ack
member which the server will copy to the response object in the ack
member. The client can check that it got the ack within some "reasonable" amount of time to detect timeouts.
If the client sends invalid traffic (such as poorly formatted objects, or objects with invalid values) the server will reply with an error
member. Receiving these objects indicates that the client has a bug.
The API can be accessed by telnet
, netcat
, or socket(2)
on port 4000. The port is also available via WebSockets at /json.ws
and json6.ws
. The payload traffic over the two interfaces is the same.
See src/application/spa/web/json_tcp_client.js
. It is recommended to create a stub test page, and use the Firefox developer console for interactive exploration.
<html>
<script src=json_tcp_client.js></script>
</html>
Then, in the developer console:
var client = json_tcp_client('ws://172.26.201.67/json.ws')
The protocol must be ws://
not http://
or https://
.
The name of the client is slightly misleading.
json_tcp_client().send()
client.send(room_name);
client.send(room_name, value);
client.send(room_name, callback);
client.send(room_name, value, callback);
The room_name
string is required.
The value
may be any plain JSON value (object, string, number, array, boolean) which is correctly serialized by JSON.stringify()
.
The callback
may be a function which accepts a single argument, which is only the value
received. It cannot access the type
, error
, or ack
members of the received object.
For example:
client.send('echo',
{
it: 'is',
my: ['test', 'object', 1]
},
function (response) {
assert(response.it === 'is');
assert(response.my[0] === 'test');
assert(response.my[1] === 'object');
assert(response.my[2] === 1);
}
);
json_tcp_client().join()
client.join(room_name, callback);
Joins a room. Calls the callback whenever the server decides to do so. The callback
is the same as in json_tcp_client().send()
.
json_tcp_client().leave()
client.leave(room_name);
Leaves a room. The server will no longer send these objects.
json_tcp_client().onconnect()
The onconnect
method accepts one callback argument which is called with no arguments when the client connects. The client will automatically reconnect.
All other client methods must occur within an onconnect
callback.
json_tcp_client().ondisconnect()
The onconnect
method accepts one callback argument which is called with no arguments when the client disconnects. The client will automatically disconnect on certain timeouts.
The server will send unsolicited objects to all clients who have joined that room.
client.join("some_room", function (room_message) {
console.log("Server sent " + room_message + " to some_room");
});
The server will usually send some initial state immediately on joining, then more information will arrive over time. Each piece of initial state is the same as each successive object, meaning the client should make no distinction between initial state and updates which follow.
A useful analogy is IRC chat rooms. Objects may arrive at any time as long as the client is still joined to that room. At a console, try:
$ telnet 172.26.x.x 4000
{"type": "join", "value": "setting-value"}
Then, open that same IP address in a browser and change a setting. The values of the settings will be sent to the telnet client. Other rooms operate similarly.
scpi-log
{
errors: [
{
num: <Number>,
description: <Human readable string>
}
],
command: <Literal SCPI string as entered by the user>
quiet: <Boolean. True if the command was used internally by the GUI>
}
setting-value
{
id: <Number, non-unique>,
command: <Shortest form SCPI command string>,
value: <String, might have numeric meaning>
}
gps
{
state: <Boolean, enabled if true>,
good_fix: <Boolean, true if good fix>,
timestamp: <Number, milliseconds since UNIX epoch>,
latitude: <Number>,
longitude: <Number>,
altitude: <Number, meters>,
num_satellites: <Number><
}
iq-capture-result
<Boolean, was the capture successful>
overheat-status
<Boolean, is the unit overheating>
fwupdate
{
what: <String description, "error" or "started">,
message: <String>,
url: <String of where update is being downloaded from>,
}
limitFailure
{} <The empty object, no additional information>
echo
Any JSON value
Copies input to output
scpi
Provides a direct interface to the instrument's SCPI engine. It cheats the one-connection-only rule. Rejects compound commands, e.g. "SENS:FREQ:STAR 2ghz; SENS:FREQ:STAR?"
. Rejects commands related to occupied bandwidth and channel power, e.g. "FETCH:OBW?"
.
All SCPI commands and their responses sent via this request will be copied to all other clients joined to the scpi-log
room.
This and scpi-quiet
are the only way to change settings. It is not the recommended way to query trace data or setting values.
<A string with exactly one SCPI command>
For example:
"SENS:FREQ:STAR 3ghz"
"*idn?"
{
errors: [
{
num: <Number>,
description: <Human readable string>
}
],
command: <Literal SCPI string as entered by the user>
quiet: false
}
scpi-quiet
Same as scpi
except it does not broadcast to the scpi-log
room. Instead, it replies only to the sender.
fw-update-sources
Used to get a list of the available firmware updates. Does not initiate a firmware update.
None
[
{
name: <String, identifies the packge, e.g. "1.0.0">
recommended: <Boolean, should be only one recommended entry>
},
]
app-version
None
<String, description of the running firmware version>
trace-data
The preferred way to read trace data for interactive use. Will send empty responses if nothing has changed since this client last issued the command, e.g. during single sweep mode the same trace data will not be sent repeatedly.
Always contains all points. Does not distinguish between currently sweeping trace, and the points from the previous trace.
None
{
data: <String, described below>,
start: <Number, start index of trace data. Always 0>,
count: <Number of points in the sweep>
stale: <String, described below>,
status: <String, described below>,
sweep_id: <Number, increments when a sweep completes>
}
trace-data
FormatStarting each trace point is the sign (plus or minus). 8 hex digits follow. No endian conversion is required.
Using the example:
"-00000001+000000a0"
Two trace points are described: -1
and +0xa0
. The length of the encoded string is nine (sign plus eight hex digits) times the count
.
Each point has units milli-dBm. To convert +0xa0
to dBm, divide by one thousand:
+0xa0 / 1000.0 == 0.16 dBm
trace-data
Stale FormatThe stale member is a string of ASCII one's and zero's (1
and 0
). Each character describes whether the trace point at that index is stale (1
) or fresh (0
). Stale data may be styled differently (e.g. trace is partially transparent at that point). Data will be stale when e.g. reference level changes.
Using the example:
"1001"
Four points are described: the first is stale, the second is fresh, the third is fresh, and the fourth is stale.
trace-data
Status FormatEight ASCII bytes describe the status of each trace point. For example:
"0000000012345678"
Two trace points are described: 0
and 0x12345678
. No endian conversion is required. There is no delimiter between points.
The trace statuses are a 32-bit bitwise mask of possible measurement problems when that point was taken:
Bit 0: ADC Overrange
Bit 1: Power Saturated
Bit 2: SLO Lock Fail
Bit 3: LO1 Lock Fail
Bit 4: LO2 Lock Fail
Bit 5: TG Lock Fail
Bits 6 - 31: Reserved
So, a status 00000003
is 0x03
, which means ADC Overrange and Power Saturated.
If any point has a non-zero status, the entire sweep is considered invalid, although it may still be useful. An invalid trace should still be displayed.
spectrum-limits
This is related to the limit SCPI commands. It translates the ~6 SCPI commands into one object. The SCPI commands define arbitrary line paths (many points connected by slopes), upper, and lower limits. In contrast, the spectrum-limits
object defines many disconnected, flat upper limits only.
If the request argument is the empty object {}
, then the request is treated as a query. The returned object has form:
{
segments: [
{
amplitude: {
value: <Number>,
unit: <String, e.g. dBm>
},
frequency: {
start: <Number, Hz>,
stop: <Number, Hz>,
}
},
],
frequencyRelative: <Bool>,
amplitudeRelative: <Bool>,
enabled: <Bool>
}
If that object is sent as the request argument, then it will set the limits on the instrument.
The instrument is very strict about the input schema but does not indicate the specific reason for rejecting the input.
December 14, 2015