Skip to main content

Lockbox Python API

Python 3.11+ synchronous controls for the Lockbox server. Install from this directory with python -m pip install . (or python -m pip install -e ".[dev]" for development).

from lockbox import Server, SlopePreference

with Server("http://lockboxcontrol.internal:5106") as server:
    box = server.get_lockbox(1)  # Choose an ID from server.list_lockboxes().
    box.on_coarse_resonance_found(lambda: print("Coarse resonance found"))
    box.on_fine_resonance_found(lambda: print("Fine resonance found"))
    box.on_lock_acquired(server.stop)

    box.set_setpoint(0.0)
    box.set_low_high_threshold(-100, 100, SlopePreference.POSITIVE)
    box.lock()
    server.run_forever()  # Wait until lock is acquired, or press Ctrl+C.

Server owns one HTTP client and two SignalR connections: /Hubs/Status for events and /Hubs/Run for setpoint and threshold commands. It uses the server's JSON hub protocol. Entering the context connects both hubs; exiting closes all connections without changing the hardware. There are no reconnects or retries. Server(url, timeout=10.0) sets HTTP timeouts and the time allowed for hub handshake readiness and command completion, not for physical device operations. Supply the server root URL, including any hosting path prefix, without /api.

list_lockboxes() and get_lockbox(id) reuse objects by ID, preserving callbacks while refreshing their name, IP and MAC metadata. Objects are registered for status routing automatically. connect_to_status_hub(box) also registers manually created objects; repeating registration of the same object is harmless.

Keeping an experiment running

Call server.run_forever() inside the with block to keep the script alive. It blocks the calling thread while callbacks continue on the SignalR status receiver thread. Call server.stop() from a callback or another thread to return from the wait. Do not call run_forever() inside a callback.

stop() is idempotent and only stops waiting: it leaves connections open and does not change hardware state. Exiting the context closes connections. Ctrl+C propagates KeyboardInterrupt, so leaving the context also cleans up normally. The wait uses 0.1-second blocking intervals to stay responsive on Windows.

A stop received before run_forever() is remembered. Subsequent waits in the same context return immediately; re-entering the context resets this state. Calling run_forever() outside an active context raises RuntimeError. If either hub closes unexpectedly, the wait raises ConnectionError identifying the hub, including when the disconnect happened before the wait. A recorded disconnect takes precedence over stop(). Intentional context cleanup releases the wait without creating a disconnect error. There is no automatic reconnection.

Commands

All command methods return None after HTTP success or Run hub invocation completion. They do not wait for a status or physical completion. Failed HTTP responses raise Exception(response.text); hub errors use the server-provided error message. Transport errors propagate.

Method Argument convention
set_gain(gain) Input gain, at least 1
set_demodulation_cutoff(frequency) Hz
set_demodulation_phase(phase) Radians
set_modulation_amplitude(amplitude) Integer DAC codes, 0–524287
set_demodulation_amplitude(amplitude) Server amplitude scale, 0–100
set_setpoint(setpoint) Native device setpoint units
set_pid(p, i, d) Native device PID coefficients
set_threshold(threshold) Integer reflection threshold
set_low_high_threshold(low, high, slope_preference=SlopePreference.NONE) Integer error-signal limits; enum values NONE, NEGATIVE, POSITIVE
set_fine_output_code(code), set_coarse_output_code(code) Signed DAC codes, -524287–524287
lock(), unlock() Normal lock; unlock without starting a scan
coarse_scan_only(lower_bound=-524287, upper_bound=524287, period_ms=0) Coarse-only scan; bounds in DAC codes, period in milliseconds
fine_scan_only(lower_bound=-524287, upper_bound=524287, period_ms=0) Fine-only scan; bounds in DAC codes, period in milliseconds
freeze(), unfreeze() Both toggle freeze through the same endpoint
reload_configuration() Apply the server's saved configuration

Setters change runtime values, not saved configuration. Validation is left to the server, except conversion of the slope enum. Slope preference is forwarded as the server enum value (0, 1, or 2). The current firmware ThresholdsEndpoint updates limits but does not read the slope query parameter; actual slope behavior depends on firmware support. This package does not change firmware.

The freeze endpoint may translate a firmware rejection (including not-ready 409) into a server 500. The client reports the response it receives.

Scan-only operation

Both scan methods use the GUI's POST /api/Run/scan endpoint and start a scan without attempting to acquire lock. A zero period leaves the device's scan period unchanged. All three scan parameters are sent so custom bounds are not discarded by the server when the period is omitted in Python.

box.coarse_scan_only()  # Full DAC range, existing scan period.
# Or select a fine-only scan with explicit bounds and timing:
box.fine_scan_only(lower_bound=-1000, upper_bound=1000, period_ms=2000)

These are runtime commands: they return on HTTP success, do not wait for the scan, and do not save configuration. Calling the second method switches the scan mode.

Callbacks

Register callbacks after entering the context. Each registration stores one zero-argument function; registering again replaces it.

Registration Incoming Arduino status
on_coarse_resonance_found(callback) CoarseResonanceFound
on_fine_resonance_found(callback) FineResonanceFound
on_lock_acquired(callback) Locked
on_lock_lost(callback) Unlocked
on_lockbox_frozen(callback) FrozenEnabled
on_lockbox_unfrozen(callback) FrozenDisabled
on_lockbox_freeze_ready(callback) DriftEstimateReady

on_lockbox_recentering raises NotImplementedError because no dedicated status exists. HTTP results never trigger callbacks. Unregistered devices and unrelated statuses are ignored.

on_lock_acquired requires firmware and server versions supporting Locked (status 26). It reports completed acquisition and entry into feedback control; it does not guarantee continued lock stability. Each successful reacquisition emits another event. FineResonanceFound remains a separate event and does not trigger this callback. Initial startup and freeze toggles do not emit Locked.

Functions run directly on the status receiver thread, without arguments or custom exception recovery. They may issue commands using the separate HTTP/Run connections. While a callback runs, it delays further status delivery: do not wait inside it for another callback. There is no replay, ordering contract, or experiment scheduler.

Saved configuration

Fetch an independent snapshot, edit its supported fields, and explicitly save it:

from lockbox import Server, PidControllerType, SlopePreference

with Server("http://lockboxcontrol.internal:5106") as server:
    box = server.get_lockbox(1)
    config = box.get_configuration()
    config.pid.proportional_gain = 2.0
    config.pid.controller_type = PidControllerType.FILTERED_PID
    config.demodulation.set_point = 0.1
    config.lock_thresholds.out_of_lock_threshold = 80
    config.lock_thresholds.ms_without_lock = 250
    config.lock_thresholds.delock_smoothing_constant = 0.25
    config.coarse_scan.scan_min = -2000
    config.coarse_scan.scan_max = 2000
    config.fine_scan.scan_time_ms = 1500
    if config.pdh is not None:
        config.pdh.low_limit = -100.5
        config.pdh.high_limit = 100.25
        config.pdh.slope_preference = SlopePreference.POSITIVE
    box.save_configuration(config)

    # Apply only when wanted:
    box.reload_configuration()

get_configuration() reads GET /api/LockboxConfigurations/arduino/{id} on every call. It returns a LockboxConfiguration with typed, snake_case sections: pid, demodulation, lock_thresholds, pdh, coarse_scan, fine_scan, coarse_refinement, fine_refinement, and top-level modulation_amplitude. An absent optional PDH section is None. Runtime setters do not modify snapshots. PidControllerType has SIMPLE_PI and FILTERED_PID; slope settings reuse SlopePreference.NONE, NEGATIVE, and POSITIVE.

All lock-threshold and coarse/fine scan settings are editable. IDs, device identity, section references, notch_filters, and filter_selections are read-only. Filter selections expose notch_filter_id, enabled, and the associated read-only notch_filter definition when present. Snapshots update existing records only; a missing configuration raises the normal HTTP error. They cannot create records or replace section identities.

save_configuration(config) returns None after these eight sequential writes (seven when PDH is absent):

Saved settings Endpoint
PID coefficients, controller type, derivative cutoff PUT /api/PidConfigurations/{pidId}/persist
Demodulation cutoff, phase, amplitude, setpoint PUT /api/DemodulationConfigurations/{id}/persist
Modulation amplitude PUT /api/PidConfigurations/{pidId}/modulation
All lock-threshold settings PUT /api/LockThresholdSettings/{id}
PDH low/high limits and slope, when present PUT /api/PdhSettings/{id}
Coarse scan limits and time PUT /api/ScanSettings/{coarseId}
Fine scan limits and time PUT /api/ScanSettings/{fineId}
Coarse and fine refinement PUT /api/PidConfigurations/{pidId}/refinement

Saving does not apply hardware settings or modify filters. It sends all supported values, including unchanged ones. Fetch again before editing if another client may have changed the saved configuration; there is no conflict detection.

The snapshot's device/server identity, required linked records, and payloads are checked before any writes, including both required scan records. Nonfinite or unserializable payloads are rejected before writing. PDH limits accept decimal values and are transmitted without integer conversion. Range and ordering validation is performed by the server. PDH is skipped only when both its section and linked ID are absent; a linked ID with a missing section is an error.

Saving is not atomic. A failed response stops subsequent requests and raises Exception(response.text); earlier successful writes remain saved. There is no rollback or retry. Other server-side validation errors can therefore leave a partially saved configuration. The ordinary PID update endpoint is not used because it also applies changes to hardware.

This version requires the server's resource-style PUT endpoints for LockThresholdSettings, PdhSettings, and ScanSettings. There is no fallback to the old integer-only threshold-saving endpoint. Notch-filter editing remains excluded; definitions, selections, and enabled flags are preserved unchanged. Runtime setters still use their existing interfaces; fractional PDH support here applies to saved configuration.

Development

Run python -m pytest and python -m ruff check src tests from this directory. Tests use mock HTTP responses and hub connections; they do not operate hardware.

Metadata

Release files for bouwmeester-lockbox-api 0.1.3

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

Source distribution (sdist)

Source distribution for bouwmeester-lockbox-api 0.1.3
File Size Uploaded
bouwmeester_lockbox_api-0.1.3.tar.gz 32.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for bouwmeester-lockbox-api 0.1.3
File Interpreter ABI Platform
bouwmeester_lockbox_api-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 58.4 kB

Release files / bouwmeester_lockbox_api-0.1.3.tar.gz

Download URL bouwmeester_lockbox_api-0.1.3.tar.gz
Size 32.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9c8c244478347d392c0f97ec19a1e024ca41fe79aa0013ffbc92e3013e43cd8b
BLAKE2b-256 checksum
How to use checksums
9684e6c4ea9ce11af061ebfa459ec0b984a5bca100c9331d530805838a59ac8d
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 Oct 7, 2026.

Transparency log

Release files / bouwmeester_lockbox_api-0.1.3-py3-none-any.whl

Download URL bouwmeester_lockbox_api-0.1.3-py3-none-any.whl
Size 26.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
266f33ad147495b30c8300f5143c4f631d65c1bf2a643324b0bf9900e2d4f968
BLAKE2b-256 checksum
How to use checksums
97a81e0d8d2a7d03298e501ac4fed12f65533965a58f08138f981006d7afbb37
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 Oct 7, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

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