mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
164 lines
7.0 KiB
Go
164 lines
7.0 KiB
Go
package roverd
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"log"
|
|
"testing"
|
|
)
|
|
|
|
func TestDisabledNativeRolesResolveToFirmataOnEveryHostBuild(t *testing.T) {
|
|
description := PeripheralDescription{
|
|
Name: "Rover GPIO",
|
|
RoverControls: PeripheralRoverControls{
|
|
CameraServo: &PeripheralCameraServo{
|
|
Pin: 14, MinimumAngleDegrees: -15, MaximumAngleDegrees: 30,
|
|
HomeAngleDegrees: 0, NudgeDegrees: 2,
|
|
MinimumPulseMicroseconds: 900, MaximumPulseMicroseconds: 2100,
|
|
},
|
|
Headlight: &PeripheralDigitalRole{Pin: 18, ActiveLow: true, InitiallyOn: true},
|
|
Laser: &PeripheralDigitalRole{Pin: 16, ActiveLow: false, InitiallyOn: false},
|
|
},
|
|
Controls: []PeripheralControl{},
|
|
}
|
|
connection := scriptedPeripheralConnection(t, description)
|
|
manager, err := discoverPeripheralManager(
|
|
context.Background(),
|
|
"/dev/roomba",
|
|
discardLogger(),
|
|
testPeripheralDiscoveryDependencies([]string{"/dev/rover-gpio"}, map[string]*scriptedConnection{"/dev/rover-gpio": connection}),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("discover: %v", err)
|
|
}
|
|
defer manager.Close()
|
|
|
|
// All native entries are disabled, exactly as they can be on either a Pi or
|
|
// laptop rover. The shared resolver must therefore select every ESP32 role.
|
|
baseline := len(connection.Bytes())
|
|
controllers, err := ResolveRoverHardwareControllers(&Config{}, manager, discardLogger())
|
|
if err != nil {
|
|
t.Fatalf("resolve: %v", err)
|
|
}
|
|
defer controllers.Close()
|
|
if controllers.CameraServo == nil || controllers.Headlight == nil || controllers.Laser == nil {
|
|
t.Fatalf("missing Firmata controller: %#v", controllers)
|
|
}
|
|
if !controllers.CameraServo.Configuration().Enabled || !controllers.Headlight.Configuration().Enabled || !controllers.Laser.Configuration().Enabled {
|
|
t.Fatal("ESP32-backed roles were not advertised as enabled")
|
|
}
|
|
wantHardwareBroadcast := "Rover hardware ready: camera servo via ESP32 firmata-0, headlight via ESP32 firmata-0, laser via ESP32 firmata-0."
|
|
if messages := controllers.StartupBroadcasts(); len(messages) != 1 || messages[0] != wantHardwareBroadcast {
|
|
t.Fatalf("hardware broadcasts = %#v, want %q", messages, wantHardwareBroadcast)
|
|
}
|
|
|
|
// Initialization uses only standard Firmata: servo calibration and mode,
|
|
// followed by the home position and digital initial states. The active-low
|
|
// headlight starts logically on, so its physical output is low.
|
|
writes := connection.Bytes()[baseline:]
|
|
wantPrefix := []byte{
|
|
firmataStartSysex, firmataServoConfig, 14, 4, 7, 52, 16, firmataEndSysex,
|
|
firmataSetPinMode, 14, FirmataPinModeServo,
|
|
firmataStartSysex, firmataExtendedAnalog, 14, 60, firmataEndSysex,
|
|
firmataSetPinMode, 18, FirmataPinModeOutput,
|
|
firmataSetDigitalPin, 18, 0,
|
|
firmataSetPinMode, 16, FirmataPinModeOutput,
|
|
firmataSetDigitalPin, 16, 0,
|
|
}
|
|
if !bytes.Equal(writes, wantPrefix) {
|
|
t.Fatalf("initial controller bytes = %v, want %v", writes, wantPrefix)
|
|
}
|
|
|
|
baseline = len(connection.Bytes())
|
|
if err := controllers.Headlight.HandleAction("off"); err != nil {
|
|
t.Fatalf("turn headlight off: %v", err)
|
|
}
|
|
if controllers.Headlight.On() {
|
|
t.Fatal("headlight remained logically on")
|
|
}
|
|
// Active-low means logical off becomes a high electrical output.
|
|
if got, want := connection.Bytes()[baseline:], []byte{firmataSetDigitalPin, 18, 1}; !bytes.Equal(got, want) {
|
|
t.Fatalf("headlight bytes = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestMissingNativeAndFirmataRolesRemainDisabled(t *testing.T) {
|
|
manager := &PeripheralManager{byID: make(map[string]*managedPeripheral)}
|
|
controllers, err := ResolveRoverHardwareControllers(&Config{}, manager, discardLogger())
|
|
if err != nil {
|
|
t.Fatalf("resolve: %v", err)
|
|
}
|
|
if controllers.CameraServo != nil || controllers.Headlight != nil || controllers.Laser != nil {
|
|
t.Fatalf("unexpected controllers without providers: %#v", controllers)
|
|
}
|
|
}
|
|
|
|
func TestEnabledNativeRolesWinEvenWithSeveralFirmataProviders(t *testing.T) {
|
|
roleDescription := PeripheralDescription{RoverControls: PeripheralRoverControls{
|
|
CameraServo: &PeripheralCameraServo{},
|
|
Headlight: &PeripheralDigitalRole{},
|
|
Laser: &PeripheralDigitalRole{},
|
|
}}
|
|
manager := &PeripheralManager{
|
|
byID: make(map[string]*managedPeripheral),
|
|
peripherals: []*managedPeripheral{
|
|
{metadata: RoverPeripheralMetadata{ID: "firmata-0"}, description: roleDescription},
|
|
{metadata: RoverPeripheralMetadata{ID: "firmata-1"}, description: roleDescription},
|
|
},
|
|
}
|
|
cfg := &Config{
|
|
CameraServo: CameraServoConfig{Enabled: true},
|
|
Headlight: GPIOToggleConfig{Enabled: true},
|
|
Laser: GPIOToggleConfig{Enabled: true},
|
|
}
|
|
nativeCamera := &testCameraServoController{cfg: cfg.CameraServo}
|
|
nativeToggles := map[string]*testToggleController{}
|
|
factories := nativeHardwareControllerFactories{
|
|
newCameraServo: func(_ CameraServoConfig, _ *log.Logger) (CameraServoController, error) {
|
|
return nativeCamera, nil
|
|
},
|
|
newToggle: func(name string, config GPIOToggleConfig, _ *log.Logger) (ToggleController, error) {
|
|
controller := &testToggleController{cfg: config}
|
|
nativeToggles[name] = controller
|
|
return controller, nil
|
|
},
|
|
}
|
|
|
|
// Duplicate Firmata declarations are irrelevant when native hardware wins;
|
|
// selection must neither fail nor initialize either ESP32 provider.
|
|
controllers, err := resolveRoverHardwareControllers(cfg, manager, discardLogger(), factories)
|
|
if err != nil {
|
|
t.Fatalf("resolve native precedence: %v", err)
|
|
}
|
|
if controllers.CameraServo != nativeCamera || controllers.Headlight != nativeToggles["headlight"] || controllers.Laser != nativeToggles["laser"] {
|
|
t.Fatal("resolver did not retain native controllers")
|
|
}
|
|
messages := controllers.StartupBroadcasts()
|
|
if len(messages) != 2 || messages[0] != "Ignored ESP32 camera servo, headlight, laser because native GPIO is enabled." || messages[1] != "Rover hardware ready: camera servo via native GPIO, headlight via native GPIO, laser via native GPIO." {
|
|
t.Fatalf("native precedence broadcasts = %#v", messages)
|
|
}
|
|
}
|
|
|
|
type testCameraServoController struct {
|
|
cfg CameraServoConfig
|
|
}
|
|
|
|
func (controller *testCameraServoController) SetAngle(float64) error { return nil }
|
|
func (controller *testCameraServoController) Nudge(float64) error { return nil }
|
|
func (controller *testCameraServoController) SetPulseWidth(int) error { return nil }
|
|
func (controller *testCameraServoController) CurrentAngle() float64 { return 0 }
|
|
func (controller *testCameraServoController) Configuration() CameraServoConfig { return controller.cfg }
|
|
func (controller *testCameraServoController) BackendDescription() string { return "native GPIO" }
|
|
func (controller *testCameraServoController) Close() {}
|
|
|
|
type testToggleController struct {
|
|
cfg GPIOToggleConfig
|
|
on bool
|
|
}
|
|
|
|
func (controller *testToggleController) HandleAction(string) error { return nil }
|
|
func (controller *testToggleController) On() bool { return controller.on }
|
|
func (controller *testToggleController) Configuration() GPIOToggleConfig { return controller.cfg }
|
|
func (controller *testToggleController) BackendDescription() string { return "native GPIO" }
|
|
func (controller *testToggleController) Close() {}
|