Manage controllers' PV names from yaml config and modify GUI screen generation#63
Manage controllers' PV names from yaml config and modify GUI screen generation#63ggayDiamond wants to merge 12 commits into
Conversation
Now 'controllers' is correctly defined as an array rather than a keyed object. Used commands: cd /workspaces/CATio/src/fastcs_catio uv run fastcs-catio schema > schema.json
- Add `IONodeType.Box` enum value and handle it like a coupler in
`get_type_name()` (assigns RIO{node} PV name)
- Detect Box terminals via regex `_BOX_TYPE_RE` in `client.py` and
assign `IONodeType.Box` category during chain location calculation
- Include Box nodes in the controller dispatch match-case alongside
Coupler and Slave; always resolve terminal controllers via
`get_terminal_controller_class()`
- Rename `SUPPORTED_CONTROLLERS` → `SUPPORTED_DEVICE_CONTROLLERS` and
remove commented-out legacy terminal entries (only `ETHERCAT` device
controller remains)
- Rename `get_supported_hardware` → `get_supported_devices` to reflect
device-only scope
- Update unit tests for new Box node type and renamed test method
Couplers/boxes whose resolved path is a single segment (e.g. BL04I-EA-E1RIO-01) are now registered as direct sub-controllers of the server controller rather than of the device controller. Their group_layout is set to GroupLayout.INLINE so PVI renders them as Grid panels on the top-level server screen. PVI forbids nesting a Group(Grid()) inside another Group, so couplers must be at the server level (not inside the device SubScreen) to appear inline. Their FastCS PV paths are unchanged — path resolution is independent of the FastCS parent-child hierarchy. Multi-segment-path couplers (e.g. BL04I-EA-CATIO-01:ETH01:E1RIO01) are unaffected and remain as SubScreen children of the device.
- Pin fastcs[epics]==0.15.0b4 (was >=0.15.0b3) and pvi==0.14.0b1 (was unpinned at 0.12.0); update uv.lock accordingly. - Remove 'title: CATio Demo' from fastcs.yaml; update schema.json so EpicsGUIOptions.title defaults to null rather than a required string, matching the upstream fastcs change. - Fix docstring and unit-test examples: BL04I-EA-ERIO-01 → BL04I-EA-E1RIO-01 (correct real-world device name). - Update fastcs-epics-ioc.md to reference CATioNameMappings templates instead of the removed ecat_name / get_type_name() implementation details.
- Remove verbose per-channel AI Settings / Internal data / Vendor data CoE object groups from EP4374 in terminal_types.yaml to reduce GUI screen clutter. - Independently refresh fastcs-epics-ioc.md: correct class names (CATioDeviceController → EtherCATMasterController), add coupler-hoisting explanation, expand device/terminal attribute tables, and document the three IO handler classes and CATioServerControllerOptions sub-dataclasses.
There was a problem hiding this comment.
Pull request overview
This PR adopts a YAML-driven naming scheme for EtherCAT controllers and reworks PVI screen layout so that single-segment coupler/box controllers are hoisted to the server root and rendered inline (working around a PVI Group-nesting restriction). It pins new beta versions of fastcs and pvi, refreshes the EPICS IOC explanation doc, allows a null EpicsGUIOptions.title, and trims verbose EP4374 CoE objects to reduce screen clutter.
Changes:
- Introduce
CATioNameMappingsdataclass withdevice_prefix/node_prefix/module_prefixtemplates, validated at construction; replace hardcodedget_type_name()methods with template-driven_resolve_controller_name_and_path. - Hoist single-segment coupler/box controllers to the server with
GroupLayout.INLINE; addBoxIONodeType and recognizeE[PQR]P?\d{4}boxes in chain traversal. - Pin
fastcs[epics]==0.15.0b4,pvi==0.14.0b1; expand and correct thefastcs-epics-ioc.mdexplanation document.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pyproject.toml / uv.lock | Pin fastcs/pvi to specific beta versions. |
| src/fastcs_catio/catio_controller.py | New name-template machinery, hoisting logic, path injection into subcontrollers. |
| src/fastcs_catio/catio_hardware.py | Rename SUPPORTED_CONTROLLERS → SUPPORTED_DEVICE_CONTROLLERS, drop terminal entries from the device map. |
| src/fastcs_catio/client.py | Recognize EtherCAT box terminals and assign them node positions. |
| src/fastcs_catio/devices.py | Add Box node type, remove get_type_name() methods, convert ChainLocation to NamedTuple. |
| src/fastcs_catio/utils.py | Add make_node_prefix helper (currently unused) and beamline regex. |
| src/fastcs_catio/main.py | Pass path to server controller constructor. |
| src/fastcs_catio/fastcs.yaml | Switch to absolute id, fully-qualified controller type, add name_mappings. |
| src/fastcs_catio/schema.json | Schema for new CATioNameMappings, nullable title, transports tightening. |
| src/catio_terminals/terminals/terminal_types.yaml | Drop bulky EP4374 per-channel AI/AO CoE objects. |
| tests/test_catio_units.py | New TestControllerNameMappings suite; update hyphen-trim fixtures and remove tests for deleted methods. |
| docs/explanations/fastcs-epics-ioc.md | Documentation refresh aligned with new naming + hoisting behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def make_node_prefix(parent_path: list[str], substitution: str) -> list[str]: | ||
| """ | ||
| Create a node prefix for the CATio controller based on the parent path. | ||
| If the server prefix matches the beamline pattern, the substitution string is used \ | ||
| to create a new prefix based on that pattern (e.g. "BL04I-EA-E1RIO-01"). | ||
| Otherwise, the substitution is simply appended to the parent path \ | ||
| (e.g. "BL04I-EA-CATIO-01:ETH1:E1RIO1"). | ||
|
|
||
| :param parent_path: the parent path provided by the user | ||
| :param substitution: the substitution string to use if the server prefix matches \ | ||
| the beamline pattern | ||
|
|
||
| :returns: a list of strings representing the node path for the controller | ||
| """ | ||
| server_prefix = parent_path[0] | ||
| if _BEAMLINE_NAME_RE.match(server_prefix): | ||
| formatted_sub = "-".join( | ||
| p.zfill(2) if p.isdigit() else p | ||
| for p in [ | ||
| "".join(x) for _, x in itertools.groupby(substitution, key=str.isdigit) | ||
| ] | ||
| ) | ||
| return [ | ||
| re.sub( | ||
| _BEAMLINE_NAME_RE, | ||
| r"\1-\2-" + formatted_sub, | ||
| server_prefix, | ||
| ) | ||
| ] | ||
|
|
||
| return parent_path + [substitution] |
| Log the list of I/O ETherCAT devices currently supported by the CATio driver. | ||
| """ | ||
| logger.info( | ||
| "List of I/O hardware currently supported by the CATio driver:\n " | ||
| + f"{list(SUPPORTED_CONTROLLERS.keys())}" | ||
| "List of I/O ETherCAT devices currently supported by the CATio driver:\n " | ||
| + f"{list(SUPPORTED_DEVICE_CONTROLLERS.keys())}" |
| def get_supported_devices(self) -> None: | ||
| """ | ||
| Log the list of I/O hardware currently supported by the CATio driver. | ||
| Log the list of I/O ETherCAT devices currently supported by the CATio driver. | ||
| """ | ||
| logger.info( | ||
| "List of I/O hardware currently supported by the CATio driver:\n " | ||
| + f"{list(SUPPORTED_CONTROLLERS.keys())}" | ||
| "List of I/O ETherCAT devices currently supported by the CATio driver:\n " | ||
| + f"{list(SUPPORTED_DEVICE_CONTROLLERS.keys())}" | ||
| ) |
|
|
||
| logger.verbose( | ||
| f"{len(subcontrollers)} subcontrollers were found for {node.data.name}." | ||
| f"{len(subcontrollers)} subcontrollers were found for " |
| self, | ||
| options: CATioServerControllerOptions, | ||
| *, | ||
| path=None, |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #63 +/- ##
==========================================
+ Coverage 75.26% 75.63% +0.37%
==========================================
Files 19 19
Lines 4096 4146 +50
==========================================
+ Hits 3083 3136 +53
+ Misses 1013 1010 -3 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
The previous defaults (ETH{}, E1RIO{}, MOD{}) rendered to bare single-segment
names, causing devices, couplers and modules to appear at the top level of the
PV tree instead of being nested under their parents.
New defaults:
device_prefix "{id}:ETH{:02d}" → [<root>, ETH01]
node_prefix "{device_prefix}:E1RIO{:02d}" → [<root>, ETH01, E1RIO01]
module_prefix "{node_prefix}:MOD{:02d}" → [<root>, ETH01, E1RIO01, MOD03]
Tests updated to assert the correct nested paths and confirm that the old
single-segment behaviour no longer applies by default.
Expose device_prefix, node_prefix and module_prefix as --device-prefix, --node-prefix and --module-prefix options on the 'ioc' command, mirroring the name_mappings block already available in the YAML-driven 'run' command. Also adds docs/how-to/run-ioc.md documenting both launch modes and the PV name template system.
Underscores in EPICS PV name components produce invalid PV names.
Two validation points added:
- CATioNameMappings.__post_init__: rejects underscores in template
literal text at construction time (before any hardware is touched).
- CATioServerController._render: rejects underscores in the fully
rendered result, catching cases where a substituted value such as
{id} contributes an underscore at runtime.
Tests added for both cases. docs/how-to/run-ioc.md updated with a
warning explaining the restriction and pointing to hyphens as the
correct separator.
- Add '# pragma: no cover' to two unreachable defensive guards in CATioServerController (_resolve_controller_name_and_path else-branch and the empty-path fallback) and to CLI-only runtime branches in __main__.py (terminal_defs block and screens_dir fallback). - Add TestGetSupportedDevices: verifies get_supported_devices() is callable with no arguments (fixes the stray 'self' parameter removed in the same session). - Add TestEtherCATChainCategorisation: unit tests for _get_ethercat_chains covering the Box-type branch (EP/EQ/ER terminals), the Coupler branch (EK1100), and plain slaves — covering the previously uncovered client.py lines 1446-1449. - Remove dead make_node_prefix() from utils.py together with its exclusive dependencies (_BEAMLINE_NAME_RE, import itertools). - Fix CATioServerController.__init__ path parameter: remove type annotation from the keyword-only 'path=' argument so FastCS's _launch() introspection correctly identifies CATioServerControllerOptions as the options type instead of raising a LaunchError on startup.
In some CI environments (e.g. GitHub Actions with FORCE_COLOR set), Rich/Typer emits colour codes that split option names such as '--device-prefix' into separate escape sequences, causing plain-string membership tests to fail.
Inline GUI layout for root-path couplers/boxes
Couplers and EtherCAT boxes whose resolved PV path is a single segment (e.g. BL04I-EA-E1RIO-01) are now registered as direct sub-controllers of the server controller rather than nested inside the device controller. Their group_layout is set to GroupLayout.INLINE, so PVI renders them as Grid panels directly on the top-level server screen instead of behind a SubScreen click.
This works around a PVI constraint that forbids nesting a Group(Grid()) inside another Group. Multi-segment-path couplers (e.g. BL04I-EA-CATIO-01:ETH01:E1RIO01) are unaffected and remain as SubScreen children of the device controller. FastCS PV paths are unchanged — path resolution is independent of the FastCS parent–child hierarchy.
Configurable PV name templates (name_mappings)
A new CATioNameMappings dataclass lets operators control how EtherCAT device, node (coupler/box), and module PV path segments are generated from fastcs.yaml, without touching any Python code. Templates use Python str.format placeholders — {} / {n} for numeric index, {id} for the IOC root prefix, {device_prefix} and {node_prefix} for parent path segments. Using {id} or {node_prefix} causes the rendered result to be treated as an absolute PV path (split on :), enabling standalone coupler roots like BL04I-EA-E1RIO-01 alongside relative names like ETH01. Unknown placeholder keys are validated at construction time, before any hardware connection is attempted.
Also included:
Pin fastcs[epics]==0.15.0b4 and pvi==0.14.0b1; update
uv.lock.Allow EpicsGUIOptions.title to be null (removes the requirement for a title field in
fastcs.yaml).Remove verbose per-channel AI Settings / Internal / Vendor data CoE object groups from EP4374 in
terminal_types.yamlto reduce GUI screen clutter.Refresh f
astcs-epics-ioc.md: correct class names (CATioDeviceController → EtherCATMasterController), add coupler-hoisting explanation, expand device/terminal attribute tables, and document the three IO handler classes and CATioServerControllerOptions sub-dataclasses.