From 1cb0f64fcea339bcb2858d7814af4c6059ad302b Mon Sep 17 00:00:00 2001 From: mgineer85 Date: Fri, 25 Oct 2024 18:42:24 +0200 Subject: [PATCH] fix backend dynamic import, fix shutdown app if stream is currently active. --- node/app_api.py | 30 ++++++++++---- .../backends/cameras/abstractbackend.py | 22 ++++++++++- .../backends/cameras/virtualcamera.py | 39 +++++++++++++++++-- node/services/sync_acquisition_service.py | 2 +- pyproject.toml | 2 +- 5 files changed, 80 insertions(+), 15 deletions(-) diff --git a/node/app_api.py b/node/app_api.py index 473f294..0dfa204 100644 --- a/node/app_api.py +++ b/node/app_api.py @@ -68,16 +68,32 @@ async def validation_exception_handler(request, exc): def main(): - # to allow api is runnable via project.scripts shortcut + # main function to allow api is runnable via project.scripts shortcut # ref: https://stackoverflow.com/a/70393344 - uvicorn.run( - app, - host="0.0.0.0", - port=8000, - reload=False, - log_level="debug", + server = uvicorn.Server( + uvicorn.Config( + app, + host="0.0.0.0", + port=8000, + reload=False, + log_level="debug", + ) ) + # shutdown app workaround: + # workaround until https://github.com/encode/uvicorn/issues/1579 is fixed and + # shutdown can be handled properly. + # Otherwise the stream.mjpg if open will block shutdown of the server + # signal CTRL-C and systemctl stop would have no effect, app stalls + # signal.signal(signal.SIGINT, signal_handler) and similar + # don't work, because uvicorn is eating up signal handler + # currently: https://github.com/encode/uvicorn/issues/1579 + # the workaround: currently we set force_exit to True to shutdown the server + server.force_exit = True # leads to many exceptions on shutdown, but ... it is what it is... + + # run + server.run() + if __name__ == "__main__": main() diff --git a/node/services/backends/cameras/abstractbackend.py b/node/services/backends/cameras/abstractbackend.py index 9c8c445..8c5fef3 100644 --- a/node/services/backends/cameras/abstractbackend.py +++ b/node/services/backends/cameras/abstractbackend.py @@ -29,12 +29,30 @@ def __init__(self): def __repr__(self): return f"{self.__class__}" - def start(self): + @abstractmethod + def start(self, nominal_framerate: int = None): self._is_running: bool = True + @abstractmethod def stop(self): self._is_running: bool = False @abstractmethod - def test(self): + def start_stream(self): + pass + + @abstractmethod + def stop_stream(self): + pass + + @abstractmethod + def wait_for_lores_image(self): + pass + + @abstractmethod + def do_capture(self, filename: str = None, number_frames: int = 1): + pass + + @abstractmethod + def sync_tick(self, timestamp_ns: int): pass diff --git a/node/services/backends/cameras/virtualcamera.py b/node/services/backends/cameras/virtualcamera.py index bb258ba..94d0f77 100644 --- a/node/services/backends/cameras/virtualcamera.py +++ b/node/services/backends/cameras/virtualcamera.py @@ -1,17 +1,48 @@ +import io import logging +import time +import numpy +from PIL import Image + +from ...config.models import ConfigBackendVirtualcamera from .abstractbackend import AbstractBackend logger = logging.getLogger(__name__) -class VirtualcameraBackend(AbstractBackend): - def __init__(self): +class VirtualCameraBackend(AbstractBackend): + def __init__(self, config: ConfigBackendVirtualcamera): super().__init__() # init with arguments + self._config = config - def start(self): - pass + def start(self, nominal_framerate: int = None): + super().start() def stop(self): + super().stop() + + def start_stream(self): + pass + + def stop_stream(self): + pass + + def wait_for_lores_image(self): + time.sleep(0.05) + + byte_io = io.BytesIO() + imarray = numpy.random.rand(200, 200, 3) * 255 + random_image = Image.fromarray(imarray.astype("uint8"), "RGB") + random_image.save(byte_io, format="JPEG", quality=50) + # random_image = Image.new("RGB", (64, 64), color="green") + # random_image.save(byte_io, format="JPEG", quality=50) + + return byte_io.getbuffer() + + def do_capture(self, filename: str = None, number_frames: int = 1): + pass + + def sync_tick(self, timestamp_ns: int): pass diff --git a/node/services/sync_acquisition_service.py b/node/services/sync_acquisition_service.py index 07f8a2c..d8c6841 100644 --- a/node/services/sync_acquisition_service.py +++ b/node/services/sync_acquisition_service.py @@ -54,7 +54,7 @@ def start(self): super().start() self._camera_backend: AbstractBackend = self._import_backend(self._config.backends.active_backend)( - getattr(self._config.backends, str((self._config.backends.active_backend).lower())) + getattr(self._config.backends, str(self._config.backends.active_backend).lower()) ) self._gpio_backend.start() diff --git a/pyproject.toml b/pyproject.toml index 236497a..5bf3832 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ test = [ "pytest-benchmark>=4.0.0", "pytest-cov>=4.1.0", "coverage[toml]>=7.4.0", - + "numpy>=2.0.0", ] lint = [ "ruff"