Errors and timeouts
Every SDK reports the same failures under the same names, so a test bench can
handle them the same way in any language. The exceptions are the argument checks
listed under Errors that are not a ScanaStudioError,
which use each language's own exception types.
The taxonomy​
| Meaning | Python / NodeJS | Rust | C |
|---|---|---|---|
| Base type for all of them | ScanaStudioError | Error | — |
| The connection went away | ConnectionClosed | Error::Closed | ss_status_Disconnected |
| The server refused the command | ProtocolError (with code) | Error::Protocol | ss_status_Refused |
| Nothing answered in time | Timeout | Error::Timeout | ss_status_Timeout |
| The server speaks another protocol | VersionMismatch | Error::Version | NULL from ss_server_connect |
| The server refused to start the acquisition, with its written reason | Refused | Error::Refused | ss_status_Refused |
| The acquisition started, then ended in error | CaptureFailed | Error::CaptureFailed | ss_status_Failed |
| The workspace is frozen by an export | Busy | Error::Busy | ss_status_Refused |
| A script failed or rejected its config | ScriptError (with logs) | Error::Script | ss_status_Failed |
| A file operation failed | IoError | Error::Io | ss_status_Failed |
| An option the script does not know | UnknownOption (with known) | Error::UnknownOption | ss_status_Failed |
| A script-library operation failed | ScriptOpError (with skipped, failed) | Error::Io | ss_status_Failed |
| A message could not be encoded or decoded as JSON | — | Error::Encoding | ss_status_Failed |
| The socket itself failed, or nothing listens at the URL | — | Error::Socket | NULL from ss_server_connect |
A NULL handle, a NULL or non-UTF-8 string, or request JSON that does not parse | — | — | ss_status_BadArgument |
- Python
- NodeJS
- Rust
- C
from ikalogic_scanastudio import (
CaptureFailed, ScanaStudio, ScanaStudioError, Refused, ScriptError, Timeout,
VersionMismatch,
)
try:
with ScanaStudio.connect() as server:
workspace = server.create("hw:1004250000604")
workspace.capture.run(samples=1_000_000, sample_rate=1_000_000_000)
except VersionMismatch as exc:
print(f"server speaks protocol {exc.spoken}, this SDK speaks {exc.supported}")
except Refused as exc:
print(f"the server would not start the capture: {exc}")
except CaptureFailed as exc:
print(f"the capture started, then failed: {exc}")
except ScriptError as exc:
print(exc)
for line in exc.logs: # the script's own output
print(" ", line)
except Timeout:
print("no answer in time")
except ScanaStudioError as exc:
print(f"something else went wrong: {exc}")
import {
CaptureFailed, ScanaStudio, ScanaStudioError, Refused, Timeout, VersionMismatch,
ScriptError,
} from '@ikalogic/scanastudio';
const server = await ScanaStudio.connect();
try {
const workspace = await server.create('hw:1004250000604');
await workspace.capture.run({ samples: 1_000_000, sample_rate: 1_000_000_000 });
} catch (error) {
if (error instanceof VersionMismatch) {
console.error('protocol mismatch');
} else if (error instanceof Refused) {
console.error(`the server would not start the capture: ${error.message}`);
} else if (error instanceof CaptureFailed) {
console.error(`the capture started, then failed: ${error.message}`);
} else if (error instanceof ScriptError) {
console.error(error.message, error.logs);
} else if (error instanceof Timeout) {
console.error('no answer in time');
} else if (error instanceof ScanaStudioError) {
throw error;
}
} finally {
server.close();
}
use scanastudio_client::{Error, ScanaStudio, DEFAULT_URL};
match ScanaStudio::connect(DEFAULT_URL).await {
Ok(server) => { /* ... */ }
Err(Error::Socket(error)) => eprintln!("nothing listens there: {error}"),
Err(Error::Version { spoken, supported }) => {
eprintln!("server speaks {spoken}, this crate speaks {supported}");
}
Err(Error::Refused(reason)) => eprintln!("refused: {reason}"),
Err(Error::CaptureFailed(reason)) => eprintln!("the capture failed: {reason}"),
Err(Error::Script { message, logs }) => {
eprintln!("{message}");
for line in logs { eprintln!(" {line}"); }
}
Err(Error::Timeout(what)) => eprintln!("timed out waiting for {what}"),
Err(other) => return Err(other.into()),
}
/* Three conventions, one error channel.
- A pointer-returning function returns NULL on failure.
- A status-returning function returns an ss_status.
- A number-returning function uses -1, or 0 for an id.
In every case, ss_last_error() has the reason. */
ss_server *server = ss_server_connect(NULL);
if (!server) {
fprintf(stderr, "connect: %s\n", ss_last_error());
return 1;
}
/* A number-returning call: -1 is the failure, not an ss_status. */
const char *request = "{\"samples\": 1000000, \"sample_rate\": 25000000}";
if (ss_capture_run(workspace, request, 300.0) < 0) {
fprintf(stderr, "capture: %s\n", ss_last_error());
}
The status codes:
ss_status_Ok /* 0 */
ss_status_BadArgument /* 1 */
ss_status_Refused /* 2 */
ss_status_Disconnected /* 3 */
ss_status_Timeout /* 4 */
ss_status_Failed /* 5 */
ss_last_error is thread-local and short-livedIt describes the calling thread's last failing call and is cleared by the next
call that can fail, so read it immediately, as with errno, and copy the string
if you need to keep it. The _free, _disconnect and _unsubscribe functions
and the plain accessors leave it alone. See
The C library
for the details, including the event callback thread.
A failed batch read follows the same rule: ss_markers_read, ss_packets_read,
ss_measures_wait and the rest return NULL, and there is nothing to free:
ss_packets *batch = ss_packets_read(workspace, NULL);
if (!batch) {
fprintf(stderr, "packets: %s\n", ss_last_error());
return 1;
}
Errors that are not a ScanaStudioError​
Some arguments are checked by the SDK before anything is sent. Those checks
raise the language's own exceptions, so an except ScanaStudioError or
instanceof ScanaStudioError does not catch them:
| Check | Python | NodeJS |
|---|---|---|
A trigger built with a position outside 0 to 0.9, or an invalid mode, polarity or sequence order | ValueError | RangeError |
| A measurement name outside the 18 kinds | ValueError | RangeError |
An export source that is not samples, raw, packets or hex | ValueError | RangeError |
workspace.channel() with a name the device does not have | KeyError | RangeError |
An option value of the wrong type, such as None for a channel | TypeError | TypeError |
| An option value out of range: not a number, an unknown combo entry, an index past the end | ValueError | RangeError |
| An option key the script does not declare | UnknownOption, a subclass of KeyError | UnknownOption, a ScanaStudioError |
In Rust the same checks are either enforced by the types (MeasureKind,
Mode) or returned as an Error: an unknown channel name is an
Error::Protocol with the code no_such_channel.
Protocol error codes​
When the server refuses a command outright it sends a stable code, meant to be
matched on rather than parsed out of the message:
| Code | Meaning |
|---|---|
bad_message | The command was unreadable, or names a command that does not exist. Usually an SDK/server version mismatch. An add_measure naming an unknown measurement is refused whole with this code. |
no_workspace | The command needs a workspace and this connection is not attached to one. |
generation | A generation row could not be filled (generation.fill()). The message says why. |
Not every failure is a protocol error. A capture the server will not start
arrives as Refused, and an operation that started and then failed reports
through its own error: CaptureFailed, IoError, ScriptError.
Timeouts​
Every blocking call takes a timeout, and the defaults differ because the operations do:
| Operation | Default |
|---|---|
| Any single request/answer | 30 s |
| Creating or attaching a workspace | 60 s |
Waiting for a capture (wait(), run()) | 300 s |
run() waiting for the capture to start | the timeout, at most 30 s |
| Waiting for decoding | 300 s |
| Waiting for measurements | 60 s |
events.wait_for | 60 s |
A script's GUI, decoders.add, pattern.add | 60 s |
pattern.preview, scripts.sync, generation builders | 300 s |
scripts.online | 120 s |
| Other script-library operations | 180 s |
| Save, open, export | 600 s |
Python counts seconds as a float. NodeJS counts milliseconds, in a
parameter named timeout_ms. Rust takes a std::time::Duration. C takes
seconds as a double.
Timing out means you stopped waiting. The capture is still running, the export
is still writing. Decide explicitly whether to stop() or to wait again, and
remember the workspace is still there on the server either way.
A datalogger capture that runs for hours will exceed the 300 s default. Do not
raise the timeout to match: start() without waiting, let the script exit, and
come back later with attach(). See
Headless datalogger.