# Copyright (C) 2026 Hector van der Aa # Copyright (C) 2026 Association Exergie # SPDX-License-Identifier: GPL-3.0-or-later from collections.abc import Callable from enum import IntEnum import inspect from queue import Empty, Queue from threading import Thread import threading from time import sleep from typing import Literal from uuid import UUID from dynalab_core.errors import ( DeriveUnitArgsMismatchError, DeriveUnitInvalidSignatureError, ) from dynalab_core.protocols.packets.data import ValueDescriptor from dynalab_core.protocols.packets.handshake import SignalDescriptor class ParserState(IntEnum): INIT = 0 VALUES_DONE = 1 SIGNAL_DONE = 2 class DeriveUnit: def __init__( self, unit_uuid: UUID, processing_function: Callable[..., ValueDescriptor], input_signals: list[SignalDescriptor], return_signal: SignalDescriptor, ) -> None: self._id: UUID = unit_uuid self._worker_thread: Thread | None = None self._process_function: Callable = processing_function self._return_signal: SignalDescriptor = return_signal self._stop_event: threading.Event = threading.Event() self._input_queue: Queue[list[ValueDescriptor]] = [] self._output_queue: Queue[ValueDescriptor] = [] self._num_input_args: int = 0 self._parser_state: int = ParserState.INIT sig = inspect.signature(self._process_function) for name, param in sig.parameters.items(): if self._parser_state == ParserState.INIT: if param.annotation is ValueDescriptor: self._num_input_args += 1 else: self._parser_state = ParserState.VALUES_DONE if self._parser_state == ParserState.VALUES_DONE: if param.annotation is SignalDescriptor: self._parser_state = ParserState.SIGNAL_DONE continue else: raise DeriveUnitInvalidSignatureError if self._parser_state == ParserState.SIGNAL_DONE: raise DeriveUnitInvalidSignatureError if sig.return_annotation is not ValueDescriptor: raise DeriveUnitInvalidSignatureError print(f"Signature validated with {self._num_input_args} input values") # TODO: Define and start worker thread def worker_function(self) -> None: while not self._stop_event.is_set(): try: input_args = self._input_queue.get(timeout=0.1) except Empty: continue if len(input_args) != self._num_input_args: continue self._output_queue.put( self._process_function(*input_args, self._return_signal) ) def put_data(self, input: list[ValueDescriptor]) -> None: self._input_queue.put(input) def get_output(self) -> ValueDescriptor | None: try: return self._output_queue.get_nowait() except Empty: return None def get_all_output(self) -> list[ValueDescriptor]: output: list[ValueDescriptor] = [] while True: try: output.append(self._output_queue.get_nowait()) except Empty: return output def process_offline(self, input_args: list[ValueDescriptor]) -> ValueDescriptor: if len(input_args) == self._num_input_args: return self._process_function(*input_args, self._return_signal) raise DeriveUnitArgsMismatchError