Loading...

API

API

The SynapseDevice gRPC service defines the control plane for a Synapse device. Clients use these RPCs to inspect and operate a device. Data produced by a running signal chain is exposed separately through Taps.

See the SynapseDevice service definition for the canonical request and response types.

RPCRequest/response patternPurpose
InfoUnaryReturn device identity, state, peripherals, storage, and the active configuration.
ConfigureUnaryValidate and install a signal-chain configuration.
StartUnaryStart the configured signal chain.
StopUnaryStop the running signal chain.
QueryUnaryRun a diagnostic or metadata query and return one response.
StreamQueryServer streamingRun a diagnostic query that returns a sequence of responses.
DeployAppBidirectional streamingUpload and install a packaged Synapse App.
ListAppsUnaryList installed Synapse Apps and their versions.
ListFilesUnaryList files exposed by the device.
WriteFileUnaryWrite a file to the device.
ReadFileServer streamingDownload a file in chunks.
DeleteFileUnaryDelete a file from the device.
GetLogsUnaryReturn historical logs matching a time and severity filter.
TailLogsServer streamingStream new log entries as they are produced.
UpdateDeviceSettingsUnaryValidate, persist, and apply device-defined settings.

Info

Info returns the current identity and state of a Synapse device.

rpc Info(google.protobuf.Empty) returns (DeviceInfo)

Allowed device state: Any. Info does not change the state.

Request

Empty

This message has no fields.

Response

DeviceInfo

JSON fieldTypeRules
namestringsingular
serialstringsingular
synapseVersion

protobuf: synapse_version

uint32singular
firmwareVersion

protobuf: firmware_version

uint32singular
statusStatussingular
peripheralsPeripheralrepeated
configurationDeviceConfigurationsingular

The headstage implementation returns kOk for a healthy device. If the device is in its error state, the response reports kUndefinedError and includes the message Device in error state.

import synapse as syn

device = syn.Device("192.168.42.1")
info = device.info()

if info is None:
    raise RuntimeError("Unable to read device information")

print(info.name)
print(info.status.state)
print(info.peripherals)

Configure

Configure validates and installs a signal chain on the device.

rpc Configure(DeviceConfiguration) returns (Status)

Allowed device state: kStopped. The device transitions through kInitializing and returns to kStopped. Other states return kFailedPrecondition.

Request

DeviceConfiguration

JSON fieldTypeRules
nodesNodeConfigrepeated
connectionsNodeConnectionrepeated

Response

Status

JSON fieldTypeRulesDescription
messagestringsingular
codeStatusCodesingular
stateDeviceStatesingular
powerDevicePowersingular
storageDeviceStoragesingular
signalChain

protobuf: signal_chain

SignalChainStatussingular
timeSyncPort

protobuf: time_sync_port

uint32singularIf the device supports a time sync server (see time.proto) Then the UDP echo server will be available at this port

A DeviceConfiguration contains:

  • nodes: the configuration for every node in the signal chain
  • connections: directed connections from each source node ID to a destination node ID

See JSON Configuration for instructions and complete examples.

The device validates node support, peripheral IDs, node parameters, connection compatibility, and available resources before accepting the configuration. You can inspect the returned Status.code and Status.message to determine whether configuration succeeded.

import synapse as syn

channels = [
    syn.Channel(id=channel_id, electrode_id=channel_id, reference_id=520)
    for channel_id in range(32)
]
broadband = syn.BroadbandSource(
    peripheral_id=1000,
    bit_width=12,
    sample_rate_hz=10000,
    gain=1.0,
    signal=syn.SignalConfig(
        electrode=syn.ElectrodeConfig(
            channels=channels,
            low_cutoff_hz=57.0,
            high_cutoff_hz=4365.0,
        )
    ),
)

config = syn.Config()
config.add_node(broadband)

device = syn.Device("192.168.42.1")
status = device.configure_with_status(config)
if status is None or status.code != 0:
    raise RuntimeError(status.message if status else "Configure RPC failed")

Start

Start starts the signal chain previously installed by Configure. See Device Lifecycle for the complete state model.

rpc Start(google.protobuf.Empty) returns (Status)

Allowed device state: kStopped with a non-empty configured signal chain. Success transitions the device to kRunning.

Request

Empty

This message has no fields.

Response

Status

JSON fieldTypeRulesDescription
messagestringsingular
codeStatusCodesingular
stateDeviceStatesingular
powerDevicePowersingular
storageDeviceStoragesingular
signalChain

protobuf: signal_chain

SignalChainStatussingular
timeSyncPort

protobuf: time_sync_port

uint32singularIf the device supports a time sync server (see time.proto) Then the UDP echo server will be available at this port

The headstage implementation returns:

  • kOk when the device starts.
  • kInvalidConfiguration when the device has not been configured.
  • kFailedPrecondition when the device is already running or a configured peripheral cannot be found.
  • kInternalError when the device cannot start.
import synapse as syn

device = syn.Device("192.168.42.1")
status = device.start_with_status()

if status is None or status.code != 0:
    raise RuntimeError(status.message if status else "Start RPC failed")

Stop

Stop stops the running signal chain. See Device Lifecycle for the complete state model.

rpc Stop(google.protobuf.Empty) returns (Status)

Allowed device state: kRunning. Success transitions the device to kStopped; every other state returns kFailedPrecondition.

Request

Empty

This message has no fields.

Response

Status

JSON fieldTypeRulesDescription
messagestringsingular
codeStatusCodesingular
stateDeviceStatesingular
powerDevicePowersingular
storageDeviceStoragesingular
signalChain

protobuf: signal_chain

SignalChainStatussingular
timeSyncPort

protobuf: time_sync_port

uint32singularIf the device supports a time sync server (see time.proto) Then the UDP echo server will be available at this port

The headstage implementation returns kOk when the device stops, kFailedPrecondition when the device is not running, and kInternalError when the device cannot stop.

import synapse as syn

device = syn.Device("192.168.42.1")
status = device.stop_with_status()

if status is None or status.code != 0:
    raise RuntimeError(status.message if status else "Stop RPC failed")

Query

Query runs a diagnostic or metadata operation and returns one response.

rpc Query(QueryRequest) returns (QueryResponse)

Allowed device state: Any. Query does not change the state. A peripheral query can fail if that peripheral is in use.

Request

QueryRequest

JSON fieldTypeRules
queryType

protobuf: query_type

QueryTypesingular
impedanceQuery

protobuf: impedance_query

ImpedanceQueryoneof query
sampleQuery

protobuf: sample_query

SampleQueryoneof query
selfTestQuery

protobuf: self_test_query

SelfTestQueryoneof query
listTapsQuery

protobuf: list_taps_query

ListTapsQueryoneof query
getSettingsQuery

protobuf: get_settings_query

GetSettingsQueryoneof query

Response

QueryResponse

JSON fieldTypeRules
statusStatussingular
datauint32repeated
impedanceResponse

protobuf: impedance_response

ImpedanceResponseoneof response
selfTestResponse

protobuf: self_test_response

SelfTestResponseoneof response
listTapsResponse

protobuf: list_taps_response

ListTapsResponseoneof response
getSettingsResponse

protobuf: get_settings_response

GetSettingsResponseoneof response

Only one Query or StreamQuery can execute at a time.

The headstage implementation handles:

  • impedance_query: measure the magnitude and phase for selected electrodes on a peripheral.
  • self_test_query: run the self-test exposed by a peripheral.
  • list_taps_query: list the Taps currently exposed by the configured signal chain.
  • get_settings_query: return current device settings and the device-defined settings schema.

The response contains a Status and the response message corresponding to the selected query. A concurrent Query or StreamQuery returns gRPC RESOURCE_EXHAUSTED.

import synapse as syn
from synapse.api.query_pb2 import QueryRequest

device = syn.Device("192.168.42.1")
request = QueryRequest(
    query_type=QueryRequest.kListTaps,
    list_taps_query={},
)
response = device.query(request)

if response is None or response.status.code != 0:
    raise RuntimeError(response.status.message if response else "Query RPC failed")

for tap in response.list_taps_response.taps:
    print(tap)

Read device settings

Settings are read through Query using get_settings_query.

The resulting GetSettingsResponse contains the current DeviceSettings.values and a schema describing every accepted key, value type, default, description, and allowed value.

import synapse as syn
from synapse.client import settings

device = syn.Device("192.168.42.1")

print(settings.get_available_settings(device))
print(settings.get_all_settings(device))

StreamQuery

StreamQuery runs a diagnostic operation and streams results as they become available.

rpc StreamQuery(StreamQueryRequest) returns (stream StreamQueryResponse)

Allowed device state: Any. StreamQuery does not change the state. A peripheral query can fail if that peripheral is in use.

Request

StreamQueryRequest

JSON fieldTypeRules
requestQueryRequestsingular

Response stream

StreamQueryResponse

JSON fieldTypeRules
codeStatusCodesingular
messagestringsingular
timestampNs

protobuf: timestamp_ns

uint64singular
impedanceImpedanceResponseoneof response
selfTest

protobuf: self_test

SelfTestResponseoneof response

Only one Query or StreamQuery can execute at a time.

The headstage implementation supports streaming impedance and self-test queries. Each response includes a status code, message, timestamp, and the impedance or self-test result. Unsupported query types return gRPC INVALID_ARGUMENT.

import synapse as syn
from synapse.api.query_pb2 import QueryRequest, StreamQueryRequest

device = syn.Device("192.168.42.1")
request = StreamQueryRequest(
    request=QueryRequest(
        query_type=QueryRequest.kImpedance,
        impedance_query={
            "peripheral_id": 100,
            "electrode_ids": [0, 1, 2, 3],
        },
    )
)

for response in device.stream_query(request):
    if response.code != 0:
        raise RuntimeError(response.message)
    for measurement in response.impedance.measurements:
        print(measurement.electrode_id, measurement.magnitude, measurement.phase)

DeployApp

DeployApp uploads and installs a packaged Synapse App.

rpc DeployApp(stream AppPackageChunk) returns (stream AppDeployResponse)

Allowed device state: Any. DeployApp does not change the state.

Request stream

AppPackageChunk

JSON fieldTypeRules
metadataPackageMetadataoneof data
fileChunk

protobuf: file_chunk

bytesoneof data

Response stream

AppDeployResponse

JSON fieldTypeRulesDescription
statusStatusCodesingular
messagestringsingularInformational messages

The client first sends PackageMetadata, including the package name, version, size, and SHA-256 checksum, followed by chunks of package data. The device streams informational and status responses while it validates and installs the package. A failed installation returns a gRPC INTERNAL error.

The Python client's deployment helper calculates the package metadata, streams the package, and processes the deployment responses:

from synapse.cli.deploy import deploy_package

deployed = deploy_package(
    "192.168.42.1",
    "/path/to/synapse-example-app_0.1.0_arm64.deb",
)
if not deployed:
    raise RuntimeError("DeployApp RPC failed")

ListApps

ListApps returns the Synapse Apps installed on the device.

rpc ListApps(ListAppsRequest) returns (ListAppsResponse)

Allowed device state: Any. ListApps does not change the state.

Request

ListAppsRequest

This message has no fields.

Response

ListAppsResponse

JSON fieldTypeRules
appsAppInforepeated

Each AppInfo in the response contains the installed app's name and version. ListAppsRequest has no fields.

import synapse as syn

device = syn.Device("192.168.42.1")
response = device.list_apps()

if response is None:
    raise RuntimeError("ListApps RPC failed")

for app in response.apps:
    print(app.name, app.version)

ListFiles

ListFiles returns the files exposed by the device. See Filesystem for the device filesystem mental model.

rpc ListFiles(google.protobuf.Empty) returns (ListFilesResponse)

Request

Empty

This message has no fields.

Response

ListFilesResponse

JSON fieldTypeRules
filesFilerepeated

Each file entry contains its name, size, creation and modification timestamps, and type.

The Python client exposes this RPC through the generated stub:

import synapse as syn
from google.protobuf.empty_pb2 import Empty

device = syn.Device("192.168.42.1")
response = device.rpc.ListFiles(Empty())

for file in response.files:
    print(file.name, file.size, file.type)

WriteFile

WriteFile writes the supplied bytes to a named file on the device. See Filesystem for the device filesystem mental model, reserved folders, and SciFi 2 USB access.

rpc WriteFile(WriteFileRequest) returns (WriteFileResponse)

Request

WriteFileRequest

JSON fieldTypeRules
namestringsingular
databytessingular

Response

WriteFileResponse

JSON fieldTypeRules
namestringsingular
bytesWritten

protobuf: bytes_written

uint64singular

The request contains name and data. The response returns the file name and number of bytes written.

import synapse as syn
from synapse.api.files_pb2 import WriteFileRequest

device = syn.Device("192.168.42.1")
response = device.rpc.WriteFile(
    WriteFileRequest(name="/data/example.bin", data=b"example data")
)
print(response.name, response.bytes_written)

ReadFile

ReadFile downloads a named file as a server stream. See Filesystem for the device filesystem mental model, reserved folders, and SciFi 2 USB access.

rpc ReadFile(ReadFileRequest) returns (stream ReadFileResponse)

Request

ReadFileRequest

JSON fieldTypeRules
namestringsingular

Response stream

ReadFileResponse

JSON fieldTypeRules
namestringsingular
databytessingular
startOffset

protobuf: start_offset

uint32singular
fileTotalLength

protobuf: file_total_length

uint32singular

Each response contains a chunk of file data, its starting offset, and the total file length. Use the offset and total length to assemble and verify the downloaded file.

import synapse as syn
from synapse.api.files_pb2 import ReadFileRequest

device = syn.Device("192.168.42.1")

with open("example.bin", "wb") as output:
    for response in device.rpc.ReadFile(
        ReadFileRequest(name="/data/example.bin")
    ):
        output.seek(response.start_offset)
        output.write(response.data)

DeleteFile

DeleteFile deletes a named file from the device. See Filesystem for the device filesystem mental model, reserved folders, and SciFi 2 USB access.

rpc DeleteFile(DeleteFileRequest) returns (DeleteFileResponse)

Request

DeleteFileRequest

JSON fieldTypeRules
namestringsingular

Response

DeleteFileResponse

JSON fieldTypeRules
namestringsingular
statusCode

protobuf: status_code

StatusCodesingular

The response identifies the file and reports the resulting StatusCode.

import synapse as syn
from synapse.api.files_pb2 import DeleteFileRequest
from synapse.api.status_pb2 import StatusCode

device = syn.Device("192.168.42.1")
response = device.rpc.DeleteFile(
    DeleteFileRequest(name="/data/example.bin")
)

if response.status_code != StatusCode.kOk:
    raise RuntimeError(f"DeleteFile failed: {response.status_code}")

GetLogs

GetLogs returns retained log entries matching a time range and minimum severity.

rpc GetLogs(LogQueryRequest) returns (LogQueryResponse)

Allowed device state: Any state. GetLogs does not change the state.

Request

LogQueryRequest

JSON fieldTypeRulesDescription
startTimeNs

protobuf: start_time_ns

uint64singularOptional start time, inclusive
endTimeNs

protobuf: end_time_ns

uint64singularOptional end time, exclusive
sinceMs

protobuf: since_ms

uint64singularOptional time since current time, in milliseconds Will ignore the start and end time
minLevel

protobuf: min_level

LogLevelsingularOptional minimum level, defaults to INFO

Response

LogQueryResponse

JSON fieldTypeRules
entriesLogEntryrepeated

Set either:

  • since_ms to request entries from a recent interval, or
  • start_time_ns and end_time_ns to request an explicit time range.

When since_ms is set, the headstage ignores the explicit start and end times. If min_level is unspecified, the headstage defaults to LOG_LEVEL_INFO. Each returned entry includes its timestamp, severity, source, and message.

import synapse as syn

device = syn.Device("192.168.42.1")
response = device.get_logs(log_level="WARNING", since_ms=60_000)

if response is None:
    raise RuntimeError("GetLogs RPC failed")

for entry in response.entries:
    print(entry.timestamp_ns, entry.level, entry.source, entry.message)

TailLogs

TailLogs streams new log entries at or above a minimum severity.

rpc TailLogs(TailLogsRequest) returns (stream LogEntry)

Allowed device state: Any state. TailLogs does not change the state.

Request

TailLogsRequest

JSON fieldTypeRules
minLevel

protobuf: min_level

LogLevelsingular

Response stream

LogEntry

JSON fieldTypeRulesDescription
timestampNs

protobuf: timestamp_ns

uint64singular
levelLogLevelsingular
sourcestringsingularWhich entity is the source of the log (e.g. filename or module name)
messagestringsingular

The stream remains active until the client cancels it or the server's stream duration limit is reached. The headstage limits the number of simultaneous log subscribers and returns gRPC RESOURCE_EXHAUSTED when no subscription slot is available.

import synapse as syn

device = syn.Device("192.168.42.1")

for entry in device.tail_logs(log_level="INFO"):
    print(entry.timestamp_ns, entry.level, entry.source, entry.message)

UpdateDeviceSettings

UpdateDeviceSettings validates, persists, and applies one or more settings. See the Settings concept for schema behavior. Device-specific settings are listed in the Product Details for SciFi and SciFi 2.

rpc UpdateDeviceSettings(UpdateDeviceSettingsRequest)
    returns (UpdateDeviceSettingsResponse)

Allowed device state: kStopped. Every other state returns gRPC FAILED_PRECONDITION. The update does not change the state.

Request

UpdateDeviceSettingsRequest

JSON fieldTypeRulesDescription
settingsDeviceSettingssingularOnly keys present in settings.values will be updated.

Response

UpdateDeviceSettingsResponse

JSON fieldTypeRules
statusStatussingular
updatedSettings

protobuf: updated_settings

DeviceSettingssingular

Only keys present in settings.values are updated. The response includes a Status and the resulting settings. The accepted keys are device-defined; clients should read the schema before constructing an update.

Current SciFi settings include the user-facing device name, timestamp source, FPGA clock frequency, and per-network WiFi MAC address policy.

import synapse as syn
from synapse.client import settings

device = syn.Device("192.168.42.1")
device.stop()

updated_name = settings.set_setting(device, "name", "recording-room-a")
print(updated_name)