homelabby/internal/usb/device.go
Mikkel Georgsen 11aea60aee feat(05-01): TesterDriver interface, 4 mock drivers, KnownDevices tester entries
- TesterDriver interface: Connect/Read/Disconnect
- StreamingTesterDriver interface: embeds TesterDriver, adds Stream()
- TestResult struct: CableType, versions, speed, power, continuity, eMarker, resistance
- LiveReading struct: Voltage, CurrentAmps, PowerWatts, PDProtocol, Timestamp
- MockUSBDriver: deterministic USB 3.2 Gen 2 result, ErrNotConnected guard
- MockDPDriver: deterministic DP 1.4 result
- MockHDMIDriver: deterministic HDMI 2.1 result
- MockFNB58Driver: 3 LiveReading samples at 100ms, context-cancelled via Disconnect()
- KnownDevices: 4 RoleCableTester placeholder entries (dead0:0001-0004)
2026-04-10 07:07:27 +00:00

82 lines
2.7 KiB
Go

package usb
import (
"errors"
"strings"
)
// DeviceRole classifies what a USB device is used for in HWLab.
type DeviceRole int
const (
RolePrinter DeviceRole = iota
RoleCableTester // reserved for Phase 5
RoleUnknown
)
// DeviceSpec describes a known USB peripheral by VID and PID.
type DeviceSpec struct {
VID string // 4-hex-digit vendor ID, e.g. "0525"
PID string // 4-hex-digit product ID, e.g. "a4a7"
Name string // human-readable label for logs
Role DeviceRole
BaudRate int // serial baud rate; 0 means use 9600 default
}
// VIDPID returns the canonical "VID:PID" key string.
func (s DeviceSpec) VIDPID() string { return s.VID + ":" + s.PID }
// String returns "VID:PID (Name)" format for logging.
func (s DeviceSpec) String() string { return s.VIDPID() + " (" + s.Name + ")" }
// KnownDevices maps "VID:PID" to DeviceSpec for all peripherals HWLab manages.
// Update VID/PID values after hardware characterization on 2026-04-13.
var KnownDevices = map[string]DeviceSpec{
"0525:a4a7": {VID: "0525", PID: "a4a7", Name: "PRT Qutie", Role: RolePrinter, BaudRate: 9600},
// TODO(hardware): Replace placeholder VID:PIDs after characterization 2026-04-13
"dead0:0001": {VID: "dead0", PID: "0001", Name: "Treedix USB Tester", Role: RoleCableTester, BaudRate: 115200},
"dead0:0002": {VID: "dead0", PID: "0002", Name: "Treedix DP Tester", Role: RoleCableTester, BaudRate: 115200},
"dead0:0003": {VID: "dead0", PID: "0003", Name: "Treedix HDMI Tester", Role: RoleCableTester, BaudRate: 115200},
// TODO(hardware): BaudRate 0 = HID protocol — replace VID:PID after characterization
"dead0:0004": {VID: "dead0", PID: "0004", Name: "FNIRSI FNB58", Role: RoleCableTester, BaudRate: 0},
}
// DeviceState represents the current connection status of a managed device.
type DeviceState int
const (
StateDisconnected DeviceState = iota
StateConnected
)
// DeviceEvent is emitted on the Manager.Events() channel for any state change.
type DeviceEvent struct {
VIDPID string
Spec DeviceSpec
State DeviceState
}
// Command is sent to a device goroutine via its command channel.
type Command struct {
Type CommandType
Payload []byte
Reply chan<- error // caller closes or receives result
}
// CommandType discriminates Command variants.
type CommandType int
const (
CmdWrite CommandType = iota
CmdClose
)
// ParseVIDPID splits a "VID:PID" string into its constituent parts.
// Returns an error if the format is not exactly "xxxx:xxxx".
func ParseVIDPID(s string) (vid, pid string, err error) {
parts := strings.SplitN(s, ":", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", errors.New("invalid VID:PID format: expected \"xxxx:xxxx\", got " + s)
}
return parts[0], parts[1], nil
}