From ed23fef5121f8f71b47b3e6aefe409a7e36fd9a7 Mon Sep 17 00:00:00 2001 From: Maxim Devaev Date: Fri, 25 Mar 2022 21:19:28 +0300 Subject: fan monitoring --- PKGBUILD | 1 + configs/kvmd/main/v3-hdmi-rpi4.yaml | 4 ++ kvmd/apps/__init__.py | 5 ++ kvmd/apps/kvmd/info/__init__.py | 2 + kvmd/apps/kvmd/info/fan.py | 98 ++++++++++++++++++++++++++ web/kvm/index.html | 38 ++++++++++- web/kvm/navbar-health.pug | 13 ++++ web/kvm/window-about.pug | 2 +- web/share/js/kvm/session.js | 133 +++++++++++++++++++++++++++--------- 9 files changed, 258 insertions(+), 38 deletions(-) create mode 100644 kvmd/apps/kvmd/info/fan.py diff --git a/PKGBUILD b/PKGBUILD index 7979ace7..e20a515d 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -200,6 +200,7 @@ for _variant in "${_variants[@]}"; do if [ -f configs/kvmd/fan/$_platform.ini ]; then backup=(\"\${backup[@]}\" etc/kvmd/fan.ini) + depends=(\"\${depends[@]}\" \"kvmd-fan>=0.18\") install -DTm444 configs/kvmd/fan/$_platform.ini \"\$pkgdir/etc/kvmd/fan.ini\" fi diff --git a/configs/kvmd/main/v3-hdmi-rpi4.yaml b/configs/kvmd/main/v3-hdmi-rpi4.yaml index 6d113d7c..7de62ccd 100644 --- a/configs/kvmd/main/v3-hdmi-rpi4.yaml +++ b/configs/kvmd/main/v3-hdmi-rpi4.yaml @@ -12,6 +12,10 @@ kvmd: auth: !include auth.yaml + info: + fan: + unix: /run/kvmd/fan.sock + hid: type: otg keyboard: diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py index b31ac936..75600390 100644 --- a/kvmd/apps/__init__.py +++ b/kvmd/apps/__init__.py @@ -375,6 +375,11 @@ def _get_config_scheme() -> Dict: "vcgencmd_cmd": Option(["/opt/vc/bin/vcgencmd"], type=valid_command), "state_poll": Option(10.0, type=valid_float_f01), }, + "fan": { + "unix": Option("", type=valid_abs_path, if_empty="", unpack_as="unix_path"), + "timeout": Option(5.0, type=valid_float_f01), + "state_poll": Option(5.0, type=valid_float_f01), + }, }, "hid": { diff --git a/kvmd/apps/kvmd/info/__init__.py b/kvmd/apps/kvmd/info/__init__.py index 458eec52..53a02f88 100644 --- a/kvmd/apps/kvmd/info/__init__.py +++ b/kvmd/apps/kvmd/info/__init__.py @@ -29,6 +29,7 @@ from .system import SystemInfoSubmanager from .meta import MetaInfoSubmanager from .extras import ExtrasInfoSubmanager from .hw import HwInfoSubmanager +from .fan import FanInfoSubmanager # ===== @@ -39,6 +40,7 @@ class InfoManager: "meta": MetaInfoSubmanager(config.kvmd.info.meta), "extras": ExtrasInfoSubmanager(config), "hw": HwInfoSubmanager(**config.kvmd.info.hw._unpack()), + "fan": FanInfoSubmanager(**config.kvmd.info.fan._unpack()), } def get_subs(self) -> Set[str]: diff --git a/kvmd/apps/kvmd/info/fan.py b/kvmd/apps/kvmd/info/fan.py new file mode 100644 index 00000000..d4529150 --- /dev/null +++ b/kvmd/apps/kvmd/info/fan.py @@ -0,0 +1,98 @@ +# ========================================================================== # +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2022 Maxim Devaev # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +# ========================================================================== # + + +import copy +import asyncio + +from typing import Dict +from typing import AsyncGenerator +from typing import Optional + +import aiohttp + +from ....logging import get_logger + +from .... import aiotools +from .... import htclient + +from .base import BaseInfoSubmanager + + +# ===== +class FanInfoSubmanager(BaseInfoSubmanager): + def __init__( + self, + unix_path: str, + timeout: float, + state_poll: float, + ) -> None: + + self.__unix_path = unix_path + self.__timeout = timeout + self.__state_poll = state_poll + + async def get_state(self) -> Dict: + return { + "monitored": bool(self.__unix_path), + "state": ((await self.__get_fan_state() if self.__unix_path else None)), + } + + async def poll_state(self) -> AsyncGenerator[Dict, None]: + prev_state: Dict = {} + while True: + if self.__unix_path: + pure = state = await self.get_state() + if pure["state"] is not None: + try: + pure = copy.deepcopy(state) + pure["state"]["service"]["now_ts"] = 0 + except Exception: + pass + if pure != prev_state: + yield state + prev_state = pure + await asyncio.sleep(self.__state_poll) + else: + yield (await self.get_state()) + await aiotools.wait_infinite() + + # ===== + + async def __get_fan_state(self) -> Optional[Dict]: + try: + async with self.__make_http_session() as session: + async with session.get("http://localhost/state") as response: + htclient.raise_not_200(response) + return (await response.json())["result"] + except Exception as err: + get_logger(0).error("Can't read fan state: %s", err) + return None + + def __make_http_session(self) -> aiohttp.ClientSession: + kwargs: Dict = { + "headers": { + "User-Agent": htclient.make_user_agent("KVMD"), + }, + "timeout": aiohttp.ClientTimeout(total=self.__timeout), + "connector": aiohttp.UnixConnector(path=self.__unix_path) + } + return aiohttp.ClientSession(**kwargs) diff --git a/web/kvm/index.html b/web/kvm/index.html index 1000ddaa..1007bb99 100644 --- a/web/kvm/index.html +++ b/web/kvm/index.html @@ -106,6 +106,38 @@ +
  • System