Add ASI 6-position objective turret on the shared MFC-2000 (USE_ASI_OBJECTIVE_TURRET)#594
Open
Alpaca233 wants to merge 15 commits into
Open
Add ASI 6-position objective turret on the shared MFC-2000 (USE_ASI_OBJECTIVE_TURRET)#594Alpaca233 wants to merge 15 commits into
Alpaca233 wants to merge 15 commits into
Conversation
CombinedStage.z_stage, LS50Controller.serial, ASIZStage.ms2000_serial, and a module-level find_shared_ms2000(stage) so a same-controller addon (the ASI objective turret) can reuse the Z stage's lock-protected transport without reaching through private attrs. Simulation-aware (None for _SimulatedLS50); ownership stays with the Z stage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e LS50 Z) control/asi_objective_turret.py: ASIObjectiveTurret + simulation implementing ObjectiveChangerProtocol. Rotates with 'M T=<slot>' (RAW slot index 1..6, not scaled like linear-axis units); reads position via best-effort 'W T' (probed at connect, verified after each move) degrading to last-commanded-slot tracking; NO homing exists -- home() only refreshes the tracked slot; Z retract/restore dance on every switch (template semantics); shared-serial ownership: never closes a transport the Z stage owns, closes its own. Wiring: USE_ASI_OBJECTIVE_TURRET + ASI_OBJECTIVE_TURRET_* flags (SN/port/baud default to the ASI_Z_* values, resolved at construction time), 3-way mutual-exclusion validator, MicroscopeAddons elif branch using find_shared_ms2000(stage), GUI cleanup condition widened. 31 new turret tests + 6 config tests, incl. simulated end-to-end Microscope builds and the raw-slot-framing proof. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--turret: read-only raw 'W <axis>' + '/' replies (answers the W-T-semantics question). --turret-slot N (behind --allow-motion + confirmation): rotate, log '/' busy transitions with timestamps (measures whether the global busy byte covers turret rotation), read back, repeat same slot. read_only_checks switched to the public backend.serial accessor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…posite.py The composite went multi-vendor (PI V-308, ASI LS50) but lived in the vendor-named pi.py, forcing the ASI turret's find_shared_ms2000 to duck-type around an import cycle and microscope.py's ASI branch to import through the PI module. CombinedStage now lives in squid/stage/composite.py; pi.py keeps a compatibility re-export; find_shared_ms2000 uses a real isinstance; canonical import sites (microscope.py, tests) migrated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # software/tests/squid/test_stage.py
# Conflicts: # software/tools/asi_z_bringup.py
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ocol close, lazy import - retract_z_if_possible/restore_z_if_captured promoted to module level in objective_turret_controller.py; all four changer classes (NiMotion real+sim, ASI real+sim) delegate — the objective-crash-avoidance policy now has one home instead of four copies. - ASI turret consumes the MS2000Serial transport helpers (wait_idle, command_with_settle_retry, parse_ms2000_number) and utils.resolve_port instead of re-implementing them; unused threading import dropped. - Xeryon changers implement the close() that ObjectiveChangerProtocol always promised (no-op), so the GUI cleanup closes any changer unconditionally — no more vendor-flag OR chain. - ASI turret classes imported inside their construction branch instead of a module-level conditional with None sentinels; the e2e tests drop the class-name monkeypatch boilerplate that pattern forced. - pi.py: dead CombinedStage re-export removed (zero consumers after the composite migration); docstring grammar fixed. - Turret tests use the shared tests.tools fakes (FakeSerialConn, FakeZStage). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hardware-confirmed: the objective turret rides the F axis, not T. The configurable ASI_OBJECTIVE_TURRET_AXIS_LETTER now defaults to F, along with the driver/tool defaults, tests, and prose. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds support for a 6-position ASI objective turret that shares the MS-2000 controller/transport with the ASI LS50 Z stage, including Microscope integration, a vendor-neutral CombinedStage relocation, shared Z-safety helpers, and extensive tests/bring-up tooling.
Changes:
- Introduces
ASIObjectiveTurret(+ simulation) usingMS2000Serialcommands (M <axis>=<slot>,W <axis>,/busy) with shared-serial ownership semantics. - Moves
CombinedStageintosquid.stage.compositeand updates call sites/tests accordingly; addsfind_shared_ms2000()accessor plumbing. - Consolidates Z retract/restore “safety dance” helpers and ensures GUI shutdown can call
close()on any objective changer implementation.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| software/tools/asi_z_bringup.py | Adds turret probe/move bring-up options for MS-2000 turret axis. |
| software/tests/squid/test_stage.py | Updates CombinedStage import path and tests new shared-MS2000 accessor behavior. |
| software/tests/control/test_objective_turret_controller.py | Refactors tests to reuse shared FakeZStage helper. |
| software/tests/control/test_objective_changer_config.py | Extends objective-changer mutual exclusion coverage for the new ASI turret flag. |
| software/tests/control/test_asi_objective_turret.py | Adds comprehensive unit tests for ASI turret behavior and ownership semantics. |
| software/squid/stage/pi.py | Removes CombinedStage from PI module and updates docs to point at squid.stage.composite. |
| software/squid/stage/composite.py | New vendor-neutral CombinedStage implementation for composing XY + external Z-only stages. |
| software/squid/stage/asi.py | Exposes MS2000 serial accessors and adds find_shared_ms2000() for turret sharing. |
| software/control/objective_turret_controller.py | Hoists shared Z retract/restore helpers for all objective changers to use. |
| software/control/objective_changer_2_pos_controller.py | Adds close() implementations for Xeryon changer(s) to match protocol. |
| software/control/microscope.py | Wires ASI turret creation into Microscope build and clarifies homing semantics. |
| software/control/gui_hcs.py | Simplifies shutdown by calling close() on any configured objective changer. |
| software/control/asi_objective_turret.py | New ASI turret driver + simulation implementing the objective changer protocol. |
| software/control/_def.py | Adds ASI turret flags/config and extends objective-changer mutual exclusion validation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+39
to
+42
| def close(self): | ||
| # ObjectiveChangerProtocol promises close(); the Xeryon library holds no OS handle | ||
| # that needs explicit release (cleanup zeroes the axis via moveToZero instead). | ||
| pass |
Comment on lines
+195
to
+198
| parser.add_argument("--turret", action="store_true", help="Probe the objective turret axis (read-only)") | ||
| parser.add_argument("--turret-axis", default="F", help="Turret axis letter (default F, per the MFC-2000)") | ||
| parser.add_argument("--turret-slot", type=int, help="Rotate to this slot (1..6); requires --allow-motion") | ||
|
|
Comment on lines
+13
to
+17
| import squid.logging | ||
| from squid.abc import AbstractStage, Pos, StageStage | ||
| from squid.config import StageConfig | ||
|
|
||
| _log = squid.logging.get_logger(__name__) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the 6-position objective turret driven by the same MFC-2000 controller as the LS50 Z stage, as a Squid objective changer: the GUI objective dropdown rotates the turret with the standard Z retract→rotate→restore safety dance.
What's in here
control/asi_objective_turret.py(new):ASIObjectiveTurret+ simulation implementingObjectiveChangerProtocol. Rotation viaM F=<slot>(axis F, hardware-confirmed; raw slot index 1–6, never scaled — a test proves it). Position via best-effortW F(probed at connect, read back after each move as a cross-check) degrading to last-commanded-slot tracking. No homing exists:home()only refreshes the tracked slot; the startupmove_to_objective(DEFAULT_OBJECTIVE)lands the turret on a known slot at every boot.MS2000Serialviafind_shared_ms2000(stage)(new public accessors) and never closes it; standalone, it opens its own connection (SN/port/baud defaulting to theASI_Z_*values) and owns the close. Consumes the transport-level helpers from Add ASI LS50 Z-only focus stage (USE_ASI_Z_STAGE) #591 (wait_idle,command_with_settle_retry,parse_ms2000_number) rather than re-implementing them.CombinedStagerelocated to vendor-neutralsquid/stage/composite.py(it composes both PI and ASI Z stages now);pi.pykeeps no stale re-export.objective_turret_controller.py; all four changer classes (NiMotion real+sim, ASI real+sim) delegate to one implementation.close()the protocol always promised, so GUI shutdown closes any changer unconditionally — no vendor-flag OR chain.USE_ASI_OBJECTIVE_TURRET+ASI_OBJECTIVE_TURRET_*(three-way mutual exclusion with Xeryon/NiMotion validated at config load); positions dict keyed by objectives.csv names.tools/asi_z_bringup.py --turret/--turret-slot N: read-onlyW F+ busy probes and a timestamped rotation test for the remaining hardware unknowns.Config
No SN/port/baud needed when the LS50 Z stage is enabled — the turret rides its connection.
Tests
31 turret tests (simulation + real-over-scripted-serial: raw-slot framing, busy polling, timeout-still-restores-Z, probe seeding/degradation, close-ownership shared vs owned, KeyError dialog path) + simulated end-to-end
Microscopebuilds incl. startup objective selection and PI+ASI mutual-exclusion. Affected suites: 225 passed.Parked hardware questions (answered by the bring-up probe before enabling)
W Freport the slot (and in what format)? Driver degrades to software tracking either way./busy byte cover turret rotation? If not, the timestamped probe shows it and a settle-wait follow-up is trivial.🤖 Generated with Claude Code