"""Base class for waiting for a specific answer frame from Velux API.""" import asyncio from typing import TYPE_CHECKING from ..log import PYVLXLOG from .frames import FrameBase if TYPE_CHECKING: from pyvlx import PyVLX class ApiEvent: """Base class for waiting a specific frame from API connection. Objects of this class are single-use only, i.e. after one call to do_api_call() they must be discarded. """ def __init__(self, pyvlx: "PyVLX", timeout_in_seconds: int = 10): """Initialize ApiEvent.""" self.pyvlx = pyvlx self.response_received_or_timeout = asyncio.Event() self.success = False self.timeout_in_seconds = timeout_in_seconds self.used = False async def do_api_call(self) -> None: """Start. Sending and waiting for answer.""" assert not self.used, "ApiEvent objects are single-use only" self.used = True # We check for connection before entering the semaphore section # because otherwise we might try to connect, which calls this, and we get stuck on # the semaphore. await self.pyvlx.ensure_connected() if self.pyvlx.get_connected(): async with self.pyvlx.api_call_semaphore: self.pyvlx.connection.register_frame_received_cb(self.response_rec_callback) try: await self.send_frame() try: async with asyncio.timeout(self.timeout_in_seconds): await self.response_received_or_timeout.wait() except TimeoutError: # timeout does not change the result of self.success (which at this point can be true or false) # it just finishes waiting for completion if set to true PYVLXLOG.debug( "ApiEvent %s: wait ended on timeout after %ss (success=%s); " "the operation is not aborted by this", type(self).__name__, self.timeout_in_seconds, self.success, ) finally: self.pyvlx.connection.unregister_frame_received_cb(self.response_rec_callback) else: self.success = False async def handle_frame(self, frame: FrameBase) -> bool: """Handle incoming frame. Return True if this frame completes the API call. Note that the return value only indicates if the API call is completed, not if it was successful. The success attribute has to be checked for that. """ raise NotImplementedError("handle_frame has to be implemented") async def send_frame(self) -> None: """Send frame to API connection.""" await self.pyvlx.send_frame(self.request_frame()) def request_frame(self) -> FrameBase: """Construct initiating frame.""" raise NotImplementedError("request_frame has to be implemented") async def response_rec_callback(self, frame: FrameBase) -> None: """Handle frame. Callback from internal api connection.""" if await self.handle_frame(frame): self.response_received_or_timeout.set()