diff --git a/CHANGELOG.md b/CHANGELOG.md index d2de987..873bfa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### ✨ Improved * Report last time the heartbeat was set in the PLC in the `status` command. - +* Report timeout of the engineering mode. ## 1.0.1 - December 26, 2024 diff --git a/python/lvmecp/actor/actor.py b/python/lvmecp/actor/actor.py index 1320861..fa327c0 100644 --- a/python/lvmecp/actor/actor.py +++ b/python/lvmecp/actor/actor.py @@ -163,12 +163,13 @@ async def _run_eng_mode(self, timeout: float | None = None): """ started_at: float = time.time() - timeout = timeout or self._engineering_mode_timeout + if timeout is not None: + self._engineering_mode_timeout = timeout while True: await self.emit_heartbeat() - if time.time() - started_at > timeout: + if time.time() - started_at > self._engineering_mode_timeout: self.write("w", text="Engineering mode timed out and was disabled.") await self.engineering_mode(False) return diff --git a/python/lvmecp/actor/commands/engineering.py b/python/lvmecp/actor/commands/engineering.py index f89533a..f415b5e 100644 --- a/python/lvmecp/actor/commands/engineering.py +++ b/python/lvmecp/actor/commands/engineering.py @@ -12,6 +12,8 @@ import click +from lvmecp.tools import timestamp_to_iso + from . import parser @@ -55,4 +57,10 @@ async def disable(command: ECPCommand): async def status(command: ECPCommand): """Returns the status of the engineering mode.""" - return command.finish(engineering_mode=command.actor.is_engineering_mode_enabled()) + enabled = command.actor.is_engineering_mode_enabled() + timeout = command.actor._engineering_mode_timeout + + return command.finish( + engineering_mode_enabled=enabled, + engineering_mode_timeout=timestamp_to_iso(timeout), + ) diff --git a/python/lvmecp/etc/schema.json b/python/lvmecp/etc/schema.json index f980bfc..b9b1961 100644 --- a/python/lvmecp/etc/schema.json +++ b/python/lvmecp/etc/schema.json @@ -26,7 +26,10 @@ "last_heartbeat_set": { "oneOf": [{ "type": "number" }, { "type": "null" }] }, - "engineering_mode": { "type": "boolean" } + "engineering_mode_enabled": { "type": "boolean" }, + "engineering_mode_timeout": { + "oneOf": [{ "type": "string" }, { "type": "null" }] + } }, "additionalProperties": true } diff --git a/python/lvmecp/tools.py b/python/lvmecp/tools.py index 3c660dd..a8512c4 100644 --- a/python/lvmecp/tools.py +++ b/python/lvmecp/tools.py @@ -10,11 +10,12 @@ import asyncio from contextlib import suppress +from datetime import datetime, timezone from typing import Any, Callable, Coroutine -__all__ = ["loop_coro", "cancel_tasks_by_name"] +__all__ = ["loop_coro", "cancel_tasks_by_name", "timestamp_to_iso"] def loop_coro( @@ -46,3 +47,16 @@ async def cancel_tasks_by_name(name: str): task.cancel() with suppress(asyncio.CancelledError): await task + + +def timestamp_to_iso(ts: float | None, timespec: str = "seconds") -> str | None: + """Converts a timestamp to an ISO string.""" + + if ts is None: + return None + + return ( + datetime.fromtimestamp(ts, timezone.utc) + .isoformat(timespec=timespec) + .replace("+00:00", "Z") + )