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.
|
||||
var KnownDevices = map[string]DeviceSpec{
|
||||
"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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue