Plesty attoDRY2100XL eNSPIRE server
A Plesty device server for the attoDRY2100XL eNSPIRE controller. It translates a small, explicitly typed Plesty API into the controller's JSON-RPC 2.0 protocol. Laboratory clients can read cryostat telemetry and use guarded temperature and magnet controls over ZeroMQ; arbitrary JSON-RPC forwarding is intentionally not available.
Architecture
The server runs on the computer that is directly connected to eNSPIRE:
laboratory client
| Plesty over ZeroMQ, normally TCP 5555
v
this device server on the control computer
| eNSPIRE JSON-RPC 2.0, normally TCP 9090
v
eNSPIRE controller
The two TCP ports serve different protocols and must not be interchanged:
DEVICE_PORTis the vendor JSON-RPC port on the eNSPIRE controller, normally9090. Only the directly connected control computer needs to reach it.DEVICE_TCP_PORT(or--tcp-port) is the ZeroMQ port that this server binds, normally5555. Approved laboratory clients connect to this port.
The server keeps one persistent controller connection and uses one dedicated worker thread by default. This serializes controller calls and preserves the connection's thread affinity.
Installation
Python 3.12 or newer and uv are required.
git clone https://gitlab.com/plesty/hub/devices/attocube/plesty-attodry2100xl-enspire-server.git
cd plesty-attodry2100xl-enspire-server
uv sync
Copy .env.example to .env, then edit .env on the control computer. The
real .env is ignored by Git and must never be committed or pasted into issue
reports. Process-environment values override .env; command-line connection
arguments override both.
Copy-Item .env.example .env
At minimum, set:
DEVICE_HOST=<controller host>
DEVICE_PORT=9090
DEVICE_TCP_PORT=5555
DEVICE_HOST and DEVICE_PORT select the eNSPIRE endpoint. DEVICE_TCP_PORT
selects the Plesty listener. --host, --port, and --tcp-port are temporary
command-line overrides. --id changes the Plesty device instance ID.
The remaining settings are:
| Variable | Purpose |
|---|---|
ENSPIRE_RUN_HARDWARE_TESTS |
Explicit opt-in for the harmless real-controller pytest; leave 0 for normal test runs. |
ENSPIRE_MAGNET_{X,Y,Z}_CHANNEL |
Vendor channel number for an installed magnet axis. |
ENSPIRE_MAGNET_{X,Y,Z}_MIN_T / MAX_T |
Installation-approved axis bounds in tesla. |
ENSPIRE_MAGNET_COUPLED_VECTOR_MAX_T |
Maximum target-vector magnitude whenever X or Y is nonzero. |
ENSPIRE_MAGNET_TRANSVERSE_ZERO_TOLERANCE_T |
Measured X/Y tolerance for entering or starting Z-only operation. |
ENSPIRE_MAGNET_ZERO_TOLERANCE_T |
Measured absolute-field tolerance used by magnet_off. |
ENSPIRE_MAGNET_ZERO_TIMEOUT_S |
Maximum time magnet_off waits for measured zero. |
ENSPIRE_MAGNET_POLL_INTERVAL_S |
Interval between measured-field checks during magnet_off. |
DEVICE_ADDRESS / PLESTY_REPORT_ARCHIVE |
Optional Plesty field-test settings; not needed to run the server. |
Magnet writes are disabled unless at least one axis has a complete channel,
minimum, and maximum configuration and both global magnet settings are set.
Leave an unavailable axis completely blank. Partial configuration is a startup
error. Installation limits belong in the ignored .env, not in tracked files.
The Z-axis minimum and maximum define the available Z-only range.
Running the server
On the directly connected control computer, run:
uv run python -m plesty.attodry2100xl_enspire_server --tcp-port 5555
The process binds ZeroMQ to all network interfaces. Stop it with Ctrl+C; the
shutdown path releases the ZeroMQ listener and closes the controller socket.
Run --help to see all command-line overrides. Keep the default dedicated-thread
mode. --pool-threading exists for diagnostics and is not recommended for normal
instrument operation.
For unattended operation, use the laboratory's approved Windows service wrapper
or task supervisor such as for example Windows Task Scheduler, WinSW, NSSM, or
another managed service system. Configure its working directory as this
repository, run the same uv run python -m ... command under a dedicated
account that can read the local .env, and send a normal termination before
forcing the process to stop. Do not configure two instances to use the same
controller or ZeroMQ port. Avoid an unbounded automatic restart loop: after an
unexpected exit, an operator must check the controller's current command,
targets, and measured state before the server is returned to service. After a
restart, perform read-only health queries before allowing state-changing calls.
Dedicated account explanation: Use a dedicated Windows user account created specifically for this service—not a personal or administrator account. It should have only the permissions it needs: reading the repository and .env, executing the application, accessing the controller network, and listening on the configured ZeroMQ port.
Client examples
Install plesty-lib on a laboratory client and connect to the control computer's
ZeroMQ port. Parameter keys are grouped with a dot:
from plesty.lib.service import build_client
with build_client("tcp://<control-computer>:5555", timeout=10_000) as client:
sane = client.query("system.is_cryostat_sane")
sample_k = client.query("temperature.sample")
pressure_mbar = client.query("pressure.cryo_in")
field_t = client.query("magnet.channel_0_field")
api = client.describe()
describe() returns the discoverable parameter and operation schema. The public
parameter groups are identity, system, temperature, pressure, and
magnet. Important writable parameters are:
# These calls change hardware state. Use only with operator approval.
client.write("temperature.sample_setpoint", 4.0)
client.write("temperature.sample_ramp_rate", 1.0)
client.set_temperature_ramp_control("sample", True)
client.set_temperature_control("sample", True)
Sample and VTI setpoints accept 0 through 310 K. Ramp rates accept 0 (limit
disabled) or 0.1 through 100 K/min. Writes change stored settings; the named
operations start or stop control. Calls return after controller acknowledgement,
not after thermal equilibrium. A ramp from below 10 K to 310 K can take at least
three hours, so monitor the measured value, setpoint, and control-status
parameters asynchronously.
The magnet API is also explicit:
# Installation limits from the server's .env are enforced before any write.
client.set_magnetic_field(z_t=0.0, y_t=0.0, x_t=0.0)
client.set_field_control(True)
# This blocks until every configured measured field is within zero tolerance.
client.magnet_off(timeout=7_300)
set_magnetic_field validates every axis and the full vector before changing
targets. It does not start field control, although changing a target while field
control is already active can start motion immediately. Starting field control
checks for quench, driven mode, and a safe stored target. Full-scale ramps can
take tens of minutes. The magnet_off client timeout is in seconds and must be
longer than ENSPIRE_MAGNET_ZERO_TIMEOUT_S; the build_client default timeout is
in milliseconds and is too short for this blocking operation.
Fields above the coupled limit require exact-zero X/Y targets and measured X/Y values within
ENSPIRE_MAGNET_TRANSVERSE_ZERO_TOLERANCE_T. Envelope changes are deliberately
non-blocking and must be staged. To enter high-Z operation, first set X/Y to zero
while Z remains in the coupled envelope, wait for transverse telemetry to settle,
then request high Z. To leave it, lower Z with X/Y still zero, wait until measured
field is inside the coupled envelope, then request a transverse target. Premature
crossings fail before any setpoint write.
The other named operation, cancel_current_command(expected_command), cancels
only when the controller's current command exactly matches the supplied value.
This guard prevents a stale client from cancelling a different command.
Security assumptions
The current Plesty ZeroMQ transport does not authenticate clients or encrypt
traffic, and this server exposes hardware-changing operations. Bind it only on a
trusted, access-controlled laboratory network. Use the host firewall to allow
DEVICE_TCP_PORT only from approved client addresses; never expose it to the
public internet or an untrusted wireless network. Keep the eNSPIRE network itself
isolated so only the control computer can reach DEVICE_PORT.
Do not put credentials, controller addresses, serial numbers, installation field limits, or measured laboratory values in commits or shared logs. The server does not expose controller network configuration, firmware updates, calibration, pumps, valves, shutdown, quench reset, service termination, or an unrestricted raw JSON-RPC method.
Validation
Normal validation uses a local fake TCP controller and an in-memory eNSPIRE simulator, and does not touch hardware:
uv run pytest
uv run plesty check
The module is held to the quantum standard, so plesty check runs the full gate
set locally, including the eight-gate device mock pipeline.
host="mock" (or --host mock) replaces the TCP client with the simulator in
mock_controller.py, so the whole stack — read specs, decoders, device-error
validation, state changes — runs with no cryostat and nothing patched out. The
simulated controller is idealised: a control loop settles on its target at once.
It therefore says nothing about ramp behaviour, controller result layouts, or
transport pathologies on the real instrument.
The public parameter surface is declared as data in
plesty/attodry2100xl_enspire_server/schema_param.json and shipped in the wheel;
the vendor method behind each key stays in device.READ_SPECS / WRITE_SPECS,
and a test asserts the two never drift apart.
On Windows consoles that cannot encode the compliance command's Unicode status symbols, run the equivalent check in UTF-8 mode:
uv run python -X utf8 -c "from plesty.sdk.cli import app; app()" check
The real-controller pytest remains skipped unless
ENSPIRE_RUN_HARDWARE_TESTS=1 is explicitly present. That opt-in test is
read-only; do not use live state-changing calls as a generic release check.
Troubleshooting
- Startup says
DEVICE_HOSTorDEVICE_PORTis missing: create.envin the repository root or provide--hostand--port. Check spelling and the process account's working directory. - Startup reports invalid magnet safety configuration: define channel,
minimum, and maximum together for every installed axis plus
ENSPIRE_MAGNET_COUPLED_VECTOR_MAX_TandENSPIRE_MAGNET_TRANSVERSE_ZERO_TOLERANCE_T, or blank all magnet settings to disable writes. - Controller connection fails: verify the eNSPIRE Web GUI from the control computer, then verify that its JSON-RPC port is reachable. Do not substitute the ZeroMQ port for the vendor port.
- A remote client cannot connect: confirm the server is running, the client
uses the control computer rather than the eNSPIRE address, the ZeroMQ port
matches
DEVICE_TCP_PORT, and the host firewall permits that client. - A request times out: ordinary reads should finish quickly. For long ramps,
setters return immediately and clients should poll telemetry. Only
magnet_offwaits for a physical target; give it a timeout longer than the configured zero timeout and continue monitoring if it reports a timeout. - Field control is rejected: inspect quench state, driven mode, configured axes, stored targets, per-axis bounds, and vector magnitude. Do not weaken the local limits to bypass a controller condition.
- A ramp is slower than expected: read
read_ramp_rates(channel). The rate is not one number per magnet — it is a table of current zones, and the rate changes abruptly between them. The bounds are in amperes and the rates in A/s; converting to tesla needs the magnet's amps-per-tesla, which this API does not supply anywhere, so it has to come from the magnet's specification or from timing a known ramp.set_ramp_ratewill not exceed a zone's factory rate and will not move a zone boundary. - The controller drops or returns malformed JSON-RPC: the server closes the vendor socket and reports the failure, then reopens it on the next request — a stalled reply costs one call, not the session. Never blindly retry a state-changing call whose delivery is uncertain: the failed one may already have executed.
- Replies stall while a ramp starts: a control computer that does not answer
within
ENSPIRE_REQUEST_TIMEOUT_S(30 s by default) drops the socket, because a late reply would desync every later request. Raise that budget for an installation that stalls longer; it bounds one round trip, never a ramp. - The ZeroMQ port is already in use: stop the previous server cleanly or choose one approved alternate port and update clients. Never run competing instances against the same controller.
Scope
This first release supports enumerated telemetry, sample/VTI temperature settings and controls, guarded current-command cancellation, installation-bounded field targets and control, and verified magnet-off. Go-to-base and sample exchange are intentionally absent. Refer to the generated Plesty parameter and function pages for the complete machine-readable API.
Metadata
Release files for plesty-attodry2100xl-enspire-server 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| plesty_attodry2100xl_enspire_server-0.1.0.tar.gz | 171.2 kB | Without provenance | Details for plesty_attodry2100xl_enspire_server-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| plesty_attodry2100xl_enspire_server-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for plesty_attodry2100xl_enspire_server-0.1.0-py3-none-any.whl |
Total release size: 209.2 kB
Release files / plesty_attodry2100xl_enspire_server-0.1.0.tar.gz
| Download URL | plesty_attodry2100xl_enspire_server-0.1.0.tar.gz |
|---|---|
| Size | 171.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5569a15560de5f29b92d1d2b384f8cbac78aad6e1db6ea82fe026be955207807
|
|
BLAKE2b-256 checksum How to use checksums |
7eccc77e8457d535b81b2cc9a2351745971cf46c5096f11fb9179232721eb078
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / plesty_attodry2100xl_enspire_server-0.1.0-py3-none-any.whl
| Download URL | plesty_attodry2100xl_enspire_server-0.1.0-py3-none-any.whl |
|---|---|
| Size | 38.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b371af2129c66f5211ca50efaf86795b70ecfa9b280331295e902e77af685350
|
|
BLAKE2b-256 checksum How to use checksums |
a658028ea5e6773ec9f68bc829d43446baf4865b08c36e3aaf2bb39a0fa405b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for plesty-attodry2100xl-enspire-server 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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.