common component interface

This commit is contained in:
Devaev Maxim 2020-06-06 06:29:29 +03:00
parent 4f3ebf0fd1
commit 1d7d4100a5
7 changed files with 60 additions and 53 deletions

View File

@ -41,7 +41,7 @@ class AtxApi:
@exposed_http("GET", "/atx")
async def __state_handler(self, _: Request) -> Response:
return make_json_response(self.__atx.get_state())
return make_json_response(await self.__atx.get_state())
@exposed_http("POST", "/atx/power")
async def __power_handler(self, request: Request) -> Response:

View File

@ -38,7 +38,7 @@ class WolApi:
@exposed_http("GET", "/wol")
async def __state_handler(self, _: Request) -> Response:
return make_json_response(self.__wol.get_state())
return make_json_response(await self.__wol.get_state())
@exposed_http("POST", "/wol/wakeup")
async def __wakeup_handler(self, _: Request) -> Response:

View File

@ -23,14 +23,14 @@
import os
import signal
import asyncio
import dataclasses
import json
from enum import Enum
from typing import List
from typing import Dict
from typing import Set
from typing import Callable
from typing import Coroutine
from typing import AsyncGenerator
from typing import Optional
from typing import Any
@ -86,13 +86,23 @@ from .api.streamer import StreamerApi
# =====
class _Events(Enum):
INFO_STATE = "info_state"
WOL_STATE = "wol_state"
HID_STATE = "hid_state"
ATX_STATE = "atx_state"
MSD_STATE = "msd_state"
STREAMER_STATE = "streamer_state"
@dataclasses.dataclass(frozen=True)
class _Component:
name: str
event_type: str
obj: object
get_state: Optional[Callable[[], Coroutine[Any, Any, Dict]]] = None
poll_state: Optional[Callable[[], AsyncGenerator[Dict, None]]] = None
cleanup: Optional[Callable[[], Coroutine[Any, Any, Dict]]] = None
def __post_init__(self) -> None:
if isinstance(self.obj, BasePlugin):
object.__setattr__(self, "name", f"{self.name} ({self.obj.get_plugin_name()})")
for field in ["get_state", "poll_state", "cleanup"]:
object.__setattr__(self, field, getattr(self.obj, field, None))
if self.get_state or self.poll_state:
assert self.event_type, self
class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-instance-attributes
@ -115,16 +125,21 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
) -> None:
self.__auth_manager = auth_manager
self.__info_manager = info_manager
self.__wol = wol
self.__hid = hid
self.__atx = atx
self.__msd = msd
self.__streamer = streamer
self.__heartbeat = heartbeat
self.__components = [
_Component("Auth manager", "", auth_manager),
_Component("Info manager", "info_state", info_manager),
_Component("Wake-on-LAN", "wol_state", wol),
_Component("HID", "hid_state", hid),
_Component("ATX", "atx_state", atx),
_Component("MSD", "msd_state", msd),
_Component("Streamer", "streamer_state", streamer),
]
self.__apis: List[object] = [
self,
AuthApi(auth_manager),
@ -180,12 +195,9 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
await self.__register_socket(ws)
try:
await asyncio.gather(*[
self.__broadcast_event(_Events.INFO_STATE, (await self.__info_manager.get_state())),
self.__broadcast_event(_Events.WOL_STATE, self.__wol.get_state()),
self.__broadcast_event(_Events.HID_STATE, (await self.__hid.get_state())),
self.__broadcast_event(_Events.ATX_STATE, self.__atx.get_state()),
self.__broadcast_event(_Events.MSD_STATE, (await self.__msd.get_state())),
self.__broadcast_event(_Events.STREAMER_STATE, (await self.__streamer.get_state())),
self.__broadcast_event(component.event_type, await component.get_state())
for component in self.__components
if component.get_state
])
async for msg in ws:
if msg.type == aiohttp.web.WSMsgType.TEXT:
@ -224,10 +236,9 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
app.on_cleanup.append(self.__on_cleanup)
self.__run_system_task(self.__stream_controller)
self.__run_system_task(self.__poll_state, _Events.HID_STATE, self.__hid.poll_state())
self.__run_system_task(self.__poll_state, _Events.ATX_STATE, self.__atx.poll_state())
self.__run_system_task(self.__poll_state, _Events.MSD_STATE, self.__msd.poll_state())
self.__run_system_task(self.__poll_state, _Events.STREAMER_STATE, self.__streamer.poll_state())
for component in self.__components:
if component.poll_state:
self.__run_system_task(self.__poll_state, component.event_type, component.poll_state())
for api in self.__apis:
for http_exposed in get_exposed_http(api):
@ -282,26 +293,19 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
async def __on_cleanup(self, _: aiohttp.web.Application) -> None:
logger = get_logger(0)
for (name, obj) in [
("Auth manager", self.__auth_manager),
("Streamer", self.__streamer),
("MSD", self.__msd),
("ATX", self.__atx),
("HID", self.__hid),
]:
if isinstance(obj, BasePlugin):
name = f"{name} ({obj.get_plugin_name()})"
logger.info("Cleaning up %s ...", name)
try:
await obj.cleanup() # type: ignore
except Exception:
logger.exception("Cleanup error on %s", name)
for component in self.__components:
if component.cleanup:
logger.info("Cleaning up %s ...", component.name)
try:
await component.cleanup() # type: ignore
except Exception:
logger.exception("Cleanup error on %s", component.name)
async def __broadcast_event(self, event_type: _Events, event: Dict) -> None:
async def __broadcast_event(self, event_type: str, event: Dict) -> None:
if self.__sockets:
await asyncio.gather(*[
ws.send_str(json.dumps({
"event_type": event_type.value,
"event_type": event_type,
"event": event,
}))
for ws in list(self.__sockets)
@ -351,6 +355,6 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
prev = cur
await self.__streamer_notifier.wait()
async def __poll_state(self, event_type: _Events, poller: AsyncGenerator[Dict, None]) -> None:
async def __poll_state(self, event_type: str, poller: AsyncGenerator[Dict, None]) -> None:
async for state in poller:
await self.__broadcast_event(event_type, state)

View File

@ -50,7 +50,7 @@ class WakeOnLan:
assert len(mac) == 17, mac
self.__magic = bytes.fromhex("FF" * 6 + mac.replace(":", "") * 16)
def get_state(self) -> Dict:
async def get_state(self) -> Dict:
return {
"enabled": bool(self.__magic),
"target": {

View File

@ -47,7 +47,7 @@ class AtxIsBusyError(IsBusyError, AtxError):
# =====
class BaseAtx(BasePlugin):
def get_state(self) -> Dict:
async def get_state(self) -> Dict:
raise NotImplementedError
async def poll_state(self) -> AsyncGenerator[Dict, None]:

View File

@ -37,7 +37,7 @@ class AtxDisabledError(AtxOperationError):
# =====
class Plugin(BaseAtx):
def get_state(self) -> Dict:
async def get_state(self) -> Dict:
return {
"enabled": False,
"busy": False,
@ -49,7 +49,7 @@ class Plugin(BaseAtx):
async def poll_state(self) -> AsyncGenerator[Dict, None]:
while True:
yield self.get_state()
yield (await self.get_state())
await aiotools.wait_infinite()
# =====

View File

@ -92,7 +92,7 @@ class Plugin(BaseAtx): # pylint: disable=too-many-instance-attributes
"state_poll": Option(0.1, type=valid_float_f01),
}
def get_state(self) -> Dict:
async def get_state(self) -> Dict:
return {
"enabled": True,
"busy": self.__region.is_busy(),
@ -105,7 +105,7 @@ class Plugin(BaseAtx): # pylint: disable=too-many-instance-attributes
async def poll_state(self) -> AsyncGenerator[Dict, None]:
prev_state: Dict = {}
while True:
state = self.get_state()
state = await self.get_state()
if state != prev_state:
yield state
prev_state = state
@ -124,25 +124,25 @@ class Plugin(BaseAtx): # pylint: disable=too-many-instance-attributes
# =====
async def power_on(self) -> bool:
if not self.get_state()["leds"]["power"]:
if not (await self.__get_power()):
await self.click_power()
return True
return False
async def power_off(self) -> bool:
if self.get_state()["leds"]["power"]:
if (await self.__get_power()):
await self.click_power()
return True
return False
async def power_off_hard(self) -> bool:
if self.get_state()["leds"]["power"]:
if (await self.__get_power()):
await self.click_power_long()
return True
return False
async def power_reset_hard(self) -> bool:
if self.get_state()["leds"]["power"]:
if (await self.__get_power()):
await self.click_reset()
return True
return False
@ -160,6 +160,9 @@ class Plugin(BaseAtx): # pylint: disable=too-many-instance-attributes
# =====
async def __get_power(self) -> bool:
return (await self.get_state())["leds"]["power"]
@aiotools.atomic
async def __click(self, name: str, pin: int, delay: float) -> None:
await aiotools.run_region_task(