summaryrefslogtreecommitdiff
path: root/kvmd
diff options
context:
space:
mode:
Diffstat (limited to 'kvmd')
-rw-r--r--kvmd/__init__.py2
-rw-r--r--kvmd/apps/__init__.py4
-rw-r--r--kvmd/apps/kvmd/api/export.py10
-rw-r--r--kvmd/apps/kvmd/api/ugpio.py3
-rw-r--r--kvmd/apps/kvmd/server.py5
-rw-r--r--kvmd/apps/kvmd/ugpio.py56
6 files changed, 61 insertions, 19 deletions
diff --git a/kvmd/__init__.py b/kvmd/__init__.py
index 3e3c37d4..b570eafa 100644
--- a/kvmd/__init__.py
+++ b/kvmd/__init__.py
@@ -20,4 +20,4 @@
# ========================================================================== #
-__version__ = "1.98"
+__version__ = "1.99"
diff --git a/kvmd/apps/__init__.py b/kvmd/apps/__init__.py
index 28fbfc75..7202cfbc 100644
--- a/kvmd/apps/__init__.py
+++ b/kvmd/apps/__init__.py
@@ -187,6 +187,7 @@ def _patch_dynamic( # pylint: disable=too-many-locals
}
if mode == "output":
ch_scheme.update({
+ "busy_delay": Option(0.2, type=valid_float_f01),
"initial": Option(False, type=valid_bool),
"switch": Option(True, type=valid_bool),
"pulse": {
@@ -328,8 +329,7 @@ def _get_config_scheme() -> Dict:
"scheme": {}, # Dymanic content
"view": {
"header": {
- "title": Option("Switches"),
- "leds": Option([], type=valid_string_list),
+ "title": Option("GPIO"),
},
"table": Option([], type=valid_ugpio_view_table),
},
diff --git a/kvmd/apps/kvmd/api/export.py b/kvmd/apps/kvmd/api/export.py
index c37feb57..6dae0ce1 100644
--- a/kvmd/apps/kvmd/api/export.py
+++ b/kvmd/apps/kvmd/api/export.py
@@ -32,27 +32,33 @@ from aiohttp.web import Response
from ....plugins.atx import BaseAtx
from ..info import InfoManager
+from ..ugpio import UserGpio
from ..http import exposed_http
# =====
class ExportApi:
- def __init__(self, info_manager: InfoManager, atx: BaseAtx) -> None:
+ def __init__(self, info_manager: InfoManager, atx: BaseAtx, user_gpio: UserGpio) -> None:
self.__info_manager = info_manager
self.__atx = atx
+ self.__user_gpio = user_gpio
# =====
@exposed_http("GET", "/export/prometheus/metrics")
async def __prometheus_metrics_handler(self, _: Request) -> Response:
- (atx_state, hw_state) = await asyncio.gather(*[
+ (atx_state, hw_state, gpio_state) = await asyncio.gather(*[
self.__atx.get_state(),
self.__info_manager.get_submanager("hw").get_state(),
+ self.__user_gpio.get_state(),
])
rows: List[str] = []
self.__append_prometheus_rows(rows, atx_state["enabled"], "pikvm_atx_enabled")
self.__append_prometheus_rows(rows, atx_state["leds"]["power"], "pikvm_atx_power")
+ for mode in ["input", "output"]:
+ for (channel, gch) in gpio_state[f"{mode}s"].items():
+ self.__append_prometheus_rows(rows, gch["state"], f"pikvm_gpio_input_{channel}")
if hw_state is not None:
self.__append_prometheus_rows(rows, hw_state["health"], "pikvm_hw")
return Response(text="\n".join(rows))
diff --git a/kvmd/apps/kvmd/api/ugpio.py b/kvmd/apps/kvmd/api/ugpio.py
index 69ac6646..66f7b358 100644
--- a/kvmd/apps/kvmd/api/ugpio.py
+++ b/kvmd/apps/kvmd/api/ugpio.py
@@ -44,8 +44,7 @@ class UserGpioApi:
@exposed_http("GET", "/gpio")
async def __state_handler(self, _: Request) -> Response:
return make_json_response({
- "scheme": (await self.__user_gpio.get_scheme()),
- "view": (await self.__user_gpio.get_view()),
+ "model": (await self.__user_gpio.get_model()),
"state": (await self.__user_gpio.get_state()),
})
diff --git a/kvmd/apps/kvmd/server.py b/kvmd/apps/kvmd/server.py
index 30d5c035..57d8c3e9 100644
--- a/kvmd/apps/kvmd/server.py
+++ b/kvmd/apps/kvmd/server.py
@@ -191,7 +191,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
AtxApi(atx),
MsdApi(msd, sync_chunk_size),
StreamerApi(streamer),
- ExportApi(info_manager, atx),
+ ExportApi(info_manager, atx, user_gpio),
]
self.__ws_handlers: Dict[str, Callable] = {}
@@ -244,8 +244,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
await client.ws.prepare(request)
await self.__register_ws_client(client)
try:
- await self.__broadcast_event("gpio_scheme_state", await self.__user_gpio.get_scheme())
- await self.__broadcast_event("gpio_view_state", await self.__user_gpio.get_view())
+ await self.__broadcast_event("gpio_model_state", await self.__user_gpio.get_model())
await asyncio.gather(*[
self.__broadcast_event(component.event_type, await component.get_state())
for component in self.__components
diff --git a/kvmd/apps/kvmd/ugpio.py b/kvmd/apps/kvmd/ugpio.py
index 97556aba..c6cfc121 100644
--- a/kvmd/apps/kvmd/ugpio.py
+++ b/kvmd/apps/kvmd/ugpio.py
@@ -23,6 +23,7 @@
import asyncio
import operator
+from typing import List
from typing import Dict
from typing import AsyncGenerator
from typing import Optional
@@ -88,6 +89,7 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
self.__pulse_delay: float = config.pulse.delay
self.__min_pulse_delay: float = config.pulse.min_delay
self.__max_pulse_delay: float = config.pulse.max_delay
+ self.__busy_delay: float = config.busy_delay
self.__state = config.initial
self.__region = aiotools.AioExclusiveRegion(GpioChannelIsBusyError, notifier)
@@ -97,8 +99,8 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
"switch": self.__switch,
"pulse": {
"delay": self.__pulse_delay,
- "min_delay": self.__min_pulse_delay,
- "max_delay": self.__max_pulse_delay,
+ "min_delay": (self.__min_pulse_delay if self.__pulse_delay else 0),
+ "max_delay": (self.__max_pulse_delay if self.__pulse_delay else 0),
},
}
@@ -125,8 +127,10 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
self.__write(state)
self.__state = state
get_logger(0).info("Switched GPIO %s to %d", self, state)
+ await asyncio.sleep(self.__busy_delay)
return True
self.__state = real_state
+ await asyncio.sleep(self.__busy_delay)
return False
@aiotools.atomic
@@ -146,7 +150,7 @@ class _GpioOutput: # pylint: disable=too-many-instance-attributes
await asyncio.sleep(delay)
finally:
self.__write(False)
- await asyncio.sleep(1)
+ await asyncio.sleep(self.__busy_delay)
get_logger(0).info("Pulsed GPIO %s", self)
def __read(self) -> bool:
@@ -185,15 +189,15 @@ class UserGpio:
else: # output:
self.__outputs[channel] = _GpioOutput(channel, ch_config, self.__state_notifier)
- async def get_scheme(self) -> Dict:
+ async def get_model(self) -> Dict:
return {
- "inputs": {channel: gin.get_scheme() for (channel, gin) in self.__inputs.items()},
- "outputs": {channel: gout.get_scheme() for (channel, gout) in self.__outputs.items()},
+ "scheme": {
+ "inputs": {channel: gin.get_scheme() for (channel, gin) in self.__inputs.items()},
+ "outputs": {channel: gout.get_scheme() for (channel, gout) in self.__outputs.items()},
+ },
+ "view": self.__make_view(),
}
- async def get_view(self) -> Dict:
- return self.__view
-
async def get_state(self) -> Dict:
return {
"inputs": {channel: gin.get_state() for (channel, gin) in self.__inputs.items()},
@@ -240,3 +244,37 @@ class UserGpio:
if gout is None:
raise GpioChannelNotFoundError()
await gout.pulse(delay)
+
+ # =====
+
+ def __make_view(self) -> Dict:
+ table: List[Optional[List[Dict]]] = []
+ for row in self.__view["table"]:
+ if len(row) == 0:
+ table.append(None)
+ continue
+
+ items: List[Dict] = []
+ for item in map(str.strip, row):
+ if item.startswith("#") or len(item) == 0:
+ items.append({
+ "type": "label",
+ "text": item[1:].strip(),
+ })
+ elif (parts := list(map(str.strip, item.split(",", 1)))):
+ if parts[0] in self.__inputs:
+ items.append({
+ "type": "input",
+ "channel": parts[0],
+ })
+ elif parts[0] in self.__outputs:
+ items.append({
+ "type": "output",
+ "channel": parts[0],
+ "text": (parts[1] if len(parts) > 1 else "Click"),
+ })
+ table.append(items)
+ return {
+ "header": self.__view["header"],
+ "table": table,
+ }