Use semver library for checking sangaboard firmware version.

This commit is contained in:
Julian Stirling 2025-12-02 11:55:56 +00:00
parent 797dda3514
commit 240b7d1ba5
3 changed files with 83 additions and 31 deletions

View file

@ -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]

View file

@ -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,7 +18,8 @@ from . import BaseStage
LOGGER = logging.getLogger(__name__) LOGGER = logging.getLogger(__name__)
REQUIRED_VERSION = (1, 0, 4) REQUIRED_VERSION = semver.Version.parse("1.0.0")
RECOMMENDED_VERSION = semver.Version.parse("1.0.4")
class SangaboardThing(BaseStage): class SangaboardThing(BaseStage):
@ -89,40 +91,31 @@ class SangaboardThing(BaseStage):
) )
def check_firmware(self) -> None: def check_firmware(self) -> None:
"""Check the Sangaboard firmware version and log warnings or info messages. """Error/warn if firmware doesn't meet requirements/recommendations.
Logs a warning if the numeric firmware version is below ``REQUIRED_VERSION``. Raise a Runtime Error if the version is below REQUIRED_VERSION
Logs an info message if the version ends with ``'-dev'``. Malformed version
strings trigger a warning. Log a warning if the version is below RECOMMENDED_VERSION
""" """
with self.sangaboard() as sb: with self.sangaboard() as sb:
version = sb.firmware_version # 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)
# Remove '-dev' for numeric comparison # Warn if version is below required
is_dev = version.endswith("-dev") if version < REQUIRED_VERSION:
base_version = version[:-4] if is_dev else version raise RuntimeError(
f"Sangaboard firmware version {version} is below the required "
# Parse the numeric part f"{REQUIRED_VERSION}."
parts = base_version.split(".")
try:
major, minor, patch = (int(p) for p in parts)
except ValueError:
LOGGER.warning(
f"Unrecognized Sangaboard firmware version format: {version}"
)
return
current = (major, minor, patch)
# Warn if version is below minimum
if current < REQUIRED_VERSION:
LOGGER.warning(
f"Sangaboard firmware version {version} is below the required 1.0.4."
) )
# Log dev info # Warn if version is below recommended
if is_dev: if version < RECOMMENDED_VERSION:
LOGGER.info(f"Firmware version {version} is a development build.") LOGGER.warning(
f"Sangaboard firmware version {version} is below the recommended "
f"{RECOMMENDED_VERSION}."
)
def _hardware_move_relative( def _hardware_move_relative(
self, self,

View file

@ -1,8 +1,14 @@
"""Tests for the Sangaboard thing.""" """Tests for the Sangaboard thing."""
import logging
import pytest import pytest
from openflexure_microscope_server.things.stage.sangaboard import SangaboardThing from openflexure_microscope_server.things.stage.sangaboard import (
SangaboardThing,
REQUIRED_VERSION,
RECOMMENDED_VERSION,
)
@pytest.fixture @pytest.fixture
@ -15,6 +21,58 @@ def mock_sanga_thing(mocker):
def test_check_old_firmware(mock_sanga_thing): def test_check_old_firmware(mock_sanga_thing):
"""Check firmware prior to version 1 throws a Runtime Error.""" """Check firmware prior to version 1 throws a Runtime Error."""
mock_sanga_thing._sangaboard.firmware_version.return_value = "0.1.1" mock_sanga_thing._sangaboard.firmware_version = "0.1.1"
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
mock_sanga_thing.check_firmware() 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