summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDevaev Maxim <[email protected]>2018-07-02 21:17:19 +0300
committerDevaev Maxim <[email protected]>2018-07-02 21:17:19 +0300
commit0582398521610e3c8dc0a43d7b30c1eb1b9dd705 (patch)
treeb49dbf58f5f421051c909bd0d26ef3c8b5b8f800
parent87f8cb350b0301da99cb36760027980a8d899e4e (diff)
better api, refactoring
-rw-r--r--kvmd/kvmd/__init__.py26
-rw-r--r--kvmd/kvmd/atx.py14
-rw-r--r--kvmd/kvmd/msd.py10
-rw-r--r--kvmd/kvmd/server.py125
-rw-r--r--kvmd/kvmd/streamer.py16
-rw-r--r--os/platforms/v1/kvmd.yaml29
6 files changed, 136 insertions, 84 deletions
diff --git a/kvmd/kvmd/__init__.py b/kvmd/kvmd/__init__.py
index 8b950212..a8c80097 100644
--- a/kvmd/kvmd/__init__.py
+++ b/kvmd/kvmd/__init__.py
@@ -25,12 +25,12 @@ def main() -> None:
)
atx = Atx(
- power_led=int(config["atx"]["leds"]["pinout"]["power"]),
- hdd_led=int(config["atx"]["leds"]["pinout"]["hdd"]),
- power_switch=int(config["atx"]["switches"]["pinout"]["power"]),
- reset_switch=int(config["atx"]["switches"]["pinout"]["reset"]),
- click_delay=float(config["atx"]["switches"]["click_delay"]),
- long_click_delay=float(config["atx"]["switches"]["long_click_delay"]),
+ power_led=int(config["atx"]["pinout"]["power_led"]),
+ hdd_led=int(config["atx"]["pinout"]["hdd_led"]),
+ power_switch=int(config["atx"]["pinout"]["power_switch"]),
+ reset_switch=int(config["atx"]["pinout"]["reset_switch"]),
+ click_delay=float(config["atx"]["click_delay"]),
+ long_click_delay=float(config["atx"]["long_click_delay"]),
)
msd = MassStorageDevice(
@@ -40,10 +40,12 @@ def main() -> None:
)
streamer = Streamer(
- cap_power=int(config["video"]["pinout"]["cap"]),
- conv_power=int(config["video"]["pinout"]["conv"]),
- sync_delay=float(config["video"]["sync_delay"]),
- cmd=list(map(str, config["video"]["cmd"])),
+ cap_power=int(config["streamer"]["pinout"]["cap"]),
+ conv_power=int(config["streamer"]["pinout"]["conv"]),
+ sync_delay=float(config["streamer"]["sync_delay"]),
+ width=int(config["streamer"]["size"]["width"]),
+ height=int(config["streamer"]["size"]["height"]),
+ cmd=list(map(str, config["streamer"]["cmd"])),
loop=loop,
)
@@ -53,8 +55,8 @@ def main() -> None:
msd=msd,
streamer=streamer,
heartbeat=float(config["server"]["heartbeat"]),
- atx_leds_poll=float(config["atx"]["leds"]["poll"]),
- video_shutdown_delay=float(config["video"]["shutdown_delay"]),
+ atx_state_poll=float(config["atx"]["state_poll"]),
+ streamer_shutdown_delay=float(config["streamer"]["shutdown_delay"]),
msd_chunk_size=int(config["msd"]["chunk_size"]),
loop=loop,
).run(
diff --git a/kvmd/kvmd/atx.py b/kvmd/kvmd/atx.py
index 686531a1..cbf8b34b 100644
--- a/kvmd/kvmd/atx.py
+++ b/kvmd/kvmd/atx.py
@@ -1,6 +1,6 @@
import asyncio
-from typing import Tuple
+from typing import Dict
from .logging import get_logger
@@ -29,11 +29,13 @@ class Atx:
self.__lock = asyncio.Lock()
- def get_leds(self) -> Tuple[bool, bool]:
- return (
- not gpio.read(self.__power_led),
- not gpio.read(self.__hdd_led),
- )
+ def get_state(self) -> Dict:
+ return {
+ "leds": {
+ "power": (not gpio.read(self.__power_led)),
+ "hdd": (not gpio.read(self.__hdd_led)),
+ },
+ }
async def click_power(self) -> None:
if (await self.__click(self.__power_switch, self.__click_delay)):
diff --git a/kvmd/kvmd/msd.py b/kvmd/kvmd/msd.py
index 58a4d8ae..84dbf50f 100644
--- a/kvmd/kvmd/msd.py
+++ b/kvmd/kvmd/msd.py
@@ -118,8 +118,12 @@ class MassStorageDevice:
get_logger().info("Using bind %r as mass-storage device", self._bind)
try:
loop.run_until_complete(self.connect_to_kvm(no_delay=True))
- except Exception:
- get_logger().exception("Mass-storage device is not operational")
+ except Exception as err:
+ if isinstance(err, MassStorageError):
+ log = get_logger().warning
+ else:
+ log = get_logger().exception
+ log("Mass-storage device is not operational: %s", err)
self._bind = ""
else:
get_logger().warning("Missing bind; mass-storage device is not operational")
@@ -133,7 +137,7 @@ class MassStorageDevice:
await asyncio.sleep(self.__init_delay)
path = locate_by_bind(self._bind)
if not path:
- raise RuntimeError("Can't locate device by bind %r" % (self._bind))
+ raise MassStorageError("Can't locate device by bind %r" % (self._bind))
self.__device_info = explore_device(path)
get_logger().info("Mass-storage device switched to KVM: %s", self.__device_info)
diff --git a/kvmd/kvmd/server.py b/kvmd/kvmd/server.py
index 0a9e848a..1d9ed066 100644
--- a/kvmd/kvmd/server.py
+++ b/kvmd/kvmd/server.py
@@ -1,9 +1,11 @@
import os
import signal
import asyncio
+import json
import time
from typing import List
+from typing import Dict
from typing import Set
from typing import Callable
from typing import Optional
@@ -38,19 +40,29 @@ def _system_task(method: Callable) -> Callable:
def _exceptions_as_400(msg: str, exceptions: List[Type[Exception]]) -> Callable:
def make_wrapper(method: Callable) -> Callable:
- async def wrap(self: "Server", request: aiohttp.web.Request) -> aiohttp.web.WebSocketResponse:
+ async def wrap(self: "Server", request: aiohttp.web.Request) -> aiohttp.web.Response:
try:
return (await method(self, request))
except tuple(exceptions) as err: # pylint: disable=catching-non-exception
get_logger().exception(msg)
return aiohttp.web.json_response({
- "error": type(err).__name__,
- "error_msg": str(err),
+ "ok": False,
+ "result": {
+ "error": type(err).__name__,
+ "error_msg": str(err),
+ },
}, status=400)
return wrap
return make_wrapper
+def _json_200(result: Optional[Dict]=None) -> aiohttp.web.Response:
+ return aiohttp.web.json_response({
+ "ok": True,
+ "result": (result or {}),
+ })
+
+
class Server: # pylint: disable=too-many-instance-attributes
def __init__(
self,
@@ -59,8 +71,8 @@ class Server: # pylint: disable=too-many-instance-attributes
msd: MassStorageDevice,
streamer: Streamer,
heartbeat: float,
- atx_leds_poll: float,
- video_shutdown_delay: float,
+ atx_state_poll: float,
+ streamer_shutdown_delay: float,
msd_chunk_size: int,
loop: asyncio.AbstractEventLoop,
) -> None:
@@ -70,8 +82,8 @@ class Server: # pylint: disable=too-many-instance-attributes
self.__msd = msd
self.__streamer = streamer
self.__heartbeat = heartbeat
- self.__video_shutdown_delay = video_shutdown_delay
- self.__atx_leds_poll = atx_leds_poll
+ self.__streamer_shutdown_delay = streamer_shutdown_delay
+ self.__atx_state_poll = atx_state_poll
self.__msd_chunk_size = msd_chunk_size
self.__loop = loop
@@ -80,17 +92,25 @@ class Server: # pylint: disable=too-many-instance-attributes
self.__system_tasks: List[asyncio.Task] = []
- self.__restart_video = False
+ self.__reset_streamer = False
def run(self, host: str, port: int) -> None:
self.__keyboard.start()
app = aiohttp.web.Application(loop=self.__loop)
- app.router.add_get("/", self.__root_handler)
+
app.router.add_get("/ws", self.__ws_handler)
+
+ app.router.add_get("/atx", self.__atx_state_handler)
+ app.router.add_post("/atx/click", self.__atx_click_handler)
+
app.router.add_get("/msd", self.__msd_state_handler)
app.router.add_post("/msd/connect", self.__msd_connect_handler)
app.router.add_post("/msd/write", self.__msd_write_handler)
+
+ app.router.add_get("/streamer", self.__streamer_state_handler)
+ app.router.add_post("/streamer/reset", self.__streamer_reset_handler)
+
app.on_shutdown.append(self.__on_shutdown)
app.on_cleanup.append(self.__on_cleanup)
@@ -98,44 +118,55 @@ class Server: # pylint: disable=too-many-instance-attributes
self.__loop.create_task(self.__keyboard_watchdog()),
self.__loop.create_task(self.__stream_controller()),
self.__loop.create_task(self.__poll_dead_sockets()),
- self.__loop.create_task(self.__poll_atx_leds()),
+ self.__loop.create_task(self.__poll_atx_state()),
])
aiohttp.web.run_app(app, host=host, port=port, print=self.__run_app_print)
- async def __root_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
- return aiohttp.web.Response(text="OK")
-
async def __ws_handler(self, request: aiohttp.web.Request) -> aiohttp.web.WebSocketResponse:
ws = aiohttp.web.WebSocketResponse(heartbeat=self.__heartbeat)
await ws.prepare(request)
await self.__register_socket(ws)
async for msg in ws:
if msg.type == aiohttp.web.WSMsgType.TEXT:
- retval = await self.__execute_command(msg.data)
- if retval:
- await ws.send_str(retval)
+ await ws.send_str(json.dumps({"msg_type": "echo", "msg": msg.data}))
else:
break
return ws
+ async def __atx_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
+ return _json_200(self.__atx.get_state())
+
+ @_exceptions_as_400("Click error", [RuntimeError])
+ async def __atx_click_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
+ button = request.query.get("button")
+ if button == "power":
+ await self.__atx.click_power()
+ elif button == "power_long":
+ await self.__atx.click_power_long()
+ elif button == "reset":
+ await self.__atx.click_reset()
+ else:
+ raise RuntimeError("Missing or invalid 'button=%s'" % (button))
+ return _json_200({"clicked": button})
+
async def __msd_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
- return aiohttp.web.json_response(self.__msd.get_state())
+ return _json_200(self.__msd.get_state())
@_exceptions_as_400("Mass-storage error", [MassStorageError, RuntimeError])
async def __msd_connect_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
to = request.query.get("to")
if to == "kvm":
await self.__msd.connect_to_kvm()
- await self.__broadcast("EVENT msd connected_to_kvm")
+ await self.__broadcast_event("msd_state", state="connected_to_kvm") # type: ignore
elif to == "pc":
await self.__msd.connect_to_pc()
- await self.__broadcast("EVENT msd connected_to_pc")
+ await self.__broadcast_event("msd_state", state="connected_to_pc") # type: ignore
else:
raise RuntimeError("Missing or invalid 'to=%s'" % (to))
- return aiohttp.web.json_response(self.__msd.get_state())
+ return _json_200(self.__msd.get_state())
- @_exceptions_as_400("Can't write image to mass-storage device", [MassStorageError, RuntimeError, OSError])
+ @_exceptions_as_400("Can't write data to mass-storage device", [MassStorageError, RuntimeError, OSError])
async def __msd_write_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
logger = get_logger(0)
reader = await request.multipart()
@@ -146,18 +177,25 @@ class Server: # pylint: disable=too-many-instance-attributes
raise RuntimeError("Missing 'data' field")
async with self.__msd:
- await self.__broadcast("EVENT msd busy")
+ await self.__broadcast_event("msd_state", state="busy") # type: ignore
logger.info("Writing image to mass-storage device ...")
while True:
chunk = await field.read_chunk(self.__msd_chunk_size)
if not chunk:
break
writed = await self.__msd.write(chunk)
- await self.__broadcast("EVENT msd free")
+ await self.__broadcast_event("msd_state", state="free") # type: ignore
finally:
if writed != 0:
logger.info("Writed %d bytes to mass-storage device", writed)
- return aiohttp.web.json_response({"writed": writed})
+ return _json_200({"writed": writed})
+
+ async def __streamer_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
+ return _json_200(self.__streamer.get_state())
+
+ async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
+ self.__reset_streamer = True
+ return _json_200()
def __run_app_print(self, text: str) -> None:
logger = get_logger()
@@ -198,16 +236,16 @@ class Server: # pylint: disable=too-many-instance-attributes
if not self.__streamer.is_running():
await self.__streamer.start()
elif prev > 0 and cur == 0:
- shutdown_at = time.time() + self.__video_shutdown_delay
+ shutdown_at = time.time() + self.__streamer_shutdown_delay
elif prev == 0 and cur == 0 and time.time() > shutdown_at:
if self.__streamer.is_running():
await self.__streamer.stop()
- if self.__restart_video:
+ if self.__reset_streamer:
if self.__streamer.is_running():
await self.__streamer.stop()
await self.__streamer.start()
- self.__restart_video = False
+ self.__reset_streamer = False
prev = cur
await asyncio.sleep(0.1)
@@ -221,36 +259,25 @@ class Server: # pylint: disable=too-many-instance-attributes
await asyncio.sleep(0.1)
@_system_task
- async def __poll_atx_leds(self) -> None:
+ async def __poll_atx_state(self) -> None:
while True:
if self.__sockets:
- await self.__broadcast("EVENT atx_leds %d %d" % (self.__atx.get_leds()))
- await asyncio.sleep(self.__atx_leds_poll)
+ await self.__broadcast_event("atx_state", **self.__atx.get_state())
+ await asyncio.sleep(self.__atx_state_poll)
- async def __broadcast(self, msg: str) -> None:
+ async def __broadcast_event(self, event: str, **kwargs: Dict) -> None:
await asyncio.gather(*[
- ws.send_str(msg)
+ ws.send_str(json.dumps({
+ "msg_type": "event",
+ "msg": {
+ "event": event,
+ "event_attrs": kwargs,
+ },
+ }))
for ws in list(self.__sockets)
if not ws.closed and ws._req.transport # pylint: disable=protected-access
], return_exceptions=True)
- async def __execute_command(self, command: str) -> Optional[str]:
- (command, args) = (command.strip().split(" ", maxsplit=1) + [""])[:2]
- if command == "CLICK":
- method = {
- "power": self.__atx.click_power,
- "power_long": self.__atx.click_power_long,
- "reset": self.__atx.click_reset,
- }.get(args)
- if method:
- await method()
- return None
- elif command == "RESTART_VIDEO":
- self.__restart_video = True
- return None
- get_logger().warning("Received an incorrect command: %r", command)
- return "ERROR incorrect command"
-
async def __register_socket(self, ws: aiohttp.web.WebSocketResponse) -> None:
async with self.__sockets_lock:
self.__sockets.add(ws)
diff --git a/kvmd/kvmd/streamer.py b/kvmd/kvmd/streamer.py
index 72f818cb..089d79e1 100644
--- a/kvmd/kvmd/streamer.py
+++ b/kvmd/kvmd/streamer.py
@@ -2,6 +2,7 @@ import asyncio
import asyncio.subprocess
from typing import List
+from typing import Dict
from typing import Optional
from .logging import get_logger
@@ -16,6 +17,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
cap_power: int,
conv_power: int,
sync_delay: float,
+ width: int,
+ height: int,
cmd: List[str],
loop: asyncio.AbstractEventLoop,
) -> None:
@@ -25,7 +28,9 @@ class Streamer: # pylint: disable=too-many-instance-attributes
self.__cap_power = gpio.set_output(cap_power)
self.__conv_power = (gpio.set_output(conv_power) if conv_power > 0 else conv_power)
self.__sync_delay = sync_delay
- self.__cmd = cmd
+ self.__width = width
+ self.__height = height
+ self.__cmd = [part.format(width=width, height=height) for part in cmd]
self.__loop = loop
@@ -48,6 +53,15 @@ class Streamer: # pylint: disable=too-many-instance-attributes
def is_running(self) -> bool:
return bool(self.__proc_task)
+ def get_state(self) -> Dict:
+ return {
+ "is_running": self.is_running(),
+ "size": {
+ "width": self.__width,
+ "height": self.__height,
+ },
+ }
+
async def cleanup(self) -> None:
if self.is_running():
await self.stop()
diff --git a/os/platforms/v1/kvmd.yaml b/os/platforms/v1/kvmd.yaml
index 95e748a6..125ef3ee 100644
--- a/os/platforms/v1/kvmd.yaml
+++ b/os/platforms/v1/kvmd.yaml
@@ -8,21 +8,20 @@ kvmd:
pinout:
clock: 17
data: 4
+
pulse: 0.0002
atx:
- leds:
- pinout:
- power: 16
- hdd: 12
- poll: 0.1
+ pinout:
+ power_led: 16
+ hdd_led: 12
+ power_switch: 26
+ reset_switch: 20
- switches:
- pinout:
- power: 26
- reset: 20
- click_delay: 0.1
- long_click_delay: 5.5
+ click_delay: 0.1
+ long_click_delay: 5.5
+
+ state_poll: 0.1
msd:
# FIXME: It's for laptop lol
@@ -30,17 +29,21 @@ kvmd:
init_delay: 2.0
chunk_size: 8192
- video:
+ streamer:
pinout:
cap: 21
conv: 25
sync_delay: 1.0
shutdown_delay: 10.0
+ size:
+ width: 720
+ height: 576
+
cmd:
- "/usr/bin/mjpg_streamer"
- "-i"
- - "input_uvc.so -d /dev/video0 -e 2 -y -n -r 720x576"
+ - "input_uvc.so -d /dev/video0 -e 2 -y -n -r {width}x{height}"
- "-o"
- "output_http.so -l localhost -p 8082"