big config refactoring

This commit is contained in:
Devaev Maxim 2019-02-15 07:20:26 +03:00
parent cdddf10b5d
commit 7d79f48fd8
11 changed files with 178 additions and 250 deletions

View File

@ -7,21 +7,18 @@ kvmd:
port: 8081
hid:
pinout:
reset: 4
reset_pin: 4
device: /dev/kvmd-hid
atx:
pinout:
power_led: 24
hdd_led: 22
power_switch: 23
reset_switch: 27
power_led_pin: 24
hdd_led_pin: 22
power_switch_pin: 23
reset_switch_pin: 27
msd:
pinout:
target: 12
reset: 13
target_pin: 12
reset_pin: 13
device: /dev/kvmd-msd
streamer:

View File

@ -7,27 +7,23 @@ kvmd:
port: 8081
hid:
pinout:
reset: 4
reset_pin: 4
device: /dev/kvmd-hid
atx:
pinout:
power_led: 24
hdd_led: 22
power_switch: 23
reset_switch: 27
power_led_pin: 24
hdd_led_pin: 22
power_switch_pin: 23
reset_switch_pin: 27
msd:
pinout:
target: 12
reset: 13
target_pin: 12
reset_pin: 13
device: /dev/kvmd-msd
streamer:
pinout:
cap: 17
conv: 18
cap_pin: 17
conv_pin: 18
init_restart_after: 1
host: 127.0.0.1
port: 8082

View File

@ -111,87 +111,83 @@ def _get_config_scheme() -> Dict:
return {
"kvmd": {
"server": {
"host": Option(default="localhost"),
"port": Option(default=0),
"unix": Option(default="", type=_as_optional_path),
"unix_rm": Option(default=False),
"unix_mode": Option(default=0),
"heartbeat": Option(default=3.0),
"access_log_format": Option(default="[%P / %{X-Real-IP}i] '%r' => %s; size=%b ---"
" referer='%{Referer}i'; user_agent='%{User-Agent}i'"),
"host": Option("localhost"),
"port": Option(0),
"unix": Option("", type=_as_optional_path, rename="unix_path"),
"unix_rm": Option(False),
"unix_mode": Option(0),
"heartbeat": Option(3.0),
"access_log_format": Option("[%P / %{X-Real-IP}i] '%r' => %s; size=%b ---"
" referer='%{Referer}i'; user_agent='%{User-Agent}i'"),
},
"auth": {
"htpasswd": Option(default="/etc/kvmd/htpasswd", type=_as_path),
"htpasswd": Option("/etc/kvmd/htpasswd", type=_as_path, rename="htpasswd_path"),
},
"info": {
"meta": Option(default="/etc/kvmd/meta.yaml", type=_as_path),
"extras": Option(default="/usr/share/kvmd/extras", type=_as_path),
"meta": Option("/etc/kvmd/meta.yaml", type=_as_path, rename="meta_path"),
"extras": Option("/usr/share/kvmd/extras", type=_as_path, rename="extras_path"),
},
"hid": {
"pinout": {
"reset": Option(default=0, type=_as_pin),
},
"reset_delay": Option(default=0.1),
"device": Option(default="", type=_as_path),
"speed": Option(default=115200),
"read_timeout": Option(default=2.0),
"read_retries": Option(default=10),
"common_retries": Option(default=100),
"retries_delay": Option(default=0.1),
"noop": Option(default=False),
"state_poll": Option(default=0.1),
"reset_pin": Option(0, type=_as_pin),
"reset_delay": Option(0.1),
"device": Option("", type=_as_path, rename="device_path"),
"speed": Option(115200),
"read_timeout": Option(2.0),
"read_retries": Option(10),
"common_retries": Option(100),
"retries_delay": Option(0.1),
"noop": Option(False),
"state_poll": Option(0.1),
},
"atx": {
"pinout": {
"power_led": Option(default=0, type=_as_pin),
"hdd_led": Option(default=0, type=_as_pin),
"power_switch": Option(default=0, type=_as_pin),
"reset_switch": Option(default=0, type=_as_pin),
},
"click_delay": Option(default=0.1),
"long_click_delay": Option(default=5.5),
"state_poll": Option(default=0.1),
"power_led_pin": Option(0, type=_as_pin),
"hdd_led_pin": Option(0, type=_as_pin),
"power_switch_pin": Option(0, type=_as_pin),
"reset_switch_pin": Option(0, type=_as_pin),
"click_delay": Option(0.1),
"long_click_delay": Option(5.5),
"state_poll": Option(0.1),
},
"msd": {
"pinout": {
"target": Option(default=0, type=_as_pin),
"reset": Option(default=0, type=_as_pin),
},
"device": Option(default="", type=_as_path),
"init_delay": Option(default=2.0),
"reset_delay": Option(default=1.0),
"write_meta": Option(default=True),
"chunk_size": Option(default=65536),
"target_pin": Option(0, type=_as_pin),
"reset_pin": Option(0, type=_as_pin),
"device": Option("", type=_as_path, rename="device_path"),
"init_delay": Option(2.0),
"reset_delay": Option(1.0),
"write_meta": Option(True),
"chunk_size": Option(65536),
},
"streamer": {
"pinout": {
"cap": Option(default=-1, type=_as_optional_pin),
"conv": Option(default=-1, type=_as_optional_pin),
},
"cap_pin": Option(-1, type=_as_optional_pin),
"conv_pin": Option(-1, type=_as_optional_pin),
"sync_delay": Option(default=1.0),
"init_delay": Option(default=1.0),
"init_restart_after": Option(default=0.0),
"shutdown_delay": Option(default=10.0),
"state_poll": Option(default=1.0),
"sync_delay": Option(1.0),
"init_delay": Option(1.0),
"init_restart_after": Option(0.0),
"shutdown_delay": Option(10.0),
"state_poll": Option(1.0),
"quality": Option(default=80),
"desired_fps": Option(default=0),
"quality": Option(80),
"desired_fps": Option(0),
"host": Option(default="localhost"),
"port": Option(default=0),
"unix": Option(default="", type=_as_optional_path),
"timeout": Option(default=2.0),
"host": Option("localhost"),
"port": Option(0),
"unix": Option("", type=_as_optional_path, rename="unix_path"),
"timeout": Option(2.0),
"cmd": Option(default=["/bin/true"], type=_as_string_list),
"cmd": Option(["/bin/true"], type=_as_string_list),
},
},
"logging": Option(default={}),
"logging": Option({}),
}

View File

@ -20,101 +20,18 @@ from .server import Server
def main() -> None:
config = init("kvmd", description="The main Pi-KVM daemon")[2].kvmd
with gpio.bcm():
# pylint: disable=protected-access
loop = asyncio.get_event_loop()
auth_manager = AuthManager(
htpasswd_path=config.auth.htpasswd,
)
info_manager = InfoManager(
meta_path=config.info.meta,
extras_path=config.info.extras,
loop=loop,
)
log_reader = LogReader(loop)
hid = Hid(
reset=config.hid.pinout.reset,
reset_delay=config.hid.reset_delay,
device_path=config.hid.device,
speed=config.hid.speed,
read_timeout=config.hid.read_timeout,
read_retries=config.hid.read_retries,
common_retries=config.hid.common_retries,
retries_delay=config.hid.retries_delay,
noop=config.hid.noop,
state_poll=config.hid.state_poll,
)
atx = Atx(
power_led=config.atx.pinout.power_led,
hdd_led=config.atx.pinout.hdd_led,
power_switch=config.atx.pinout.power_switch,
reset_switch=config.atx.pinout.reset_switch,
click_delay=config.atx.click_delay,
long_click_delay=config.atx.long_click_delay,
state_poll=config.atx.state_poll,
)
msd = MassStorageDevice(
target=config.msd.pinout.target,
reset=config.msd.pinout.reset,
device_path=config.msd.device,
init_delay=config.msd.init_delay,
reset_delay=config.msd.reset_delay,
write_meta=config.msd.write_meta,
loop=loop,
)
streamer = Streamer(
cap_power=config.streamer.pinout.cap,
conv_power=config.streamer.pinout.conv,
sync_delay=config.streamer.sync_delay,
init_delay=config.streamer.init_delay,
init_restart_after=config.streamer.init_restart_after,
state_poll=config.streamer.state_poll,
quality=config.streamer.quality,
desired_fps=config.streamer.desired_fps,
host=config.streamer.host,
port=config.streamer.port,
unix_path=config.streamer.unix,
timeout=config.streamer.timeout,
cmd=config.streamer.cmd,
loop=loop,
)
Server(
auth_manager=auth_manager,
info_manager=info_manager,
log_reader=log_reader,
auth_manager=AuthManager(**config.auth._unpack_renamed()),
info_manager=InfoManager(loop=loop, **config.info._unpack_renamed()),
log_reader=LogReader(loop=loop),
hid=hid,
atx=atx,
msd=msd,
streamer=streamer,
access_log_format=config.server.access_log_format,
heartbeat=config.server.heartbeat,
streamer_shutdown_delay=config.streamer.shutdown_delay,
msd_chunk_size=config.msd.chunk_size,
hid=Hid(**config.hid._unpack_renamed()),
atx=Atx(**config.atx._unpack_renamed()),
msd=MassStorageDevice(loop=loop, **config.msd._unpack_renamed()),
streamer=Streamer(loop=loop, **config.streamer._unpack_renamed()),
loop=loop,
).run(
host=config.server.host,
port=config.server.port,
unix_path=config.server.unix,
unix_rm=config.server.unix_rm,
unix_mode=config.server.unix_mode,
)
).run(**config.server._unpack_renamed())
get_logger().info("Bye-bye")

View File

@ -17,21 +17,22 @@ class AtxIsBusy(aioregion.RegionIsBusyError):
class Atx: # pylint: disable=too-many-instance-attributes
def __init__(
self,
power_led: int,
hdd_led: int,
power_switch: int,
reset_switch: int,
power_led_pin: int,
hdd_led_pin: int,
power_switch_pin: int,
reset_switch_pin: int,
click_delay: float,
long_click_delay: float,
state_poll: float,
) -> None:
self.__power_led = gpio.set_input(power_led)
self.__hdd_led = gpio.set_input(hdd_led)
self.__power_led_pin = gpio.set_input(power_led_pin)
self.__hdd_led_pin = gpio.set_input(hdd_led_pin)
self.__power_switch = gpio.set_output(power_switch)
self.__reset_switch = gpio.set_output(reset_switch)
self.__power_switch_pin = gpio.set_output(power_switch_pin)
self.__reset_switch_pin = gpio.set_output(reset_switch_pin)
self.__click_delay = click_delay
self.__long_click_delay = long_click_delay
@ -43,8 +44,8 @@ class Atx: # pylint: disable=too-many-instance-attributes
return {
"busy": self.__region.is_busy(),
"leds": {
"power": (not gpio.read(self.__power_led)),
"hdd": (not gpio.read(self.__hdd_led)),
"power": (not gpio.read(self.__power_led_pin)),
"hdd": (not gpio.read(self.__hdd_led_pin)),
},
}
@ -55,15 +56,15 @@ class Atx: # pylint: disable=too-many-instance-attributes
async def click_power(self) -> None:
get_logger().info("Clicking power ...")
await self.__click(self.__power_switch, self.__click_delay)
await self.__click(self.__power_switch_pin, self.__click_delay)
async def click_power_long(self) -> None:
get_logger().info("Clicking power (long press) ...")
await self.__click(self.__power_switch, self.__long_click_delay)
await self.__click(self.__power_switch_pin, self.__long_click_delay)
async def click_reset(self) -> None:
get_logger().info("Clicking reset")
await self.__click(self.__reset_switch, self.__click_delay)
await self.__click(self.__reset_switch_pin, self.__click_delay)
async def __click(self, pin: int, delay: float) -> None:
self.__region.enter()

View File

@ -85,7 +85,7 @@ class _MouseWheelEvent(NamedTuple):
class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attributes
def __init__( # pylint: disable=too-many-arguments
self,
reset: int,
reset_pin: int,
reset_delay: float,
device_path: str,
@ -101,7 +101,7 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu
super().__init__(daemon=True)
self.__reset = gpio.set_output(reset)
self.__reset_pin = gpio.set_output(reset_pin)
self.__reset_delay = reset_delay
self.__device_path = device_path
@ -137,9 +137,9 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu
async def reset(self) -> None:
async with self.__lock:
gpio.write(self.__reset, True)
gpio.write(self.__reset_pin, True)
await asyncio.sleep(self.__reset_delay)
gpio.write(self.__reset, False)
gpio.write(self.__reset_pin, False)
async def send_key_event(self, key: str, state: bool) -> None:
if not self.__stop_event.is_set():
@ -188,7 +188,7 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu
else:
get_logger().warning("Emergency cleaning up HID events ...")
self.__emergency_clear_events()
gpio.write(self.__reset, False)
gpio.write(self.__reset_pin, False)
def __unsafe_clear_events(self) -> None:
for button in self.__pressed_mouse_buttons:

View File

@ -167,24 +167,26 @@ def _msd_operated(method: Callable) -> Callable:
class MassStorageDevice: # pylint: disable=too-many-instance-attributes
def __init__(
self,
target: int,
reset: int,
target_pin: int,
reset_pin: int,
device_path: str,
init_delay: float,
reset_delay: float,
write_meta: bool,
chunk_size: int,
loop: asyncio.AbstractEventLoop,
) -> None:
self.__target = gpio.set_output(target)
self.__reset = gpio.set_output(reset)
self.__target_pin = gpio.set_output(target_pin)
self.__reset_pin = gpio.set_output(reset_pin)
self._device_path = device_path
self.__init_delay = init_delay
self.__reset_delay = reset_delay
self.__write_meta = write_meta
self.chunk_size = chunk_size
self.__loop = loop
@ -236,15 +238,15 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
async def cleanup(self) -> None:
await self.__close_device_file()
gpio.write(self.__target, False)
gpio.write(self.__reset, False)
gpio.write(self.__target_pin, False)
gpio.write(self.__reset_pin, False)
@_msd_operated
async def connect_to_kvm(self, no_delay: bool=False) -> Dict:
with self.__region:
if self.__device_info:
raise MsdAlreadyConnectedToKvmError()
gpio.write(self.__target, False)
gpio.write(self.__target_pin, False)
if not no_delay:
await asyncio.sleep(self.__init_delay)
await self.__load_device_info()
@ -258,7 +260,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
with self.__region:
if not self.__device_info:
raise MsdAlreadyConnectedToPcError()
gpio.write(self.__target, True)
gpio.write(self.__target_pin, True)
self.__device_info = None
state = self.get_state()
await self.__state_queue.put(state)
@ -269,9 +271,9 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
async def reset(self) -> None:
with self.__region:
get_logger().info("Mass-storage device reset")
gpio.write(self.__reset, True)
gpio.write(self.__reset_pin, True)
await asyncio.sleep(self.__reset_delay)
gpio.write(self.__reset, False)
gpio.write(self.__reset_pin, False)
await self.__state_queue.put(self.get_state())
@_msd_operated

View File

@ -209,7 +209,7 @@ class _Events(Enum):
class Server: # pylint: disable=too-many-instance-attributes
def __init__( # pylint: disable=too-many-arguments
def __init__(
self,
auth_manager: AuthManager,
info_manager: InfoManager,
@ -220,11 +220,6 @@ class Server: # pylint: disable=too-many-instance-attributes
msd: MassStorageDevice,
streamer: Streamer,
access_log_format: str,
heartbeat: float,
streamer_shutdown_delay: float,
msd_chunk_size: int,
loop: asyncio.AbstractEventLoop,
) -> None:
@ -237,13 +232,9 @@ class Server: # pylint: disable=too-many-instance-attributes
self.__msd = msd
self.__streamer = streamer
self.__access_log_format = access_log_format
self.__heartbeat = heartbeat
self.__streamer_shutdown_delay = streamer_shutdown_delay
self.__msd_chunk_size = msd_chunk_size
self.__loop = loop
self.__heartbeat: Optional[float] = None # Assigned in run() for consistance
self.__sockets: Set[aiohttp.web.WebSocketResponse] = set()
self.__sockets_lock = asyncio.Lock()
@ -252,11 +243,22 @@ class Server: # pylint: disable=too-many-instance-attributes
self.__reset_streamer = False
self.__streamer_params = streamer.get_params()
def run(self, host: str, port: int, unix_path: str, unix_rm: bool, unix_mode: int) -> None:
def run(
self,
host: str,
port: int,
unix_path: str,
unix_rm: bool,
unix_mode: int,
heartbeat: float,
access_log_format: str,
) -> None:
self.__hid.start()
setproctitle.setproctitle("[main] " + setproctitle.getproctitle())
self.__heartbeat = heartbeat
app = aiohttp.web.Application(loop=self.__loop)
app.on_shutdown.append(self.__on_shutdown)
app.on_cleanup.append(self.__on_cleanup)
@ -290,7 +292,7 @@ class Server: # pylint: disable=too-many-instance-attributes
aiohttp.web.run_app(
app=app,
access_log_format=self.__access_log_format,
access_log_format=access_log_format,
print=self.__run_app_print,
**socket_kwargs,
)
@ -354,6 +356,7 @@ class Server: # pylint: disable=too-many-instance-attributes
@_exposed("GET", "/ws")
async def __ws_handler(self, request: aiohttp.web.Request) -> aiohttp.web.WebSocketResponse:
logger = get_logger(0)
assert self.__heartbeat is not None
ws = aiohttp.web.WebSocketResponse(heartbeat=self.__heartbeat)
await ws.prepare(request)
await self.__register_socket(ws)
@ -476,7 +479,7 @@ class Server: # pylint: disable=too-many-instance-attributes
logger.info("Writing image %r to mass-storage device ...", image_name)
await self.__msd.write_image_info(image_name, False)
while True:
chunk = await field.read_chunk(self.__msd_chunk_size)
chunk = await field.read_chunk(self.__msd.chunk_size)
if not chunk:
break
written = await self.__msd.write_image_chunk(chunk)
@ -581,7 +584,7 @@ class Server: # pylint: disable=too-many-instance-attributes
if not self.__streamer.is_running():
await self.__streamer.start(self.__streamer_params)
elif prev > 0 and cur == 0:
shutdown_at = time.time() + self.__streamer_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()

View File

@ -19,12 +19,13 @@ from ... import gpio
class Streamer: # pylint: disable=too-many-instance-attributes
def __init__( # pylint: disable=too-many-arguments,too-many-locals
self,
cap_power: int,
conv_power: int,
cap_pin: int,
conv_pin: int,
sync_delay: float,
init_delay: float,
init_restart_after: float,
shutdown_delay: float,
state_poll: float,
quality: int,
@ -40,12 +41,13 @@ class Streamer: # pylint: disable=too-many-instance-attributes
loop: asyncio.AbstractEventLoop,
) -> None:
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.__cap_pin = (gpio.set_output(cap_pin) if cap_pin > 0 else cap_pin)
self.__conv_pin = (gpio.set_output(conv_pin) if conv_pin > 0 else conv_pin)
self.__sync_delay = sync_delay
self.__init_delay = init_delay
self.__init_restart_after = init_restart_after
self.shutdown_delay = shutdown_delay
self.__state_poll = state_poll
self.__params = {
@ -155,12 +157,12 @@ class Streamer: # pylint: disable=too-many-instance-attributes
async def __set_hw_enabled(self, enabled: bool) -> None:
# XXX: This sequence is very important to enable converter and cap board
if self.__cap_power > 0:
gpio.write(self.__cap_power, enabled)
if self.__conv_power > 0:
if self.__cap_pin > 0:
gpio.write(self.__cap_pin, enabled)
if self.__conv_pin > 0:
if enabled:
await asyncio.sleep(self.__sync_delay)
gpio.write(self.__conv_power, enabled)
gpio.write(self.__conv_pin, enabled)
if enabled:
await asyncio.sleep(self.__init_delay)

View File

@ -44,35 +44,52 @@ class Section(dict):
dict.__init__(self)
self.__meta: Dict[str, Dict[str, Any]] = {}
def _set_meta(self, name: str, default: Any, help: str) -> None: # pylint: disable=redefined-builtin
self.__meta[name] = {
def _unpack_renamed(self) -> Dict[str, Any]:
unpacked: Dict[str, Any] = {}
for (key, value) in self.items():
assert not isinstance(value, Section), (key, value)
key = (self.__meta[key]["rename"] or key)
unpacked[key] = value
return unpacked
def _set_meta(self, key: str, default: Any, help: str, rename: str) -> None: # pylint: disable=redefined-builtin
self.__meta[key] = {
"default": default,
"help": help,
"rename": rename,
}
def _get_default(self, name: str) -> Any:
return self.__meta[name]["default"]
def _get_default(self, key: str) -> Any:
return self.__meta[key]["default"]
def _get_help(self, name: str) -> str:
return self.__meta[name]["help"]
def _get_help(self, key: str) -> str:
return self.__meta[key]["help"]
def __getattribute__(self, name: str) -> Any:
if name in self:
return self[name]
def __getattribute__(self, key: str) -> Any:
if key in self:
return self[key]
else: # For pickling
return dict.__getattribute__(self, name)
return dict.__getattribute__(self, key)
class Option:
__type = type
def __init__(self, default: Any, help: str="", type: Optional[Callable[[Any], Any]]=None) -> None: # pylint: disable=redefined-builtin
def __init__(
self,
default: Any,
help: str="", # pylint: disable=redefined-builtin
type: Optional[Callable[[Any], Any]]=None, # pylint: disable=redefined-builtin
rename: str="",
) -> None:
self.default = default
self.help = help
self.type: Callable[[Any], Any] = (type or (self.__type(default) if default is not None else str)) # type: ignore
self.rename = rename
def __repr__(self) -> str:
return "<Option(default={self.default}, type={self.type}, help={self.help})>".format(self=self)
return "<Option(default={self.default}, type={self.type}, help={self.help}, rename={self.rename})>".format(self=self)
# =====
@ -93,9 +110,10 @@ def make_config(raw: Dict[str, Any], scheme: Dict[str, Any], _keys: Tuple[str, .
raise ValueError("Invalid value '{value}' for key '{key}'".format(key=full_name, value=value))
config[key] = value
config._set_meta( # pylint: disable=protected-access
name=key,
key=key,
default=option.default,
help=option.help,
rename=option.rename,
)
elif isinstance(option, dict):
config[key] = make_config(raw.get(key, {}), option, full_key)

View File

@ -4,28 +4,24 @@ kvmd:
port: 8081
hid:
pinout:
reset: 4
reset_pin: 4
device: /dev/ttyS10
noop: true
atx:
pinout:
power_led: 24
hdd_led: 22
power_switch: 23
reset_switch: 27
power_led_pin: 24
hdd_led_pin: 22
power_switch_pin: 23
reset_switch_pin: 27
msd:
pinout:
target: 12
reset: 13
target_pin: 12
reset_pin: 13
device: /dev/kvmd-msd
streamer:
pinout:
cap: 17
conv: 18
cap_pin: 17
conv_pin: 18
init_restart_after: 1
host: 127.0.0.1
port: 8082