Release notes¶
0.3.0 (2026-10-05)¶
This release adds typed async devices, monitoring health checks, ACNET UDP support, direct GETS32/RETDAT front-end reads, and Booster skew-quadrupole ramps. It also improves write verification, subscription shutdown, and timeout handling.
Upgrading from 0.2.2¶
Installation¶
grpcio >= 1.80.0 and Paramiko >= 3.2, < 5 are now required. Kerberos and SSH dependencies remain part of the default installation:
pip install --upgrade pacsys # Includes Kerberos and SSH dependencies
pip install --upgrade "pacsys[all]" # + Parquet and MCP dependencies
Kerberos credentials are still required for DPM writes, all DMQ operations, and SSH with GSSAPI authentication.
API replacements¶
| Removed API | Replacement |
|---|---|
pacsys.ssh(...) |
pacsys.SSHClient(...) |
pacsys.devdb(...) |
pacsys.DevDBClient(...) |
pacsys.supervised(...) |
pacsys.SupervisedServer(...) |
pacsys.acnet.AcnetConnection |
AcnetConnectionTCP or AcnetConnectionUDP, imported from pacsys.acnet |
SlewRatePolicy and SlewLimit |
Implement a custom Policy if slew limiting is required |
pacsys.ssh, pacsys.devdb, and pacsys.supervised now resolve to public submodules. pacsys.dpm(), pacsys.dpm_http(), pacsys.grpc(), pacsys.dmq(), and pacsys.acl() remain backend factories.
Replace AcnetConnection("MYTASK") with AcnetConnectionUDP(name="MYTASK"): the first positional argument is now host. UDP requires a local acnetd; use AcnetConnectionTCP for remote connections.
Scaler.from_property_info(prop, input_len) now requires the raw data width in bytes (1, 2, or 4). PropertyInfo does not provide it.
The optional MCP server now requires mcp >= 2.2, < 3. create_server() now returns an MCPServer; direct callers must pass the SSE port to server.run(transport="sse", port=...).
Endpoints and configuration¶
- gRPC DAQ: the default is now
dce08.fnal.gov:50051. Sethost/portorPACSYS_GRPC_HOST/PACSYS_GRPC_PORTfor a local proxy or tunnel. - DevDB: the default is now
ad-services.fnal.gov:443with TLS. Supply a hostname without a URL path. For plaintext tunnels, passtls=Falseor setPACSYS_DEVDB_TLS=0. The positional order remainsDevDBClient(host, port, timeout, cache_ttl);tlsis keyword-only. - DPM factories:
dpm(),dpm_http(), andaio.dpm()usePACSYS_DPM_HOST,PACSYS_DPM_PORT,PACSYS_POOL_SIZE, andPACSYS_TIMEOUTfor omitted arguments. - Backend binding: module-level operations honor a
Device's backend. Batches must contain devices bound to one backend or entirely unbound targets; mixing bound devices with bare DRFs or another backend raises before I/O. - Validation: invalid configuration is rejected before replacing the backend or acquiring credentials. Malformed ACNET DRFs, invalid fields and ranges, and out-of-range timing values now raise.
VOLTS/COMMONnormalize toPRIMARY/SCALED, preserving write units. - Device identity: canonical ACNET names are uppercase, so case variants share identity and fake-backend data. EPICS names retain case.
digital_status()rejects non-ACNET devices before lookup.
Writes and policies¶
WriteResult.confirmed means the write succeeded and any requested verification did not fail. It does not imply verification was requested: verified=None means no readback check ran; verified=True means readback matched.
Module-level pacsys.write(..., BasicControl.ON) and BasicControl values in mixed write_ramps() settings target CONTROL. For Device and AsyncDevice, use control() or shortcuts such as on() and reset(); write(BasicControl.ON) raises TypeError.
For range-limited devices, ValueRangePolicy rejects raw bytes and RAW/PRIMARY/VOLTS writes without an explicit allow_raw exemption. Matching ranges are intersected. CONTROL commands follow device access policies. Invalid MCP configuration fails at startup, and audit failures block writes. MCP tools reject device-index aliases (0:1234, #:1234). See supervised mode and MCP configuration.
acput --verify preserves the requested setting, basic control, or single alarm field. Unsupported targets, including whole alarm blocks and ACNET RAW fields, reject the entire command before any write.
DMQ scalar integer settings must fit signed 32-bit values; out-of-range values raise before I/O. DPM text writes reject non-Latin-1 characters before connection setup. gRPC writes reject reserved DPM list directives per item, preserving batch result alignment.
Results and experiment workflows¶
- CSV:
CsvWriterappendsfacility_code,error_code, andmessageto the existing four columns and always writes UTF-8. Update consumers requiring an exact header or column count. - Parquet:
ParquetWriterinsertsint_value(int64) aftervalueand appendsfacility_code(int16) andmessage(string) aftercycle. Integer and boolean scalars are stored inint_valuewithvaluenull; coalesce the two columns. Update exact-schema and positional consumers; mixed old/new datasets may need explicit schema unification. - Arrays: NumPy arrays in
Reading.valueandWriteResult.readbackare read-only. Construction freezes caller-owned owning arrays in place; views are copied. Use.copy()for mutable data. - Data loss:
DataLogger.dropped_countcounts lost readings, andfailedindicates data loss. Dropped readings causestop()and normal context-manager exit to raiseRuntimeError. - Scans: restoration failure after a completed scan raises
ScanRestoreErrorwith the collected result; errors during the scan remain primary. Resolved read DRFs are deduplicated in first-seen order; usereadings_per_stepfor repeated sampling. ACNET write targets must use READING or SETTING; EPICS PVs remain supported. - Ramps: the default is 15 physical slots (indices 0–14); subclasses can override
MAX_SLOTS. - Fake backends: partial ranged writes fail unless a whole value was seeded with
set_reading(), even with a configured successful write result. Empty subscriptions raiseValueError. - Low-level ACNET:
FTPStreambatches are keyed by position in the device list passed tostart_continuous(), not by device index.DPMAcnetreturns array data as NumPy arrays instead of lists. - CLI:
acget --format jsonrepresents raw bytes as base64 strings.
Additions and improvements¶
- Async devices:
AsyncScalarDevice,AsyncArrayDevice,AsyncTextDevice, andAsyncDevice.await_next(). Fluent methods preserve subclasses in sync and async APIs. - Batch reads: sync and async backends expose
read_many(). Unusable readings raiseReadError, retaining the batch readings. - Monitoring:
Monitor.health(), stale/recovery callbacks, per-element array statistics, dictionary export, and result channel lookup and iteration. - Scans:
scan(values=...)accepts generators and NumPy arrays. - Results and errors:
Reading,WriteResult, andDeviceMetasupport dictionary round trips and value-based equality, preserving array types.PacsysErroris a common exception base;ReadErrorandDeviceErrorpreserve details across processes. - Front-end reads:
Gets32ClientandRetdatClientsupport one-shot and streaming raw reads. See GETS32 and RETDAT. - ACNET and ramps: FTPMAN snapshots over local UDP, and
BoosterSQRamp/BoosterSQRampGroup(thanks M. Balcewicz for reporting).RampGroupaccepts numeric nested lists; summaries count points with nonzero delta time as active. - Ramp serialization:
RampandRampGroupsupport JSON-safeto_dict()/from_dict()round trips that restore built-in subclasses. - Mixed ramp writes:
write_ramps()accepts ramps, groups, and(drf, value)settings in one backend call, returning results in flattened input order. Batches are not atomic; check each result. - Active-span ramp writes:
write_mode="active"sends only the active nonzero subset. Full writes remain the default and are required to clear shortened or empty ramps.
Correctness and reliability¶
Backends and transport¶
- gRPC keepalive: five-minute pings only during active RPCs avoid excessive-ping disconnects on quiet streams under the default server policy. Silent connection loss can take about five minutes plus ten seconds to detect; ordinary RPC timeouts are unchanged.
- gRPC reads and values: one-shot reads normalize omitted and
@Uevents to@I, preserving explicit non-default events and historical requests. Malformed DRFs reject the batch before the Read RPC. Warnings carrying data remain usable in batch and logger aggregation; uneven logger array records fail only the affected device. Status readings preserve display text, and writes support one-dimensional NumPy string arrays. - DPM deadlines and errors: reads and writes enforce end-to-end deadlines. Once sending settings starts, connection failures are not retried; missing acknowledgements report "outcome unknown" because settings may have executed. Authentication and setup failures preserve server status codes.
- DPM authentication: async Kerberos initialization keeps the event loop responsive and stops waiting at the authentication deadline.
- DPM history and text: repeated LOGGER/LOGGERDURATION reads retrieve the requested window reliably (thanks R. Santucci for reporting). Device read helpers preserve logger events; historical arrays retain rows and microsecond timestamps. DPM and DMQ preserve non-ASCII units and descriptions.
- DMQ lifecycle: startup failures, missing heartbeats, and connection closure surface promptly as errors. Unexpected subscription closures identify affected devices in logs; stopping a subscription preserves the shared connection.
- DMQ writes: initialization honors the caller's timeout. Queued writes survive idle cleanup, and aborted batches do not send queued settings later.
- Low-level ACNET: handshake and read failures preserve server status codes; setup timeouts consistently raise
AcnetUnavailableErroron Python 3.10. FTP streams and event-armed snapshots tolerate empty heartbeats and preserve front-end rejection status. - ACL reads: raw reads return correct little-endian bytes; basic-status fields parse as booleans and preserve clock events and delays.
- SSH: private keys load by type, channel setup and command start are bounded by
timeout, and failed interactive sessions are cleaned up. ACL script cleanup reports transport failures without masking the original result or error; see SSH error handling.
Streaming and cleanup¶
- Async DPM and fake backend iterator subscriptions report stream failures through both
on_errorandreadings(). Error notifications are retained even when the reading queue is full. - Caller-initiated stop suppresses queued reading callbacks, including remaining duplicate DMQ deliveries. Iterators honor timeouts even with queued data.
watch,read_fresh, andDataLoggercontinue through recoverable subscription errors. Terminal logger failures setfailed/last_errorand raise fromstop()after writer cleanup.- Monitor ignores recoverable retry notifications. After terminal stream failure, it continues reporting stale channels and notifies waiting readers.
- gRPC, DPM HTTP, and synchronous ACNET deliver results and errors completed during shutdown to waiting callers. Interrupted cleanup releases pooled connections and cancels requests.
- Supervised streams support timed arrays, basic-status maps, and one-dimensional NumPy string arrays; individual encoding failures do not abort the batch. Shutdown gives pending cleanup time to finish and logs unfinished cleanup. The caller retains ownership of the backend.
Devices and data¶
- Write verification: sync and async readback
ReadErrorcounts as a failed attempt, allowing remaining configured attempts after a successful write. - Logging: CSV and Parquet writers accept historical timed arrays. CSV batch conversion finishes before rows are written, and a batch that fails mid-write is removed, so
DataLoggerretries never duplicate rows. - Diagnostics: CLI monitoring and MCP serialization preserve status diagnostics.
- Scaling: transforms 48 and 68 return NaN for fractional powers of negative bases, allowing array processing to continue. Analytic inverse transforms no longer require limits needed only for numerical searches.
- Alarm edits: server-scaled limits are preserved, and backend-provided readings stay unchanged even when
modify()raises. Blocks distinguish nominal and percent tolerance; conflicting engineering-limit and format/mode changes are rejected before writing.AnalogAlarm.write()raisesValueErrorforminimum/maximumedits instead of silently dropping them; usemodify()for limits.