Errors and timeouts
Every SDK raises the same set of failures under the same names, so a test bench can handle them the same way in any language.
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_Failed |
| Nothing answered in time | Timeout | Error::Timeout | ss_status_Timeout |
| The server speaks another protocol | VersionMismatch | Error::Version | NULL from ss_server_connect |
| The device could not do it | Refused | Error::Refused | ss_status_Refused |
| The workspace is frozen by an export | Busy | Error::Busy | ss_status_Failed |
| 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_BadArgument |
| A script-library operation failed | ScriptOpError | — | — |
- Python
- NodeJS
- Rust
- C
from ikalogic_scanastudio import (
ScanaStudio, ScanaStudioError, Refused, ScriptError, Timeout, VersionMismatch,
)
try:
with ScanaStudio.connect() as server:
workspace = server.create("hw:SP259-000123")
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 device would not do it: {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 {
ScanaStudio, ScanaStudioError, Refused, Timeout, VersionMismatch, ScriptError,
} from '@ikalogic/scanastudio';
const server = await ScanaStudio.connect();
try {
const workspace = await server.create('hw:SP259-000123');
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 device would not do it: ${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::Version { spoken, supported }) => {
eprintln!("server speaks {spoken}, this crate speaks {supported}");
}
Err(Error::Refused(reason)) => eprintln!("refused: {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
ss_status_Refused
ss_status_Disconnected
ss_status_Timeout
ss_status_Failed
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;
}
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. |
no_workspace | The command needs a workspace and this connection is not attached to one. |
Timeouts​
Every blocking call takes a timeout, and the defaults differ because the operations do:
| Operation | Default |
|---|---|
| Any single request/answer | 30 s |
| Opening or attaching a workspace | 60 s |
| Waiting for a capture | 300 s |
| Waiting for decoding | 300 s |
| Waiting for measurements | 60 s |
| Save, load, export | 600 s |
Python and Rust count seconds; NodeJS counts milliseconds, in a
parameter named timeout_ms.
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.