diff --git a/docs/device_v4_migration_plan.md b/docs/device_v4_migration_plan.md new file mode 100644 index 0000000..7518f7a --- /dev/null +++ b/docs/device_v4_migration_plan.md @@ -0,0 +1,129 @@ +# SuperXAS device v4 migration plan + +## Scope and source mapping + +| Debye source | SuperXAS target | +| --- | --- | +| `debye_bec/debye_bec/devices/utils/utils.py` | `superxas_bec/superxas_bec/devices/utils/utils.py` | +| `debye_bec/debye_bec/devices/mo1_bragg/mo1_bragg.py` | `superxas_bec/superxas_bec/devices/mo1_bragg/mo1_bragg.py` | +| `debye_bec/debye_bec/devices/nidaq/nidaq.py` | `superxas_bec/superxas_bec/devices/nidaq/nidaq.py` | + +The migration should preserve SuperXAS hardware naming, EPICS prefixes, enum +modules, and beamline-specific details. Only the scan metadata access pattern +should be changed to match the Debye v4 migration pattern. + +## Shared helper + +Add `superxas_bec/superxas_bec/devices/utils/utils.py` with Debye's +`fetch_scan_info(scan_info)` helper. + +The helper should: + +- read `scan_info.msg.info` +- convert list-valued `positions` back to `numpy.ndarray` +- validate the message as `bec_server.scan_server.scans.scan_base.ScanInfo` +- fall back for legacy scan messages by converting old `"fly"` scan type to + `"hardware_triggered"` and other old scan types to `"software_triggered"` + +This keeps devices compatible during the migration window where both old and v4 +scan status messages may still appear. + +## `mo1_bragg` migration + +Current SuperXAS behavior: + +- reads the scan name from `self.scan_info.msg.scan_name` +- populates a local `ScanParameter` pydantic model from + `self.scan_info.msg.request_inputs["inputs"]` and `["kwargs"]` +- reads old-style parameters such as `scan_parameter.start`, + `scan_parameter.stop`, `scan_parameter.scan_time`, and + `scan_parameter.scan_duration` + +Target v4 behavior: + +- import `ScanInfo as ScanServerScanInfo` +- store `self.scan_parameters: ScanServerScanInfo | None` +- call `self.scan_parameters = fetch_scan_info(self.scan_info)` at the start of + `on_stage` +- validate the scan with `_check_if_scan_name_is_valid(self.scan_parameters)` +- read start/stop from `self.scan_parameters.positions` +- read scan arguments from + `self.scan_parameters.additional_scan_parameters` + +For the scans migrated in step 2: + +- `xas_simple_scan` + - required: `positions`, `scan_time`, `scan_duration` + - configure simple XAS settings + - disable trigger settings + - set scan-control mode to `ScanControlMode.SIMPLE` +- `xas_advanced_scan` + - required: `positions`, `scan_time`, `scan_duration`, `p_kink`, `e_kink` + - configure advanced XAS settings + - disable trigger settings + - set scan-control mode to `ScanControlMode.ADVANCED` + +Keep SuperXAS-specific hardware behavior: + +- keep the SuperXAS prefix and imports +- keep `set_trig_settings(...)` matching the current SuperXAS IOC API unless + XRD scan classes are reintroduced in the scan migration +- keep `convert_angle_energy(...)` available for the v4 scan's gonio centering +- consider adding `"convert_angle_energy"` to `USER_ACCESS`, matching Debye, + because the scan migration now relies on the method being callable through the + device container/device manager path + +Remove or stop using `_update_scan_parameter()` and the old `ScanParameter` +model once all parameter reads come from `self.scan_parameters`. + +## `nidaq` migration + +Current SuperXAS behavior: + +- checks scan validity through `self.scan_info.msg.scan_name` +- reads continuous scan settings from + `self.scan_info.msg.scan_parameters["scan_duration"]` and + `["compression"]` +- `_progress_update` also reads `self.scan_info.msg.scan_parameters` + +Target v4 behavior: + +- import `ScanInfo as ScanServerScanInfo` +- store `self.scan_parameters: ScanServerScanInfo | None` +- call `self.scan_parameters = fetch_scan_info(self.scan_info)` at the start of + `on_stage` +- change `_check_if_scan_name_is_valid(...)` to accept the normalized + `scan_parameters` +- read the scan name from `self.scan_parameters.scan_name` +- read continuous scan settings from + `self.scan_parameters.additional_scan_parameters` +- make `_progress_update` use + `self.scan_parameters.additional_scan_parameters.get("scan_duration")` + +The Debye runtime behavior should be mirrored: + +- invalid scan names return without touching the IOC +- non-continuous XAS scans stage NIDAQ as triggered, duration `0`, + compression enabled +- `nidaq_continuous_scan` stages NIDAQ as continuous and applies + `scan_duration` plus `compression` +- non-continuous XAS scans kick off during stage +- `nidaq_continuous_scan` waits for explicit scan-core kickoff +- `on_complete` stops the backend only for non-continuous scans + +## Tests to migrate or add + +Use Debye's NIDAQ device tests as the starting point and adapt imports to +SuperXAS: + +- verify `fetch_scan_info` accepts v4 scan info and legacy fly scan info +- verify NIDAQ valid/invalid scan name checks +- verify NIDAQ pre-scan and complete behavior for XAS versus + `nidaq_continuous_scan` +- add a focused `mo1_bragg` staging test that feeds v4 scan info and asserts the + correct settings methods are called for `xas_simple_scan` and + `xas_advanced_scan` + +Keep the test blast radius focused on metadata parsing and device decisions. +The EPICS signal behavior is already covered by existing device-level patterns. +