mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
udev instead own bycicles
This commit is contained in:
parent
f9a69c7467
commit
476018aeb8
@ -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"]),
|
||||||
|
|||||||
@ -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")
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
from . import main
|
|
||||||
main()
|
|
||||||
@ -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:
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -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/
|
||||||
|
|||||||
@ -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"
|
||||||
|
|
||||||
|
|||||||
4
os/platforms/v1/udev.rules
Normal file
4
os/platforms/v1/udev.rules
Normal 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"
|
||||||
Loading…
x
Reference in New Issue
Block a user