Merge branch 'Sanga-firmware-check' into 'v3'
Log an error if the sangaboard firmware is below v1.0.4 Closes #623 See merge request openflexure/openflexure-microscope-server!447
This commit is contained in:
commit
38c596fb49
3 changed files with 112 additions and 5 deletions
|
|
@ -30,6 +30,7 @@ dependencies = [
|
||||||
"piexif",
|
"piexif",
|
||||||
"pydantic ~= 2.10.6",
|
"pydantic ~= 2.10.6",
|
||||||
"simplejpeg >= 1.8.2",
|
"simplejpeg >= 1.8.2",
|
||||||
|
"semver ~= 3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from types import TracebackType
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
|
|
||||||
|
import semver
|
||||||
import sangaboard
|
import sangaboard
|
||||||
import labthings_fastapi as lt
|
import labthings_fastapi as lt
|
||||||
|
|
||||||
|
|
@ -17,6 +18,9 @@ from . import BaseStage
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
REQUIRED_VERSION = semver.Version.parse("1.0.0")
|
||||||
|
RECOMMENDED_VERSION = semver.Version.parse("1.0.4")
|
||||||
|
|
||||||
|
|
||||||
class SangaboardThing(BaseStage):
|
class SangaboardThing(BaseStage):
|
||||||
"""A Thing to manage a Sangaboard motor controller.
|
"""A Thing to manage a Sangaboard motor controller.
|
||||||
|
|
@ -42,18 +46,15 @@ class SangaboardThing(BaseStage):
|
||||||
"""
|
"""
|
||||||
self.sangaboard_kwargs = copy(kwargs)
|
self.sangaboard_kwargs = copy(kwargs)
|
||||||
self.sangaboard_kwargs["port"] = port
|
self.sangaboard_kwargs["port"] = port
|
||||||
|
self._sangaboard_lock = threading.RLock()
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
def __enter__(self) -> None:
|
||||||
"""Connect to the sangaboard when the Thing context manager is opened."""
|
"""Connect to the sangaboard when the Thing context manager is opened."""
|
||||||
self._sangaboard = sangaboard.Sangaboard(**self.sangaboard_kwargs)
|
self._sangaboard = sangaboard.Sangaboard(**self.sangaboard_kwargs)
|
||||||
self._sangaboard_lock = threading.RLock()
|
|
||||||
with self.sangaboard() as sb:
|
with self.sangaboard() as sb:
|
||||||
if sb.version_tuple[0] != 1:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Please update your Sangaboard Firmware. v1 is required."
|
|
||||||
)
|
|
||||||
sb.query("blocking_moves false")
|
sb.query("blocking_moves false")
|
||||||
|
self.check_firmware()
|
||||||
self.update_position()
|
self.update_position()
|
||||||
|
|
||||||
def __exit__(
|
def __exit__(
|
||||||
|
|
@ -89,6 +90,33 @@ class SangaboardThing(BaseStage):
|
||||||
zip(self.axis_names, sb.position, strict=True)
|
zip(self.axis_names, sb.position, strict=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def check_firmware(self) -> None:
|
||||||
|
"""Error/warn if firmware doesn't meet requirements/recommendations.
|
||||||
|
|
||||||
|
Raise a Runtime Error if the version is below REQUIRED_VERSION
|
||||||
|
|
||||||
|
Log a warning if the version is below RECOMMENDED_VERSION
|
||||||
|
"""
|
||||||
|
with self.sangaboard() as sb:
|
||||||
|
# This will raise a ValueError is if the firmware version cannot be parsed
|
||||||
|
# this error will stop the microscope booting as we cannot ensure valid
|
||||||
|
# firmware.
|
||||||
|
version = semver.Version.parse(sb.firmware_version)
|
||||||
|
|
||||||
|
# Raise an error if version is below required
|
||||||
|
if version < REQUIRED_VERSION:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Sangaboard firmware version {version} is below the required "
|
||||||
|
f"{REQUIRED_VERSION}."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Warn if version is below recommended
|
||||||
|
if version < RECOMMENDED_VERSION:
|
||||||
|
LOGGER.warning(
|
||||||
|
f"Sangaboard firmware version {version} is below the recommended "
|
||||||
|
f"{RECOMMENDED_VERSION}."
|
||||||
|
)
|
||||||
|
|
||||||
def _hardware_move_relative(
|
def _hardware_move_relative(
|
||||||
self,
|
self,
|
||||||
cancel: lt.deps.CancelHook,
|
cancel: lt.deps.CancelHook,
|
||||||
|
|
|
||||||
78
tests/test_sangaboard.py
Normal file
78
tests/test_sangaboard.py
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
"""Tests for the Sangaboard thing."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from openflexure_microscope_server.things.stage.sangaboard import (
|
||||||
|
SangaboardThing,
|
||||||
|
REQUIRED_VERSION,
|
||||||
|
RECOMMENDED_VERSION,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_sanga_thing(mocker):
|
||||||
|
"""Return a Sangaboard thing with a MagicMock for self._sangaboard."""
|
||||||
|
sanga_thing = SangaboardThing()
|
||||||
|
sanga_thing._sangaboard = mocker.MagicMock()
|
||||||
|
return sanga_thing
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_old_firmware(mock_sanga_thing):
|
||||||
|
"""Check firmware prior to version 1 throws a Runtime Error."""
|
||||||
|
mock_sanga_thing._sangaboard.firmware_version = "0.1.1"
|
||||||
|
with pytest.raises(RuntimeError):
|
||||||
|
mock_sanga_thing.check_firmware()
|
||||||
|
|
||||||
|
|
||||||
|
def test_firmware_before_recommended(mock_sanga_thing, caplog):
|
||||||
|
"""Check required version warns if it is lower than the recommended version."""
|
||||||
|
if REQUIRED_VERSION == RECOMMENDED_VERSION:
|
||||||
|
# If required is currently the same as recommended then this test isn't valid
|
||||||
|
return
|
||||||
|
mock_sanga_thing._sangaboard.firmware_version = str(REQUIRED_VERSION)
|
||||||
|
with caplog.at_level(logging.WARNING):
|
||||||
|
mock_sanga_thing.check_firmware()
|
||||||
|
assert len(caplog.records) == 1
|
||||||
|
msg = (
|
||||||
|
f"Sangaboard firmware version {REQUIRED_VERSION} is below the recommended "
|
||||||
|
f"{RECOMMENDED_VERSION}."
|
||||||
|
)
|
||||||
|
assert caplog.records[0].message == msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_dev_is_before_recommended(mock_sanga_thing, caplog):
|
||||||
|
"""Check that a dev version of the recommended version warns."""
|
||||||
|
if REQUIRED_VERSION == RECOMMENDED_VERSION:
|
||||||
|
# If required is currently the same as recommended then this test isn't valid
|
||||||
|
return
|
||||||
|
dev_version = RECOMMENDED_VERSION.replace(prerelease="dev1")
|
||||||
|
mock_sanga_thing._sangaboard.firmware_version = str(dev_version)
|
||||||
|
with caplog.at_level(logging.WARNING):
|
||||||
|
mock_sanga_thing.check_firmware()
|
||||||
|
assert len(caplog.records) == 1
|
||||||
|
msg = (
|
||||||
|
f"Sangaboard firmware version {dev_version} is below the recommended "
|
||||||
|
f"{RECOMMENDED_VERSION}."
|
||||||
|
)
|
||||||
|
assert caplog.records[0].message == msg
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_firmware(mock_sanga_thing, caplog):
|
||||||
|
"""Check valid firmware doesn't error."""
|
||||||
|
versions = [
|
||||||
|
RECOMMENDED_VERSION, # Recommended
|
||||||
|
RECOMMENDED_VERSION.bump_major(), # Higher versions
|
||||||
|
RECOMMENDED_VERSION.bump_minor(),
|
||||||
|
RECOMMENDED_VERSION.bump_patch(),
|
||||||
|
RECOMMENDED_VERSION.bump_patch().replace(prerelease="dev1"),
|
||||||
|
]
|
||||||
|
# Note that dev1 will not warn in check version, but will warn in the underlying
|
||||||
|
# Sangaboard library as it is a pre-release.
|
||||||
|
|
||||||
|
for version in versions:
|
||||||
|
mock_sanga_thing._sangaboard.firmware_version = str(version)
|
||||||
|
with caplog.at_level(logging.WARNING):
|
||||||
|
mock_sanga_thing.check_firmware()
|
||||||
|
assert len(caplog.records) == 0
|
||||||
Loading…
Add table
Add a link
Reference in a new issue