Catalog PyPI
PACKAGE EXPLORERView in registry

accordionq2

PyPI

Python client for the AccordionQ2 Hardware Management REST API

Versions collected1
Direct dependencies1From release 5.28.1
Last observed20 Oct, 18:00 UTC

README

Version 5.28.1 documentation

AccordionQ2 Python Client

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.

PyPI NuGet Documentation

Requirements

RequirementDetails
Python3.11 or later
PlatformWindows, Linux (including ARM / Raspberry Pi), macOS
Architecture32-bit and 64-bit
DependenciesNone — stdlib only (http.client, json, ssl, threading, dataclasses)

Installation

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 (local checkout)

pip install -e .

Quick Start

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()

Constructor

AccordionQ2Client(base_url, timeout=30.0, auth=None, verify=True, default_headers=None)
ParameterTypeDefaultDescription
base_urlstr—Base URL of the AccordionQ2 WebApi, e.g. "http://raspberrypi:5000"
timeoutfloat30.0HTTP request timeout in seconds
authtuple[str, str] | NoneNoneOptional (username, password) for HTTP Basic Auth
verifybool | strTrueTLS certificate verification. False disables (useful for self-signed certs). A string is treated as a path to a CA bundle.
default_headersdict[str, str] | NoneNoneAdditional 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"})

API Reference

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

MethodReturnsDescription
get_status()ConnectionStatusDtoCheck 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 Values

Resources 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").

MethodReturnsDescription
get_names()list[str]List all available resource names.
get_value(name)strRead 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)strWrite 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 Channels

Channels represent multi-purpose I/O pins (analog, digital, I2C, SPI, etc.).

MethodReturnsDescription
get_all()list[ChannelDto]Return every configured channel.
get_channel(alias=, net_name=)ChannelDtoLook 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

MethodReturnsDescription
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()PhysicalSystemDtoHardware 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

MethodReturnsDescription
get_name()strApplication module name.
get_identification()strApplication identification string.
get_status()ModuleStatusCurrent 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)bytesDownload 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

MethodReturnsDescription
list_files()list[str]List media files on the device.
download_file(name)bytesDownload 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.

MethodReturnsDescription
i2c(device_name, address, action, ...)BusTransactionResponseI2C bus transaction (Send, Receive, SendReceive, or Scan).
uart(device_name, action, ...)BusTransactionResponseUART transaction (Send, Receive, SendReceive, or ClearBuffers).
spi(device_name, action, ...)BusTransactionResponseSPI transaction (Send, Receive, or SendReceive).
socket(device_name, action, ...)BusTransactionResponseTCP socket transaction (Send, Receive, or SendReceive).

I2C parameters:

ParameterTypeDescription
device_namestrDevice name as registered in the hardware manager
addressintI2C 7-bit device address (0–127)
actionBusActionsSEND, RECEIVE, SEND_RECEIVE, or SCAN
data_to_sendbytesBytes to transmit (required for Send/SendReceive)
number_of_bytes_to_receiveintExpected byte count for Receive/SendReceive
max_retriesintRetry 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 Sampling

NumericResult channels perform high-speed acquisition on physical channels, computing summary statistics (mean, min, max, stdev) server-side.

MethodReturnsDescription
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)NumericMeasureResultDtoTrigger an acquisition (result cached server-side).
get_mean(channel_net_name)floatMean of the last measurement.
get_min(channel_net_name)floatMinimum of the last measurement.
get_max(channel_net_name)floatMaximum of the last measurement.
get_stdev(channel_net_name)floatStandard 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 Lease

One 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.

MethodReturnsDescription
get()LeaseDtoWho holds the lease, if anyone.
acquire(owner, ttl_ms=30000)LeaseDtoTake the lease (409 while someone else holds it).
renew(ttl_ms=None)LeaseDtoRenew the lease this client holds.
release()—Release it.
hold(owner, ttl_ms=30000)context managerAcquire, 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

MethodReturnsDescription
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()ClockStatusThe Pi's clock.
set_clock(utc=None, force=False)ClockStatusSet it, to this computer's clock by default.
boot.get()BootConfigThe hardware app's start-up configuration (boot.config).
boot.set_startup(enabled=None, alias_files=None)BootConfigTurn boot.config on or off, and/or replace the alias files.
boot.update(*, if_modified=None, ...)BootConfigEdit 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 Files

Files in the station's folders (config, alias, fsms, media, extensions, and the read-only logs and webapi-logs).

MethodReturnsDescription
get_roots()list[FileRoot]The folders the file API reaches.
list(root, path="")FileListingWhat a folder holds.
download(root, path)bytesA file's bytes.
upload(root, path, data, overwrite=False)FileEntryUpload 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 Updates

The 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.

MethodReturnsDescription
get_state()FirmwareStateInstalled version, source, minimum version, last update.
get_releases(include_beta=False)FirmwareReleasesReleases, newest first.
set_source(location)FirmwareSourceAn http(s) address or a folder on the Pi; None for the default.
start_update(version, include_beta=False)FirmwareUpdateStatusStart installing and return at once.
get_update() / get_update_log()FirmwareUpdateStatus / strHow the update stands, and its log.
wait_for_update(timeout=900.0, poll=2.0)FirmwareUpdateStatusWait until it has ended.
install(version, include_beta=False, timeout=900.0)FirmwareUpdateStatusStart and wait.
upload_package(package)FirmwareReleaseSend a rel-x.y.z.zip from this computer.
install_package(package, timeout=900.0)FirmwareUpdateStatusUpload, 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")

Models

All models live in accordionq2.models.

ClassDescription
ConnectionStatusDtois_connected (bool), last_error (str or None)
ChannelDtoFull channel description (18 fields including type, direction, alias, unit, etc.)
ChannelConfigRequestPartial-update request — only non-None fields are applied
ChannelLookupRequestIdentify a channel by alias or net_name
ModuleSettingsDtoModule configuration (name, enabled, class_name, initial_data, etc.)
PhysicalSystemDtoHardware topology (host, mac, firmware, modules tuple)
PhysicalModuleDtoOne hardware slot (index, name, product_id, revision, serial_number)
AppLicenseDtoLicense info (name, key, expires, type)
BusTransactionResponsedevice_name, action, received (bytes), number_of_bytes_received
NumericResultChannelDtonet_name, alias, possible_target_names, sample_rate, default_samples
NumericMeasureResultDtochannel_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.

Enumerations

All enums live in accordionq2.enums.

EnumTypeValues
ModuleStatusstr, EnumUNKNOWN, OK, WARNING, ERROR, DISABLED
AppTypesstr, EnumUNKNOWN, SOFTWARE_MODULE, HARDWARE_MODULE
MpioUsageTypesstr, EnumUNDEFINED, HIDDEN_SYSTEM_CONTROL, READ_ONLY_SYSTEM_CONTROL, USER_ALLOCATABLE, BUS_SIGNAL
DirectionTypesIntFlagUNDEFINED, IN, OUT
BusActionsstr, EnumUNDEFINED, SEND, RECEIVE, SEND_RECEIVE, SCAN, BREAK, CLEAR_BUFFERS, RECONFIGURE
ChannelTypesIntFlagUNDEFINED, 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")

Error Handling

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}")

Running the Tests

The test suite uses pytest.

# Install dev dependencies and run all unit tests
uv sync --dev
uv run pytest tests/unit/ -v

Using pip

# Install with test dependencies
pip install -e ".[dev]"

Unit Tests (no hardware required)

pytest tests/unit/ -v

Unit tests use mocks and run entirely offline.

Integration Tests (live device required)

# 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

Comparison with the .NET Client

Both clients expose the same API surface with idiomatic naming for their respective language:

Concept.NET (AccordionQ2Client)Python (AccordionQ2Client)
LifecycleIDisposable / usingContext manager / with
MethodsGetValueAsync(name)get_value(name)
Concurrencyasync / awaitSynchronous (thread-safe)
Nullabilitystring?None
ConfigChannelConfigRequest.Enabled = trueChannelConfigRequest(enabled=True)
EnumsChannelTypes.AnalogChannelTypes.ANALOG
ErrorsAccordionQ2ApiExceptionAccordionQ2ApiError
DependenciesNewtonsoft.JsonNone (stdlib only)
Installdotnet add package AccordionQ2.WebApiClientpip install accordionq2
PackageNuGetPyPI

License

Proprietary — see your license agreement for details.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
5.28.13 Oct 20261

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
esharpab/esharp-accordion-projects-webapiclient-python

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 18:00 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:d092ce91dc812c9230bb8a1cfa7daa8fdf46a539ade1561d3cdf9155378611fe