| name | ask-happi |
| description | Query the PCDS happi device database for LCLS beamline components. Use when asked about device names, PV prefixes, z-positions, beamline maps, mirrors, YAGs, slits, attenuators, or any hardware device registered in the PCDS happi database. |
ask-happi Skill
You answer questions about LCLS beamline hardware by querying the happi device database —
the PCDS registry of all beamline components (mirrors, YAGs, slits, imagers, attenuators, valves, etc.).
Access Method
The happi database is a JSON flat file. It is not accessible from S3DF directly.
| Resource | Path |
|---|
| Database file | /cds/group/pcds/pyps/apps/hutch-python/device_config/db.json |
| Access via | SSH to psdev |
| Python required | System Python on psdev (Python 3.9, stdlib only — no happi package needed, just json) |
/sdf is NOT mounted on psdev. Scripts must be piped via stdin:
ssh -o ConnectTimeout=10 psdev "python3 -" << 'PYEOF'
PYEOF
Always use the -o ConnectTimeout=10 SSH flag and set a 30 s bash timeout.
For the general /cds bridge pattern, other accessible paths, and EPICS CA access, see the
cds-bridge skill.
Database Schema
Each entry in the JSON is a flat dict keyed by device name. Key fields:
| Field | Type | Notes |
|---|
name | str | Unique device name (e.g. im3l0, mr1l0_homs, mfx_dg1_pim) |
prefix | str | EPICS PV prefix |
beamline | str | Uppercase (e.g. MFX, LFE, KFE, HXX, FEE, RIX, CXI) |
device_class | str | Python class path (e.g. pcdsdevices.pim.XPIM) |
z | float|None | Position along beamline in metres |
stand | str | Optical stand label (e.g. DG1, DG2) |
location_group | str | Grouping label |
active | bool | Whether device is currently active |
lightpath | bool | Whether device is on the photon lightpath |
functional_group | str | Functional category |
documentation | str | Free-text description |
kwargs | dict | Extra constructor kwargs |
ioc_name, ioc_hutch, ioc_location | str | IOC info |
Known beamlines in the DB: MFX, LFE, KFE, HXX, FEE, RIX, CXI, MEC, TMO, QRIX, ICL, LAS, FTL, TIMING, and others.
Key Device Classes (pcdsdevices)
| Class (suffix) | What it is |
|---|
pim.XPIM, pim.PPM, pim.PIMWithLED | YAG / imager screens |
mirror.XOffsetMirror2D4PState | HOMS offset mirror |
slits.PowerSlits, slits.BeckhoffSlits | Slits |
attenuator.FeeAtt, attenuator.FEESolidAttenuator | Attenuators |
valve.VGC, valve.VFS, valve.PPSStopper | Valves / stoppers |
spectrometer.DCCMono | DCCM / monochromator |
ipm.BeckhoffIntensityProfileTarget | IPM / intensity monitor |
transfocate.Transfocator | Transfocator / TFS lens stack |
MFX-Area Beamline Map (from happi, sorted by z)
Key devices along the LFE → MFX path (as of 2026-06-24):
| z (m) | name | beamline | type |
|---|
| 699.60 | im1l0 | LFE | XPIM (imager) |
| 724.43 | sl1l0 | LFE | PowerSlits |
| 729.30 | at1l0 | FEE | FeeAtt (attenuator) |
| 734.50 | at2l0 | LFE | FEESolidAttenuator |
| 736.00 | im2l0 | LFE | XPIM (imager) |
| 737.32 | sp1l0 | LFE | DCCMono (DCCM) |
| 740.00 | mr1l0_homs | LFE | XOffsetMirror2D4PState |
| 746.00 | im3l0 | LFE | IM3L0 (imager) — undulator pointing handoff |
| 747.29 | mr2l0_homs | LFE | XOffsetMirrorStateCool |
| 752.50 | em3l0 | LFE | IPM |
| 753.60 | im4l0 | LFE | XPIM (imager) |
| 755.26 | st1l0_pps | LFE | PPSStopper |
| 774.63 | im5l0 | LFE | PPM (imager) |
| 774.63 | sl3l0 | LFE | BeckhoffSlits |
| 984.85 | mfx_dia_pim | MFX | PIMWithFocus |
| 1019.79 | mfx_dg1_pim | MFX | PIMWithLED |
| 1021.74 | mfx_dg2_pim | MFX | PIMWithBoth |
Alignment policy (per Matt Seaberg, 2026-06-24): Use undulator pointing in both X and Y at im3l0 and upstream. For anything downstream of im3l0: use mirrors for horizontal (X), undulator pointing still OK for vertical (Y).
Workflow
- Understand the question — device name lookup, prefix, z-position, beamline map, or filtering by type?
- Check the embedded map above — for common MFX-area devices, the answer may already be here.
- If a live query is needed, pipe a Python script to psdev via SSH stdin (see template below).
- For live EPICS status — use
lfe-console via psdev jump (see EPICS Status section below).
- Present results clearly — sort by z when showing a spatial map; highlight the most relevant field.
EPICS Live Status — LFE/KFE Devices
Key Finding (2026-07-06)
LFE device PVs are not reachable via Channel Access from S3DF or from mfx-control/mfx-monitor
directly. The correct path is:
S3DF → psdev (jump) → lfe-console (172.21.88.22) → LFE IOCs
lfe-console is on the LFE gateway subnet (172.21.88.x) and can reach IOCs via the LFE broadcast
172.21.91.255 and KFE broadcast 172.21.95.255.
EPICS Access Pattern for LFE/KFE Devices
ssh -o ConnectTimeout=10 -J psdev lfe-console "source /reg/g/pcds/pyps/conda/pcds_conda 2>/dev/null; \
export EPICS_CA_AUTO_ADDR_LIST=NO; \
export EPICS_CA_ADDR_LIST='172.21.91.255 172.21.95.255'; \
python3 -W ignore << 'PYEOF'
import epics, time
pv = epics.PV('IM1L0:XTES:MMS:STATE:GET_RBV', auto_monitor=False)
time.sleep(4)
print(pv.connected, pv.get(as_string=True))
PYEOF
"
- pcds_conda is at
/reg/g/pcds/pyps/conda/pcds_conda on lfe-console
- Python 3.12 with
epics (pyepics) and pcdsdevices available after sourcing pcds_conda
- Wait 4–6 seconds after creating PV objects before reading — CA connect takes time
auto_monitor=False on PVs avoids callback threading issues in non-interactive sessions
EPICS Broadcast Addresses (from gateway config)
| Network | Broadcast | What it reaches |
|---|
| LFE | 172.21.91.255 | LFE imagers, valves, attenuator, PPS stopper, DCCM, IPM |
| KFE | 172.21.95.255 | KFE imagers, slits (sl1k0), valves |
| MFX | 172.21.75.255 | MFX hutch devices (use from mfx-control) |
Source: /cds/group/pcds/gateway/scripts/epicscagp
Correct PV Suffix Map by Device Class (verified 2026-07-06)
| pcdsdevices class | Status PV | Notes |
|---|
XPIM, PPM, IM3L0 | {prefix}:MMS:STATE:GET_RBV | Returns enum string e.g. OUT, YAG, RETICLE |
VGC | {prefix}:STATE_RBV + {prefix}:POS_STATE_RBV | state=e.g. Differential Pressure, pos=OPEN/CLOSED |
VFS | {prefix}:STATE_RBV + {prefix}:POS_STATE_RBV | Same as VGC; state=AT Vacuum when good |
PPSStopper | happi prefix PV directly | e.g. PPS:NEH1:1:ST1L0INSUM → NOT_IN = open (beam clear) |
FEESolidAttenuator | AT2L0:CALC:SYS:ActualTransmission_RBV | Returns float 0.0–1.0 |
DCCMono | {prefix}:MMS:STATE:GET_RBV | Returns OUT when not in beam |
BeckhoffIntensityProfileTarget | {prefix}:MMS:STATE:GET_RBV | Returns TARGET1, OUT, etc. |
GEM | {prefix}:MMS:STATE:GET_RBV | Same TwinCAT state pattern |
❌ NOT reachable from lfe-console (different IOC subnet — Beckhoff PLCs on separate network):
PowerSlits (SL1L0, SL1K0, SL2K0), BeckhoffSlits (SL3L0)
XOffsetMirror2D4PState / XOffsetMirrorStateCool (MR1L0:HOMS, MR2L0:HOMS)
GEM (EM1L0:GEM, EM2L0:GEM) — XGMDs
These devices did not respond to CA on any tried subnet. They may require MCC access or a dedicated
controls machine with Beckhoff network routing.
Upstream Status Script
A ready-to-run script is saved at:
skills/ask-happi/scripts/upstream_status.py
Run it as:
ssh -o ConnectTimeout=10 -J psdev lfe-console \
"source /reg/g/pcds/pyps/conda/pcds_conda 2>/dev/null; \
export EPICS_CA_AUTO_ADDR_LIST=NO; \
export EPICS_CA_ADDR_LIST='172.21.91.255 172.21.95.255'; \
python3 -W ignore -" \
< /sdf/home/f/fpoitevi/.claude/skills/ask-happi/scripts/upstream_status.py
Note: /sdf is NOT mounted on lfe-console — pipe the script via stdin redirection (-), not by path.
Query Templates
Find devices by beamline and keyword
import json
DB = "/cds/group/pcds/pyps/apps/hutch-python/device_config/db.json"
with open(DB) as f:
db = json.load(f)
results = [
v for v in db.values()
if (v.get("beamline") or "").upper() == "MFX"
and "yag" in (v.get("device_class") or "").lower()
]
results.sort(key=lambda v: float(v["z"]) if v.get("z") not in (None, "", "None") else float("inf"))
for v in results:
print(v["name"], v.get("z"), v.get("prefix"), v.get("device_class"))
Look up a device by name
import json
DB = "/cds/group/pcds/pyps/apps/hutch-python/device_config/db.json"
with open(DB) as f:
db = json.load(f)
name = "im3l0"
v = db.get(name) or next((x for x in db.values() if x.get("name") == name), None)
if v:
for k, val in sorted(v.items()):
if val not in (None, "", [], {}):
print(f"{k}: {val}")
List all devices between two z positions
import json
DB = "/cds/group/pcds/pyps/apps/hutch-python/device_config/db.json"
Z_MIN, Z_MAX = 735.0, 760.0
with open(DB) as f:
db = json.load(f)
results = []
for v in db.values():
z = v.get("z")
try:
zf = float(z)
except (TypeError, ValueError):
continue
if Z_MIN <= zf <= Z_MAX:
results.append(v)
results.sort(key=lambda v: float(v["z"]))
for v in results:
print("{:8.2f} {:<35} {:<8} {}".format(float(v["z"]), v["name"], v.get("beamline",""), v.get("device_class","")))
Gotchas
- Beamline field is uppercase — search with
.upper(), not lowercase.
z can be None, "", or the string "None" — always guard before calling float().
- DB keys ≠
name field — the top-level dict key and v["name"] are usually the same but not always. Search by v["name"] for reliability.
/sdf not mounted on psdev — never try to run a script from /sdf/... on psdev; always pipe via stdin.
/sdf not mounted on lfe-console — same constraint; pipe scripts via stdin redirection.
- SSH timeout — use
-o ConnectTimeout=10; set bash timeout to 30 s.
mfx_tfs has z = -1.0 — the transfocator is stored with a placeholder z; treat as "in hutch, position TBD".
- PV suffix
MMS:STATE_RBV is WRONG — the correct suffix for TwinCAT state devices is MMS:STATE:GET_RBV (note the extra :GET_RBV level). STATE_RBV will not connect.
- EPICS CA does NOT work from psdev — psdev is a file-access gateway only; it is not on any controls subnet. Always use
lfe-console for LFE/KFE PV reads.
- EPICS CA does NOT work from mfx-monitor/mfx-control for LFE PVs — mfx machines can only reach MFX-subnet IOCs. LFE devices require
lfe-console.
- pcds_conda does NOT set ADDR_LIST on lfe-console — after sourcing, you must manually set
EPICS_CA_AUTO_ADDR_LIST=NO and EPICS_CA_ADDR_LIST='172.21.91.255 172.21.95.255'.
- CA connect needs time — always
time.sleep(4) or more after creating epics.PV() objects before reading values. caget with -w 5 or pyepics with timeout=5 is the minimum.
- Slits and HOMS mirrors are unreachable via this path — PowerSlits (SL1L0 etc.) and XOffsetMirror (MR1L0:HOMS, MR2L0:HOMS) did not respond on any tried broadcast. Their IOCs are likely on a Beckhoff/TwinCAT network not accessible from lfe-console.
- at1l0 (FeeAtt) is INACTIVE in happi — skip it; at2l0 (FEESolidAttenuator) is the active attenuator.
- PPSStopper prefix in happi is the full PV name — for
st1l0_pps, the happi prefix PPS:NEH1:1:ST1L0INSUM is the PV itself. NOT_IN means stopper is open (beam clear); IN means beam blocked.