# dlsPLC migration reference
This page documents which older DLS vacuum/interlock support modules are
obsolete and how their builder XML entities map to `dlsPLC` equivalents in
`ioc.yaml`.
`builder2ibek xml2yaml` performs most of these translations **automatically**.
The sections below note where automatic conversion is implemented, where it is
incomplete, and the argument transformations applied.
**Origin**: the authoritative migration guide is the DLS Confluence page
*"Upgrade a Vacuum IOC to use dlsPLC"* (internal access required). This page
captures the same rules in a form usable without Confluence access.
---
## Why these modules are obsolete
The older modules (`vacuumValve`, `vacuumPump`, `interlock`, `temperature`,
`flow`) each shipped separate EPICS templates for the two PLC communication
protocols — FINS and Hostlink — doubling maintenance burden. `dlsPLC` provides
a single protocol-independent layer 3, so only one device template per device
type is needed.
---
## Build requirements for dlsPLC IOCs
Add to `configure/RELEASE`:
```
calc = ...
busy = ...
```
Add to the DBD file:
```
calcSupport.dbd
busyRecord.dbd
```
If migrating from **Hostlink** to FINS, in the startup script replace:
```
HostlinkInterposeInit("ty_40_0")
```
with:
```
finsDEVInit("VLVCC1", "ty_40_0")
```
and update all references to `ty_40_0` → `VLVCC1`.
---
## Module replacement table
### vacuumValve module
| Old XML type | dlsPLC replacement | Auto-converted? | Notes |
|---|---|---|---|
| `vacuumValve.vacuumValveRead` | `dlsPLC.read100` | **Yes** | `century=0` |
| `vacuumValve.vacuumValveRead2` | `dlsPLC.read100` | **No** — raises error | `century` = block number (e.g. `2` reads DM200–299); see below |
| `vacuumValve.vacuumValve` | `dlsPLC.vacValve` | **Yes** | `valve×10 → addr`; `crate → vlvcc` (full crate name) |
| `vacuumValve.vacuumValve_callback` | `dlsPLC.vacValve` | **Yes** | Same as above |
| `vacuumValve.vacuumValveBistable` | `dlsPLC.vacValveBistable` | No | `valve×10 → addr` |
| `vacuumValve.vacuumValveGroup` | `dlsPLC.vacValveGroup` | **Yes** | Arguments identical |
| `vacuumValve.vacuumValveReadOnly` | `dlsPLC.vacValveReadOnly` | No | Direct replacement |
| `vacuumValve.vacuumValveReadExtra` | *(no direct replacement)* | No | Use multiple `dlsPLC.read100` instances |
| `vacuumValve.externalValve` | `dlsPLC.externalValve` | No | Direct replacement |
For valves that used `tclose` macros, use `dlsPLC_vacValveTclose.template`
(entity type `dlsPLC.vacValveTclose`).
For endstation PLCs with more than 6 valves, use `dlsPLC.vacValveDebounce`
instead of `dlsPLC.vacValve`.
### vacuumPump module
| Old XML type | dlsPLC replacement | Auto-converted? | Notes |
|---|---|---|---|
| `vacuumPump.vacuumScrollPump` / `_asyn` | `dlsPLC.vacPump` | No | `valve×10 → addr` |
| `vacuumPump.vacuumTurboPump` / `_asyn` | `dlsPLC.vacPump` | No | `valve×10 → addr` |
| `vacuumPump.vacuumRootsPump` | `dlsPLC.vacPump` | No | `valve×10 → addr` |
### interlock module
| Old XML type | dlsPLC replacement | Auto-converted? | Notes |
|---|---|---|---|
| `interlock.interlock` | `dlsPLC.interlock` | **Yes** | See address and field-name changes below |
| `interlock.overrideRequestMain` | `dlsPLC.overrideRequestMain` | **Yes** | `addr`+`in`+`out` → `outaddr`; see below |
| `interlock.overrideRequestIndividual` | `dlsPLC.overrideRequestIndividual` | **Yes** | `FIELD` argument removed |
### temperature module
| Old XML type | dlsPLC replacement | Auto-converted? | Notes |
|---|---|---|---|
| `temperature.temperaturePLCRead` / `_asyn` | 2× `dlsPLC.read100` | **Partial WIP** | `century=1` (setpoints), `century=2` (readbacks) |
| `temperature.temperaturePLC` / `_asyn` | `dlsPLC.temperature` | No | `addr`+`indx` → 2-digit `offset`; see below |
### flow module
| Old XML type | dlsPLC replacement | Auto-converted? | Notes |
|---|---|---|---|
| `flow.flow` | *(no replacement)* | No | Still works; `LOLO` PV points at `dlsPLC.interlock` output |
| `flow.flow_asyn` | `dlsPLC.flow` | No | Address recalculation required; see below |
### Other templates
| Old XML type | dlsPLC replacement | Auto-converted? | Notes |
|---|---|---|---|
| `vacuumValve.reboot_rga` | `dlsPLC.reboot_rga` | No | `DM → addr` |
| `vacuumValve.fvg_asyn` | *(remove)* | No | Point GUI directly at gauge interlock bit |
| `vacuumValve.overrideRequestMain` | `dlsPLC.overrideRequestMain` | **Yes** | Via interlock converter |
---
## Argument transformation rules
### `valve → addr`
All valve and pump templates used a `valve` number (1-based, sometimes
zero-padded). dlsPLC uses `addr` = `valve × 10`.
```
valve=1 → addr=10
valve=2 → addr=20
valve=12 → addr=120
```
### `vlvcc` — full crate name
The `vlvcc` argument in dlsPLC templates must be the **full** PV device name of
the valve crate controller, not a short alias:
```
# Old
vlvcc="VLVCC01"
# New
vlvcc="BL04I-VA-VLVCC-01"
```
### `interlock` — field names and addresses
Interlock bit labels `ilkA`–`ilkF` become `ilk10`–`ilk15` (decimal):
```
ilkA → ilk10
ilkB → ilk11
ilkC → ilk12
ilkD → ilk13
ilkE → ilk14
ilkF → ilk15
```
The `addr` argument is used **directly** (not multiplied by 10). However, if
your original interlock used a reduced address because `vacuumValveRead2` was
subtracting an offset, **add that offset back**:
```
# Old: addr=07 with vacuumValveRead2 READFROM2=800 → effective DM=807
# New: addr=807
```
### `overrideRequestMain` — `outaddr`
The three arguments `addr`, `in`, `out` collapse into a single `outaddr`:
```
outaddr = (addr × 10) + in
```
The input address is always `outaddr + 1`.
Example: `addr=08, in=1, out=0` → writes DM80, reads DM81 → `outaddr=80`.
The `in`, `out`, and `addr` arguments are removed; `name` is also removed.
### `temperaturePLC` — `offset`
The `addr` and `indx` arguments are combined into a 2-digit `offset`:
```
offset = (addr × 10) + indx
```
Example: `addr=3, indx=4` → reads from DM234, writes to DM134 → `offset=34`.
### `flow_asyn` — address recalculation
If your `flow_asyn.template` had non-zero `loarea`/`loloarea`, look up the
`READFROM2` value in the corresponding `vacuumValveRead2_asyn.template` and add
it to `loaddress`/`loloaddress`:
```
# Old: loloarea=2, loloaddress=12, READFROM2=800
# New: loloaddress=812 (loloarea removed)
```
---
## ioc.yaml examples
### vacuumValveRead → dlsPLC.read100
**Old XML:**
```xml
```
**New ioc.yaml:**
```yaml
- type: dlsPLC.read100
century: 0
device: BL04I-VA-VLVCC-01
port: ty_40_0
```
Note: `name` is removed (it was only used to cross-reference other components
in builder XML; ibek uses the `device` PV name instead).
### vacuumValve → dlsPLC.vacValve
**Old XML:**
```xml
```
**New ioc.yaml** (blank `ilk` fields are dropped automatically):
```yaml
- type: dlsPLC.vacValve
addr: 10
device: BL04I-VA-VALVE-01
vlvcc: BL04I-VA-VLVCC-01
port: ty_40_0
ilk0: Air Healthy
ilk1: IMG 03 Healthy
ilk2: IMG 04 Healthy
ilk3: PIRG 03 Healthy
ilk4: PIRG 04 Healthy
ilk5: QBPM1 Out/V1 Open
ilk7: QBPM3 Out/V1 Open
ilk15: Valve OK
gilk0: GAUGE-01
gilk1: GAUGE-02
gilk2: GAUGE-03
gilk3: GAUGE-04
gilk4: GAUGE-05
gilk5: GAUGE-06
```
### interlock → dlsPLC.interlock
**Old XML:**
```xml
```
**New ioc.yaml** (ilkA–ilkF → ilk10–ilk15; blank fields dropped):
```yaml
- type: dlsPLC.interlock
addr: 7
desc: Water Flow Interlocks
device: BL04I-VA-VLVCC-01
ilk0: I04 Filter
ilk1: X+ Slit Water Flow
ilk2: X- Slit Water Flow
ilk3: Y+ Slit Water Flow
ilk4: Y- Slit Water Flow
ilk5: Screen Water Flow
ilk6: I04-1 Filter
ilk7: DCM Water Flow
interlock: ':INT1'
port: ty_40_0
```
### overrideRequestMain → dlsPLC.overrideRequestMain
**Old XML:**
```xml
```
**New ioc.yaml** (`outaddr = 08×10 = 80`; `in`, `out`, `addr`, `name` removed):
```yaml
- type: dlsPLC.overrideRequestMain
P: BL04I-VA-VLVCC-01
Q: :OVERRIDE
outaddr: 80
port: ty_40_0
```
### overrideRequestIndividual → dlsPLC.overrideRequestIndividual
**Old XML:**
```xml
```
**New ioc.yaml** (`FIELD` removed):
```yaml
- type: dlsPLC.overrideRequestIndividual
BIT: 1
DESC: HFM Piezo Override
OVERRIDE: BL04I-VA-VLVCC-01:OVERRIDE
P: BL04I-OP-HFM-01:ILK
PRESSURE1: BL04I-VA-GAUGE-04:P
PRESSURE2: BL04I-VA-GAUGE-04:P
SETPOINT: 500
```
---
## What xml2yaml handles automatically
The following conversions happen without manual intervention when you run
`builder2ibek xml2yaml`:
| Conversion | Source converter |
|---|---|
| `vacuumValve.vacuumValveRead` → `dlsPLC.read100` (century=0) | `converters/vacuumValve.py` |
| `vacuumValve.vacuumValve` / `vacuumValve_callback` → `dlsPLC.vacValve` with `valve×10` | `converters/vacuumValve.py` |
| `vacuumValve.vacuumValveGroup` → `dlsPLC.vacValveGroup` | `converters/vacuumValve.py` |
| `interlock.interlock` → `dlsPLC.interlock` with hex→int on ilk fields | `converters/interlock.py` |
| `interlock.overrideRequestMain` → `dlsPLC.overrideRequestMain` with outaddr | `converters/interlock.py` |
| `interlock.overrideRequestIndividual` → `dlsPLC.overrideRequestIndividual` (FIELD dropped) | `converters/interlock.py` |
| Blank `ilk*` fields dropped from all dlsPLC entities | `converters/dlsPLC.py` |
| `dlsPLC.fastVacuumChannel` id zero-padded to 2 digits | `converters/dlsPLC.py` |
## What requires manual attention after xml2yaml
| Case | Action |
|---|---|
| `vacuumValve.vacuumValveRead2` | Raises `NotImplementedError`; manually add `dlsPLC.read100` with correct `century` |
| `temperature.*` | Converter is partial WIP; verify output and add `dlsPLC.temperature` / `dlsPLC.read100` manually |
| `vacuumPump.*` | No converter; add `dlsPLC.vacPump` with `valve×10 → addr` manually |
| `flow.flow_asyn` | No converter; add `dlsPLC.flow` with recalculated addresses manually |
| `vacuumValve.vacuumValveBistable` | No converter; add `dlsPLC.vacValveBistable` with `valve×10 → addr` manually |
| `vacuumValve.fvg_asyn` | Remove from ioc.yaml; update GUI to point at gauge interlock bit directly |
| Hostlink → FINS protocol | Update startup script manually; see [Build requirements](#build-requirements) |
---
## Reference: I04 beamline conversion
I04 (`BL04I-VA-IOC-01`) was fully migrated in March 2025 and serves as the
reference example. The before/after can be seen in the builder2ibek test
samples:
- Before: `tests/samples/BL04I-VA-IOC-01.xml`
- After: `tests/samples/bl04i-va-ioc-01.yaml`