SignalScope serves its UI and JSON/text API over HTTP from the device access point. The usual base URL is:
http://192.168.4.1
There is no TLS, user account, or API token in the standalone firmware. Treat access to the local AP as access to the development device.
This document describes the native standalone routes. An installed application can add behavior behind the generic /api/app/* routes, but it does not replace the core API.
- Read routes return JSON unless they explicitly stream a text/binary file.
- Simple commands accept URL query or
application/x-www-form-urlencodedfields through ArduinoWebServer::arg(). - DBC, rule-package, replay CSV, and application-resource uploads use the raw request body (
text/plainis appropriate). - CAN IDs may be decimal or
0xhexadecimal in numeric request fields. - Directions are
A_TO_BandB_TO_A. - Boolean parsing accepts common true/false text; examples use
1and0. - Error JSON normally has
ok:falseand a stableerrorcode. Use the HTTP status as well as the body.
Examples use curl.exe to avoid PowerShell's historical curl alias behavior.
Returns the broad runtime snapshot used by the dashboard. Important groups include:
- Bus A/B readiness and controller recovery counters;
- ingress, forwarded, dropped, deferred, and failed-transmit counts;
- queue depth and fast/active path latency;
- active/staged rule counts and
rule_epoch; - active DBC/package paths and DBC capacity/completeness;
- observation and replay state;
- up to 40 recent frames, including decoded signals where available.
curl.exe http://192.168.4.1/api/statusFields may be added as diagnostics improve. Clients should read the keys they need and tolerate additional keys.
Returns up to 40 frame snapshots:
{
"ok": true,
"count": 1,
"frames": [{
"can_id": 801,
"direction": "A_TO_B",
"dlc": 8,
"timestamp_us": 123456,
"rate_hz": 10,
"mutated": false,
"data": "82 40 1F 00 00 00 00 00"
}]
}Searches the active DBC and attaches a recent decoded value from physical frame history.
Query fields:
q: optional name/identity search, maximum 64 characters;offset: result offset, default 0;limit: 1–96, default 48;indexes: optional comma-separated DBC signal indexes instead of a text search.
curl.exe "http://192.168.4.1/api/signal_catalog?q=OilTemperature&limit=16"Each signal includes:
{
"startBit": 0,
"length": 8,
"littleEndian": true,
"signed": false,
"factor": 1.0,
"offset": -40.0,
"index": 0,
"canId": 801,
"canIdHex": "0x321",
"name": "OilTemperature",
"valid": true,
"live": true,
"value": 90.0,
"ageMs": 12,
"direction": "A_TO_B"
}live currently means the source physical frame is no older than 1.5 seconds. value:null, ageMs:null, or direction:null are valid unavailable states.
Returns native signal-cache entries. Use indexes=0,4,9 to request particular DBC indexes; without it, the endpoint snapshots available entries up to the 384-entry API limit.
Fields include index, can_id, name, value, generation, valid, and subscribed.
For a browser signal picker, prefer /api/signal_catalog. The cache endpoint is useful when an app already persists selected indexes for the current DBC generation.
Changes optional DBC decoding work without changing physical forwarding.
curl.exe -X POST -d "mode=ALL" http://192.168.4.1/api/observe
curl.exe -X POST -d "mode=NONE" http://192.168.4.1/api/observeSPECIFIC also accepts an ids list in the firmware's CAN-ID/direction subscription syntax. The web interface should construct that list from selected catalog entries rather than asking a beginner to type it. Native signal subscriptions automatically create mandatory observations for their CAN IDs.
Parses the raw DBC body into a candidate, prepares a matching signal cache, persists it as /dbc/active.dbc, and swaps it into the runtime only after validation.
curl.exe -X POST -H "Content-Type: text/plain" `
--data-binary "@my-network.dbc" `
http://192.168.4.1/api/dbcSuccess returns message and signal counts. Changing the DBC clears subscriptions, observations, replay, and rules associated with the old database.
Loads the first valid startup candidate using the firmware's autoload order. Returns 404 when no valid file exists under /dbc.
Loads an existing .dbc under /dbc/:
curl.exe -X POST -d "path=/dbc/my-network.dbc" `
http://192.168.4.1/api/dbc/selectTraversal and non-DBC paths are rejected.
Stages one rule. It does not change active CAN behavior.
Static bit-range example:
curl.exe -X POST -d "rule_kind=BIT_RANGE&can_id=0x321&direction=A_TO_B&start_bit=0&length=8&little_endian=1&replace_value=130&enabled=1" `
http://192.168.4.1/api/rules/stageFields:
rule_kind=BIT_RANGE(default);can_id,direction,start_bit,length,little_endian;replace_value(or compatibility aliasop_value1);dynamic=1to create a runtime-updatable bit-range rule;enabled.
Raw-mask example:
curl.exe -X POST -d "rule_kind=RAW_MASK&can_id=0x321&direction=A_TO_B&mask=FF00000000000000&value=8200000000000000&enabled=1" `
http://192.168.4.1/api/rules/stagemask and value are eight hex bytes. Separators accepted by the shared byte parser may vary; a continuous 16-hex-digit value is the least ambiguous.
The same handler retains a legacy signal-mutation adapter when operation is supplied without rule_kind. It still recognizes the historical operation names, but the current rule engine accepts only REPLACE and PASS_THROUGH. ADD_OFFSET, MULTIPLY, and CLAMP are parsed for compatibility and then rejected; they are not active runtime features. New applications should use explicit rule staging and packages because those map directly to the active rule model.
The query selects which complete table is listed:
GET /api/ruleslists the active table currently used by live CAN traffic.GET /api/rules?view=staginglists the candidate table being assembled for the next Apply.
Use the candidate view in an editor after calling /api/rules/stage; otherwise the newly accepted rule will not appear in the review table. Omitting view remains the deliberate, backward-compatible way to inspect live behavior.
curl.exe http://192.168.4.1/api/rules
curl.exe "http://192.168.4.1/api/rules?view=staging"Both views return their rules and the current table epoch:
{
"ok": true,
"count": 1,
"candidate_dirty": false,
"rule_epoch": 12,
"rules": [{
"rule_id": 0,
"rule_epoch": 12,
"priority": 0,
"active": true,
"kind": "BIT_RANGE",
"can_id": 801,
"direction": "A_TO_B",
"enabled": true,
"start_bit": 0,
"length": 8,
"little_endian": true,
"dynamic": false,
"manual_dynamic": false,
"value_source": "",
"replace_value": 130,
"replace_value_text": "130",
"runtime_value": 0,
"runtime_value_text": "0",
"runtime_value_kind": "none",
"sequence_count": 0
}]
}replace_value_text is the exact unsigned decimal representation. Browser
clients should prefer it when displaying or regenerating rules because JSON
numbers cannot represent every 64-bit integer exactly. replace_value remains
for compatibility and convenience with ordinary-width rules.
enabled tells whether that rule is enabled. active is retained as a compatibility alias for the same enabled state; it does not tell you whether a row came from the active or candidate table. The request URL selects the table, so a candidate editor should label every row from ?view=staging as candidate regardless of active.
candidate_dirty tells whether the candidate differs from the last committed table. A clean candidate is the editable mirror of Live now, not a second set of unapplied rules.
For dynamic bit ranges, manual_dynamic is true only when /api/rules/value is a meaningful manual control. value_source names the application-published source for SOURCE_INT/SOURCE_SELECT_INT; those rows reject manual value writes because the source supplies their value on every matching frame.
runtime_value_kind identifies the writable runtime state exposed through /api/rules/value: raw for a manual dynamic bit range, counter_state for the next counter value, sequence_index for the next sequence entry, or none. runtime_value_text is its exact decimal spelling. sequence_count bounds a sequence index.
The raw body selects a table action:
curl.exe -X POST -H "Content-Type: text/plain" --data "apply_commit" http://192.168.4.1/api/rules
curl.exe -X POST -H "Content-Type: text/plain" --data "revert" http://192.168.4.1/api/rules
curl.exe -X POST -H "Content-Type: text/plain" --data "clear_staging" http://192.168.4.1/api/rules
curl.exe -X POST -H "Content-Type: text/plain" --data "clear_rules" http://192.168.4.1/api/rulesapply_commit: atomically publishes the complete candidate table in RAM. It does not write a package.revert: restores staging from the active table.clear_staging: clears only candidates.clear_rules: clears the mutation tables and selected-package state in RAM.
Changes a rule's writable runtime state: a manual dynamic BIT_RANGE raw value, a COUNTER's next state, or a SEQUENCE8 next-entry index. The table epoch is mandatory in practice. Add view=staging when editing a candidate so the change is guaranteed to wait for Apply:
curl.exe -X POST -d "rule_id=0&rule_epoch=12&view=staging&value=42" `
http://192.168.4.1/api/rules/valueWith view=staging, the value changes only the candidate and is published by Apply. Explicit view=active always targets the live rule even when another candidate edit is pending for the same slot. Omitting view retains the backward-compatible behavior in which a brand-new or pending slot receives the value. A sequence value is an index from zero through sequence_count - 1. Static and application-source replacements reject /value; restage a static rule or publish the registered application source instead.
HTTP 409 with stale_rule_handle means the table changed. Fetch the same table view again and resolve the intended rule rather than retrying an old slot number.
curl.exe -X POST -d "rule_id=0&rule_epoch=12&view=staging&enabled=0" `
http://192.168.4.1/api/rules/enableThe handler can also resolve a rule by CAN ID/direction/field identity, but stable clients should use the returned ID plus epoch and refresh on conflict.
With view=staging, enabling or disabling a row changes only the RAM candidate and cannot affect live traffic until Apply. Explicit view=active always changes the live rule, even when a candidate for the same slot is pending. This does not rewrite .ssrules package source; edit or remove the corresponding package row when the choice must persist after reboot.
See Rules and packages for the file format.
Validates, activates, and stores the raw .ssrules body. The default path is /rules/active.ssrules.
curl.exe -X POST -H "Content-Type: text/plain" `
--data-binary "@my-profile.ssrules" `
"http://192.168.4.1/api/rules/package?path=/rules/active.ssrules"A short name such as my-profile.ssrules is normalized under /rules/. Success returns path, active count, and the new rule_epoch.
Streams the selected package as plain text. With no path, it reads the active package path when known, otherwise /rules/active.ssrules.
curl.exe "http://192.168.4.1/api/rules/package?path=/rules/active.ssrules"Add download=1 to receive a content-disposition filename. This reads the stored file; it does not synthesize package text from arbitrary active RAM rules.
Loads and activates an existing package:
curl.exe -X POST -d "path=/rules/my-profile.ssrules" `
http://192.168.4.1/api/rules/selectReturns authoritative recorder state, counts, storage status, and download readiness.
Start a recording:
curl.exe -X POST -d "action=start&scope=physical&durable=1" http://192.168.4.1/api/logScopes are:
physical(default);all;a_to_b;b_to_a;mutated.
durable=1 enables checkpoint-oriented storage behavior. Other actions are stop, delete/clear, and retry after a save failure. Stopping is asynchronous; poll GET /api/log until the saved artifact is ready.
Streams the completed session-log artifact when downloadReady is true. It returns a conflict/error while a durable file is not available.
Use tools/read_signalscope_session.py to inspect a saved session off-device.
Replay can physically transmit. User-facing tools should set dry_run=1 by default and require an explicit choice for physical dispatch.
Builds a short replay from one frame:
curl.exe -X POST -d "can_id=0x321&direction=A_TO_B&dlc=8&data=8200000000000000&repeat=1&interval_us=0&auto_start=1&dry_run=1" `
http://192.168.4.1/api/replay/sendLimits and fields:
dlc: clamped to 0–8;repeat: clamped to 1–256;interval_us,start_delay_us;auto_start(default true);dry_run(API default false—set it explicitly).
Loads up to 1024 timestamped CSV frames from the raw body. Use query/form fields direction as a fallback and dry_run=1 for non-physical dispatch.
Full row shape:
timestamp_us,can_id,dlc,b0,b1,b2,b3,b4,b5,b6,b7,synthetic,direction
Example:
0,0x321,8,82,00,00,00,00,00,00,00,0,A_TO_B
100000,0x321,8,83,00,00,00,00,00,00,00,0,A_TO_B
Controls the loaded replay:
curl.exe -X POST -d "action=start&loop_mode=PLAY_ONCE&dry_run=1&start_delay_us=0" http://192.168.4.1/api/replay
curl.exe -X POST -d "action=stop" http://192.168.4.1/api/replayLoop modes are PLAY_ONCE, LOOP_RAW, and LOOP_WITH_COUNTER_CONTINUATION. If dry_run is omitted on start, the dispatch mode selected when loading is retained.
These return 404 when no ApplicationExtension is installed.
Returns the JSON produced by the extension's required writeStatusJson() callback.
Passes key and value to the extension, then applies any requested DBC/rule resource pair transactionally:
curl.exe -X POST -d "key=displayUnit&value=celsius" http://192.168.4.1/api/app/configReads a bounded application-owned resource. The extension declares capacity; the host rejects zero or more than 96 KiB.
Passes the raw body to the extension's bounded resource writer. Diagnostic commands receive delivery-receipt semantics so a browser does not retry a non-idempotent request after an ambiguous response.
/api/mutations/stage, /api/mutations, and /api/mutations/toggle are retained for older clients. New applications should use /api/rules/*; the rule vocabulary matches the current engine, epochs, and persistence model directly.
| Status | Meaning |
|---|---|
| 200 | Command or read succeeded |
| 400 | Missing/malformed field or unknown action |
| 404 | File, rule, route capability, or application not found |
| 409 | State conflict such as stale epoch, empty replay, or unavailable download |
| 413 | Uploaded package size invalid |
| 422 | Input was understood but failed validation/compilation |
| 503 | Application, recorder, decoder, replay, or UI scratch resource busy/unavailable |
| 507 | Runtime memory or LittleFS write/allocation failure |