Skip to main content

devicectl-core

Shared building blocks for programs that drive one device at a time: a subcommand table, a progress protocol, a serialising worker that owns the one connection a device allows, and a build-free browser UI -- its HTTP server, its event stream and the design system it is drawn with.

It exists because two such programs -- alfenctl for an Alfen wallbox on the network and jkctl for a JK BMS on an RS485 bus -- turned out to be the same program with different protocols underneath, and the second one was produced by copying the first. Line for line, after normalising the two names, their frontend linters agreed on 528 lines out of 529 and their event broadcasters on 161 out of 167. The drift had begun anyway: fixes were being made on one side only. This package is the part that was the same, with one name each.

What is here

Module What it holds
devicectl.errors DeviceError, the root of every expected failure
devicectl.report Reporter, Wait, SILENT
devicectl.progress the stderr progress bar and the live line
devicectl.doctor Finding, Report, finding_json, and the three weights
devicectl.fields FieldSpec, one description of a setting for all its audiences
devicectl.paths config_dir, the per-user config directory by platform
devicectl.cli.command Command, Need, Handler
devicectl.cli.exits the exit codes that mean the same thing everywhere
devicectl.cli.target first_set, the one-line precedence rule
devicectl.web.worker Worker, the one thread that owns the one connection
devicectl.web.events Broadcaster, Subscription, Event
devicectl.web.http Request, Response, ApiError, Route, the upload spool
devicectl.web.server serve, Branding, parse_listen, and the four guards
devicectl.devtools frontlint, htmcheck and rendercheck, run as python -m
web/static/ the design system (core.css) and the shared widget library, over the vendored Preact + htm

What is deliberately not here

Any runtime dependency. Everything is standard library, so a program that depends on this pays for its own protocol layer and nothing else -- and the package stays installable on a Raspberry Pi, where a dependency with no armv7 wheel is a compiler run rather than a download.

Anything that knows what a device is. No transport, no retry policy, no register catalog. Retry in particular does not generalise: one program retries at the authentication layer (a 401 means log in again), the other at the protocol layer (a framing fault is not a Modbus exception), and a shared abstraction over the two would describe neither.

Using it

from devicectl.cli.command import Command, Need
from devicectl.errors import DeviceError
from devicectl.web import http


class WidgetError(DeviceError):
    """Anything this program refuses to do, in one line."""


def get_state(ctx: "Context", req: http.Request) -> http.Response:
    return http.ok({"ready": ctx.worker.ready})


ROUTES: dict[tuple[str, str], http.Route["Context"]] = {
    ("GET", "/api/state"): http.Route(get_state),
}

The frontend checks take the static root to check:

python -m devicectl.devtools.frontlint src/widgetctl/web/static
python -m devicectl.devtools.htmcheck  src/widgetctl/web/static

htmcheck runs each html template through the real vendored parser inside V8, so it needs an engine. That engine is ~80 MB and publishes no armv7 wheel, so it lives in a dependency group of its own and the check skips itself, saying so, without it:

uv sync --group browser

Development

uv sync
uv run ruff check . && uv run ruff format --check . && uv run ty check && uv run pytest

Licence

EUPL-1.2, except the vendored Preact + htm bundle, which is MIT -- see NOTICE.

Release files for devicectl-core 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 devicectl-core 0.1.0
File Size Uploaded
devicectl_core-0.1.0.tar.gz 232.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for devicectl-core 0.1.0
File Interpreter ABI Platform
devicectl_core-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 431.4 kB

Release files / devicectl_core-0.1.0.tar.gz

Download URL devicectl_core-0.1.0.tar.gz
Size 232.5 kB
Tags Source
SHA-256 checksum
How to use checksums
8f02dafe305f9662c854b50a71db310fd5cd1e59ea10fcc3a6234a239460bad3
BLAKE2b-256 checksum
How to use checksums
662e2becc045eaa817820e114977e02433b523c9e88b2b8a85cf4f59d8872127
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 26, 2026.

Transparency log

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

Download URL devicectl_core-0.1.0-py3-none-any.whl
Size 198.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b67bb1f8ea3c5692a62f278dbb20faf3e2ad16e2c17e100cebe976e6957321c3
BLAKE2b-256 checksum
How to use checksums
5dc4bbfb98aca86a7e775006b17ae179d52c0aaf47fb14e9532ede40cd9ea044
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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