Browse Source

added scanner for ipkvm device tree

TC 2 weeks ago
parent
commit
2cd8be0626
7 changed files with 303 additions and 95 deletions
  1. 18 0
      remdeskd/debug.sh
  2. 5 0
      remdeskd/ipkvm.go
  3. 43 5
      remdeskd/kvmscan.go
  4. 39 49
      remdeskd/main.go
  5. 0 22
      remdeskd/mod/remdesaux/remdesaux.go
  6. 98 0
      remdeskd/tools.go
  7. 100 19
      remdeskd/usbkvm.go

+ 18 - 0
remdeskd/debug.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+
+echo "This script helps debug audio and USB KVM devices."
+echo "Make sure you have the necessary permissions to access audio and USB devices."
+echo ""
+
+
+# List all audio devices
+echo "------------------"
+echo "Listing audio devices"
+sudo ./remdeskd -mode=debug -tool=audio-devices
+
+# List all USB KVM devices
+echo "------------------"
+echo "Listing USB KVM devices"
+sudo ./remdeskd -mode=debug -tool=list-usbkvm
+echo "------------------"
+sudo ./remdeskd -mode=debug -tool=list-usbkvm-json

+ 5 - 0
remdeskd/ipkvm.go

@@ -0,0 +1,5 @@
+package main
+
+func init_ipkvm_mode() error {
+	return nil
+}

+ 43 - 5
remdeskd/kvmscan.go

@@ -2,10 +2,13 @@ package main
 
 import (
 	"errors"
+	"log"
 	"os/exec"
 	"path/filepath"
 	"regexp"
 	"strings"
+
+	"imuslab.com/remdeskvm/remdeskd/mod/remdesaux"
 )
 
 /*
@@ -20,12 +23,38 @@ Commonly found devices are:
 -- USB CDC ACM device (auxiliary MCU)
 -- USB Video Class device (webcam capture)
 -- USB Audio Class device (audio capture)
+
+The AuxMCU will provide a UUID to uniquely identify
+the USB KVM device subtree.
 */
 type UsbKvmDevice struct {
-	USBKVMDevicePath   string
-	AuxMCUDevicePath   string
-	CaptureDevicePaths []string
-	AlsaDevicePaths    []string
+	UUID               string   // 16 bytes UUID obtained from AuxMCU, might change after power cycle
+	USBKVMDevicePath   string   // e.g. /dev/ttyUSB0
+	AuxMCUDevicePath   string   // e.g. /dev/ttyACM0
+	CaptureDevicePaths []string // e.g. /dev/video0, /dev/video1, etc.
+	AlsaDevicePaths    []string // e.g. /dev/snd/pcmC1D0c, etc.
+}
+
+// populateUsbKvmUUID tries to get the UUID from the AuxMCU device
+func populateUsbKvmUUID(dev *UsbKvmDevice) error {
+	if dev.AuxMCUDevicePath == "" {
+		return nil
+	}
+
+	// The standard baudrate for AuxMCU is 115200
+	aux, err := remdesaux.NewAuxOutbandController(dev.AuxMCUDevicePath, 115200)
+	if err != nil {
+		return err
+	}
+	defer aux.Close()
+
+	uuid, err := aux.GetUUID()
+	if err != nil {
+		return err
+	}
+
+	dev.UUID = uuid
+	return nil
 }
 
 func discoverUsbKvmSubtree() ([]*UsbKvmDevice, error) {
@@ -70,11 +99,12 @@ func discoverUsbKvmSubtree() ([]*UsbKvmDevice, error) {
 	alsas := getSys(alsaDevs)
 
 	// Find common USB root hub prefix
+	hubPattern := regexp.MustCompile(`^\d+-\d+(\.\d+)*$`)
 	getHub := func(sys string) string {
 		parts := strings.Split(sys, "/")
 		for i := range parts {
 			// Look for USB hub pattern (e.g. 1-2, 2-1, etc.)
-			if matched, _ := regexp.MatchString(`^\d+-\d+(\.\d+)*$`, parts[i]); matched {
+			if hubPattern.MatchString(parts[i]) {
 				return strings.Join(parts[:i+1], "/")
 			}
 		}
@@ -144,6 +174,14 @@ func discoverUsbKvmSubtree() ([]*UsbKvmDevice, error) {
 		}
 	}
 
+	// Populate UUIDs
+	for _, dev := range result {
+		err := populateUsbKvmUUID(dev)
+		if err != nil {
+			log.Printf("Warning: could not get UUID for AuxMCU %s: %v, is this a third party device?", dev.AuxMCUDevicePath, err)
+		}
+	}
+
 	if len(result) == 0 {
 		return nil, errors.New("no USB KVM device found")
 	}

+ 39 - 49
remdeskd/main.go

@@ -6,27 +6,19 @@ import (
 	"io/fs"
 	"log"
 	"net/http"
-
-	"imuslab.com/remdeskvm/remdeskd/mod/remdesaux"
-	"imuslab.com/remdeskvm/remdeskd/mod/remdeshid"
-	"imuslab.com/remdeskvm/remdeskd/mod/usbcapture"
+	"os"
 )
 
-const defaultDevMode = true
+const (
+	defaultDevMode   = true
+	configPath       = "./config"
+	usbKvmConfigPath = configPath + "/usbkvm.json"
+)
 
 var (
-	developent            = flag.Bool("dev", defaultDevMode, "Enable development mode with local static files")
-	mode                  = flag.String("mode", "usbkvm", "Mode of operation: kvm or capture")
-	usbKVMDeviceName      = flag.String("usbhid", "/dev/ttyUSB0", "USB KVM device file path")
-	usbAuxilaryDeviceName = flag.String("auxmcu", "/dev/ttyACM0", "USB Auxiliary MCU device file path")
-	usbKVMBaudRate        = flag.Int("baudrate", 115200, "USB KVM baud rate")
-	usbAuxBaudRate        = flag.Int("auxbaudrate", 115200, "USB Auxiliary MCU baud rate")
-	captureDeviceName     = flag.String("capture", "/dev/video0", "Video capture device file path")
-
-	/* Internal Modules */
-	usbKVM       *remdeshid.Controller
-	auxMCU       *remdesaux.AuxMcu
-	videoCapture *usbcapture.Instance
+	developent = flag.Bool("dev", defaultDevMode, "Enable development mode with local static files")
+	mode       = flag.String("mode", "usbkvm", "Mode of operation: usbkvm, ipkvm or debug")
+	tool       = flag.String("tool", "", "Run debug tool, must be used with -mode=debug")
 )
 
 /* Web Server Static Files */
@@ -46,16 +38,18 @@ func init() {
 		}
 		webfs = http.FS(subFS)
 	}
+
+	// Initiate the config folder if not exists
+	err := os.MkdirAll("./config", 0755)
+	if err != nil {
+		log.Fatal("Failed to create config folder:", err)
+	}
+
 }
 
 func main() {
 	flag.Parse()
-	// Initiate the HID controller
-	usbKVM = remdeshid.NewHIDController(&remdeshid.Config{
-		PortName:          *usbKVMDeviceName,
-		BaudRate:          *usbKVMBaudRate,
-		ScrollSensitivity: 0x01, // Set mouse scroll sensitivity
-	})
+
 	switch *mode {
 	case "cfgchip":
 		err := SetupHIDCommunication()
@@ -63,44 +57,40 @@ func main() {
 			log.Fatal(err)
 		}
 	case "debug":
-		result, err := discoverUsbKvmSubtree()
+		err := handle_debug_tool()
 		if err != nil {
 			log.Fatal(err)
 		}
-		for i, dev := range result {
-			log.Printf("Device %d:\n", i)
-			log.Printf(" - USB KVM Device: %s\n", dev.USBKVMDevicePath)
-			log.Printf(" - Aux MCU Device: %s\n", dev.AuxMCUDevicePath)
-			for _, cap := range dev.CaptureDevicePaths {
-				log.Printf(" - Capture Device: %s\n", cap)
-			}
-			for _, snd := range dev.AlsaDevicePaths {
-				log.Printf(" - ALSA Device: %s\n", snd)
-			}
-		}
-	case "usbkvm":
-		err := startUsbKvmMode()
+	case "ipkvm":
+		//Check runtime dependencies
+		err := run_dependency_precheck()
 		if err != nil {
 			log.Fatal(err)
 		}
-	case "list-audio-devices":
-		log.Println("Starting in List Audio Devices mode...")
-		//Get the audio devices
-		path, err := usbcapture.FindHDMICapturePCMPath()
+
+		//Start IP-KVM mode
+		err = init_ipkvm_mode()
 		if err != nil {
-			log.Fatalf("Failed to find HDMI capture PCM path: %v", err)
+			log.Fatal(err)
 		}
-		log.Printf("Found HDMI capture PCM path: %s\n", path)
-		//List all audio capture devices
-		captureDevs, err := usbcapture.ListCaptureDevices()
+	case "usbkvm":
+		//Check runtime dependencies
+		err := run_dependency_precheck()
 		if err != nil {
-			log.Fatalf("Failed to list capture devices: %v", err)
+			log.Fatal(err)
 		}
-		log.Println("Available audio capture devices:")
-		for _, dev := range captureDevs {
-			log.Printf(" - %s\n", dev)
+
+		//Load config file or create default one
+		kvmCfg, err := loadUsbKvmConfig()
+		if err != nil {
+			log.Fatal("Failed to load or create USB KVM config:", err)
 		}
 
+		//Start USB KVM mode
+		err = startUsbKvmMode(kvmCfg)
+		if err != nil {
+			log.Fatal(err)
+		}
 	default:
 		log.Fatalf("Unknown mode: %s. Supported modes are: usbkvm, capture", *mode)
 	}

+ 0 - 22
remdeskd/mod/remdesaux/remdesaux.go

@@ -116,25 +116,3 @@ func (c *AuxMcu) GetUSBMassStorageSide() USB_mass_storage_side {
 	defer c.mu.Unlock()
 	return c.usb_mass_storage_side
 }
-
-// Example usage
-/*
-func Example() {
-	dev, err := NewCH552G("/dev/ttyUSB0", 115200)
-	if err != nil {
-		log.Fatal(err)
-	}
-	defer dev.Close()
-
-	if err := dev.SwitchUSBToKVM(); err != nil {
-		log.Println("Switch to KVM failed:", err)
-	}
-	uuid, err := dev.GetUUID()
-	if err != nil {
-		log.Println("Get UUID failed:", err)
-	} else {
-		fmt.Println("Device UUID:", uuid)
-	}
-}
-
-*/

+ 98 - 0
remdeskd/tools.go

@@ -0,0 +1,98 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"os/exec"
+
+	"imuslab.com/remdeskvm/remdeskd/mod/usbcapture"
+)
+
+func handle_debug_tool() error {
+	switch *tool {
+	case "dependency-precheck":
+		err := run_dependency_precheck()
+		if err != nil {
+			return err
+		}
+	case "list-usbkvm-json":
+		result, err := discoverUsbKvmSubtree()
+		if err != nil {
+			return err
+		}
+		jsonData, err := json.MarshalIndent(result, "", "  ")
+		if err != nil {
+			return err
+		}
+		fmt.Println(string(jsonData))
+	case "audio-devices":
+		err := list_all_audio_devices()
+		if err != nil {
+			return err
+		}
+	case "list-usbkvm":
+		err := list_usb_kvm_devcies()
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("please specify a valid tool with -tool option")
+	}
+	return nil
+}
+
+// run_dependency_precheck checks if required dependencies are available in the system
+func run_dependency_precheck() error {
+	log.Println("Running precheck...")
+	// Dependencies of USB capture card
+	if _, err := exec.LookPath("v4l2-ctl"); err != nil {
+		return fmt.Errorf("v4l2-ctl not found in PATH")
+	}
+	if _, err := exec.LookPath("arecord"); err != nil {
+		return fmt.Errorf("arecord not found in PATH")
+	}
+	log.Println("v4l2-ctl and arecord found in PATH.")
+	return nil
+}
+
+// list_usb_kvm_devcies lists all discovered USB KVM devices and their associated sub-devices
+func list_usb_kvm_devcies() error {
+	result, err := discoverUsbKvmSubtree()
+	if err != nil {
+		return err
+	}
+	for i, dev := range result {
+		log.Printf("USB KVM Device Tree %d:\n", i)
+		log.Printf(" - USB KVM Device: %s\n", dev.USBKVMDevicePath)
+		log.Printf(" - Aux MCU Device: %s\n", dev.AuxMCUDevicePath)
+		for _, cap := range dev.CaptureDevicePaths {
+			log.Printf(" - Capture Device: %s\n", cap)
+		}
+		for _, snd := range dev.AlsaDevicePaths {
+			log.Printf(" - ALSA Device: %s\n", snd)
+		}
+	}
+	return nil
+}
+
+// list_all_audio_devices lists all available audio capture devices
+func list_all_audio_devices() error {
+	log.Println("Starting in List Audio Devices mode...")
+	// Get the audio devices
+	path, err := usbcapture.FindHDMICapturePCMPath()
+	if err != nil {
+		return err
+	}
+	log.Printf("Found HDMI capture PCM path: %s\n", path)
+	// List all audio capture devices
+	captureDevs, err := usbcapture.ListCaptureDevices()
+	if err != nil {
+		return err
+	}
+	log.Println("Available audio capture devices:")
+	for _, dev := range captureDevs {
+		log.Printf(" - %s\n", dev)
+	}
+	return nil
+}

+ 100 - 19
remdeskd/usbkvm.go

@@ -1,6 +1,16 @@
 package main
 
+/*
+	usbkvm.go
+
+	Handles the USB KVM device connections and auxiliary devices
+	running in USB KVM mode. This mode only support 1 USB KVM device
+	at a time.
+
+	For running multiple USB KVM devices, use the ipkvm mode.
+*/
 import (
+	"encoding/json"
 	"log"
 	"net/http"
 	"os"
@@ -8,27 +18,101 @@ import (
 	"syscall"
 
 	"imuslab.com/remdeskvm/remdeskd/mod/remdesaux"
+	"imuslab.com/remdeskvm/remdeskd/mod/remdeshid"
 	"imuslab.com/remdeskvm/remdeskd/mod/usbcapture"
 )
 
-func startUsbKvmMode() error {
+type UsbKvmConfig struct {
+	ListeningAddress        string
+	USBKVMDevicePath        string
+	AuxMCUDevicePath        string
+	VideoCaptureDevicePath  string
+	AudioCaptureDevicePath  string
+	CaptureResolutionWidth  int
+	CaptureResolutionHeight int
+	CaptureResolutionFPS    int
+	USBKVMBaudrate          int
+	AuxMCUBaudrate          int
+}
+
+var (
+	/* Internal variables for USB-KVM mode only */
+	usbKVM              *remdeshid.Controller
+	auxMCU              *remdesaux.AuxMcu
+	videoCapture        *usbcapture.Instance
+	defaultUsbKvmConfig = &UsbKvmConfig{
+		ListeningAddress:        ":9000",
+		USBKVMDevicePath:        "/dev/ttyUSB0",
+		AuxMCUDevicePath:        "/dev/ttyACM0",
+		VideoCaptureDevicePath:  "/dev/video0",
+		AudioCaptureDevicePath:  "/dev/snd/pcmC1D0c",
+		CaptureResolutionWidth:  1920,
+		CaptureResolutionHeight: 1080,
+		CaptureResolutionFPS:    25,
+		USBKVMBaudrate:          115200,
+		AuxMCUBaudrate:          115200,
+	}
+)
+
+func loadUsbKvmConfig() (*UsbKvmConfig, error) {
+	if _, err := os.Stat(usbKvmConfigPath); os.IsNotExist(err) {
+		file, err := os.OpenFile(usbKvmConfigPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755)
+		if err != nil {
+			return nil, err
+		}
+
+		// Save default config as JSON
+		enc := json.NewEncoder(file)
+		enc.SetIndent("", "  ")
+		if err := enc.Encode(defaultUsbKvmConfig); err != nil {
+			file.Close()
+			return nil, err
+		}
+		file.Close()
+		return defaultUsbKvmConfig, nil
+	}
+
+	// Load config from file
+	file, err := os.Open(usbKvmConfigPath)
+	if err != nil {
+		return nil, err
+	}
+
+	cfg := &UsbKvmConfig{}
+	dec := json.NewDecoder(file)
+	if err := dec.Decode(cfg); err != nil {
+		file.Close()
+		return nil, err
+	}
+	file.Close()
+	return cfg, nil
+}
+
+func startUsbKvmMode(config *UsbKvmConfig) error {
 	log.Println("Starting in USB KVM mode...")
+	// Initiate the HID controller
+	usbKVM = remdeshid.NewHIDController(&remdeshid.Config{
+		PortName:          config.USBKVMDevicePath,
+		BaudRate:          config.USBKVMBaudrate,
+		ScrollSensitivity: 0x01, // Set mouse scroll sensitivity
+	})
+
 	//Start the HID controller
 	err := usbKVM.Connect()
 	if err != nil {
-		log.Fatal(err)
+		return err
 	}
 
 	//Start auxiliary MCU connections
-	auxMCU, err = remdesaux.NewAuxOutbandController(*usbAuxilaryDeviceName, *usbAuxBaudRate)
+	auxMCU, err = remdesaux.NewAuxOutbandController(config.AuxMCUDevicePath, config.AuxMCUBaudrate)
 	if err != nil {
-		log.Fatal(err)
+		return err
 	}
 
 	//Try get the UUID from the auxiliary MCU
 	uuid, err := auxMCU.GetUUID()
 	if err != nil {
-		log.Println("Get UUID failed:", err)
+		log.Println("Get UUID failed:", err, " - Auxiliary MCU may not be connected.")
 	} else {
 		log.Println("Auxiliary MCU found with UUID:", uuid)
 
@@ -38,37 +122,34 @@ func startUsbKvmMode() error {
 
 	// Initiate the video capture device
 	videoCapture, err = usbcapture.NewInstance(&usbcapture.Config{
-		DeviceName:  *captureDeviceName,
+		DeviceName:  config.VideoCaptureDevicePath,
 		AudioConfig: usbcapture.GetDefaultAudioConfig(),
 	})
 
 	if err != nil {
-		log.Fatalf("Failed to create video capture instance: %v", err)
+		log.Println("Video capture device init failed:", err, " - Video capture device may not be connected.")
+		return err
 	}
 
 	//Get device information for debug
-	usbcapture.PrintV4L2FormatInfo(*captureDeviceName)
+	usbcapture.PrintV4L2FormatInfo(config.VideoCaptureDevicePath)
 
 	//Start the video capture device
 	err = videoCapture.StartVideoCapture(&usbcapture.CaptureResolution{
-		Width:  1920,
-		Height: 1080,
-		FPS:    10,
+		Width:  config.CaptureResolutionWidth,
+		Height: config.CaptureResolutionHeight,
+		FPS:    config.CaptureResolutionFPS,
 	})
 	if err != nil {
-		log.Fatal(err)
+		return err
 	}
 
 	// Handle program exit to close the HID controller
-
 	c := make(chan os.Signal, 1)
 	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
 	go func() {
 		<-c
 		log.Println("Shutting down usbKVM...")
-		if usbKVM != nil {
-			//usbKVM.Close()
-		}
 
 		if auxMCU != nil {
 			auxMCU.Close()
@@ -80,11 +161,11 @@ func startUsbKvmMode() error {
 		os.Exit(0)
 	}()
 
-	// Register the API routes
+	// Register the rest of the API routes
 	registerAPIRoutes()
 
-	addr := ":9000"
-	log.Printf("Serving on http://localhost%s\n", addr)
+	addr := config.ListeningAddress
+	log.Printf("Serving on%s\n", addr)
 	err = http.ListenAndServe(addr, nil)
 	return err
 }