Browse Source

added usbkvm scanning logic

TC 2 weeks ago
parent
commit
f1eb4bfc58
5 changed files with 341 additions and 104 deletions
  1. 22 0
      remdeskd/api.go
  2. 35 0
      remdeskd/configure.go
  3. 179 0
      remdeskd/kvmscan.go
  4. 15 104
      remdeskd/main.go
  5. 90 0
      remdeskd/usbkvm.go

+ 22 - 0
remdeskd/api.go

@@ -0,0 +1,22 @@
+package main
+
+import "net/http"
+
+func registerAPIRoutes() {
+	// Start the web server
+	http.Handle("/", http.FileServer(webfs))
+	http.HandleFunc("/hid", usbKVM.HIDWebSocketHandler)
+	http.HandleFunc("/audio", videoCapture.AudioStreamingHandler)
+	http.HandleFunc("/stream", videoCapture.ServeVideoStream)
+
+}
+
+func registerLocalAuxRoutes() {
+	http.HandleFunc("/aux/switchusbkvm", auxMCU.HandleSwitchUSBToKVM)
+	http.HandleFunc("/aux/switchusbremote", auxMCU.HandleSwitchUSBToRemote)
+	http.HandleFunc("/aux/presspower", auxMCU.HandlePressPowerButton)
+	http.HandleFunc("/aux/releasepower", auxMCU.HandleReleasePowerButton)
+	http.HandleFunc("/aux/pressreset", auxMCU.HandlePressResetButton)
+	http.HandleFunc("/aux/releasereset", auxMCU.HandleReleaseResetButton)
+	http.HandleFunc("/aux/getuuid", auxMCU.HandleGetUUID)
+}

+ 35 - 0
remdeskd/configure.go

@@ -0,0 +1,35 @@
+package main
+
+import (
+	"log"
+	"time"
+)
+
+func SetupHIDCommunication() error {
+	//Start the HID controller
+	err := usbKVM.Connect()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	time.Sleep(1 * time.Second) // Wait for the controller to initialize
+	log.Println("Updating chip baudrate to 115200...")
+	//Configure the HID controller
+	err = usbKVM.ConfigureChipTo115200()
+	if err != nil {
+		log.Fatalf("Failed to configure chip baudrate: %v", err)
+		return err
+	}
+	time.Sleep(1 * time.Second)
+
+	log.Println("Setting chip USB device properties...")
+	time.Sleep(2 * time.Second) // Wait for the controller to initialize
+	_, err = usbKVM.WriteChipProperties()
+	if err != nil {
+		log.Fatalf("Failed to write chip properties: %v", err)
+		return err
+	}
+
+	log.Println("Configuration command sent. Unplug the device and plug it back in to apply the changes.")
+	return nil
+}

+ 179 - 0
remdeskd/kvmscan.go

@@ -0,0 +1,179 @@
+package main
+
+import (
+	"errors"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+/*
+Each of the USB-KVM device has the same set of USB devices
+connected under a single USB hub chip. This function
+will scan the USB device tree to find the connected
+USB devices and match them to the configured device paths.
+
+Commonly found devices are:
+- USB hub (the main hub chip)
+-- USB UART device (HID KVM)
+-- USB CDC ACM device (auxiliary MCU)
+-- USB Video Class device (webcam capture)
+-- USB Audio Class device (audio capture)
+*/
+type UsbKvmDevice struct {
+	USBKVMDevicePath   string
+	AuxMCUDevicePath   string
+	CaptureDevicePaths []string
+	AlsaDevicePaths    []string
+}
+
+func discoverUsbKvmSubtree() ([]*UsbKvmDevice, error) {
+	// Scan all /dev/tty*, /dev/video*, /dev/snd/pcmC* devices
+	getMatchingDevs := func(pattern string) ([]string, error) {
+		files, err := filepath.Glob(pattern)
+		if err != nil {
+			return nil, err
+		}
+		return files, nil
+	}
+
+	// Get all ttyUSB*, ttyACM*
+	ttyDevs1, _ := getMatchingDevs("/dev/ttyUSB*")
+	ttyDevs2, _ := getMatchingDevs("/dev/ttyACM*")
+	ttyDevs := append(ttyDevs1, ttyDevs2...)
+
+	// Get all video*
+	videoDevs, _ := getMatchingDevs("/dev/video*")
+
+	// Get all ALSA PCM devices (USB audio is usually card > 0)
+	alsaDevs, _ := getMatchingDevs("/dev/snd/pcmC*")
+
+	type devInfo struct {
+		path    string
+		sysPath string
+	}
+
+	getSys := func(devs []string) []devInfo {
+		var out []devInfo
+		for _, d := range devs {
+			sys, err := getDeviceFullPath(d)
+			if err == nil {
+				out = append(out, devInfo{d, sys})
+			}
+		}
+		return out
+	}
+
+	ttys := getSys(ttyDevs)
+	videos := getSys(videoDevs)
+	alsas := getSys(alsaDevs)
+
+	// Find common USB root hub prefix
+	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 {
+				return strings.Join(parts[:i+1], "/")
+			}
+		}
+		return ""
+	}
+
+	// Map hub -> device info
+	type hubGroup struct {
+		ttys   []string
+		acms   []string
+		videos []string
+		alsas  []string
+	}
+	hubs := make(map[string]*hubGroup)
+
+	for _, t := range ttys {
+		hub := getHub(t.sysPath)
+		if hub != "" {
+			if hubs[hub] == nil {
+				hubs[hub] = &hubGroup{}
+			}
+			if strings.Contains(t.path, "ACM") {
+				hubs[hub].acms = append(hubs[hub].acms, t.path)
+			} else {
+				hubs[hub].ttys = append(hubs[hub].ttys, t.path)
+			}
+		}
+	}
+	for _, v := range videos {
+		hub := getHub(v.sysPath)
+		if hub != "" {
+			if hubs[hub] == nil {
+				hubs[hub] = &hubGroup{}
+			}
+			hubs[hub].videos = append(hubs[hub].videos, v.path)
+		}
+	}
+	for _, alsa := range alsas {
+		hub := getHub(alsa.sysPath)
+		if hub != "" {
+			if hubs[hub] == nil {
+				hubs[hub] = &hubGroup{}
+			}
+			hubs[hub].alsas = append(hubs[hub].alsas, alsa.path)
+		}
+	}
+
+	var result []*UsbKvmDevice
+	for _, g := range hubs {
+		// At least one tty or acm, one video, optionally alsa
+		if (len(g.ttys) > 0 || len(g.acms) > 0) && len(g.videos) > 0 {
+			// Pick the first tty as USBKVMDevicePath, first acm as AuxMCUDevicePath
+			usbKvm := ""
+			auxMcu := ""
+			if len(g.ttys) > 0 {
+				usbKvm = g.ttys[0]
+			}
+			if len(g.acms) > 0 {
+				auxMcu = g.acms[0]
+			}
+			result = append(result, &UsbKvmDevice{
+				USBKVMDevicePath:   usbKvm,
+				AuxMCUDevicePath:   auxMcu,
+				CaptureDevicePaths: g.videos,
+				AlsaDevicePaths:    g.alsas,
+			})
+		}
+	}
+
+	if len(result) == 0 {
+		return nil, errors.New("no USB KVM device found")
+	}
+	return result, nil
+}
+
+func resolveSymlink(path string) (string, error) {
+	resolved, err := filepath.EvalSymlinks(path)
+	if err != nil {
+		return "", err
+	}
+	return resolved, nil
+}
+
+func getDeviceFullPath(devicePath string) (string, error) {
+	resolvedPath, err := resolveSymlink(devicePath)
+	if err != nil {
+		return "", err
+	}
+
+	// Use udevadm to get the device chain
+	out, err := exec.Command("udevadm", "info", "-q", "path", "-n", resolvedPath).Output()
+	if err != nil {
+		return "", err
+	}
+	sysPath := strings.TrimSpace(string(out))
+	if sysPath == "" {
+		return "", errors.New("could not resolve sysfs path")
+	}
+
+	fullPath := "/sys" + sysPath
+	return fullPath, nil
+}

+ 15 - 104
remdeskd/main.go

@@ -6,10 +6,6 @@ import (
 	"io/fs"
 	"log"
 	"net/http"
-	"os"
-	"os/signal"
-	"syscall"
-	"time"
 
 	"imuslab.com/remdeskvm/remdeskd/mod/remdesaux"
 	"imuslab.com/remdeskvm/remdeskd/mod/remdeshid"
@@ -62,116 +58,31 @@ func main() {
 	})
 	switch *mode {
 	case "cfgchip":
-		//Start the HID controller
-		err := usbKVM.Connect()
+		err := SetupHIDCommunication()
 		if err != nil {
 			log.Fatal(err)
 		}
-
-		time.Sleep(1 * time.Second) // Wait for the controller to initialize
-		log.Println("Updating chip baudrate to 115200...")
-		//Configure the HID controller
-		err = usbKVM.ConfigureChipTo115200()
-		if err != nil {
-			log.Fatalf("Failed to configure chip baudrate: %v", err)
-			return
-		}
-		time.Sleep(1 * time.Second)
-
-		log.Println("Setting chip USB device properties...")
-		time.Sleep(2 * time.Second) // Wait for the controller to initialize
-		_, err = usbKVM.WriteChipProperties()
-		if err != nil {
-			log.Fatalf("Failed to write chip properties: %v", err)
-			return
-		}
-
-		log.Println("Configuration command sent. Unplug the device and plug it back in to apply the changes.")
-	case "usbkvm":
-
-		log.Println("Starting in USB KVM mode...")
-
-		//Start the HID controller
-		err := usbKVM.Connect()
-		if err != nil {
-			log.Fatal(err)
-		}
-
-		//Start auxiliary MCU connections
-		auxMCU, err = remdesaux.NewAuxOutbandController(*usbAuxilaryDeviceName, *usbAuxBaudRate)
+	case "debug":
+		result, err := discoverUsbKvmSubtree()
 		if err != nil {
 			log.Fatal(err)
 		}
-
-		//Try get the UUID from the auxiliary MCU
-		uuid, err := auxMCU.GetUUID()
-		if err != nil {
-			log.Println("Get UUID failed:", err)
-		} else {
-			log.Println("Auxiliary MCU UUID:", uuid)
-		}
-
-		// Initiate the video capture device
-		videoCapture, err = usbcapture.NewInstance(&usbcapture.Config{
-			DeviceName:  *captureDeviceName,
-			AudioConfig: usbcapture.GetDefaultAudioConfig(),
-		})
-
-		if err != nil {
-			log.Fatalf("Failed to create video capture instance: %v", 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)
+			}
 		}
-
-		//Get device information for debug
-		usbcapture.PrintV4L2FormatInfo(*captureDeviceName)
-
-		//Start the video capture device
-		err = videoCapture.StartVideoCapture(&usbcapture.CaptureResolution{
-			Width:  1920,
-			Height: 1080,
-			FPS:    10,
-		})
+	case "usbkvm":
+		err := startUsbKvmMode()
 		if err != nil {
 			log.Fatal(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()
-			}
-			log.Println("Shutting down capture device...")
-			if videoCapture != nil {
-				videoCapture.Close()
-			}
-			os.Exit(0)
-		}()
-
-		// Start the web server
-		http.Handle("/", http.FileServer(webfs))
-		http.HandleFunc("/hid", usbKVM.HIDWebSocketHandler)
-		http.HandleFunc("/audio", videoCapture.AudioStreamingHandler)
-		http.HandleFunc("/stream", videoCapture.ServeVideoStream)
-
-		http.HandleFunc("/aux/switchusbkvm", auxMCU.HandleSwitchUSBToKVM)
-		http.HandleFunc("/aux/switchusbremote", auxMCU.HandleSwitchUSBToRemote)
-		http.HandleFunc("/aux/presspower", auxMCU.HandlePressPowerButton)
-		http.HandleFunc("/aux/releasepower", auxMCU.HandleReleasePowerButton)
-		http.HandleFunc("/aux/pressreset", auxMCU.HandlePressResetButton)
-		http.HandleFunc("/aux/releasereset", auxMCU.HandleReleaseResetButton)
-		http.HandleFunc("/aux/getuuid", auxMCU.HandleGetUUID)
-
-		addr := ":9000"
-		log.Printf("Serving on http://localhost%s\n", addr)
-		log.Fatal(http.ListenAndServe(addr, nil))
 	case "list-audio-devices":
 		log.Println("Starting in List Audio Devices mode...")
 		//Get the audio devices

+ 90 - 0
remdeskd/usbkvm.go

@@ -0,0 +1,90 @@
+package main
+
+import (
+	"log"
+	"net/http"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"imuslab.com/remdeskvm/remdeskd/mod/remdesaux"
+	"imuslab.com/remdeskvm/remdeskd/mod/usbcapture"
+)
+
+func startUsbKvmMode() error {
+	log.Println("Starting in USB KVM mode...")
+	//Start the HID controller
+	err := usbKVM.Connect()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	//Start auxiliary MCU connections
+	auxMCU, err = remdesaux.NewAuxOutbandController(*usbAuxilaryDeviceName, *usbAuxBaudRate)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	//Try get the UUID from the auxiliary MCU
+	uuid, err := auxMCU.GetUUID()
+	if err != nil {
+		log.Println("Get UUID failed:", err)
+	} else {
+		log.Println("Auxiliary MCU found with UUID:", uuid)
+
+		//Register the AUX routes if success
+		registerLocalAuxRoutes()
+	}
+
+	// Initiate the video capture device
+	videoCapture, err = usbcapture.NewInstance(&usbcapture.Config{
+		DeviceName:  *captureDeviceName,
+		AudioConfig: usbcapture.GetDefaultAudioConfig(),
+	})
+
+	if err != nil {
+		log.Fatalf("Failed to create video capture instance: %v", err)
+	}
+
+	//Get device information for debug
+	usbcapture.PrintV4L2FormatInfo(*captureDeviceName)
+
+	//Start the video capture device
+	err = videoCapture.StartVideoCapture(&usbcapture.CaptureResolution{
+		Width:  1920,
+		Height: 1080,
+		FPS:    10,
+	})
+	if err != nil {
+		log.Fatal(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()
+		}
+		log.Println("Shutting down capture device...")
+		if videoCapture != nil {
+			videoCapture.Close()
+		}
+		os.Exit(0)
+	}()
+
+	// Register the API routes
+	registerAPIRoutes()
+
+	addr := ":9000"
+	log.Printf("Serving on http://localhost%s\n", addr)
+	err = http.ListenAndServe(addr, nil)
+	return err
+}