From 921534f1b16182f31349d1428b926d1f99715c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20S=C3=A1nchez-Gallego?= Date: Sun, 14 Jul 2024 21:03:46 -0700 Subject: [PATCH] Add last_exposure_no to status reply (#50) * Add last_exposure_no to status reply * Add specific test for when data was not fetched * Update changelog --- CHANGELOG.md | 4 ++++ archon/actor/commands/status.py | 2 ++ archon/actor/delegate.py | 8 ++++++++ archon/etc/schema.json | 3 ++- tests/actor/test_command_expose.py | 6 ++++++ tests/actor/test_command_status.py | 3 +++ tests/actor/test_delegate.py | 19 +++++++++++++++++++ 7 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8358b70..8701db0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ * Dropped support for Python 3.9. +### 🚀 New + +* [#50](https://github.com/sdss/archon/pull/50) Added `last_exposure_no` to the output of the `status` command. + ### ⚙️ Engineering * Lint and format using `ruff`. diff --git a/archon/actor/commands/status.py b/archon/actor/commands/status.py index 81ff98d..53f838e 100644 --- a/archon/actor/commands/status.py +++ b/archon/actor/commands/status.py @@ -48,6 +48,7 @@ async def status( "controller": controller.name, "status": controller.status.value, "status_names": [flag.name for flag in controller.status.get_flags()], + "last_exposure_no": command.actor.exposure_delegate.last_exposure_no, } ) return True @@ -57,6 +58,7 @@ async def status( "controller": controller.name, "status": controller.status.value, "status_names": [flag.name for flag in controller.status.get_flags()], + "last_exposure_no": command.actor.exposure_delegate.last_exposure_no, **status, } ) diff --git a/archon/actor/delegate.py b/archon/actor/delegate.py index 14f1484..e6577e9 100644 --- a/archon/actor/delegate.py +++ b/archon/actor/delegate.py @@ -97,6 +97,8 @@ def __init__(self, actor: Actor_co): self.expose_data: ExposeData | None = None + self.last_exposure_no: int = -1 + self.use_shutter: bool = True self.is_writing: bool = False @@ -350,6 +352,10 @@ async def readout( except Exception as err: return self.fail(f"Failed reading out: {err}") + if len(c_fdata) == 0: + self.command.error("No data was fetched.") + return False + self.command.debug(f"Readout completed in {time()-t0:.1f} seconds.") if write is False: @@ -389,6 +395,8 @@ async def readout( for fd in fdata ] + self.last_exposure_no = fdata[0]["exposure_no"] + # Prepare checksum information. write_checksum: bool = self.config["checksum.write"] checksum_mode: str = self.config.get("checksum.mode", "md5") diff --git a/archon/etc/schema.json b/archon/etc/schema.json index 5849896..bab9e25 100644 --- a/archon/etc/schema.json +++ b/archon/etc/schema.json @@ -38,7 +38,8 @@ "properties": { "controller": { "type": "string" }, "status": { "type": "integer" }, - "status_names": { "type": "array", "items": { "type": "string" } } + "status_names": { "type": "array", "items": { "type": "string" } }, + "last_exposure_no": { "type": "integer" } }, "additionalProperties": true, "required": ["controller", "status", "status_names"] diff --git a/tests/actor/test_command_expose.py b/tests/actor/test_command_expose.py index 4e3c9a4..8d49332 100644 --- a/tests/actor/test_command_expose.py +++ b/tests/actor/test_command_expose.py @@ -272,6 +272,12 @@ async def test_expose_set_window(delegate, actor: ArchonActor): hdu = fits.open(filename) assert hdu[0].data.shape == (200, 200) # type: ignore + command_status = await actor.invoke_mock_command("status -s") + await command_status + + assert command_status.status.did_succeed + assert command_status.replies[-2].body["status"]["last_exposure_no"] != -1 + async def test_expose_with_dark(delegate, actor: ArchonActor, mocker): expose_mock = mocker.patch.object(delegate, "expose", return_value=True) diff --git a/tests/actor/test_command_status.py b/tests/actor/test_command_status.py index 6aada7b..5941995 100644 --- a/tests/actor/test_command_status.py +++ b/tests/actor/test_command_status.py @@ -17,6 +17,9 @@ async def test_status(actor: ArchonActor): assert command.status.did_succeed assert len(command.replies) == 3 + assert "last_exposure_no" in command.replies[1].body["status"] + assert command.replies[1].body["status"]["last_exposure_no"] == -1 + async def test_status_fail_controller(actor: ArchonActor): await actor.controllers["sp1"].stop() diff --git a/tests/actor/test_delegate.py b/tests/actor/test_delegate.py index 4506523..2c603ad 100644 --- a/tests/actor/test_delegate.py +++ b/tests/actor/test_delegate.py @@ -264,6 +264,25 @@ async def test_delegate_shutter_fails(delegate: ExposureDelegate, mocker): assert result is False +async def test_delegate_readout_no_fetch_data(delegate: ExposureDelegate): + command = Command("", actor=delegate.actor) + result = await delegate.expose( + command, + [delegate.actor.controllers["sp1"]], + flavour="object", + exposure_time=0.01, + readout=False, + ) + assert result is True + + assert delegate.expose_data + delegate.expose_data.controllers = [] + + result = await delegate.readout(command) + assert result is False + assert delegate.command.replies[-1].body["error"] == "No data was fetched." + + @pytest.mark.parametrize("window_mode", ["test_mode", "default"]) async def test_delegate_expose_window_mode(delegate: ExposureDelegate, window_mode): command = Command("", actor=delegate.actor)