DiamondLightSource / DiamondLightSource/fastcs
connections: introspection - a connection describing the device to `build`
- Dominant language
- Python
- Stars
- 6
- Forks
- 8
- Avg merge
- 2d 12h
- Merged PRs (30d)
- 3
Description
Raised at the request of @tomkane-dls, in the spec attached to [this comment on #424](https://github.com/DiamondLightSource/fastcs/pull/424#issuecomment-5604015457): introspection is "a larger design problem than it looked and belongs in its own PR". #424 removes it; this issue is where it comes back. Part of the #388 series.
## What was there, and is now gone
`Connection` was generic in `T`, and `connect()` returned a value describing the device — for `EigerConnection`, a `DetectorInfo` carrying the API version and the tuple of parameter keys the detector reports.
**At startup**, the runner opened each connection, kept what `connect()` returned, and passed it to `Controller.build()` for the controllers holding that connection. `build()` used it to create structure that cannot be known from config — for Eiger, one attribute per reported parameter key. A controller that did not introspect declared `build(self)` with no parameter, and the framework inspected the signature to decide whether to pass one.
**On every reconnect**, the runner called `connect()` again and compared the new value against the one from startup. If they differed, the device had come back describing itself differently. `build()` cannot re-run — the controller tree is fixed after startup, and pythonSoftIOC cannot create records after `iocInit` — so there was no way to accommodate the new shape, and the application exited rather than continuing against a structure that no longer matched the hardware.
**The methodology above is an open question, not a plan to re-apply.** It is recorded here so nothing is lost.
## The open problem
A connection whose *existence* depends on introspection cannot be declared in `fastcs.yaml`, because it does not exist at config-parse time. A device reporting seven sub-devices, each needing its own link, has no way to express them.
Supporting that means giving back the things #424 was able to delete:
- collecting connections from the tree after the build phase, rather than the runner's list being exactly what the launcher built;
- deduplicating them by identity;
- opening them mid-walk rather than all up front, in dependency order;
- accepting that they take framework defaults and cannot declare `depends_on`.
No known device at Diamond needs this — the multiplexed alternative, one link with the sub-device as a command parameter, is what `temperature_attr.py` already does for its ramps.
## Where things stand meanwhile
A controller that must ask the device what it has still can, and the Eiger demo still does: `build` runs with every connection already open, so the walk is ordinary driver code in `EigerDetector.build` rather than a framework mechanism. What is no longer available is the framework *carrying* that answer or *comparing* it on reconnect.
Two smaller pieces would also want revisiting here:
- **`ControllerRunner.fatal_error`.** The channel is still there, but nothing in the framework sets it now: its one producer was the introspection mismatch. Whatever this issue lands will most likely be its producer again.
- **The comparison itself.** `!=` requires the result to compare to a single bool, which a dataclass of plain fields does and an array of values does not. If the mechanism returns, that constraint needs stating in the design rather than discovered by a driver.
## Questions to settle
1. Does the framework carry the description at all, or does `build` simply ask the device itself (as it does today) and introspection become a documented pattern rather than a mechanism?
2. If it is a mechanism: does it hang off `Connection.connect()`'s return value, or a property on the connection the runner reads (floated in the review of #420, and it avoids the two-signatures-for-`build` wart)?
3. Is a re-describing device fatal, or is there a shape of controller tree that could accommodate it?
4. Is the connection-per-introspected-sub-device case in scope, and is it worth the four deletions above?
Contributor guide
Research direction
Start by reading the current EigerDetector.build flow and the ControllerRunner.fatal_error channel, then compare it with the introspection behavior described as removed in #424. Review temperature_attr.py as the existing multiplexed alternative. Done requires settling the listed design questions and defining the framework behavior for dynamic connections, reconnect changes, and comparison results.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100