Skip to main content

scietex.dac_service

scietex.dac_service is a scietex.service worker connected to Valkey that owns one or more SPI DAC devices through scietex.hal.dac (23 supported parts: voltage converters, current-loop transmitters, and digital potentiometers). It opens the configured devices at startup, exposes writes, reads, and configuration as dac:* tasks, and publishes the last-written state as telemetry without touching the bus. A device that fails to open does not stop the worker: it is recorded, reported as unavailable, and the remaining devices keep working. Configuration can also be pushed at runtime through the framework's remote-config channel.

Python ≥ 3.10 · License: MIT

Documentation

Installation

pip install "scietex.dac_service[spi]"      # spidev backend for /dev/spidev*
pip install "scietex.dac_service[gpiozero]" # plain-GPIO backend

The core package pulls in scietex.hal.dac, scietex.service[valkey], and msgspec. The backend extras are optional: a core install can still build a device from a caller-supplied transport, but an operator normally installs the extra that matches the board's wiring.

Quick start

1. Write a configuration file

The service reads dac/dac.yml under its config directory. Create it by hand, or let the service generate a default on first run. The file is the bootstrap layer of a four-layer merge: constructor defaults < dac.yml < the framework's config.yml snapshot < the remote dac section.

2. Run the service

start-dac-service --conf-dir /etc/scietex

The config directory resolves automatically when --conf-dir is omitted. The service runs in the foreground until SIGINT or SIGTERM.

3. Write a channel

Submit a dac:write task to the worker with payload {"device_id": "dac0", "channel": 0, "output": 1.5} — or use code for a raw value. Exactly one of code and output must be set. The result carries the code the channel holds and, for a write-only part, the scaled output.

Configuration at a glance

Devices are keyed by a string id, which names the device in task payloads and telemetry:

devices:
  dac0:
    type: MCP4822
    port: 0
    device: 0
    reference: 2.048
  dac1:
    type: AD420
    range: 4-20mA
    latch_pin: 27
  dac2:
    type: AD5420
    port: 0
    device: 1

MCP4822 is a dual 12-bit voltage converter with a fixed 2.048 V full scale. AD420 is a single 16-bit current transmitter: its range only declares the RANGE SELECT strap, and latch_pin is the GPIO line that latches each frame. AD5420 is a 16-bit current source that needs dac:configure to select a range and enable its output before a write reaches the loop.

Per-device fields: type (required), port/device for the SPI wiring, the gpiozero pins (clock_pin/mosi_pin/miso_pin/select_pin), reference, label, log_level, and the part-specific range, load_pin, latch_pin, r_ab, grounded. Unknown fields are rejected. port is the SPI bus number and device is the chip-select.

Supported parts

Parts Channels Resolution Readable Required extra
MCP4231, MCP4241 2 7 yes —
MCP4251, MCP4261 2 8 yes —
MCP4802, MCP4812, MCP4822 2 8 / 10 / 12 no —
MCP4902, MCP4912, MCP4922 2 8 / 10 / 12 no —
DAC8531, DAC8551 1 16 no —
DAC8552 2 16 no —
DAC121S101 1 12 no —
AD5302, AD5312, AD5322 2 8 / 10 / 12 no —
DAC8512 1 12 no load_pin
AD7390, AD7391 1 12 / 10 no load_pin
AD5410, AD5420 1 12 / 16 yes —
AD420 1 16 no range, latch_pin

"Readable" means the part can report its registers back, so dac:read and register-level dac:status work. A write-only part still accepts dac:write and reports the cached code. The potentiometers additionally take r_ab and grounded; the current-loop parts take range.

Tasks

Task Lane Request Result
dac:list control optional device_id devices[] with device_id, type, available, channels, resolution, max_code, unit, reference, readable, error
dac:status control device_id device_id, type, available, error, readable, channels, resolution, max_code, unit, reference, last_written[], status, control
dac:configure control device_id, optional range, slew, sr_clock, sr_step, rext, dcen, output device_id, range, slew, sr_clock, sr_step, rext, dcen, output_enabled
dac:read data device_id, optional channel device_id, channel, code, unit, reference, max_code
dac:write data device_id, optional channel, exactly one of code/output device_id, channel, code, output, unit, max_code

dac:configure is only supported by parts that expose a control register; a part without one answers DAC_CONFIGURE_UNSUPPORTED. A configured device that failed to open answers DEVICE_UNAVAILABLE (retryable) from dac:status, dac:read, and dac:write, while dac:list reports it as available: false instead of failing the listing.

Telemetry

The worker publishes to scietex:DacService:<instance_id>:telemetry every 2 s with a 6 s TTL. The snapshot lists every configured device with its open state, part type, and the channel state left by the last successful write. It is built from the registry's last-written cache and live channel objects only: a telemetry tick never touches the bus, so it cannot disturb an in-flight write, and a write-only part still reports state. A device that was never written reports no channels rather than fabricated zeros.

Container and quadlet

podman build --build-arg VERSION=0.1.0 -t scietex-dac-service .
podman run --rm --device /dev/spidev0.0 -v ./config:/config scietex-dac-service

The container only dials Valkey, so it publishes no port and needs no capabilities. /dev/spidevX.Y must be passed with --device; without it the worker still starts and reports the device unavailable.

For a persistent deployment, install deploy/dac-service.container as ~/.config/containers/systemd/dac-service.container and the SPI drop-in as ~/.config/containers/systemd/dac-service.container.d/50-spi-devices.conf, then systemctl --user daemon-reload && systemctl --user start dac-service. The Quadlet bind-mounts ~/.config/scietex, so dac/dac.yml stays hand-editable on the host.

Development

Dependencies are managed with uv; checks and tests run through tox:

uv sync --all-extras
uv run tox                    # format, lint, type, py314
uv run tox -e py314           # tests only

Metadata

Release files for scietex.dac-service 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for scietex.dac-service 0.1.0
File Size Uploaded Provenance Actions
scietex_dac_service-0.1.0.tar.gz 41.8 kB With provenance Details for scietex_dac_service-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for scietex.dac-service 0.1.0
File Interpreter ABI Platform Actions
scietex_dac_service-0.1.0-py3-none-any.whl Python 3 none any Details for scietex_dac_service-0.1.0-py3-none-any.whl

Total release size: 76.4 kB

Release files / scietex_dac_service-0.1.0.tar.gz

Download URL scietex_dac_service-0.1.0.tar.gz
Size 41.8 kB
Tags Source
SHA-256 checksum
How to use checksums
84c5533073da1455203beea96e460267eaa6cedc4f744e7633fb8585106a4d70
BLAKE2b-256 checksum
How to use checksums
19986a28a2622501c880cbc5b221e72c0f4870fad358f680d6f7ec0dfbe3fa09
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Release files / scietex_dac_service-0.1.0-py3-none-any.whl

Download URL scietex_dac_service-0.1.0-py3-none-any.whl
Size 34.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fc33786134ba75ecf0687732358712ea57f62beccc5e9d436a6cc0d1ba1fa8e6
BLAKE2b-256 checksum
How to use checksums
c8f6bc8cc7ba4f593f2ee1f0452368cacb29083c442be6b8217e8de1aa9bd9d9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Security for scietex.dac-service 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for scietex_dac_service-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page