diff options
author | Maxim Devaev <[email protected]> | 2025-01-11 21:22:17 +0200 |
---|---|---|
committer | Maxim Devaev <[email protected]> | 2025-01-11 21:22:17 +0200 |
commit | 3cf543a13eab752ffe3c99f0d03f2b0bde4bbbae (patch) | |
tree | 5fd69e733a986c81f81e5e2f6ee09c11cc2f65cb | |
parent | 4d89d6b222635fc99a9b28b45d5bfe7e086f54bc (diff) |
switch binary
-rw-r--r-- | PKGBUILD | 3 | ||||
-rw-r--r-- | kvmd/apps/swctl/__init__.py | 167 | ||||
-rw-r--r-- | kvmd/apps/swctl/__main__.py | 24 | ||||
-rwxr-xr-x | setup.py | 1 | ||||
-rw-r--r-- | switch/LICENSE | 15 | ||||
-rw-r--r-- | switch/Makefile | 8 | ||||
-rw-r--r-- | switch/mnt/README | 1 | ||||
-rw-r--r-- | switch/switch.uf2 | bin | 0 -> 192512 bytes | |||
-rw-r--r-- | testenv/Dockerfile | 1 |
9 files changed, 219 insertions, 1 deletions
@@ -79,6 +79,7 @@ depends=( python-mako python-luma-oled python-pyusb + python-pyudev "libgpiod>=2.1" freetype2 "v4l-utils>=1.22.1-1" @@ -167,7 +168,7 @@ package_kvmd() { install -DTm644 configs/os/tmpfiles.conf "$pkgdir/usr/lib/tmpfiles.d/kvmd.conf" mkdir -p "$pkgdir/usr/share/kvmd" - cp -r {hid,web,extras,contrib/keymaps} "$pkgdir/usr/share/kvmd" + cp -r {switch,hid,web,extras,contrib/keymaps} "$pkgdir/usr/share/kvmd" find "$pkgdir/usr/share/kvmd/web" -name '*.pug' -exec rm -f '{}' \; local _cfg_default="$pkgdir/usr/share/kvmd/configs.default" diff --git a/kvmd/apps/swctl/__init__.py b/kvmd/apps/swctl/__init__.py new file mode 100644 index 00000000..d5915d74 --- /dev/null +++ b/kvmd/apps/swctl/__init__.py @@ -0,0 +1,167 @@ +# ========================================================================== # +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2024 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +import os +import argparse +import pprint +import time + +import pyudev + +from ..kvmd.switch.device import Device +from ..kvmd.switch.proto import Edid + + +# ===== +def _find_serial_device() -> str: + ctx = pyudev.Context() + for device in ctx.list_devices(subsystem="tty"): + if ( + str(device.properties.get("ID_VENDOR_ID")).upper() == "2E8A" + and str(device.properties.get("ID_MODEL_ID")).upper() == "1080" + ): + path = device.properties["DEVNAME"] + assert path.startswith("/dev/") + return path + return "" + + +def _wait_boot_device() -> str: + stop_ts = time.time() + 5 + ctx = pyudev.Context() + while time.time() < stop_ts: + for device in ctx.list_devices(subsystem="block", DEVTYPE="partition"): + if ( + str(device.properties.get("ID_VENDOR_ID")).upper() == "2E8A" + and str(device.properties.get("ID_MODEL_ID")).upper() == "0003" + ): + path = device.properties["DEVNAME"] + assert path.startswith("/dev/") + return path + time.sleep(0.2) + return "" + + +def _create_edid(arg: str) -> Edid: + if arg == "@": + return Edid.from_data("Empty", None) + with open(arg) as file: + return Edid.from_data(os.path.basename(arg), file.read()) + + +# ===== +def main() -> None: # pylint: disable=too-many-statements + parser = argparse.ArgumentParser() + parser.add_argument("-d", "--device", default="") + parser.set_defaults(cmd="") + subs = parser.add_subparsers() + + def add_command(name: str) -> argparse.ArgumentParser: + cmd = subs.add_parser(name) + cmd.set_defaults(cmd=name) + return cmd + + add_command("poll") + + add_command("state") + + cmd = add_command("bootloader") + cmd.add_argument("unit", type=int) + + cmd = add_command("reboot") + cmd.add_argument("unit", type=int) + + cmd = add_command("switch") + cmd.add_argument("unit", type=int) + cmd.add_argument("port", type=int, choices=list(range(5))) + + cmd = add_command("beacon") + cmd.add_argument("unit", type=int) + cmd.add_argument("port", type=int, choices=list(range(6))) + cmd.add_argument("on", choices=["on", "off"]) + + add_command("leds") + + cmd = add_command("click") + cmd.add_argument("button", choices=["power", "reset"]) + cmd.add_argument("unit", type=int) + cmd.add_argument("port", type=int, choices=list(range(4))) + cmd.add_argument("delay_ms", type=int) + + cmd = add_command("set-edid") + cmd.add_argument("unit", type=int) + cmd.add_argument("port", type=int, choices=list(range(4))) + cmd.add_argument("edid", type=_create_edid) + + opts = parser.parse_args() + + if not opts.device: + opts.device = _find_serial_device() + + if opts.cmd == "bootloader" and opts.unit == 0: + if opts.device: + with Device(opts.device) as device: + device.request_reboot(opts.unit, bootloader=True) + found = _wait_boot_device() + if found: + print(found) + raise SystemExit() + raise SystemExit("Error: No switch found") + + if not opts.device: + raise SystemExit("Error: No switch found") + + with Device(opts.device) as device: + wait_rid: (int | None) = None + match opts.cmd: + case "poll": + device.request_state() + device.request_atx_leds() + case "state": + wait_rid = device.request_state() + case "bootloader" | "reboot": + device.request_reboot(opts.unit, (opts.cmd == "bootloader")) + raise SystemExit() + case "switch": + wait_rid = device.request_switch(opts.unit, opts.port) + case "leds": + wait_rid = device.request_atx_leds() + case "click": + match opts.button: + case "power": + wait_rid = device.request_atx_cp(opts.unit, opts.port, opts.delay_ms) + case "reset": + wait_rid = device.request_atx_cr(opts.unit, opts.port, opts.delay_ms) + case "beacon": + wait_rid = device.request_beacon(opts.unit, opts.port, (opts.on == "on")) + case "set-edid": + wait_rid = device.request_set_edid(opts.unit, opts.port, opts.edid) + + error_ts = time.monotonic() + 1 + while True: + for resp in device.read_all(): + pprint.pprint((int(time.time()), resp)) + print() + if resp.header.rid == wait_rid: + raise SystemExit() + if wait_rid is not None and time.monotonic() > error_ts: + raise SystemExit("No answer from unit") diff --git a/kvmd/apps/swctl/__main__.py b/kvmd/apps/swctl/__main__.py new file mode 100644 index 00000000..4827fc49 --- /dev/null +++ b/kvmd/apps/swctl/__main__.py @@ -0,0 +1,24 @@ +# ========================================================================== # +# # +# KVMD - The main PiKVM daemon. # +# # +# Copyright (C) 2018-2024 Maxim Devaev <[email protected]> # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see <https://www.gnu.org/licenses/>. # +# # +# ========================================================================== # + + +from . import main +main() @@ -94,6 +94,7 @@ def main() -> None: "kvmd.apps.otgnet", "kvmd.apps.otgmsd", "kvmd.apps.otgconf", + "kvmd.apps.swctl", "kvmd.apps.htpasswd", "kvmd.apps.totp", "kvmd.apps.edidconf", diff --git a/switch/LICENSE b/switch/LICENSE new file mode 100644 index 00000000..5c574c88 --- /dev/null +++ b/switch/LICENSE @@ -0,0 +1,15 @@ +The PiKVM Switch Firmware +Copyright (C) 2024-2025 + +This software is distributed in binary form and is allowed for run only on original PiKVM Switch hardware. + +Modifications are not allowed. + +One day we will publish the source code, but not today. + +===== +Includes other software related under other licenses: +- MIT: TinyUSB - Copyright (c) 2018, hathach (tinyusb.org). +- MIT: Pico-PIO-USB - Copyright (c) 2021 sekigon-gonnoc. +- BSD: Pico-SDK - Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +- BSD: FatFS - Copyright (C) 20xx, ChaN, all right reserved. diff --git a/switch/Makefile b/switch/Makefile new file mode 100644 index 00000000..4988126a --- /dev/null +++ b/switch/Makefile @@ -0,0 +1,8 @@ +all: + @echo "Run 'make install'" + +upload: install +install: + mount `python -m kvmd.apps.swctl bootloader 0` mnt + cp switch.uf2 mnt + umount mnt diff --git a/switch/mnt/README b/switch/mnt/README new file mode 100644 index 00000000..588522aa --- /dev/null +++ b/switch/mnt/README @@ -0,0 +1 @@ +This is a mount point for the switch. diff --git a/switch/switch.uf2 b/switch/switch.uf2 Binary files differnew file mode 100644 index 00000000..c3f37491 --- /dev/null +++ b/switch/switch.uf2 diff --git a/testenv/Dockerfile b/testenv/Dockerfile index 6373345f..f907bb3d 100644 --- a/testenv/Dockerfile +++ b/testenv/Dockerfile @@ -49,6 +49,7 @@ RUN pacman --noconfirm --ask=4 -Syy \ python-pyotp \ python-qrcode \ python-pyserial \ + python-pyudev \ python-setproctitle \ python-psutil \ python-netifaces \ |