Skip to content

feat(turret): per-machine objective turret pulse offset#575

Open
Alpaca233 wants to merge 1 commit into
masterfrom
feat/objective-turret-offset
Open

feat(turret): per-machine objective turret pulse offset#575
Alpaca233 wants to merge 1 commit into
masterfrom
feat/objective-turret-offset

Conversation

@Alpaca233

Copy link
Copy Markdown
Collaborator

Problem

Normally the objective turret's homing limit switch coincides with slot 1: after home() seeks the switch and zeroes the position counter there, every objective lands at the correct angle. On one unit the switch does not sit exactly at slot 1, so all four objectives are rotated by the same small constant.

Change

Adds a per-machine config knob OBJECTIVE_TURRET_OFFSET_PULSES (default 0, may be negative) — a single global offset added to every slot target in _rotate_to, shifting the whole turret frame uniformly relative to the homing zero:

target_pulses = (position_index - 1) * self._pulses_per_position + self._offset_pulses

Set it per machine in that unit's configuration*.ini:

[GENERAL]
objective_turret_offset_pulses = <measured, may be negative>

Every other machine keeps 0 → unchanged behavior.

Why software, not the hardware homing register

The NiMotion's own REG_HOMING_OFFSET (0x0069) can't carry this correction:

  • home() unconditionally issues SET_ZERO after the limit-switch seek, re-zeroing the counter at the physical stop — cancelling any homing-register offset.
  • _HOMING_PARAMS actively forces REG_HOMING_OFFSET back to 0 on every connect (and saves to EEPROM).

_rotate_to is the single place a slot index becomes an absolute pulse target, so it's the right seam to apply the correction. home() is untouched — the counter still zeros at the switch.

Naming

Named OBJECTIVE_TURRET_OFFSET_PULSES (not HOME_OFFSET or SLOT1_OFFSET) because it's a single global offset applied uniformly to all slots, acting at move time rather than home time — and to avoid colliding with the file's hardware-homing terms (HOMING_ORIGIN_OFFSET, REG_HOMING_OFFSET) that this value deliberately does not touch.

Validation

The value is parsed from the machine .ini (via json.loads), so it can come back as a float/string/bool. The controller validates the resolved offset is an int at init and raises ValueError otherwise, so misconfiguration fails fast with a clear message rather than deep in the signed Modbus write.

Plumbing

Threaded through the controller constructor mirroring the existing positions fallback, wired from microscope.py's shared turret_kwargs. The simulation twin accepts the kwarg for constructor parity (it tracks objectives by name and never computes pulses).

Tests

tests/control/test_objective_turret_controller.py (29 pass, black clean):

  • default (no offset) → slot N targets (N−1)·pulses_per_position
  • explicit offset → added to every slot target across all 4 slots
  • negative offset → verified through the signed 32-bit write / tolerance path
  • non-int offset (37.5, "30", True) → raises ValueError at init
  • _def fallback when the kwarg isn't passed
  • simulation twin accepts the kwarg

🤖 Generated with Claude Code

Normally the objective turret's homing limit switch coincides with slot 1,
so after homing (which zeroes the counter at the switch) every objective
lands at the correct angle. On one unit the switch does not sit exactly at
the first slot, so all objectives are rotated by the same small constant.

Add a per-machine config knob OBJECTIVE_TURRET_OFFSET_PULSES (default 0, may
be negative), loaded from the machine .ini [GENERAL] section and added to
every slot target in _rotate_to as a single global offset. The NiMotion
hardware homing-offset register can't carry this: home() unconditionally
issues SET_ZERO after the seek (re-zeroing the counter at the switch) and
_HOMING_PARAMS forces the register to 0 on every connect, so the correction
is applied purely in software.

The value is parsed from the machine .ini (via json.loads) and could be a
float/str/bool, so it is validated to be an int at controller init (raising
ValueError otherwise). Threaded through the controller constructor mirroring
the existing `positions` fallback and wired from microscope.py; the
simulation twin accepts the kwarg for constructor parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a per-machine objective turret pulse offset so units whose homing limit switch is not exactly aligned with slot 1 can apply a uniform correction in software without changing homing behavior.

Changes:

  • Introduces OBJECTIVE_TURRET_OFFSET_PULSES in control._def and plumbs it through microscope construction into the objective turret controller.
  • Applies the offset in ObjectiveTurret4PosController._rotate_to() when computing absolute target pulses.
  • Adds unit tests covering default/positive/negative offsets, non-int validation, _def fallback, and simulation kwarg parity.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
software/control/objective_turret_controller.py Adds offset_pulses init + validation and applies offset to computed slot target pulses.
software/control/microscope.py Passes the per-machine offset into the turret controller kwargs (real + simulation).
software/control/_def.py Defines OBJECTIVE_TURRET_OFFSET_PULSES default and documents intended usage.
software/tests/control/test_objective_turret_controller.py Adds test coverage for offset behavior, validation, and simulation constructor parity.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +267 to +272
offset = offset_pulses if offset_pulses is not None else OBJECTIVE_TURRET_OFFSET_PULSES
# Comes from per-machine .ini parsing; reject non-int so misconfiguration fails
# fast here instead of deep in the signed Modbus write (bool is an int subclass).
if isinstance(offset, bool) or not isinstance(offset, int):
raise ValueError(f"OBJECTIVE_TURRET_OFFSET_PULSES must be an integer number of pulses, got {offset!r}")
self._offset_pulses = offset
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants