feat: install WinUSB driver via KneronPLUS SDK (libwdi)

The KneronPLUS wheel bundles libwdi.dll and provides
kp.core.install_driver_for_windows() which handles WinUSB driver
installation internally. This replaces the unreliable pnputil +
self-signed INF approach that failed due to unsigned INF rejection.

Changes:
- Split USB setup into two steps: "Setting up USB library" (extract
  libusb DLL + driver files) and "Installing USB device driver"
  (runs after Python/kp are installed)
- New installKneronDriverViaSDK() calls kp SDK to install WinUSB
  for KL520, KL720, and KL720_LEGACY product IDs
- Driver files still extracted as manual Zadig fallback
- macOS/Linux: installUSBDriver() is a no-op

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jim800121chen 2026-03-26 20:05:15 +08:00
parent 5e7edea08b
commit 86b168c78a
4 changed files with 96 additions and 4 deletions

View File

@ -230,8 +230,9 @@ func (inst *Installer) runInstall(config InstallConfig) {
{"Extracting models and firmware", 30, true, inst.stepExtractData},
{"Extracting scripts", 48, true, inst.stepExtractScripts},
{"Configuring system", 55, false, inst.stepConfigureSystem},
{"Setting up USB driver", 62, false, inst.stepSetupLibusb},
{"Setting up USB library", 62, false, inst.stepSetupLibusb},
{"Setting up Python environment", 70, false, inst.stepSetupPython},
{"Installing USB device driver", 76, false, inst.stepInstallUSBDriver},
{"Installing media tools", 78, false, inst.stepInstallFfmpeg},
{"Writing configuration", 85, true, inst.stepWriteConfig},
{"Verifying installation", 90, false, inst.stepVerify},
@ -325,6 +326,10 @@ func (inst *Installer) stepSetupPython(config InstallConfig) error {
return inst.setupPythonVenv(config.InstallDir)
}
func (inst *Installer) stepInstallUSBDriver(config InstallConfig) error {
return installUSBDriver(config.InstallDir)
}
func (inst *Installer) stepVerify(config InstallConfig) error {
binName := "edge-ai-server"
if runtime.GOOS == "windows" {

View File

@ -102,6 +102,11 @@ func checkLibusbInstalled() bool {
return exec.Command("brew", "list", "libusb").Run() == nil
}
// installUSBDriver is a no-op on macOS (libusb handles USB access).
func installUSBDriver(installDir string) error {
return nil
}
func removeQuarantine(installDir string) {
binPath := filepath.Join(installDir, "edge-ai-server")
exec.Command("xattr", "-dr", "com.apple.quarantine", binPath).Run()

View File

@ -92,6 +92,11 @@ func checkLibusbInstalled() bool {
return exec.Command("dpkg", "-s", "libusb-1.0-0-dev").Run() == nil
}
// installUSBDriver is a no-op on Linux (udev rules handle USB access).
func installUSBDriver(installDir string) error {
return nil
}
func removeQuarantine(installDir string) {
// No-op on Linux
}

View File

@ -129,9 +129,86 @@ func installLibusb(installDir string) error {
}
}
// 2. Extract and install WinUSB driver for Kneron devices
if err := installWinUSBDriver(installDir); err != nil {
return err
// 2. Extract WinUSB driver files (for manual fallback)
extractWinUSBDriverFiles(installDir)
// 3. Install WinUSB driver via KneronPLUS SDK (uses libwdi internally)
// This is called after Python venv + kp wheel are installed (step 7),
// so we defer the actual driver install to postInstallDriver().
// Here we just extract the files.
return nil
}
// extractWinUSBDriverFiles extracts INF and co-installer DLLs from payload
// for manual fallback (Zadig). The actual driver install happens via kp SDK.
func extractWinUSBDriverFiles(installDir string) {
driverDir := filepath.Join(installDir, "drivers")
os.MkdirAll(filepath.Join(driverDir, "amd64"), 0755)
driverFiles := map[string]string{
"payload/drivers/kneron_winusb.inf": filepath.Join(driverDir, "kneron_winusb.inf"),
"payload/drivers/amd64/WdfCoInstaller01011.dll": filepath.Join(driverDir, "amd64", "WdfCoInstaller01011.dll"),
"payload/drivers/amd64/winusbcoinstaller2.dll": filepath.Join(driverDir, "amd64", "winusbcoinstaller2.dll"),
}
for src, dst := range driverFiles {
data, err := payloadFS.ReadFile(src)
if err != nil {
continue
}
os.WriteFile(dst, data, 0644)
}
}
// installUSBDriver installs WinUSB driver via KneronPLUS SDK (libwdi).
func installUSBDriver(installDir string) error {
return installKneronDriverViaSDK(installDir)
}
// installKneronDriverViaSDK uses the KneronPLUS SDK's built-in libwdi to
// install WinUSB driver for all known Kneron devices. Must be called after
// the Python venv with kp module is set up.
func installKneronDriverViaSDK(installDir string) error {
venvPython := filepath.Join(installDir, "venv", "Scripts", "python.exe")
if _, err := os.Stat(venvPython); err != nil {
return fmt.Errorf("Python venv not found, cannot install driver")
}
// Use kp.core.install_driver_for_windows() for each known product ID
// KL520=0x100, KL720=0x720, KL720_LEGACY=0x200
script := `
import sys
try:
import kp
for pid in [kp.ProductId.KP_DEVICE_KL520, kp.ProductId.KP_DEVICE_KL720, kp.ProductId.KP_DEVICE_KL720_LEGACY]:
try:
kp.core.install_driver_for_windows(pid)
print(f"OK: driver installed for {pid.name}")
except Exception as e:
print(f"SKIP: {pid.name}: {e}")
print("DONE")
except ImportError:
print("ERROR: kp module not available")
sys.exit(1)
`
cmd := exec.Command(venvPython, "-c", script)
cmd.Dir = installDir
// Ensure kp can find its DLLs
cmd.Env = append(os.Environ(),
fmt.Sprintf("PATH=%s;%s;%s",
filepath.Join(installDir, "venv", "Lib", "site-packages", "kp", "lib"),
installDir,
os.Getenv("PATH")))
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("WinUSB driver install via KneronPLUS SDK failed: %s — please install manually using Zadig (https://zadig.akeo.ie/)", strings.TrimSpace(string(out)))
}
output := string(out)
if strings.Contains(output, "ERROR:") {
return fmt.Errorf("WinUSB driver install failed: %s — please install manually using Zadig (https://zadig.akeo.ie/)", strings.TrimSpace(output))
}
return nil