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)
This commit is contained in:
parent
384ffac870
commit
11aea60aee
3 changed files with 240 additions and 1 deletions
59
internal/tester/driver.go
Normal file
59
internal/tester/driver.go
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
package tester
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNotConnected is returned by Read() when Connect() has not been called.
|
||||||
|
var ErrNotConnected = errors.New("tester: not connected — call Connect() first")
|
||||||
|
|
||||||
|
// CableType identifies the physical cable standard being tested.
|
||||||
|
type CableType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CableTypeUSB CableType = iota
|
||||||
|
CableTypeDP
|
||||||
|
CableTypeHDMI
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestResult captures all measurements from a single cable test run.
|
||||||
|
type TestResult struct {
|
||||||
|
CableType CableType
|
||||||
|
USBVersion string
|
||||||
|
DPVersion string
|
||||||
|
HDMIVersion string
|
||||||
|
SpeedGbps float64
|
||||||
|
MaxWatts int
|
||||||
|
PinContinuity bool
|
||||||
|
HasEMarker bool
|
||||||
|
ResistanceOhm float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// LiveReading is a single sample from a streaming power/protocol meter (e.g. FNB58).
|
||||||
|
type LiveReading struct {
|
||||||
|
Voltage float64
|
||||||
|
CurrentAmps float64
|
||||||
|
PowerWatts float64
|
||||||
|
PDProtocol string
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// TesterDriver is the common interface for all discrete cable testers.
|
||||||
|
// Connect must be called before Read.
|
||||||
|
// Disconnect is idempotent.
|
||||||
|
type TesterDriver interface {
|
||||||
|
Connect() error
|
||||||
|
Read() (TestResult, error)
|
||||||
|
Disconnect() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamingTesterDriver extends TesterDriver for devices that continuously
|
||||||
|
// emit live power/protocol readings (e.g. FNIRSI FNB58).
|
||||||
|
// Stream() returns a read-only channel that emits LiveReading values and is
|
||||||
|
// closed when the stream ends or Disconnect() is called.
|
||||||
|
// Calling Stream() before Connect() returns a pre-closed channel.
|
||||||
|
type StreamingTesterDriver interface {
|
||||||
|
TesterDriver
|
||||||
|
Stream() <-chan LiveReading
|
||||||
|
}
|
||||||
175
internal/tester/mock_drivers.go
Normal file
175
internal/tester/mock_drivers.go
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
package tester
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockUSBDriver simulates a Treedix USB cable tester.
|
||||||
|
// Returns deterministic USB 3.2 Gen 2 test results.
|
||||||
|
// All fields are exported so test code can construct zero values directly.
|
||||||
|
type MockUSBDriver struct {
|
||||||
|
connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time interface assertion.
|
||||||
|
var _ TesterDriver = (*MockUSBDriver)(nil)
|
||||||
|
|
||||||
|
func (d *MockUSBDriver) Connect() error {
|
||||||
|
d.connected = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockUSBDriver) Read() (TestResult, error) {
|
||||||
|
if !d.connected {
|
||||||
|
return TestResult{}, ErrNotConnected
|
||||||
|
}
|
||||||
|
return TestResult{
|
||||||
|
CableType: CableTypeUSB,
|
||||||
|
USBVersion: "USB 3.2 Gen 2",
|
||||||
|
SpeedGbps: 10.0,
|
||||||
|
MaxWatts: 100,
|
||||||
|
PinContinuity: true,
|
||||||
|
HasEMarker: true,
|
||||||
|
ResistanceOhm: 0.12,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockUSBDriver) Disconnect() error {
|
||||||
|
d.connected = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockDPDriver simulates a Treedix DisplayPort cable tester.
|
||||||
|
// Returns deterministic DP 1.4 test results.
|
||||||
|
type MockDPDriver struct {
|
||||||
|
connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time interface assertion.
|
||||||
|
var _ TesterDriver = (*MockDPDriver)(nil)
|
||||||
|
|
||||||
|
func (d *MockDPDriver) Connect() error {
|
||||||
|
d.connected = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockDPDriver) Read() (TestResult, error) {
|
||||||
|
if !d.connected {
|
||||||
|
return TestResult{}, ErrNotConnected
|
||||||
|
}
|
||||||
|
return TestResult{
|
||||||
|
CableType: CableTypeDP,
|
||||||
|
DPVersion: "1.4",
|
||||||
|
PinContinuity: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockDPDriver) Disconnect() error {
|
||||||
|
d.connected = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockHDMIDriver simulates a Treedix HDMI cable tester.
|
||||||
|
// Returns deterministic HDMI 2.1 test results.
|
||||||
|
type MockHDMIDriver struct {
|
||||||
|
connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time interface assertion.
|
||||||
|
var _ TesterDriver = (*MockHDMIDriver)(nil)
|
||||||
|
|
||||||
|
func (d *MockHDMIDriver) Connect() error {
|
||||||
|
d.connected = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockHDMIDriver) Read() (TestResult, error) {
|
||||||
|
if !d.connected {
|
||||||
|
return TestResult{}, ErrNotConnected
|
||||||
|
}
|
||||||
|
return TestResult{
|
||||||
|
CableType: CableTypeHDMI,
|
||||||
|
HDMIVersion: "2.1",
|
||||||
|
PinContinuity: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockHDMIDriver) Disconnect() error {
|
||||||
|
d.connected = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFNB58Driver simulates an FNIRSI FNB58 USB power meter.
|
||||||
|
// Stream() emits 3 deterministic LiveReading samples at 100ms intervals,
|
||||||
|
// then closes the channel. Disconnect() cancels the goroutine early.
|
||||||
|
type MockFNB58Driver struct {
|
||||||
|
connected bool
|
||||||
|
streamCh chan LiveReading
|
||||||
|
cancelFunc context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile-time interface assertion.
|
||||||
|
var _ StreamingTesterDriver = (*MockFNB58Driver)(nil)
|
||||||
|
|
||||||
|
func (d *MockFNB58Driver) Connect() error {
|
||||||
|
d.connected = true
|
||||||
|
d.streamCh = make(chan LiveReading, 8)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
d.cancelFunc = cancel
|
||||||
|
go d.runStream(ctx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockFNB58Driver) runStream(ctx context.Context) {
|
||||||
|
defer close(d.streamCh)
|
||||||
|
samples := []LiveReading{
|
||||||
|
{Voltage: 5.1, CurrentAmps: 3.0, PowerWatts: 15.3, PDProtocol: "PD3.0", Timestamp: time.Now()},
|
||||||
|
{Voltage: 5.1, CurrentAmps: 3.0, PowerWatts: 15.3, PDProtocol: "PD3.0", Timestamp: time.Now()},
|
||||||
|
{Voltage: 5.1, CurrentAmps: 3.0, PowerWatts: 15.3, PDProtocol: "PD3.0", Timestamp: time.Now()},
|
||||||
|
}
|
||||||
|
for _, s := range samples {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
s.Timestamp = time.Now()
|
||||||
|
select {
|
||||||
|
case d.streamCh <- s:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockFNB58Driver) Read() (TestResult, error) {
|
||||||
|
if !d.connected {
|
||||||
|
return TestResult{}, ErrNotConnected
|
||||||
|
}
|
||||||
|
// FNB58 is a streaming device; discrete Read returns a snapshot.
|
||||||
|
return TestResult{
|
||||||
|
CableType: CableTypeUSB,
|
||||||
|
MaxWatts: 100,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *MockFNB58Driver) Disconnect() error {
|
||||||
|
if d.cancelFunc != nil {
|
||||||
|
d.cancelFunc()
|
||||||
|
d.cancelFunc = nil
|
||||||
|
}
|
||||||
|
d.connected = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream returns the live reading channel. If called before Connect(),
|
||||||
|
// returns a pre-closed channel (never nil, never blocking).
|
||||||
|
func (d *MockFNB58Driver) Stream() <-chan LiveReading {
|
||||||
|
if !d.connected || d.streamCh == nil {
|
||||||
|
closed := make(chan LiveReading)
|
||||||
|
close(closed)
|
||||||
|
return closed
|
||||||
|
}
|
||||||
|
return d.streamCh
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,12 @@ func (s DeviceSpec) String() string { return s.VIDPID() + " (" + s.Name + ")" }
|
||||||
// Update VID/PID values after hardware characterization on 2026-04-13.
|
// Update VID/PID values after hardware characterization on 2026-04-13.
|
||||||
var KnownDevices = map[string]DeviceSpec{
|
var KnownDevices = map[string]DeviceSpec{
|
||||||
"0525:a4a7": {VID: "0525", PID: "a4a7", Name: "PRT Qutie", Role: RolePrinter, BaudRate: 9600},
|
"0525:a4a7": {VID: "0525", PID: "a4a7", Name: "PRT Qutie", Role: RolePrinter, BaudRate: 9600},
|
||||||
// Phase 5: Treedix testers will be added here after characterization
|
// 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.
|
// DeviceState represents the current connection status of a managed device.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue