Observed since 20 Oct, 18:00 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Python client for the AccordionQ2 Hardware Management REST API
Python client library for the AccordionQ2 Hardware Management REST API. This is the Python counterpart of the .NET WebApiClient; both libraries expose the same API surface so switching between them feels natural.
| Requirement | Details |
|---|---|
| Python | 3.11 or later |
| Platform | Windows, Linux (including ARM / Raspberry Pi), macOS |
| Architecture | 32-bit and 64-bit |
| Dependencies | None — stdlib only (http.client, json, ssl, threading, dataclasses) |
Install from PyPI:
pip install accordionq2
# Install the package
uv add accordionq2
# Or install from a local checkout with dev dependencies
uv sync --dev
pip install -e .
from accordionq2 import AccordionQ2Client
with AccordionQ2Client("http://agent64.local:5000") as client:
# Check the hardware connection
status = client.connection.get_status()
print("Connected:", status.is_connected)
# Read a sensor value
temp = client.resources.get_value("TempRegulator.CPU_TEMP")
print("CPU temperature:", temp)
# List all channels
for ch in client.channels.get_all():
print(f" {ch.alias}: type={ch.channel_type}, unit={ch.unit}")
The client can also be used without a with block:
client = AccordionQ2Client("http://agent64.local:5000")
names = client.resources.get_names()
client.close()
AccordionQ2Client(base_url, timeout=30.0, auth=None, verify=True, default_headers=None)
| Parameter | Type | Default | Description |
|---|---|---|---|
base_url | str | — | Base URL of the AccordionQ2 WebApi, e.g. "http://raspberrypi:5000" |
timeout | float | 30.0 | HTTP request timeout in seconds |
auth | tuple[str, str] | None | None | Optional (username, password) for HTTP Basic Auth |
verify | bool | str | True | TLS certificate verification. False disables (useful for self-signed certs). A string is treated as a path to a CA bundle. |
default_headers | dict[str, str] | None | None | Additional headers merged into every request (e.g. {"X-Api-Key": "secret"}) |
# HTTP Basic Auth
client = AccordionQ2Client("https://device.local:5001", auth=("admin", "secret"))
# Self-signed certificate — disable TLS verification
client = AccordionQ2Client("https://device.local:5001", verify=False)
# API key header
client = AccordionQ2Client("http://device.local:5000",
default_headers={"X-Api-Key": "my-api-key"})
The client exposes groups, each covering one area of the hardware
API; the main ones are below, and the
documentation
covers them all, including calibration, instruments, events and
subscriptions, and the audit log. All methods are synchronous and raise
AccordionQ2ApiError on HTTP errors.
client.connection — Connection Status| Method | Returns | Description |
|---|---|---|
get_status() | ConnectionStatusDto | Check whether the API is connected to the hardware manager. |
status = client.connection.get_status()
if not status.is_connected:
print("Error:", status.last_error)
client.resources — Resource ValuesResources represent readable/writable hardware values such as voltages,
temperatures, and firmware revisions. They are identified by a dotted name
string (e.g. "TempRegulator.CPU_TEMP").
| Method | Returns | Description |
|---|---|---|
get_names() | list[str] | List all available resource names. |
get_value(name) | str | Read the current value of a single resource. |
set_value(name, value) | — | Write a value to a single resource. |
get_values(names) | dict[str, str] | Read multiple resources in one call. |
set_values(resources) | — | Write multiple resources in one call. |
transact(name, value) | str | Write then read (command/response pattern). |
# Single read
voltage = client.resources.get_value("0.1.ESH10000158.MON_3V3")
# Batch read
values = client.resources.get_values([
"TempRegulator.CPU_TEMP",
"Engine.Uptime",
])
for name, val in values.items():
print(f"{name} = {val}")
client.channels — Hardware ChannelsChannels represent multi-purpose I/O pins (analog, digital, I2C, SPI, etc.).
| Method | Returns | Description |
|---|---|---|
get_all() | list[ChannelDto] | Return every configured channel. |
get_channel(alias=, net_name=) | ChannelDto | Look up one channel by alias or net name. |
configure(config) | — | Partial-update a single channel. |
configure_many(configs) | — | Partial-update multiple channels. |
from accordionq2.models import ChannelConfigRequest
from accordionq2.enums import ChannelTypes
# Look up by alias
ch = client.channels.get_channel(alias="0.1.ESH10000158.MON_3V3")
print(f"Type: {ch.channel_type}, Direction: {ch.direction}")
# Check flags
if ch.channel_type & ChannelTypes.ANALOG:
print("This is an analog channel")
# Partial update (only the fields you set are changed)
client.channels.configure(ChannelConfigRequest(
alias="0.1.ESH10000158.MON_3V3",
description="Main 3.3 V rail monitor",
unit="V",
))
client.modules — Module Management| Method | Returns | Description |
|---|---|---|
get_all() | list[ModuleSettingsDto] | All modules (loaded and unloaded). |
get_loaded() | list[ModuleSettingsDto] | Currently loaded modules only. |
load(module) | — | Load a module. |
unload(module) | — | Unload a module. |
configure(module) | — | Update module configuration. |
get_physical_system() | PhysicalSystemDto | Hardware topology (host, MAC, modules). |
get_licensed_apps() | list[AppLicenseDto] | Licensed applications. |
get_all_apps() | list[AppLicenseDto] | All applications. |
system = client.modules.get_physical_system()
print(f"Host: {system.host}, MAC: {system.mac}")
for mod in system.modules:
print(f" Slot {mod.index}: {mod.name} ({mod.product_id})")
for m in client.modules.get_loaded():
print(f" {m.name} (enabled={m.enabled})")
client.application — Application Lifecycle| Method | Returns | Description |
|---|---|---|
get_name() | str | Application module name. |
get_identification() | str | Application identification string. |
get_status() | ModuleStatus | Current status (enum). |
reset() | — | Reset the application engine. |
list_config_files() | list[str] | Available configuration files. |
get_loaded_config_files() | list[str] | Currently loaded config files. |
load_config_file(name) | — | Load a configuration file. |
save_config_file(name) | — | Save configuration to a file. |
download_config_file(name) | bytes | Download a config file as raw bytes. |
upload_config_file(name, data) | — | Upload a config file (bytes). |
delete_config_file(name) | — | Delete a config file from the device. |
from accordionq2.enums import ModuleStatus
status = client.application.get_status()
if status == ModuleStatus.OK:
print("Application is running normally")
# Configuration file round-trip
data = client.application.download_config_file("factory.cfg")
client.application.upload_config_file("factory_backup.cfg", data)
client.media — Media Files| Method | Returns | Description |
|---|---|---|
list_files() | list[str] | List media files on the device. |
download_file(name) | bytes | Download a media file. |
upload_file(name, data) | — | Upload a media file (bytes). |
delete_file(name) | — | Delete a media file. |
for f in client.media.list_files():
print(f)
# Download and save locally
content = client.media.download_file("waveform.bin")
with open("waveform.bin", "wb") as fp:
fp.write(content)
client.comm — Raw Bus Transactions (I2C, UART, SPI, Socket)All byte data is hex-encoded on the wire. The client handles
encoding and decoding transparently — callers work with plain
bytes objects. BusTransactionResponse.received is always bytes.
| Method | Returns | Description |
|---|---|---|
i2c(device_name, address, action, ...) | BusTransactionResponse | I2C bus transaction (Send, Receive, SendReceive, or Scan). |
uart(device_name, action, ...) | BusTransactionResponse | UART transaction (Send, Receive, SendReceive, or ClearBuffers). |
spi(device_name, action, ...) | BusTransactionResponse | SPI transaction (Send, Receive, or SendReceive). |
socket(device_name, action, ...) | BusTransactionResponse | TCP socket transaction (Send, Receive, or SendReceive). |
I2C parameters:
| Parameter | Type | Description |
|---|---|---|
device_name | str | Device name as registered in the hardware manager |
address | int | I2C 7-bit device address (0–127) |
action | BusActions | SEND, RECEIVE, SEND_RECEIVE, or SCAN |
data_to_send | bytes | Bytes to transmit (required for Send/SendReceive) |
number_of_bytes_to_receive | int | Expected byte count for Receive/SendReceive |
max_retries | int | Retry limit on NAK (-1 = device default) |
from accordionq2.enums import BusActions
# I2C: scan the bus for connected devices
resp = client.comm.i2c("0.ESH10000597.I2C00", address=0x00,
action=BusActions.SCAN)
for addr in resp.received:
print(f"Found device at 0x{addr:02X}")
# I2C: write two bytes to address 0x50
client.comm.i2c("0.ESH10000597.I2C00", address=0x50,
action=BusActions.SEND,
data_to_send=bytes([0x00, 0x10]))
# I2C: read 4 bytes from address 0x50
resp = client.comm.i2c("0.ESH10000597.I2C00", address=0x50,
action=BusActions.RECEIVE,
number_of_bytes_to_receive=4)
print(resp.received.hex()) # e.g. "aabbccdd"
# UART: send a SCPI query and read the response
resp = client.comm.uart("MyUartDevice",
action=BusActions.SEND_RECEIVE,
data_to_send=b"*IDN?\n",
number_of_bytes_to_receive=64,
timeout_ms=2000)
print(resp.received.decode("ascii"))
# SPI: full-duplex transfer
resp = client.comm.spi("MySpiDevice",
action=BusActions.SEND_RECEIVE,
data_to_send=bytes([0xAA, 0xBB]),
number_of_bytes_to_receive=2)
# Socket: send a SCPI query over TCP
resp = client.comm.socket("MySocketDevice",
action=BusActions.SEND_RECEIVE,
host_name="192.168.1.10", port=5025,
data_to_send=b"*IDN?\n",
number_of_bytes_to_receive=64)
client.numeric_results — Fast Numeric SamplingNumericResult channels perform high-speed acquisition on physical channels, computing summary statistics (mean, min, max, stdev) server-side.
| Method | Returns | Description |
|---|---|---|
get_channels() | list[NumericResultChannelDto] | All NumericResult channels with sampling capabilities. |
get_targets(channel_net_name) | list[str] | Physical channels that a NumericResult channel can sample. |
measure(channel, target, samples, reduced_set) | NumericMeasureResultDto | Trigger an acquisition (result cached server-side). |
get_mean(channel_net_name) | float | Mean of the last measurement. |
get_min(channel_net_name) | float | Minimum of the last measurement. |
get_max(channel_net_name) | float | Maximum of the last measurement. |
get_stdev(channel_net_name) | float | Standard deviation of the last measurement. |
get_samples(channel_net_name) | list[float] | Raw sample array (only if reduced_set=False). |
Typical workflow:
# 1. Discover available NumericResult channels
channels = client.numeric_results.get_channels()
for ch in channels:
print(f"{ch.net_name} (rate={ch.sample_rate} Hz)")
# 2. Check what a channel can sample
targets = client.numeric_results.get_targets(channels[0].net_name)
print("Available targets:", targets)
# 3. Trigger acquisition (result cached server-side)
meta = client.numeric_results.measure(
channels[0].net_name, targets[0],
samples=1000, reduced_set=True)
print(f"Acquired {meta.sample_count} samples in {meta.duration}")
# 4. Fetch summary statistics
mean = client.numeric_results.get_mean(channels[0].net_name)
stdev = client.numeric_results.get_stdev(channels[0].net_name)
print(f"Mean = {mean:.6f}, StdDev = {stdev:.6f}")
# 5. For raw samples, use reduced_set=False
meta = client.numeric_results.measure(
channels[0].net_name, targets[0],
samples=100, reduced_set=False)
samples = client.numeric_results.get_samples(channels[0].net_name)
print(f"First 5 samples: {samples[:5]}")
client.lease — Control LeaseOne client at a time may change the station, typically a test station for
the length of a sequence. While another client holds the lease, this
client's changes and forced reads raise AccordionQ2ApiError with status
423; reads with max_age_ms and subscriptions keep working.
| Method | Returns | Description |
|---|---|---|
get() | LeaseDto | Who holds the lease, if anyone. |
acquire(owner, ttl_ms=30000) | LeaseDto | Take the lease (409 while someone else holds it). |
renew(ttl_ms=None) | LeaseDto | Renew the lease this client holds. |
release() | — | Release it. |
hold(owner, ttl_ms=30000) | context manager | Acquire, renew in the background, release after the block. |
with client.lease.hold("TAT station 3"):
client.resources.set_value("0.4.ESH10000662.VSET1", "5")
client.system — Services, Reboot, Clock and boot.config| Method | Returns | Description |
|---|---|---|
get_services() | list[ServiceStatus] | The hardware app, the WebApi and the dashboard. |
service_action(service_id, action) | — | start, stop, restart, enable or disable. |
reboot() | — | Reboot the Pi. |
get_clock() | ClockStatus | The Pi's clock. |
set_clock(utc=None, force=False) | ClockStatus | Set it, to this computer's clock by default. |
boot.get() | BootConfig | The hardware app's start-up configuration (boot.config). |
boot.set_startup(enabled=None, alias_files=None) | BootConfig | Turn boot.config on or off, and/or replace the alias files. |
boot.update(*, if_modified=None, ...) | BootConfig | Edit boot.config section by section. |
boot.config changes apply at the hardware app's next start (alias files and
modules also on a reset). if_modified gives 409 instead of overwriting
another edit, and the Wi-Fi password is never returned.
from accordionq2 import BootAliasFile
client.system.boot.set_startup(enabled=True,
alias_files=[BootAliasFile("station.csv")])
client.system.set_clock()
client.files — Station FilesFiles in the station's folders (config, alias, fsms, media,
extensions, and the read-only logs and webapi-logs).
| Method | Returns | Description |
|---|---|---|
get_roots() | list[FileRoot] | The folders the file API reaches. |
list(root, path="") | FileListing | What a folder holds. |
download(root, path) | bytes | A file's bytes. |
upload(root, path, data, overwrite=False) | FileEntry | Upload a file. |
create_folder(root, path) | — | Create a folder. |
move(root, source, target, overwrite=False) | — | Rename or move within the folder. |
delete(root, path, recursive=False) | — | Delete a file or folder. |
data = client.files.download("alias", "station.csv")
client.files.upload("alias", "station_backup.csv", data)
client.firmware — Firmware UpdatesThe station installs only releases signed by E-Sharp, checked on the station
whichever way the package arrives, and nothing below 6.0.0. Installing
restarts the hardware app and the WebApi; install() and
install_package() keep polling until the update has ended.
| Method | Returns | Description |
|---|---|---|
get_state() | FirmwareState | Installed version, source, minimum version, last update. |
get_releases(include_beta=False) | FirmwareReleases | Releases, newest first. |
set_source(location) | FirmwareSource | An http(s) address or a folder on the Pi; None for the default. |
start_update(version, include_beta=False) | FirmwareUpdateStatus | Start installing and return at once. |
get_update() / get_update_log() | FirmwareUpdateStatus / str | How the update stands, and its log. |
wait_for_update(timeout=900.0, poll=2.0) | FirmwareUpdateStatus | Wait until it has ended. |
install(version, include_beta=False, timeout=900.0) | FirmwareUpdateStatus | Start and wait. |
upload_package(package) | FirmwareRelease | Send a rel-x.y.z.zip from this computer. |
install_package(package, timeout=900.0) | FirmwareUpdateStatus | Upload, install and wait (a station without internet). |
delete_package(file_name) | — | Remove a package from the station's cache. |
releases = client.firmware.get_releases()
newest = next(r for r in releases.releases if r.installable)
status = client.firmware.install(newest.version)
if status.state != "succeeded":
print(status.state, status.message)
print(client.firmware.get_update_log())
# A station without internet
client.firmware.install_package("C:/releases/rel-6.0.0.zip")
All models live in accordionq2.models.
| Class | Description |
|---|---|
ConnectionStatusDto | is_connected (bool), last_error (str or None) |
ChannelDto | Full channel description (18 fields including type, direction, alias, unit, etc.) |
ChannelConfigRequest | Partial-update request — only non-None fields are applied |
ChannelLookupRequest | Identify a channel by alias or net_name |
ModuleSettingsDto | Module configuration (name, enabled, class_name, initial_data, etc.) |
PhysicalSystemDto | Hardware topology (host, mac, firmware, modules tuple) |
PhysicalModuleDto | One hardware slot (index, name, product_id, revision, serial_number) |
AppLicenseDto | License info (name, key, expires, type) |
BusTransactionResponse | device_name, action, received (bytes), number_of_bytes_received |
NumericResultChannelDto | net_name, alias, possible_target_names, sample_rate, default_samples |
NumericMeasureResultDto | channel_net_name, target_net_name, sample_count, sample_rate, reduced_set, started, stopped, duration |
The lease, system, boot.config, files and firmware models (LeaseDto,
ServiceStatus, ClockStatus, BootConfig, BootAliasFile, BootModule,
BootAddress, BootService, BootWifi, FileRoot, FileListing,
FileEntry, FirmwareState, FirmwareSource, FirmwareReleases,
FirmwareRelease, FirmwareUpdateStatus) live in their groups' modules
and are importable from the top-level accordionq2 package.
Response models provide a from_dict(data) class method; request models
provide a to_dict() instance method.
All response models are immutable (frozen=True) — fields cannot be
modified after construction. Create a new instance if you need a different
value.
All enums live in accordionq2.enums.
| Enum | Type | Values |
|---|---|---|
ModuleStatus | str, Enum | UNKNOWN, OK, WARNING, ERROR, DISABLED |
AppTypes | str, Enum | UNKNOWN, SOFTWARE_MODULE, HARDWARE_MODULE |
MpioUsageTypes | str, Enum | UNDEFINED, HIDDEN_SYSTEM_CONTROL, READ_ONLY_SYSTEM_CONTROL, USER_ALLOCATABLE, BUS_SIGNAL |
DirectionTypes | IntFlag | UNDEFINED, IN, OUT |
BusActions | str, Enum | UNDEFINED, SEND, RECEIVE, SEND_RECEIVE, SCAN, BREAK, CLEAR_BUFFERS, RECONFIGURE |
ChannelTypes | IntFlag | UNDEFINED, ANALOG, DIGITAL, TEMPERATURE, I2C, SPI, UART, and many more |
IntFlag enums support bitwise operations:
from accordionq2.enums import ChannelTypes, DirectionTypes
combined = ChannelTypes.ANALOG | ChannelTypes.DIGITAL
if ch.channel_type & ChannelTypes.I2C:
print("I2C channel")
if ch.direction & DirectionTypes.IN:
print("Input capable")
All API errors raise AccordionQ2ApiError (importable from the top-level
package). The exception carries the HTTP status code and the server's error
message:
from accordionq2 import AccordionQ2ApiError
try:
ch = client.channels.get_channel(alias="does.not.exist")
except AccordionQ2ApiError as e:
print(f"HTTP {e.status_code}: {e}")
The test suite uses pytest.
# Install dev dependencies and run all unit tests
uv sync --dev
uv run pytest tests/unit/ -v
# Install with test dependencies
pip install -e ".[dev]"
pytest tests/unit/ -v
Unit tests use mocks and run entirely offline.
# Requires a live AccordionQ2 device
ACCORDIONQ2_API_URL=http://mydevice:5000 pytest tests/ -m integration -v
On Windows (PowerShell):
$env:ACCORDIONQ2_API_URL = "http://mydevice.local:5000"
pytest tests/ -m integration -v
Both clients expose the same API surface with idiomatic naming for their respective language:
| Concept | .NET (AccordionQ2Client) | Python (AccordionQ2Client) |
|---|---|---|
| Lifecycle | IDisposable / using | Context manager / with |
| Methods | GetValueAsync(name) | get_value(name) |
| Concurrency | async / await | Synchronous (thread-safe) |
| Nullability | string? | None |
| Config | ChannelConfigRequest.Enabled = true | ChannelConfigRequest(enabled=True) |
| Enums | ChannelTypes.Analog | ChannelTypes.ANALOG |
| Errors | AccordionQ2ApiException | AccordionQ2ApiError |
| Dependencies | Newtonsoft.Json | None (stdlib only) |
| Install | dotnet add package AccordionQ2.WebApiClient | pip install accordionq2 |
| Package | NuGet | PyPI |
Proprietary — see your license agreement for details.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 5.28.1 | 3 Oct 2026 | 1 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 20 Oct, 18:00 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:d092ce91dc812c9230bb8a1cfa7daa8fdf46a539ade1561d3cdf9155378611fe