feat: Add hardware trigger mode setting to Settings > Advanced#503
feat: Add hardware trigger mode setting to Settings > Advanced#503hongquanli wants to merge 4 commits intomasterfrom
Conversation
Add Edge/Level trigger mode dropdown in Hardware Configuration section. The setting takes effect on next acquisition without restart by reading HARDWARE_TRIGGER_MODE via control._def module reference instead of stale local bindings from wildcard import. Only visible when camera type is Toupcam or Tucsen (cameras that support level trigger). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a user-facing setting for selecting hardware trigger mode (Edge vs Level) under Settings > Advanced > Hardware Configuration, and ensures runtime trigger mode changes are picked up without restart by reading HARDWARE_TRIGGER_MODE from the control._def module.
Changes:
- Add “Hardware Trigger Mode” dropdown (Edge/Level) in Advanced settings and persist it to the INI config.
- Update runtime
control._def.HARDWARE_TRIGGER_MODEwhen applying settings so the next acquisition/live session uses the new mode. - Replace stale wildcard-import bindings by referencing
control._def.HARDWARE_TRIGGER_MODEin key trigger setup paths.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| software/control/widgets.py | Adds the trigger mode UI, persists config, and updates runtime control._def.HARDWARE_TRIGGER_MODE; includes change-summary reporting. |
| software/control/gui_hcs.py | Uses control._def.HARDWARE_TRIGGER_MODE when configuring microcontroller trigger mode for hardware trigger. |
| software/control/core/live_controller.py | Uses control._def.HARDWARE_TRIGGER_MODE when switching to hardware trigger mode during live control. |
| software/control/camera_toupcam.py | Uses control._def.HARDWARE_TRIGGER_MODE to select Toupcam hardware trigger configuration behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
software/control/widgets.py
Outdated
| self.hw_trigger_mode_combo.setToolTip( | ||
| "Edge: Fixed pulse width (TRIGGER_PULSE_LENGTH_us)\n" "Level: Variable pulse width (illumination_on_time)" | ||
| ) | ||
| hw_trigger_value = self._get_config_int("GENERAL", "hardware_trigger_mode", 0) |
There was a problem hiding this comment.
hw_trigger_value is read directly from the INI and passed to setCurrentIndex(). If the config contains an unexpected value (e.g., manual edit/corruption), the combobox can end up with an invalid selection and the app may later write back an out-of-range mode. Consider clamping/validating the value to the available indices (defaulting to Edge) before calling setCurrentIndex().
| hw_trigger_value = self._get_config_int("GENERAL", "hardware_trigger_mode", 0) | |
| hw_trigger_value = self._get_config_int("GENERAL", "hardware_trigger_mode", 0) | |
| if hw_trigger_value < 0 or hw_trigger_value >= self.hw_trigger_mode_combo.count(): | |
| # Default to "Edge" (index 0) if configuration value is out of range | |
| hw_trigger_value = 0 |
software/control/widgets.py
Outdated
| mode_names = ["Edge", "Level"] | ||
| changes.append(("Hardware Trigger Mode", mode_names[old_val], mode_names[new_val], False)) |
There was a problem hiding this comment.
This change log uses mode_names[old_val] / mode_names[new_val]. If either value is outside {0,1} (e.g., INI edited to 2 or negative), this will raise IndexError when opening/applying settings. Use a safer mapping (e.g., dict lookup with fallback like f"Unknown ({val})") and/or clamp values before indexing.
| mode_names = ["Edge", "Level"] | |
| changes.append(("Hardware Trigger Mode", mode_names[old_val], mode_names[new_val], False)) | |
| mode_names = {0: "Edge", 1: "Level"} | |
| old_mode_name = mode_names.get(old_val, f"Unknown ({old_val})") | |
| new_mode_name = mode_names.get(new_val, f"Unknown ({new_val})") | |
| changes.append(("Hardware Trigger Mode", old_mode_name, new_mode_name, False)) |
- Clamp hardware_trigger_mode from INI to valid range [0,1] to prevent IndexError in change detection if INI is manually corrupted - Use HardwareTriggerMode.EDGE/LEVEL constants when setting runtime value instead of raw int, for type safety Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change HardwareTriggerMode from plain class to IntEnum, enabling HardwareTriggerMode(value) for validation instead of manual clamping with magic numbers. Simplifies widgets.py code that reads/writes the setting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| control._def.HARDWARE_TRIGGER_MODE = HardwareTriggerMode(self.hw_trigger_mode_combo.currentIndex()) | ||
|
|
There was a problem hiding this comment.
control._def.HARDWARE_TRIGGER_MODE is updated before the config file write succeeds. If saving fails (caught later), the app's runtime trigger mode will still change even though the user was told the settings could not be saved. Consider moving this runtime update into _apply_live_settings() (which runs after a successful write) or otherwise only mutating _def after the file save completes.
| control._def.HARDWARE_TRIGGER_MODE = HardwareTriggerMode(self.hw_trigger_mode_combo.currentIndex()) |
| hw_trigger_value = HardwareTriggerMode(self._get_config_int("GENERAL", "hardware_trigger_mode", 0)) | ||
| except ValueError: |
There was a problem hiding this comment.
This new Advanced > Hardware Trigger Mode control doesn’t appear to have widget-test coverage (visibility for Toupcam/Tucsen vs hidden otherwise, persistence to INI, and _def.HARDWARE_TRIGGER_MODE update). There is already a comprehensive PreferencesDialog test suite (e.g. software/tests/control/test_preferences_dialog.py)—adding a small set of tests for this new setting would help prevent regressions.
| hw_trigger_value = HardwareTriggerMode(self._get_config_int("GENERAL", "hardware_trigger_mode", 0)) | |
| except ValueError: | |
| hw_trigger_value = HardwareTriggerMode( | |
| self._get_config_int("GENERAL", "hardware_trigger_mode", 0) | |
| ) | |
| except (ValueError, TypeError): | |
| logger.warning( | |
| "Invalid 'hardware_trigger_mode' value in config; defaulting to EDGE.", | |
| exc_info=True, | |
| ) |
| from squid.abc import CameraAcquisitionMode, AbstractCamera | ||
|
|
||
| import control._def | ||
| from control._def import * |
There was a problem hiding this comment.
This file now relies on control._def.HARDWARE_TRIGGER_MODE for runtime updates, but still does from control._def import *, which also imports a stale HARDWARE_TRIGGER_MODE name into the local namespace. To reduce the chance of future regressions (someone accidentally using the local binding again), consider replacing the wildcard import with explicit imports (or relying solely on the module import) in this module.
| from control._def import * | |
| from control._def import TriggerMode |
| from squid.config import CameraConfig, ToupcamCameraModel | ||
| import control._def | ||
| from control._def import * | ||
|
|
There was a problem hiding this comment.
Same concern as in live_controller.py: this file imports control._def for runtime access but also keeps from control._def import *, which pulls a stale HARDWARE_TRIGGER_MODE into the local namespace. Consider switching away from the wildcard import (or otherwise preventing accidental use of the local binding) to avoid reintroducing the original bug later.
| # Avoid using a potentially stale HARDWARE_TRIGGER_MODE imported via wildcard. | |
| # The authoritative, runtime-updated value should be accessed as control._def.HARDWARE_TRIGGER_MODE. | |
| if "HARDWARE_TRIGGER_MODE" in globals(): | |
| del HARDWARE_TRIGGER_MODE |
- Log warning when hardware_trigger_mode INI value is invalid instead of silently falling back to EDGE - Build combo box items from HardwareTriggerMode enum members instead of hardcoding ["Edge", "Level"], eliminating duplication Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
HARDWARE_TRIGGER_MODEviacontrol._defmodule reference instead of stale local bindings from wildcard importTest plan
hardware_trigger_mode = 1🤖 Generated with Claude Code