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 }