Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display mirror angle #680

Merged
merged 14 commits into from
Nov 14, 2024
Merged
33 changes: 32 additions & 1 deletion finesse/gui/stepper_motor_view.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Code for controlling the stepper motor which moves the mirror."""

from pubsub import pub
from PySide6.QtWidgets import QButtonGroup, QGridLayout, QPushButton, QSpinBox
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QButtonGroup, QGridLayout, QLabel, QPushButton, QSpinBox

from finesse.config import ANGLE_PRESETS, STEPPER_MOTOR_TOPIC
from finesse.gui.device_panel import DevicePanel
Expand Down Expand Up @@ -37,8 +38,23 @@ def __init__(self) -> None:
layout.addWidget(self.angle, 1, 2)
layout.addWidget(self.goto, 1, 3)

# Create widgets to show the current mirror position
layout.addWidget(QLabel("Current position"), 0, 4)
self.mirror_position_display = QLabel()
self.mirror_position_display.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self.mirror_position_display, 1, 4)

self.setLayout(layout)

pub.subscribe(
self._indicate_moving,
f"device.{STEPPER_MOTOR_TOPIC}.move.begin",
)
pub.subscribe(
self._update_mirror_position_display,
f"device.{STEPPER_MOTOR_TOPIC}.move.end",
)

def _add_checkable_button(self, name: str) -> QPushButton:
"""Add a selectable button to button_group."""
btn = QPushButton(name)
Expand All @@ -53,5 +69,20 @@ def _preset_clicked(self, btn: QPushButton) -> None:
# If the motor is already moving, stop it now
pub.sendMessage(f"device.{STEPPER_MOTOR_TOPIC}.stop")

pub.sendMessage(f"device.{STEPPER_MOTOR_TOPIC}.notify_on_stopped")
target = float(self.angle.value()) if btn is self.goto else btn.text().lower()
pub.sendMessage(f"device.{STEPPER_MOTOR_TOPIC}.move.begin", target=target)

def _indicate_moving(self, target) -> None:
"""Update the display the indicate that the mirror is moving."""
self.mirror_position_display.setText("Moving...")

def _update_mirror_position_display(self, moved_to: float) -> None:
"""Display the angle the mirror has moved to.

If angle corresponds to a preset, show the associated name as well as the value.
"""
text = f"{moved_to}°"
if preset := next((k for k, v in ANGLE_PRESETS.items() if v == moved_to), None):
text += f" ({preset})"
self.mirror_position_display.setText(text)
2 changes: 1 addition & 1 deletion finesse/hardware/plugins/stepper_motor/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,4 @@ def _on_move_end(self) -> None:
logging.info("Move finished")
if self._notify_requested:
self._notify_requested = False
self.send_message("move.end")
self.send_move_end_message()
8 changes: 2 additions & 6 deletions finesse/hardware/plugins/stepper_motor/st10_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import logging
from queue import Queue

from PySide6.QtCore import QThread, QTimer, Signal, Slot
from PySide6.QtCore import QThread, QTimer, Signal
from serial import Serial, SerialException, SerialTimeoutException

from finesse.config import STEPPER_MOTOR_HOMING_TIMEOUT
Expand Down Expand Up @@ -225,15 +225,11 @@ def _on_initial_move_end(self) -> None:

# For future move end messages, use a different handler
self._reader.async_read_completed.disconnect(self._on_initial_move_end)
self._reader.async_read_completed.connect(self._send_move_end_message)
self._reader.async_read_completed.connect(self.send_move_end_message)

# Signal that this device is ready to be used
self.signal_is_opened()

@Slot()
def _send_move_end_message(self) -> None:
self.send_message("move.end")

def _check_device_id(self) -> None:
"""Check that the ID is the correct one for an ST10.

Expand Down
4 changes: 4 additions & 0 deletions finesse/hardware/plugins/stepper_motor/stepper_motor_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,7 @@ def move_to(self, target: float | str) -> None:
raise ValueError("Angle must be between 0° and 270°")

self.step = round(self.steps_per_rotation * target / 360.0)

def send_move_end_message(self) -> None:
"""Send a message containing the angle moved to, once move ends."""
self.send_message("move.end", moved_to=self.angle)
27 changes: 26 additions & 1 deletion tests/gui/test_stepper_motor_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from unittest.mock import MagicMock, Mock, patch

import pytest
from PySide6.QtWidgets import QButtonGroup, QPushButton
from PySide6.QtWidgets import QButtonGroup, QLabel, QPushButton
from pytestqt.qtbot import QtBot

from finesse.config import ANGLE_PRESETS, STEPPER_MOTOR_TOPIC
Expand All @@ -29,6 +29,12 @@ def test_init(button_group_mock: Mock, qtbot: QtBot) -> None:
# Check that there's also a goto button
assert "goto" in btn_labels

# Check that mirror position widgets have been created
current_position_label = control.layout().itemAt(8).widget()
assert isinstance(current_position_label, QLabel)
assert current_position_label.text() == "Current position"
assert control.mirror_position_display.text() == ""


@pytest.mark.parametrize("preset", ANGLE_PRESETS.keys())
def test_preset_clicked(preset: str, sendmsg_mock: MagicMock, qtbot: QtBot) -> None:
Expand Down Expand Up @@ -62,3 +68,22 @@ def test_goto_clicked(sendmsg_mock: MagicMock, qtbot: QtBot) -> None:
sendmsg_mock.assert_any_call(
f"device.{STEPPER_MOTOR_TOPIC}.move.begin", target=123.0
)


def test_indicate_moving(qtbot: QtBot) -> None:
"""Test the mirror position display updates correctly."""
control = StepperMotorControl()

control._indicate_moving(target="target")
assert control.mirror_position_display.text() == "Moving..."


def test_update_mirror_position_display(qtbot: QtBot) -> None:
"""Test the mirror position display updates correctly."""
control = StepperMotorControl()

control._update_mirror_position_display(moved_to=ANGLE_PRESETS["zenith"])
assert control.mirror_position_display.text() == "180.0\u00b0 (zenith)"

control._update_mirror_position_display(moved_to=12.34)
assert control.mirror_position_display.text() == "12.34\u00b0"
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ def test_on_move_end_notify(
stepper._move_end_timer.timeout.emit()

assert not stepper._notify_requested
sendmsg_mock.assert_called_once_with(f"device.{STEPPER_MOTOR_TOPIC}.move.end")
sendmsg_mock.assert_called_once_with(
f"device.{STEPPER_MOTOR_TOPIC}.move.end", moved_to=stepper.angle
)


def test_on_move_end_no_notify(
Expand Down
18 changes: 12 additions & 6 deletions tests/hardware/plugins/stepper_motor/test_st10_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import pytest
from serial import SerialException, SerialTimeoutException

from finesse.config import STEPPER_MOTOR_TOPIC
from finesse.hardware.plugins.stepper_motor.st10_controller import (
ST10Controller,
ST10ControllerError,
Expand Down Expand Up @@ -131,16 +130,23 @@ def test_on_initial_move_end(dev: ST10Controller) -> None:
dev._on_initial_move_end
)
reader_mock.async_read_completed.connect.assert_called_once_with(
dev._send_move_end_message
dev.send_move_end_message
)
timer_mock.stop.assert_called_once_with()
signal_mock.assert_called_once_with()


def test_send_move_end_message(sendmsg_mock: MagicMock, dev: ST10Controller) -> None:
"""Test the _send_move_end_message() method."""
dev._send_move_end_message()
sendmsg_mock.assert_called_once_with(f"device.{STEPPER_MOTOR_TOPIC}.move.end")
@patch(
"finesse.hardware.plugins.stepper_motor.st10_controller.ST10Controller.angle",
new_callable=PropertyMock,
)
def test_send_move_end_message(
angle_mock: PropertyMock, sendmsg_mock: MagicMock, dev: ST10Controller
) -> None:
"""Test the send_move_end_message() method."""
angle_mock.return_value = 12.34
dev.send_move_end_message()
sendmsg_mock.assert_called_once()


def read_mock(dev: ST10Controller, return_value: str):
Expand Down