Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSDK-702: Test for input gpio defaults + errors #1646

Merged
merged 1 commit into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 52 additions & 37 deletions components/input/gpio/gpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,28 @@ const modelName = "gpio"

// Config is the overall config.
type Config struct {
Board string `json:"board"`
Buttons map[string]ButtonConfig `json:"buttons"`
Axes map[string]AxisConfig `json:"axes"`
Board string `json:"board"`
Buttons map[string]*ButtonConfig `json:"buttons"`
Axes map[string]*AxisConfig `json:"axes"`
}

// AxisConfig is a subconfig for axes.
type AxisConfig struct {
Control input.Control `json:"control"`
Min int `json:"min"`
Max int `json:"max"`
Bidirectional bool `json:"bidirectional"`
Deadzone int `json:"deadzone"`
MinChange int `json:"min_change"`
PollHz float64 `json:"poll_hz"`
Invert bool `json:"invert"`
}

// ButtonConfig is a subconfig for buttons.
type ButtonConfig struct {
Control input.Control `json:"control"`
Invert bool `json:"invert"`
DebounceMs int `json:"debounce_msec"` // set to -1 to disable, default=5
}

// Validate ensures all parts of the config are valid.
Expand All @@ -41,6 +60,28 @@ func (config *Config) Validate(path string) ([]string, error) {
return deps, nil
}

func (config *Config) validateValues() error {
for _, control := range config.Buttons {
if control.DebounceMs == 0 {
control.DebounceMs = 5
}
}

for _, axis := range config.Axes {
if axis.MinChange < 1 {
axis.MinChange = 1
}
if axis.PollHz <= 0 {
axis.PollHz = 10
}
if axis.Min >= axis.Max {
return fmt.Errorf("min (%d) must be less than max (%d)", axis.Min, axis.Max)
}
}

return nil
}

func init() {
registry.RegisterComponent(input.Subtype, modelName, registry.Component{Constructor: NewGPIOController})

Expand Down Expand Up @@ -73,20 +114,25 @@ func NewGPIOController(
return nil, errors.New("type assertion failed on input/gpio config")
}

err := cfg.validateValues()
if err != nil {
return nil, err
}

brd, err := board.FromDependencies(deps, cfg.Board)
if err != nil {
return nil, err
}

for interrupt, control := range cfg.Buttons {
err := c.newButton(ctx, brd, interrupt, control)
err := c.newButton(ctx, brd, interrupt, *control)
if err != nil {
return nil, err
}
}

for reader, axis := range cfg.Axes {
err := c.newAxis(ctx, brd, reader, axis)
err := c.newAxis(ctx, brd, reader, *axis)
if err != nil {
return nil, err
}
Expand All @@ -97,25 +143,6 @@ func NewGPIOController(
return &c, nil
}

// AxisConfig is a subconfig for axes.
type AxisConfig struct {
Control input.Control `json:"control"`
Min int `json:"min"`
Max int `json:"max"`
Bidirectional bool `json:"bidirectional"`
Deadzone int `json:"deadzone"`
MinChange int `json:"min_change"`
PollHz float64 `json:"poll_hz"`
Invert bool `json:"invert"`
}

// ButtonConfig is a subconfig for buttons.
type ButtonConfig struct {
Control input.Control `json:"control"`
Invert bool `json:"invert"`
DebounceMs int `json:"debounce_msec"` // set to -1 to disable, default=5
}

var _ = input.Controller(&Controller{})

// A Controller creates an input.Controller from DigitalInterrupts and AnalogReaders.
Expand Down Expand Up @@ -243,10 +270,6 @@ func (c *Controller) newButton(ctx context.Context, brd board.Board, intName str
intChan := make(chan bool)
interrupt.AddCallback(intChan)

if cfg.DebounceMs == 0 {
cfg.DebounceMs = 5
}

c.activeBackgroundWorkers.Add(1)
utils.ManagedGo(func() {
defer interrupt.RemoveCallback(intChan)
Expand Down Expand Up @@ -292,15 +315,7 @@ func (c *Controller) newAxis(ctx context.Context, brd board.Board, analogName st
if !ok {
return fmt.Errorf("can't find AnalogReader (%s)", analogName)
}
if cfg.MinChange < 1 {
cfg.MinChange = 1
}
if cfg.PollHz <= 0 {
cfg.PollHz = 10
}
if cfg.Min >= cfg.Max {
return fmt.Errorf("min (%d) must be less than max (%d)", cfg.Min, cfg.Max)
}

c.activeBackgroundWorkers.Add(1)
utils.ManagedGo(func() {
var prevVal int
Expand Down
87 changes: 80 additions & 7 deletions components/input/gpio/gpio_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gpio_test
package gpio

import (
"context"
Expand All @@ -16,7 +16,6 @@ import (
"go.viam.com/rdk/components/board"
fakeboard "go.viam.com/rdk/components/board/fake"
"go.viam.com/rdk/components/input"
"go.viam.com/rdk/components/input/gpio"
"go.viam.com/rdk/config"
"go.viam.com/rdk/registry"
)
Expand Down Expand Up @@ -57,9 +56,9 @@ func setup(t *testing.T) *setupResult {
deps := make(registry.Dependencies)
deps[board.Named("main")] = s.b

ic := gpio.Config{
ic := Config{
Board: "main",
Buttons: map[string]gpio.ButtonConfig{
Buttons: map[string]*ButtonConfig{
"interrupt1": {
Control: input.ButtonNorth,
Invert: false,
Expand All @@ -71,7 +70,7 @@ func setup(t *testing.T) *setupResult {
DebounceMs: -1,
},
},
Axes: map[string]gpio.AxisConfig{
Axes: map[string]*AxisConfig{
"analog1": {
Control: input.AbsoluteX,
Min: 0,
Expand Down Expand Up @@ -171,6 +170,81 @@ func teardown(t *testing.T, s *setupResult) {
}

func TestGPIOInput(t *testing.T) {
t.Run("config defaults", func(t *testing.T) {
c := &Config{
Board: "main",
Buttons: map[string]*ButtonConfig{
"interrupt1": {
Control: input.ButtonNorth,
Invert: false,
DebounceMs: 20,
},
"interrupt2": {
Control: input.ButtonSouth,
Invert: true,
DebounceMs: -1,
},
"interrupt3": {
Control: input.ButtonWest,
Invert: false,
DebounceMs: 0, // default
},
},
Axes: map[string]*AxisConfig{
"analog1": {
Control: input.AbsoluteX,
Min: 0,
Max: 1023,
Bidirectional: false,
Deadzone: 0,
MinChange: 0,
PollHz: 0,
Invert: false,
},
"analog2": {
Control: input.AbsoluteY,
Min: 0,
Max: 1023,
Bidirectional: true,
Deadzone: 20,
MinChange: 15,
PollHz: 50,
Invert: false,
},
},
}
err := c.validateValues()

test.That(t, err, test.ShouldBeNil)
test.That(t, c.Buttons["interrupt1"].DebounceMs, test.ShouldEqual, 20) // unchanged
test.That(t, c.Buttons["interrupt2"].DebounceMs, test.ShouldEqual, -1) // unchanged
test.That(t, c.Buttons["interrupt3"].DebounceMs, test.ShouldEqual, 5) // default

test.That(t, c.Axes["analog1"].PollHz, test.ShouldEqual, 10) // default
test.That(t, c.Axes["analog2"].PollHz, test.ShouldEqual, 50) // default
})

t.Run("config axis min > max", func(t *testing.T) {
c := &Config{
Board: "main",
Axes: map[string]*AxisConfig{
"analog1": {
Control: input.AbsoluteX,
Min: 1023,
Max: 0,
Bidirectional: false,
Deadzone: 0,
MinChange: 0,
PollHz: 0,
Invert: false,
},
},
}
err := c.validateValues()

test.That(t, err, test.ShouldNotBeNil)
})

t.Run("initial button state", func(t *testing.T) {
s := setup(t)
defer teardown(t, s)
Expand Down Expand Up @@ -522,8 +596,7 @@ func TestGPIOInput(t *testing.T) {
test.That(tb, atomic.LoadInt64(&s.axis1Callbacks), test.ShouldEqual, i)
})
s.axisMu.RLock()
epsilon := 15 * time.Millisecond
test.That(t, s.axis1Time.Sub(startTime), test.ShouldBeBetween, 70*time.Millisecond, 100*time.Millisecond+epsilon)
test.That(t, s.axis1Time.Sub(startTime), test.ShouldBeBetween, 70*time.Millisecond, 130*time.Millisecond)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fly by as we've seen failures :(

s.axisMu.RUnlock()
}
})
Expand Down