udev instead own bycicles

This commit is contained in:
Devaev Maxim 2018-07-07 23:37:38 +00:00
parent f9a69c7467
commit 476018aeb8
10 changed files with 30 additions and 151 deletions

View File

@ -34,16 +34,15 @@ def main() -> None:
) )
msd = MassStorageDevice( msd = MassStorageDevice(
bind=str(config["msd"]["bind"]), device_path=str(config["msd"]["device"]),
init_delay=float(config["msd"]["init_delay"]), init_delay=float(config["msd"]["init_delay"]),
write_meta=bool(config["msd"]["write_meta"]), write_meta=bool(config["msd"]["write_meta"]),
loop=loop, loop=loop,
) )
streamer = Streamer( streamer = Streamer(
cap_power=int(config["streamer"]["pinout"].get("cap", -1)), cap_power=int(config["streamer"]["pinout"]["cap"]),
conv_power=int(config["streamer"]["pinout"].get("conv", -1)), conv_power=int(config["streamer"]["pinout"]["conv"]),
bind=str(config["streamer"].get("bind", "")),
sync_delay=float(config["streamer"]["sync_delay"]), sync_delay=float(config["streamer"]["sync_delay"]),
init_delay=float(config["streamer"]["init_delay"]), init_delay=float(config["streamer"]["init_delay"]),
width=int(config["streamer"]["size"]["width"]), width=int(config["streamer"]["size"]["width"]),

View File

@ -1,48 +0,0 @@
import argparse
from ... import msd
from ... import streamer
# =====
def _probe_msd(path: str) -> bool:
info = msd.explore_device(path)
if info:
print("It's a mass-storage device")
print("--------------------------")
print("Path: ", info.path)
print("Bind: ", info.bind)
print("Size: ", info.size)
print("Manufacturer:", info.manufacturer)
print("Product: ", info.product)
print("Serial: ", info.serial)
print("Image name: ", info.image_name)
assert msd.locate_by_bind(info.bind), info.bind
return bool(info)
def _probe_streamer(path: str) -> bool:
info = streamer.explore_device(path)
if info:
print("It's a streamer device")
print("----------------------")
print("Path: ", info.path)
print("Bind: ", info.bind)
print("Driver:", info.driver)
assert streamer.locate_by_bind(info.bind), info.bind
return bool(info)
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("device")
options = parser.parse_args()
for probe in [
_probe_msd,
_probe_streamer,
]:
if probe(options.device):
break
else:
raise RuntimeError("Can't recognize device")

View File

@ -1,2 +0,0 @@
from . import main
main()

View File

@ -25,7 +25,7 @@ class MassStorageError(Exception):
class IsNotOperationalError(MassStorageError): class IsNotOperationalError(MassStorageError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("Missing bind for mass-storage device") super().__init__("Missing path for mass-storage device")
class AlreadyConnectedToPcError(MassStorageError): class AlreadyConnectedToPcError(MassStorageError):
@ -50,7 +50,6 @@ class IsBusyError(MassStorageError):
class MassStorageDeviceInfo(NamedTuple): class MassStorageDeviceInfo(NamedTuple):
path: str path: str
bind: str
size: int size: int
manufacturer: str manufacturer: str
product: str product: str
@ -108,10 +107,6 @@ def explore_device(path: str) -> Optional[MassStorageDeviceInfo]:
except KeyError: except KeyError:
return None return None
interface_device = block_device.find_parent("usb", "usb_interface")
if not interface_device:
return None
usb_device = block_device.find_parent("usb", "usb_device") usb_device = block_device.find_parent("usb", "usb_device")
if not usb_device: if not usb_device:
return None return None
@ -122,7 +117,6 @@ def explore_device(path: str) -> Optional[MassStorageDeviceInfo]:
return MassStorageDeviceInfo( return MassStorageDeviceInfo(
path=path, path=path,
bind=interface_device.sys_name,
size=size, size=size,
manufacturer=usb_device.attributes.asstring("manufacturer").strip(), manufacturer=usb_device.attributes.asstring("manufacturer").strip(),
product=usb_device.attributes.asstring("product").strip(), product=usb_device.attributes.asstring("product").strip(),
@ -131,24 +125,11 @@ def explore_device(path: str) -> Optional[MassStorageDeviceInfo]:
) )
def locate_by_bind(bind: str) -> str:
ctx = pyudev.Context()
for device in ctx.list_devices(subsystem="block"):
storage_device = device.find_parent("usb", "usb_interface")
if storage_device:
try:
device.attributes.asint("partititon")
except KeyError:
if storage_device.sys_name == bind:
return os.path.join("/dev", device.sys_name)
return ""
def _operated_and_locked(method: Callable) -> Callable: def _operated_and_locked(method: Callable) -> Callable:
async def wrap(self: "MassStorageDevice", *args: Any, **kwargs: Any) -> Any: async def wrap(self: "MassStorageDevice", *args: Any, **kwargs: Any) -> Any:
if self._device_file: # pylint: disable=protected-access if self._device_file: # pylint: disable=protected-access
raise IsBusyError() raise IsBusyError()
if not self._bind: # pylint: disable=protected-access if not self._device_path: # pylint: disable=protected-access
IsNotOperationalError() IsNotOperationalError()
async with self._lock: # pylint: disable=protected-access async with self._lock: # pylint: disable=protected-access
return (await method(self, *args, **kwargs)) return (await method(self, *args, **kwargs))
@ -156,8 +137,15 @@ def _operated_and_locked(method: Callable) -> Callable:
class MassStorageDevice: # pylint: disable=too-many-instance-attributes class MassStorageDevice: # pylint: disable=too-many-instance-attributes
def __init__(self, bind: str, init_delay: float, write_meta: bool, loop: asyncio.AbstractEventLoop) -> None: def __init__(
self._bind = bind self,
device_path: str,
init_delay: float,
write_meta: bool,
loop: asyncio.AbstractEventLoop,
) -> None:
self._device_path = device_path
self.__init_delay = init_delay self.__init_delay = init_delay
self.__write_meta = write_meta self.__write_meta = write_meta
self.__loop = loop self.__loop = loop
@ -168,8 +156,8 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
self.__writed = 0 self.__writed = 0
logger = get_logger(0) logger = get_logger(0)
if self._bind: if self._device_path:
logger.info("Using bind %r as mass-storage device", self._bind) logger.info("Using %r as mass-storage device", self._device_path)
try: try:
logger.info("Enabled metadata writing") logger.info("Enabled metadata writing")
loop.run_until_complete(self.connect_to_kvm(no_delay=True)) loop.run_until_complete(self.connect_to_kvm(no_delay=True))
@ -179,9 +167,9 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
else: else:
log = logger.exception log = logger.exception
log("Mass-storage device is not operational: %s", err) log("Mass-storage device is not operational: %s", err)
self._bind = "" self._device_path = ""
else: else:
logger.warning("Missing bind; mass-storage device is not operational") logger.warning("Mass-storage device is not operational")
@_operated_and_locked @_operated_and_locked
async def connect_to_kvm(self, no_delay: bool=False) -> None: async def connect_to_kvm(self, no_delay: bool=False) -> None:
@ -203,7 +191,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
def get_state(self) -> Dict: def get_state(self) -> Dict:
return { return {
"in_operate": bool(self._bind), "in_operate": bool(self._device_path),
"connected_to": ("kvm" if self.__device_info else "server"), "connected_to": ("kvm" if self.__device_info else "server"),
"is_busy": bool(self._device_file), "is_busy": bool(self._device_file),
"writed": self.__writed, "writed": self.__writed,
@ -255,12 +243,9 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
await self.__close_device_file() await self.__close_device_file()
async def __reread_device_info(self) -> None: async def __reread_device_info(self) -> None:
device_path = await self.__loop.run_in_executor(None, locate_by_bind, self._bind) device_info = await self.__loop.run_in_executor(None, explore_device, self._device_path)
if not device_path:
raise MassStorageError("Can't locate device by bind %r" % (self._bind))
device_info = await self.__loop.run_in_executor(None, explore_device, device_path)
if not device_info: if not device_info:
raise MassStorageError("Can't explore device %r" % (device_path)) raise MassStorageError("Can't explore device %r" % (self._device_path))
self.__device_info = device_info self.__device_info = device_info
async def __close_device_file(self) -> None: async def __close_device_file(self) -> None:

View File

@ -1,14 +1,9 @@
import os
import asyncio import asyncio
import asyncio.subprocess import asyncio.subprocess
from typing import List from typing import List
from typing import Dict from typing import Dict
from typing import NamedTuple
from typing import Optional from typing import Optional
from typing import Any
import pyudev
from .logging import get_logger from .logging import get_logger
@ -16,52 +11,11 @@ from . import gpio
# ===== # =====
class StreamerDeviceInfo(NamedTuple):
path: str
bind: str
driver: str
def explore_device(path: str) -> Optional[StreamerDeviceInfo]:
# udevadm info -a -p $(udevadm info -q path -n /dev/sda)
ctx = pyudev.Context()
video_device = pyudev.Devices.from_device_file(ctx, path)
if video_device.subsystem != "video4linux":
return None
try:
if int(video_device.attributes.get("index")) != 0:
# My strange laptop configuration
return None
except ValueError:
return None
interface_device = video_device.find_parent("usb", "usb_interface")
if not interface_device:
return None
return StreamerDeviceInfo(
path=path,
bind=interface_device.sys_name,
driver=interface_device.driver,
)
def locate_by_bind(bind: str) -> str:
ctx = pyudev.Context()
for device in ctx.list_devices(subsystem="video4linux"):
interface_device = device.find_parent("usb", "usb_interface")
if interface_device and interface_device.sys_name == bind:
return os.path.join("/dev", device.sys_name)
return ""
class Streamer: # pylint: disable=too-many-instance-attributes class Streamer: # pylint: disable=too-many-instance-attributes
def __init__( def __init__(
self, self,
cap_power: int, cap_power: int,
conv_power: int, conv_power: int,
bind: str,
sync_delay: float, sync_delay: float,
init_delay: float, init_delay: float,
width: int, width: int,
@ -70,11 +24,8 @@ class Streamer: # pylint: disable=too-many-instance-attributes
loop: asyncio.AbstractEventLoop, loop: asyncio.AbstractEventLoop,
) -> None: ) -> None:
assert cmd, cmd
self.__cap_power = (gpio.set_output(cap_power) if cap_power > 0 else cap_power) self.__cap_power = (gpio.set_output(cap_power) if cap_power > 0 else cap_power)
self.__conv_power = (gpio.set_output(conv_power) if conv_power > 0 else conv_power) self.__conv_power = (gpio.set_output(conv_power) if conv_power > 0 else conv_power)
self.__bind = bind
self.__sync_delay = sync_delay self.__sync_delay = sync_delay
self.__init_delay = init_delay self.__init_delay = init_delay
self.__width = width self.__width = width
@ -132,15 +83,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
while True: # pylint: disable=too-many-nested-blocks while True: # pylint: disable=too-many-nested-blocks
proc: Optional[asyncio.subprocess.Process] = None # pylint: disable=no-member proc: Optional[asyncio.subprocess.Process] = None # pylint: disable=no-member
try: try:
cmd_placeholders: Dict[str, Any] = {"width": self.__width, "height": self.__height} cmd = [part.format(width=self.__width, height=self.__height) for part in self.__cmd]
if self.__bind:
logger.info("Using bind %r as streamer device", self.__bind)
device_path = await self.__loop.run_in_executor(None, locate_by_bind, self.__bind)
if not device_path:
raise RuntimeError("Can't locate device by bind %r" % (self.__bind))
cmd_placeholders["device"] = device_path
cmd = [part.format(**cmd_placeholders) for part in self.__cmd]
proc = await asyncio.create_subprocess_exec( proc = await asyncio.create_subprocess_exec(
*cmd, *cmd,
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,

View File

@ -24,7 +24,6 @@ def main() -> None:
"kvmd.extras", "kvmd.extras",
"kvmd.extras.cleanup", "kvmd.extras.cleanup",
"kvmd.extras.wscli", "kvmd.extras.wscli",
"kvmd.extras.explorehw",
], ],
entry_points={ entry_points={
@ -32,7 +31,6 @@ def main() -> None:
"kvmd = kvmd:main", "kvmd = kvmd:main",
"kvmd-cleanup = kvmd.extras.cleanup:main", "kvmd-cleanup = kvmd.extras.cleanup:main",
"kvmd-wscli = kvmd.extras.wscli:main", "kvmd-wscli = kvmd.extras.wscli:main",
"kvmd-explorehw = kvmd.extras.explorehw:main",
], ],
}, },

View File

@ -4,7 +4,8 @@ RUN pkg-install \
nginx nginx
COPY stages/pikvm/config.txt /boot/ COPY stages/pikvm/config.txt /boot/
COPY stages/pikvm/99-pikvm.conf /etc/sysctl.d/ COPY stages/pikvm/sysctl.conf /etc/sysctl.d/99-pikvm.conf
COPY stages/pikvm/udev.rules /etc/udev/rules.d/pikvm.rules
COPY stages/pikvm/index.html /srv/http/ COPY stages/pikvm/index.html /srv/http/
COPY stages/pikvm/kvmd.yaml /etc/ COPY stages/pikvm/kvmd.yaml /etc/
COPY stages/pikvm/nginx.conf /etc/nginx/ COPY stages/pikvm/nginx.conf /etc/nginx/

View File

@ -24,8 +24,7 @@ kvmd:
state_poll: 0.1 state_poll: 0.1
msd: msd:
# FIXME: It's for laptop lol device: "/dev/kvmd-msd"
bind: "1-2:1.0"
init_delay: 2.0 init_delay: 2.0
write_meta: true write_meta: true
chunk_size: 8192 chunk_size: 8192
@ -46,7 +45,7 @@ kvmd:
cmd: cmd:
- "/usr/bin/mjpg_streamer" - "/usr/bin/mjpg_streamer"
- "-i" - "-i"
- "input_uvc.so -d /dev/video0 -e 2 -y -n -r {width}x{height}" - "input_uvc.so -d /dev/kvmd-streamer -e 2 -y -n -r {width}x{height}"
- "-o" - "-o"
- "output_http.so -l localhost -p 8082" - "output_http.so -l localhost -p 8082"

View File

@ -0,0 +1,4 @@
# https://unix.stackexchange.com/questions/66901/how-to-bind-usb-device-under-a-static-name
# https://wiki.archlinux.org/index.php/Udev#Setting_static_device_names
KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", KERNELS=="1-1.3:1.0", SYMLINK+="kvmd-streamer"
KERNEL=="sd[a-z]", SUBSYSTEM=="block", KERNELS=="1-1.4:1.0", SYMLINK+="kvmd-msd"