Skip to content

Commit

Permalink
gpio(all): introduce functional options (#1045)
Browse files Browse the repository at this point in the history
  • Loading branch information
gen2thomas authored Dec 3, 2023
1 parent d139c0a commit be1ccf0
Show file tree
Hide file tree
Showing 66 changed files with 3,004 additions and 1,784 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,12 @@ a shared set of drivers provided using the `gobot/drivers/gpio` package:
- Buzzer
- Direct Pin
- EasyDriver
- Grove Button
- Grove Buzzer
- Grove LED
- Grove Magnetic Switch
- Grove Relay
- Grove Touch Sensor
- Grove Button (by using driver for Button)
- Grove Buzzer (by using driver for Buzzer)
- Grove LED (by using driver for LED)
- Grove Magnetic Switch (by using driver for Button)
- Grove Relay (by using driver for Relay)
- Grove Touch Sensor (by using driver for Button)
- HC-SR04 Ultrasonic Ranging Module
- HD44780 LCD controller
- LED
Expand Down
3 changes: 2 additions & 1 deletion driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ package gobot
type Driver interface {
// Name returns the label for the Driver
Name() string
// SetName sets the label for the Driver
// SetName sets the label for the Driver.
// Please prefer to use options [gpio.WithName or aio.WithName] instead, if possible.
SetName(s string)
// Start initiates the Driver
Start() error
Expand Down
6 changes: 3 additions & 3 deletions drivers/aio/analog_sensor_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type AnalogSensorDriver struct {
*driver
sensorCfg *sensorConfiguration
pin string
halt chan bool
halt chan struct{}
gobot.Eventer
lastRawValue int
lastValue float64
Expand Down Expand Up @@ -163,7 +163,7 @@ func (a *AnalogSensorDriver) initialize() error {
// the writing to halt is blocked because there is no immediate read from channel.
// Please note, that this is special behavior caused by the first read is done immediately before the select
// statement.
a.halt = make(chan bool, 1)
a.halt = make(chan struct{}, 1)

oldRawValue := 0
oldValue := 0.0
Expand Down Expand Up @@ -205,7 +205,7 @@ func (a *AnalogSensorDriver) shutdown() error {
// cyclic reading deactivated
return nil
}
a.halt <- true
close(a.halt) // broadcast halt, also to the test
return nil
}

Expand Down
27 changes: 15 additions & 12 deletions drivers/aio/analog_sensor_driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package aio
import (
"fmt"
"strings"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -217,8 +218,8 @@ func TestAnalogSensor_WithSensorCyclicRead(t *testing.T) {
semDone <- true
})

// act: send a halt message
d.halt <- true
// act: send the halt message
require.NoError(t, d.Halt())

// assert: no event
select {
Expand All @@ -234,18 +235,20 @@ func TestAnalogSensorHalt_WithSensorCyclicRead(t *testing.T) {
// arrange
d := NewAnalogSensorDriver(newAioTestAdaptor(), "1", WithSensorCyclicRead(10*time.Millisecond))
require.NoError(t, d.Start())
done := make(chan struct{})
// act & assert
timeout := 2 * d.sensorCfg.readInterval
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
require.NoError(t, d.Halt())
close(done)
defer wg.Done()
select {
case <-d.halt: // wait until halt is broadcasted by close the channel
case <-time.After(timeout): // otherwise run into the timeout
assert.Fail(t, "halt was not received within %s", timeout)
}
}()
// test that the halt is not blocked by any deadlock with mutex and/or channel
select {
case <-done:
case <-time.After(100 * time.Millisecond):
t.Errorf("AnalogSensor was not halted")
}
// act & assert
require.NoError(t, d.Halt())
wg.Wait() // wait until the go function was really finished
}

func TestAnalogSensorCommands_WithSensorScaler(t *testing.T) {
Expand Down
195 changes: 195 additions & 0 deletions drivers/gpio/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Migration of GPIO drivers

From time to time a breaking change of API can happen. Following to [SemVer](https://semver.org/), the gobot main version
should be increased. In such case all users needs to adjust there projects for the next update, although they not using
a driver with changed API.

To prevent this scenario for most users, the main version will not always increased, but affected GPIO drivers are listed
here and a migration strategy is provided.

## Switch from version 2.2.0

### ButtonDriver, PIRMotionDriver: substitute parameter "v time.duration"

A backward compatible case is still included, but it is recommended to use "WithButtonPollInterval" instead, see example
below.

```go
// old
d := gpio.NewButtonDriver(adaptor, "1", 50*time.Millisecond)

// new
d := gpio.NewButtonDriver(adaptor, "1", gpio.WithButtonPollInterval(50*time.Millisecond))
```

### EasyDriver: optional pins

There is no need to use the direction, enable or sleep feature of the driver. Therefore the parameters are removed from
constructor. Please migrate according to the examples below. The order of the optional functions does not matter.

```go
// old
d0 := gpio.NewEasyDriver(adaptor, 0.80, "1", "", "", "")
d1 := gpio.NewEasyDriver(adaptor, 0.81, "11", "12", "", "")
d2 := gpio.NewEasyDriver(adaptor, 0.82, "21", "22", "23", "")
d3 := gpio.NewEasyDriver(adaptor, 0.83, "31", "32", "33", "34")

// new
d0 := gpio.NewEasyDriver(adaptor, 0.80, "1")
d1 := gpio.NewEasyDriver(adaptor, 0.81, "11", gpio.WithEasyDirectionPin("12"))
d2 := gpio.NewEasyDriver(adaptor, 0.82, "21", gpio.WithEasyDirectionPin("22"), gpio.WithEasyEnablePin("23"))
d3 := gpio.NewEasyDriver(adaptor, 0.83, "31", gpio.WithEasyDirectionPin("32"), gpio.WithEasyEnablePin("33"),
gpio.WithEasySleepPin("34"))
```

### BuzzerDriver: unexport 'BPM' attribute

```go
d := gpio.NewBuzzerDriver(adaptor, "1")
// old
d.BPM = 120.0
fmt.Println("BPM:", d.BPM)

// new
d.SetBPM(120.0)
fmt.Println("BPM:", d.BPM())
```

### RelayDriver: unexport 'Inverted' attribute

Usually the relay is inverted or not, except be rewired. From now on the inverted behavior can only be changed on
initialization. If there is really a different use case, please file a new issue.

```go
// old
d := gpio.NewRelayDriver(adaptor, "1")
d.Inverted = true
fmt.Println("is inverted:", d.Inverted)

// new
d := gpio.NewRelayDriver(adaptor, "1", gpio.WithRelayInverted())
fmt.Println("is inverted:", d.IsInverted())
```

### HD44780Driver: make 'SetRWPin()' an option

```go
// old
d := gpio.NewHD44780Driver(adaptor, ...)
d.SetRWPin("10")

// new
d := gpio.NewHD44780Driver(adaptor, ..., gpio.WithHD44780RWPin("10"))
```

### ServoDriver: unexport 'CurrentAngle' and rename functions 'Min()', 'Max()', 'Center()'

```go
d := gpio.NewServoDriver(adaptor, "1")
// old
d.Min()
fmt.Println("current position:", d.CurrentAngle)
d.Center()
d.Max()

// new
d.ToMin()
fmt.Println("current position:", d.Angle())
d.ToCenter()
d.ToMax()
```

### MotorDriver: unexport pin and state attributes, rename functions

The motor driver was heavily revised - sorry for the inconveniences.

affected pins:

* SpeedPin
* SwitchPin (removed, was unused)
* DirectionPin
* ForwardPin
* BackwardPin

Usually the pins will not change without a hardware rewiring. All pins, except the speed pin are optionally, so options
are designed for that.

```go
// old
d := gpio.NewMotorDriver(adaptor, "1")
d.DirectionPin = "10"

// new
d := gpio.NewMotorDriver(adaptor, "1", gpio.WithMotorDirectionPin("10"))
```

```go
// old
d := gpio.NewMotorDriver(adaptor, "1")
d.ForwardPin = "10"
d.BackWardPin = "11"

// new
d := gpio.NewMotorDriver(adaptor, "1", gpio.WithMotorForwardPin("10"), gpio.WithMotorBackwardPin("11"))
```

affected functions:

* Speed() --> SetSpeed()
* Direction() --> SetDirection()
* Max() --> RunMax()
* Min() --> RunMin()

affected states:

* CurrentState
* CurrentSpeed
* CurrentMode
* CurrentDirection

Most of the attributes were used only for reading. If there is something missing, please file a new issue.

```go
d := gpio.NewMotorDriver(adaptor, "1")
// old
d.On()
fmt.Println("is on:", d.CurrentState==1)
fmt.Println("speed:", d.CurrentSpeed)
d.Off()
fmt.Println("is off:", d.CurrentState==0)
fmt.Println("mode is digital:", d.CurrentMode=="digital")
fmt.Println("direction:", d.CurrentDirection)

// new
d.On()
fmt.Println("is on:", d.IsOn())
d.Off()
fmt.Println("is on:", d.IsOff())
fmt.Println("speed:", d.Speed())
fmt.Println("mode is digital:", d.IsDigital())
fmt.Println("direction:", d.Direction())
```

```go
d := gpio.NewMotorDriver(adaptor, "1")
// old
d.Speed(123)
fmt.Println("is mode now analog?", d.CurrentMode!="digital")

// new
d.SetSpeed(123)
fmt.Println("is mode now analog?", d.IsAnalog())
```

Although, it is working like above, it will be more clear, if the mode is defined at the beginning, like so.

```go
// old
d := gpio.NewMotorDriver(adaptor, "1")
d.CurrentMode=="analog"
d.Max()

// new
d := gpio.NewMotorDriver(adaptor, "1", gpio.WithMotorAnalog())
d.RunMax()
```
12 changes: 6 additions & 6 deletions drivers/gpio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ Gobot has a extensible system for connecting to hardware devices. The following
- Buzzer
- Direct Pin
- EasyDriver
- Grove Button
- Grove Buzzer
- Grove LED
- Grove Magnetic Switch
- Grove Relay
- Grove Touch Sensor
- Grove Button (by using driver for Button)
- Grove Buzzer (by using driver for Buzzer)
- Grove LED (by using driver for LED)
- Grove Magnetic Switch (by using driver for Button)
- Grove Relay (by using driver for Relay)
- Grove Touch Sensor (by using driver for Button)
- HC-SR04 Ultrasonic Ranging Module
- HD44780 LCD controller
- LED
Expand Down
Loading

0 comments on commit be1ccf0

Please sign in to comment.