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") @exposed_http("GET", "/atx")
async def __state_handler(self, _: Request) -> Response: 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") @exposed_http("POST", "/atx/power")
async def __power_handler(self, request: Request) -> Response: async def __power_handler(self, request: Request) -> Response:

View File

@ -38,7 +38,7 @@ class WolApi:
@exposed_http("GET", "/wol") @exposed_http("GET", "/wol")
async def __state_handler(self, _: Request) -> Response: 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") @exposed_http("POST", "/wol/wakeup")
async def __wakeup_handler(self, _: Request) -> Response: async def __wakeup_handler(self, _: Request) -> Response:

View File

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

View File

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

View File

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

View File

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