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

UI tests #9

Merged
merged 10 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/pre_commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Tests

on:
pull_request:
push:
branches: [main]

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: pre-commit/action@v3.0.1
26 changes: 26 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Tests
on: [push, pull_request]
jobs:
Linux:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os : [ubuntu-latest]
python: ["3.12"]
env:
DISPLAY: ':99.0'
steps:
- name: get repo
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
- uses: tlambert03/setup-qt-libs@v1
- name: build "display"
run: |
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX
- name: pytest
run: |
pip install -e .[test]
pytest -sv
24 changes: 0 additions & 24 deletions .github/workflows/workflows.yml

This file was deleted.

19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Motor stage UI
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Tests](https://github.com/SiLab-Bonn/motor_stage_ui/actions/workflows/tests.yml/badge.svg)](https://github.com/SiLab-Bonn/motor_stage_ui/actions/workflows/tests.yml)

Offers both a terminal and graphical user interface for the [C-863 Mercury controller](https://www.le.infn.it/~chiodini/allow_listing/pi/Manuals/C-863_UserManual_MS205E200.pdf) (Check also the [commands](https://twiki.cern.ch/twiki/bin/viewfile/ILCBDSColl/Phase2Preparations?rev=1;filename=MercuryNativeCommands_MS176E101.pdf)).
Motor stages can be arranged in daisy chains.
Expand Down Expand Up @@ -58,3 +59,21 @@ The step size of a specific stage is given in um for translation stages and deg
| `stop` | `Stop` | Immediately stops all movement of the stage | motor_name (str): name of the motorstage | - |
| `sethome` | `Set Zero` | Sets the current position of the stage as new origin | motor_name (str): name of the motorstage | - |
| `gohome` | `MV. Zero` | Goes to origin of the stage | motor_name (str): name of the motorstage | - |
| `status` | -` | Returns the status of the motor controller | motor_name (str): name of the motorstage | - |

## Tests

General UI tests, utilizing a motor controller mock, are performed when setting the environmental variable `TEST` e.g.:

```bash
TEST=True motor init x_axis
```

```bash
TEST=True motorgui
```
Utilizing [pytest](https://docs.pytest.org/en/stable/) the mock tests the software.

```bash
pytest -sv
```
22 changes: 17 additions & 5 deletions motor_stage_ui/motor_stage_gui.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from motor_stage_ui.pi_stages_interface import PIStagesInterface
from motor_stage_ui.pi_stages_interface import SerialInterface
from motor_stage_ui.test.utils import SerialInterfaceMock
from motor_stage_ui import logger
import motor_stage_ui

import yaml
import logging
from PyQt5.QtCore import QSize, Qt, QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLineEdit, QLabel
import sys
import os
from pathlib import Path

"""

Expand All @@ -17,7 +19,7 @@


class MainWindow(QMainWindow):
def __init__(self, config_path):
def __init__(self, config_path, interface=SerialInterface):
super().__init__()

self.log = logger.setup_main_logger(__class__.__name__, logging.DEBUG)
Expand All @@ -35,6 +37,7 @@ def __init__(self, config_path):
PIStagesInterface(
port=self.conf[motor]["port"],
baud_rate=self.conf[motor]["baud_rate"],
interface=interface,
)
)
stage = self.conf[motor]["stage_type"]
Expand Down Expand Up @@ -355,10 +358,19 @@ def get_position_clicked(


def main():
app = QApplication(sys.argv)
try:
if os.environ["TEST"]:
path = Path(__file__).parent / "test"
config_path = path / "test_configuration.yaml"
interface = SerialInterfaceMock

except KeyError:
path = Path(__file__).parent
config_path = path / "configuration.yaml"
interface = SerialInterface

path = os.path.dirname(motor_stage_ui.__file__)
window = MainWindow(path + "/configuration.yaml")
app = QApplication(sys.argv)
window = MainWindow(config_path, interface=interface)
window.show()

app.exec()
Expand Down
68 changes: 64 additions & 4 deletions motor_stage_ui/motor_stage_terminal.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from motor_stage_ui.pi_stages_interface import PIStagesInterface as MC
import motor_stage_ui
from motor_stage_ui.pi_stages_interface import SerialInterface
from motor_stage_ui.test.utils import SerialInterfaceMock

from pathlib import Path
import os
import click
import yaml
Expand All @@ -23,12 +26,24 @@ def motor(conf):
Args:
conf (str): Needs path to configuration yaml. This path is converted into a click object and passed to the individual functions.
"""
try:
if os.environ["TEST"]:
path = Path(__file__).parent / "test"
config_path = path / "test_configuration.yaml"
test = True

except KeyError:
path = Path(__file__).parent
config_path = path / "configuration.yaml"
test = False

conf.ensure_object(dict)
path = os.path.dirname(motor_stage_ui.__file__)
config_path = path + "/configuration.yaml"
with open(config_path, "r") as file:
conf.obj["CONF"] = yaml.full_load(file)
if test:
conf.obj["MOCK"] = True
else:
conf.obj["MOCK"] = False


@click.command()
Expand All @@ -40,9 +55,14 @@ def init(conf, motor_name: str):
Args:
motor_name (str): name of the motorstage
"""
if conf.obj["MOCK"]:
interface = SerialInterfaceMock
else:
interface = SerialInterface
mc = MC(
port=conf.obj["CONF"][motor_name]["port"],
baud_rate=conf.obj["CONF"][motor_name]["baud_rate"],
interface=interface,
)
mc.init_motor(conf.obj["CONF"][motor_name]["address"])

Expand All @@ -59,9 +79,14 @@ def move(conf, motor_name: str, a: str):
motor_name (str): name of the motorstage
a (str): Move amount
"""
if conf.obj["MOCK"]:
interface = SerialInterfaceMock
else:
interface = SerialInterface
mc = MC(
port=conf.obj["CONF"][motor_name]["port"],
baud_rate=conf.obj["CONF"][motor_name]["baud_rate"],
interface=interface,
)
mc.move_relative(
conf.obj["CONF"][motor_name]["address"],
Expand All @@ -84,9 +109,14 @@ def moveto(conf, motor_name: str, a: str):
motor_name (str): name of the motorstage
a (str): Move to position
"""
if conf.obj["MOCK"]:
interface = SerialInterfaceMock
else:
interface = SerialInterface
mc = MC(
port=conf.obj["CONF"][motor_name]["port"],
baud_rate=conf.obj["CONF"][motor_name]["baud_rate"],
interface=interface,
)
mc.move_to_position(
conf.obj["CONF"][motor_name]["address"],
Expand All @@ -106,9 +136,14 @@ def pos(conf, motor_name: str):
Args:
motor_name (str): name of the motorstage
"""
if conf.obj["MOCK"]:
interface = SerialInterfaceMock
else:
interface = SerialInterface
mc = MC(
port=conf.obj["CONF"][motor_name]["port"],
baud_rate=conf.obj["CONF"][motor_name]["baud_rate"],
interface=interface,
)
click.echo(
"Position of: "
Expand All @@ -119,7 +154,7 @@ def pos(conf, motor_name: str):
conf.obj["CONF"][motor_name]["address"],
conf.obj["CONF"][motor_name]["unit"],
conf.obj["CONF"][motor_name]["stage_type"],
conf.obj["CONF"][motor_name]["step_size"],
float(conf.obj["CONF"][motor_name]["step_size"]),
)
)
+ " "
Expand All @@ -136,9 +171,14 @@ def stop(conf, motor_name: str):
Args:
motor_name (str): name of the motorstage
"""
if conf.obj["MOCK"]:
interface = SerialInterfaceMock
else:
interface = SerialInterface
mc = MC(
port=conf.obj["CONF"][motor_name]["port"],
baud_rate=conf.obj["CONF"][motor_name]["baud_rate"],
interface=interface,
)
mc.abort(conf.obj["CONF"][motor_name]["address"])

Expand All @@ -152,9 +192,14 @@ def sethome(conf, motor_name: str):
Args:
motor_name (str): name of the motorstage
"""
if conf.obj["MOCK"]:
interface = SerialInterfaceMock
else:
interface = SerialInterface
mc = MC(
port=conf.obj["CONF"][motor_name]["port"],
baud_rate=conf.obj["CONF"][motor_name]["baud_rate"],
interface=interface,
)
mc.set_home(conf.obj["CONF"][motor_name]["address"])

Expand All @@ -168,9 +213,14 @@ def gohome(conf, motor_name: str):
Args:
motor_name (str): name of the motorstage
"""
if conf.obj["MOCK"]:
interface = SerialInterfaceMock
else:
interface = SerialInterface
mc = MC(
port=conf.obj["CONF"][motor_name]["port"],
baud_rate=conf.obj["CONF"][motor_name]["baud_rate"],
interface=interface,
)
mc.go_home(conf.obj["CONF"][motor_name]["address"])

Expand All @@ -184,11 +234,21 @@ def status(conf, motor_name: str):
Args:
motor_name (str): name of the motorstage
"""
if conf.obj["MOCK"]:
interface = SerialInterfaceMock
else:
interface = SerialInterface
mc = MC(
port=conf.obj["CONF"][motor_name]["port"],
baud_rate=conf.obj["CONF"][motor_name]["baud_rate"],
interface=interface,
)
click.echo(
"Status of: "
+ motor_name
+ " "
+ str(mc.get_stat(conf.obj["CONF"][motor_name]["address"]))
)
mc.get_stat(conf.obj["CONF"][motor_name]["address"])


motor.add_command(init)
Expand Down
17 changes: 8 additions & 9 deletions motor_stage_ui/pi_stages_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,8 @@ def get_stat(self, address: int) -> None:
address (int): Address of the motorstage
"""
err_msg = self._write_read("TS", address)
self.log.warning(
"Status of motor stage with address %i: %s" % (address, err_msg)
)
self.log.debug("Status of motor stage with address %i: %s" % (address, err_msg))
return err_msg

def abort(self, address: int) -> None:
"""Stops all movement of the motorstage.
Expand Down Expand Up @@ -269,7 +268,7 @@ def get_position(
address (int): Address of the motorstage
unit (str): output unit
stage (int): stage type either 'rotation' or translation
step_size (int): step size of the motorstage given in deg or um
step_size (float): step size of the motorstage given in deg or um

Returns:
str: current position of motorstage in unit 3 digits precision respectively
Expand Down Expand Up @@ -329,7 +328,7 @@ def _get_position(self, address: int) -> None:
return msg

def _calculate_value(
self, amount: str, unit: str, stage: str, step_size: float
self, amount: str, unit: str, stage: str, step_size: float | str
) -> int:
"""Calculates number of motor steps from a given input amount and the given units

Expand All @@ -344,12 +343,12 @@ def _calculate_value(
"""
if stage == "translation":
try:
pos = self.ureg(amount).to("um").magnitude / step_size
pos = self.ureg(amount).to("um").magnitude / float(step_size)
except:
pos = self.ureg(amount + unit).to("um").magnitude / step_size
pos = self.ureg(amount + unit).to("um").magnitude / float(step_size)
if stage == "rotation":
try:
pos = self.ureg(amount).to("deg").magnitude / step_size
pos = self.ureg(amount).to("deg").magnitude / float(step_size)
except:
pos = self.ureg(amount + unit).to("deg").magnitude / step_size
pos = self.ureg(amount + unit).to("deg").magnitude / float(step_size)
return pos
Loading
Loading