Skip to content

Quick Start Guide

Prerequisites

  1. Network access to Fermilab (on-site, VPN, or tunnel)
  2. Python 3.10+
  3. For write operations: Valid Kerberos credentials

Installation

pip install pacsys             # reads (DPM/gRPC/ACL) and gRPC token writes
pip install pacsys[kerberos]   # + Kerberos writes, DMQ backend, SSH
pip install pacsys[all]        # kerberos + parquet + mcp

Development Install

git clone https://github.com/fast-iota/pacsys.git
cd pacsys
pip install -e ".[dev]"

Read a Device

import pacsys

# Simple value
temperature = pacsys.read("M:OUTTMP")
print(f"Temperature: {temperature}")

# With metadata
reading = pacsys.get("M:OUTTMP")
if reading.ok:
    print(f"{reading.value} {reading.units}")  # e.g. "72.5 DegF"

# Multiple devices at once
readings = pacsys.get_many(["M:OUTTMP", "G:AMANDA"])
for r in readings:
    if r.ok:
        print(f"{r.name}: {r.value}")
import pacsys.aio as pacsys

# Simple value
temperature = await pacsys.read("M:OUTTMP")
print(f"Temperature: {temperature}")

# With metadata
reading = await pacsys.get("M:OUTTMP")
if reading.ok:
    print(f"{reading.value} {reading.units}")  # e.g. "72.5 DegF"

# Multiple devices at once
readings = await pacsys.get_many(["M:OUTTMP", "G:AMANDA"])
for r in readings:
    if r.ok:
        print(f"{r.name}: {r.value}")

read() raises DeviceError when no usable value is returned. get() returns a Reading; check reading.ok before using its value, and use is_error or is_warning to classify a nonzero status.

Reading Guide - all property types, value types, error handling


Stream Data

import pacsys

with pacsys.subscribe(["M:OUTTMP@p,1000"]) as stream:
    for reading, handle in stream.readings(timeout=30):
        print(f"{reading.name}: {reading.value}")
        if reading.value > 100:
            stream.stop()
import pacsys.aio as pacsys

stream = await pacsys.subscribe(["M:OUTTMP@p,1000"])
async for reading, handle in stream.readings(timeout=30):
    print(f"{reading.name}: {reading.value}")
    if reading.value > 100:
        await handle.stop()

The @p,1000 means "send data every 1000 milliseconds". For streaming, a repeating event type must be specified.

Or use the Device API:

import pacsys

dev = pacsys.Device("M:OUTTMP@p,1000")
with dev.subscribe() as stream:
    for reading, _ in stream.readings(timeout=30):
        print(reading.value)
from pacsys.aio import AsyncDevice

dev = AsyncDevice("M:OUTTMP@p,1000")
stream = await dev.subscribe()
async for reading, _ in stream.readings(timeout=30):
    print(reading.value)

Streaming Guide - callbacks, CombinedStream, error handling


Write a Value

import pacsys

auth = pacsys.KerberosAuth()  # requires kinit beforehand

with pacsys.dpm(auth=auth, role="testing") as backend:
    result = backend.write("Z:ACLTST", 45.0)
    if result.success:
        print("Write successful")
from pacsys import KerberosAuth
import pacsys.aio as aio

auth = KerberosAuth()  # requires kinit beforehand

async with aio.dpm(auth=auth, role="testing") as backend:
    result = await backend.write("Z:ACLTST", 45.0)
    if result.success:
        print("Write successful")

PACSys automatically converts READING to SETTING and STATUS to CONTROL when writing.

Writing Guide - all value types, batch writes, alarm config, device control


Choosing a Backend

Backend Factory Protocol Auth Read Write Stream
DPM/HTTP pacsys.dpm() TCP + SDD Kerberos (writes)
DPM/gRPC pacsys.grpc() TCP + gRPC JWT (writes)
DMQ pacsys.dmq() TCP + AMQP + SDD Kerberos (all)
ACL/HTTP pacsys.acl() TCP + HTTP/CGI None - -
# Default (DPM/HTTP, implicit)
value = pacsys.read("M:OUTTMP")

# Explicit backend
with pacsys.dmq(auth=pacsys.KerberosAuth()) as backend:
    value = backend.read("M:OUTTMP")

Use configure() to change the default backend and settings for all top-level calls:

pacsys.configure(backend="grpc", default_timeout=10.0)

# Now all top-level calls use gRPC
value = pacsys.read("M:OUTTMP")

configure() can be called at any time — if a backend is already running, it will be automatically shut down and replaced on the next operation. See the full list of options in the API Reference. Configuration is validated first, so a failed configure() call leaves the active backend unchanged.

Backends - architecture, configuration, comparison


Cleanup

Resources are automatically cleaned up when the Python process exits. No explicit shutdown() call is needed for normal usage:

value = pacsys.read("M:OUTTMP")
# That's it - connections close automatically at exit

Use shutdown() only if you need to explicitly close connections mid-process (e.g., before exiting a long-running script). configure() handles shutdown automatically when reconfiguring.


Environment Variables

Variable Description Default
PACSYS_DPM_HOST DPM proxy hostname acsys-proxy.fnal.gov
PACSYS_DPM_PORT DPM proxy port 6802
PACSYS_TIMEOUT Default timeout (seconds) 5.0
PACSYS_GRPC_HOST gRPC DAQ server hostname dce08.fnal.gov
PACSYS_GRPC_PORT gRPC DAQ server port 50051
PACSYS_JWT_TOKEN JWT token for gRPC auth -
PACSYS_DMQ_HOST RabbitMQ broker host appsrv2.fnal.gov
PACSYS_DMQ_PORT RabbitMQ broker port 5672
PACSYS_POOL_SIZE DPM connection pool size 4
PACSYS_DEVDB_HOST DevDB gRPC hostname ad-services.fnal.gov/services.devdb
PACSYS_DEVDB_PORT DevDB gRPC port 6802
PACSYS_ACL_URL ACL CGI base URL https://www-bd.fnal.gov/cgi-bin/acl.pl

Next Steps