diff --git a/drivers/aio/aio.go b/drivers/aio/aio.go deleted file mode 100644 index 3915ec65b..000000000 --- a/drivers/aio/aio.go +++ /dev/null @@ -1,32 +0,0 @@ -package aio - -import ( - "errors" -) - -// ErrAnalogReadUnsupported is error resulting when a driver attempts to use -// hardware capabilities which a connection does not support -var ErrAnalogReadUnsupported = errors.New("AnalogRead is not supported by this platform") - -const ( - // Error event - Error = "error" - // Data event - Data = "data" - // Value event - Value = "value" - // Vibration event - Vibration = "vibration" -) - -// AnalogReader interface represents an Adaptor which has AnalogRead capabilities -type AnalogReader interface { - // gobot.Adaptor - AnalogRead(pin string) (val int, err error) -} - -// AnalogWriter interface represents an Adaptor which has AnalogWrite capabilities -type AnalogWriter interface { - // gobot.Adaptor - AnalogWrite(pin string, val int) error -} diff --git a/drivers/aio/aio_driver.go b/drivers/aio/aio_driver.go new file mode 100644 index 000000000..2f99bd364 --- /dev/null +++ b/drivers/aio/aio_driver.go @@ -0,0 +1,119 @@ +package aio + +import ( + "log" + "sync" + + "gobot.io/x/gobot/v2" +) + +const ( + // Error event + Error = "error" + // Data event + Data = "data" + // Value event + Value = "value" + // Vibration event + Vibration = "vibration" +) + +// AnalogReader interface represents an Adaptor which has AnalogRead capabilities +type AnalogReader interface { + // gobot.Adaptor + AnalogRead(pin string) (val int, err error) +} + +// AnalogWriter interface represents an Adaptor which has AnalogWrite capabilities +type AnalogWriter interface { + // gobot.Adaptor + AnalogWrite(pin string, val int) error +} + +// optionApplier needs to be implemented by each configurable option type +type optionApplier interface { + apply(cfg *configuration) +} + +// configuration contains all changeable attributes of the driver. +type configuration struct { + name string +} + +// nameOption is the type for applying another name to the configuration +type nameOption string + +// Driver implements the interface gobot.Driver. +type driver struct { + driverCfg *configuration + connection interface{} + afterStart func() error + beforeHalt func() error + gobot.Commander + mutex *sync.Mutex // e.g. used to prevent data race between cyclic and single shot write/read to values and scaler +} + +// newDriver creates a new basic analog gobot driver. +func newDriver(a interface{}, name string) *driver { + d := driver{ + driverCfg: &configuration{name: gobot.DefaultName(name)}, + connection: a, + afterStart: func() error { return nil }, + beforeHalt: func() error { return nil }, + Commander: gobot.NewCommander(), + mutex: &sync.Mutex{}, + } + + return &d +} + +// WithName is used to replace the default name of the driver. +func WithName(name string) optionApplier { + return nameOption(name) +} + +// Name returns the name of the driver. +func (d *driver) Name() string { + return d.driverCfg.name +} + +// SetName sets the name of the driver. +// Deprecated: Please use option [aio.WithName] instead. +func (d *driver) SetName(name string) { + WithName(name).apply(d.driverCfg) +} + +// Connection returns the connection of the driver. +func (d *driver) Connection() gobot.Connection { + if conn, ok := d.connection.(gobot.Connection); ok { + return conn + } + + log.Printf("%s has no gobot connection\n", d.driverCfg.name) + return nil +} + +// Start initializes the driver. +func (d *driver) Start() error { + d.mutex.Lock() + defer d.mutex.Unlock() + + // currently there is nothing to do here for the driver + + return d.afterStart() +} + +// Halt halts the driver. +func (d *driver) Halt() error { + d.mutex.Lock() + defer d.mutex.Unlock() + + // currently there is nothing to do after halt for the driver + + return d.beforeHalt() +} + +// apply change the name in the configuration. +func (o nameOption) apply(c *configuration) { + c.name = string(o) +} diff --git a/drivers/aio/aio_driver_test.go b/drivers/aio/aio_driver_test.go new file mode 100644 index 000000000..10fdc47a0 --- /dev/null +++ b/drivers/aio/aio_driver_test.go @@ -0,0 +1,69 @@ +package aio + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gobot.io/x/gobot/v2" +) + +var _ gobot.Driver = (*driver)(nil) + +func initTestDriver() *driver { + a := newAioTestAdaptor() + d := newDriver(a, "AIO_BASIC") + return d +} + +func Test_newDriver(t *testing.T) { + // arrange + const name = "mybot" + a := newAioTestAdaptor() + // act + d := newDriver(a, name) + // assert + assert.IsType(t, &driver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), name)) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) +} + +func Test_applyWithName(t *testing.T) { + // arrange + const name = "mybot" + cfg := configuration{name: "oldname"} + // act + WithName(name).apply(&cfg) + // assert + assert.Equal(t, name, cfg.name) +} + +func TestStart(t *testing.T) { + // arrange + d := initTestDriver() + // act, assert + require.NoError(t, d.Start()) + // arrange after start function + d.afterStart = func() error { return fmt.Errorf("after start error") } + // act, assert + require.EqualError(t, d.Start(), "after start error") +} + +func TestHalt(t *testing.T) { + // arrange + d := initTestDriver() + // act, assert + require.NoError(t, d.Halt()) + // arrange after start function + d.beforeHalt = func() error { return fmt.Errorf("before halt error") } + // act, assert + require.EqualError(t, d.Halt(), "before halt error") +} diff --git a/drivers/aio/analog_actuator_driver.go b/drivers/aio/analog_actuator_driver.go index bc6a3eb88..ef2f6d780 100644 --- a/drivers/aio/analog_actuator_driver.go +++ b/drivers/aio/analog_actuator_driver.go @@ -1,39 +1,63 @@ package aio import ( - "log" + "fmt" "strconv" - - "gobot.io/x/gobot/v2" ) +// actuatorOptionApplier needs to be implemented by each configurable option type +type actuatorOptionApplier interface { + apply(cfg *actuatorConfiguration) +} + +// actuatorConfiguration contains all changeable attributes of the driver. +type actuatorConfiguration struct { + scale func(input float64) (value int) +} + +// actuatorScaleOption is the type for applying another scaler to the configuration +type actuatorScaleOption struct { + scaler func(input float64) (value int) +} + // AnalogActuatorDriver represents an analog actuator type AnalogActuatorDriver struct { - name string - pin string - connection AnalogWriter - gobot.Eventer - gobot.Commander - scale func(input float64) (value int) + *driver + pin string + actuatorCfg *actuatorConfiguration lastValue float64 lastRawValue int } -// NewAnalogActuatorDriver returns a new AnalogActuatorDriver given by an AnalogWriter and pin. +// NewAnalogActuatorDriver returns a new driver for analog actuator, given by an AnalogWriter and pin. // The driver supports customizable scaling from given float64 value to written int. // The default scaling is 1:1. An adjustable linear scaler is provided by the driver. // +// Supported options: +// +// "WithName" +// "WithActuatorScaler" +// // Adds the following API Commands: // // "Write" - See AnalogActuator.Write -// "RawWrite" - See AnalogActuator.RawWrite -func NewAnalogActuatorDriver(a AnalogWriter, pin string) *AnalogActuatorDriver { +// "WriteRaw" - See AnalogActuator.WriteRaw +func NewAnalogActuatorDriver(a AnalogWriter, pin string, opts ...interface{}) *AnalogActuatorDriver { d := &AnalogActuatorDriver{ - name: gobot.DefaultName("AnalogActuator"), - connection: a, - pin: pin, - Commander: gobot.NewCommander(), - scale: func(input float64) int { return int(input) }, + driver: newDriver(a, "AnalogActuator"), + pin: pin, + actuatorCfg: &actuatorConfiguration{scale: func(input float64) int { return int(input) }}, + } + + for _, opt := range opts { + switch o := opt.(type) { + case optionApplier: + o.apply(d.driverCfg) + case actuatorOptionApplier: + o.apply(d.actuatorCfg) + default: + panic(fmt.Sprintf("'%s' can not be applied on '%s'", opt, d.driverCfg.name)) + } } d.AddCommand("Write", func(params map[string]interface{}) interface{} { @@ -44,55 +68,66 @@ func NewAnalogActuatorDriver(a AnalogWriter, pin string) *AnalogActuatorDriver { return d.Write(val) }) - d.AddCommand("RawWrite", func(params map[string]interface{}) interface{} { + d.AddCommand("WriteRaw", func(params map[string]interface{}) interface{} { val, _ := strconv.Atoi(params["val"].(string)) - return d.RawWrite(val) + return d.WriteRaw(val) }) return d } -// Start starts driver -func (a *AnalogActuatorDriver) Start() error { return nil } - -// Halt is for halt -func (a *AnalogActuatorDriver) Halt() error { return nil } +// WithActuatorScaler substitute the default 1:1 return value function by a new scaling function +func WithActuatorScaler(scaler func(input float64) (value int)) actuatorOptionApplier { + return actuatorScaleOption{scaler: scaler} +} -// Name returns the drivers name -func (a *AnalogActuatorDriver) Name() string { return a.name } +// SetScaler substitute the default 1:1 return value function by a new scaling function +// If the scaler is not changed after initialization, prefer to use [aio.WithActuatorScaler] instead. +func (a *AnalogActuatorDriver) SetScaler(scaler func(float64) int) { + a.mutex.Lock() + defer a.mutex.Unlock() -// SetName sets the drivers name -func (a *AnalogActuatorDriver) SetName(n string) { a.name = n } + WithActuatorScaler(scaler).apply(a.actuatorCfg) +} // Pin returns the drivers pin func (a *AnalogActuatorDriver) Pin() string { return a.pin } -// Connection returns the drivers Connection -func (a *AnalogActuatorDriver) Connection() gobot.Connection { - if conn, ok := a.connection.(gobot.Connection); ok { - return conn - } +// Write writes the given value to the actuator +func (a *AnalogActuatorDriver) Write(val float64) error { + a.mutex.Lock() + defer a.mutex.Unlock() - log.Printf("%s has no gobot connection\n", a.name) + rawValue := a.actuatorCfg.scale(val) + if err := a.WriteRaw(rawValue); err != nil { + return err + } + a.lastValue = val return nil } // RawWrite write the given raw value to the actuator +// Deprecated: Please use [aio.WriteRaw] instead. func (a *AnalogActuatorDriver) RawWrite(val int) error { - a.lastRawValue = val - return a.connection.AnalogWrite(a.Pin(), val) + return a.WriteRaw(val) } -// SetScaler substitute the default 1:1 return value function by a new scaling function -func (a *AnalogActuatorDriver) SetScaler(scaler func(float64) int) { - a.scale = scaler +// WriteRaw write the given raw value to the actuator +func (a *AnalogActuatorDriver) WriteRaw(val int) error { + writer, ok := a.connection.(AnalogWriter) + if !ok { + return fmt.Errorf("AnalogWrite is not supported by the platform '%s'", a.Connection().Name()) + } + if err := writer.AnalogWrite(a.Pin(), val); err != nil { + return err + } + a.lastRawValue = val + return nil } -// Write writes the given value to the actuator -func (a *AnalogActuatorDriver) Write(val float64) error { - a.lastValue = val - rawValue := a.scale(val) - return a.RawWrite(rawValue) +// Value returns the last written value +func (a *AnalogActuatorDriver) Value() float64 { + return a.lastValue } // RawValue returns the last written raw value @@ -100,9 +135,12 @@ func (a *AnalogActuatorDriver) RawValue() int { return a.lastRawValue } -// Value returns the last written value -func (a *AnalogActuatorDriver) Value() float64 { - return a.lastValue +func (o actuatorScaleOption) String() string { + return "scaler option for analog actuators" +} + +func (o actuatorScaleOption) apply(cfg *actuatorConfiguration) { + cfg.scale = o.scaler } // AnalogActuatorLinearScaler creates a linear scaler function from the given values. diff --git a/drivers/aio/analog_actuator_driver_test.go b/drivers/aio/analog_actuator_driver_test.go index 88e3d22ce..3abe91f3f 100644 --- a/drivers/aio/analog_actuator_driver_test.go +++ b/drivers/aio/analog_actuator_driver_test.go @@ -1,106 +1,164 @@ +//nolint:forcetypeassert // ok here package aio import ( "strings" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestAnalogActuatorDriver(t *testing.T) { +func TestNewAnalogActuatorDriver(t *testing.T) { + // arrange + const pin = "47" a := newAioTestAdaptor() - d := NewAnalogActuatorDriver(a, "47") - - assert.NotNil(t, d.Connection()) - assert.Equal(t, "47", d.Pin()) - - err := d.RawWrite(100) - require.NoError(t, err) - assert.Len(t, a.written, 1) - assert.Equal(t, 100, a.written[0]) - - err = d.Write(247.0) - require.NoError(t, err) - assert.Len(t, a.written, 2) - assert.Equal(t, 247, a.written[1]) - assert.Equal(t, 247, d.RawValue()) - assert.InDelta(t, 247.0, d.Value(), 0.0) + // act + d := NewAnalogActuatorDriver(a, pin) + // assert: driver attributes + assert.IsType(t, &AnalogActuatorDriver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), "AnalogActuator")) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) + // assert: actuator attributes + assert.Equal(t, pin, d.Pin()) + assert.InDelta(t, 0.0, d.lastValue, 0, 0) + assert.Equal(t, 0, d.lastRawValue) + require.NotNil(t, d.actuatorCfg) + assert.NotNil(t, d.actuatorCfg.scale) } -func TestAnalogActuatorDriverWithScaler(t *testing.T) { - // commands - a := newAioTestAdaptor() - d := NewAnalogActuatorDriver(a, "7") - d.SetScaler(func(input float64) int { return int((input + 3) / 2.5) }) - - err := d.Command("RawWrite")(map[string]interface{}{"val": "100"}) - assert.Nil(t, err) - assert.Len(t, a.written, 1) - assert.Equal(t, 100, a.written[0]) - - err = d.Command("Write")(map[string]interface{}{"val": "247.0"}) - assert.Nil(t, err) - assert.Len(t, a.written, 2) - assert.Equal(t, 100, a.written[1]) +func TestNewAnalogActuatorDriver_options(t *testing.T) { + // This is a general test, that options are applied in constructor by using the common WithName() option, least one + // option of this driver and one of another driver (which should lead to panic). Further tests for options can also + // be done by call of "WithOption(val).apply(cfg)". + // arrange + myName := "relay 1" + myScaler := func(input float64) int { return int(2 * input) } + panicFunc := func() { + NewAnalogActuatorDriver(newAioTestAdaptor(), "1", WithName("crazy"), WithSensorCyclicRead(10*time.Millisecond)) + } + // act + d := NewAnalogActuatorDriver(newAioTestAdaptor(), "1", WithName(myName), WithActuatorScaler(myScaler)) + // assert + assert.Equal(t, myName, d.Name()) + assert.Equal(t, 3, d.actuatorCfg.scale(1.5)) + assert.PanicsWithValue(t, "'read interval option for analog sensors' can not be applied on 'crazy'", panicFunc) } -func TestAnalogActuatorDriverLinearScaler(t *testing.T) { +func TestAnalogActuatorWriteRaw(t *testing.T) { tests := map[string]struct { - fromMin float64 - fromMax float64 - input float64 - want int + inputVal int + simulateWriteErr bool + wantWritten int + wantErr string }{ - "byte_range_min": {fromMin: 0, fromMax: 255, input: 0, want: 0}, - "byte_range_max": {fromMin: 0, fromMax: 255, input: 255, want: 255}, - "signed_percent_range_min": {fromMin: -100, fromMax: 100, input: -100, want: 0}, - "signed_percent_range_mid": {fromMin: -100, fromMax: 100, input: 0, want: 127}, - "signed_percent_range_max": {fromMin: -100, fromMax: 100, input: 100, want: 255}, - "voltage_range_min": {fromMin: 0, fromMax: 5.1, input: 0, want: 0}, - "voltage_range_nearmin": {fromMin: 0, fromMax: 5.1, input: 0.02, want: 1}, - "voltage_range_mid": {fromMin: 0, fromMax: 5.1, input: 2.55, want: 127}, - "voltage_range_nearmax": {fromMin: 0, fromMax: 5.1, input: 5.08, want: 254}, - "voltage_range_max": {fromMin: 0, fromMax: 5.1, input: 5.1, want: 255}, - "upscale": {fromMin: 0, fromMax: 24, input: 12, want: 127}, - "below_min": {fromMin: -10, fromMax: 10, input: -11, want: 0}, - "exceed_max": {fromMin: 0, fromMax: 20, input: 21, want: 255}, + "write_raw": {inputVal: 100, wantWritten: 100}, + "error_write": {inputVal: 12345, wantWritten: 12345, simulateWriteErr: true, wantErr: "write error"}, } - a := newAioTestAdaptor() - d := NewAnalogActuatorDriver(a, "7") - - for name, tt := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange - d.SetScaler(AnalogActuatorLinearScaler(tt.fromMin, tt.fromMax, 0, 255)) - a.written = []int{} // reset previous write + const pin = "47" + a := newAioTestAdaptor() + d := NewAnalogActuatorDriver(a, pin) + a.simulateWriteError = tc.simulateWriteErr + a.written = nil // reset previous writes // act - err := d.Write(tt.input) + err := d.WriteRaw(tc.inputVal) // assert - require.NoError(t, err) - assert.Len(t, a.written, 1) - assert.Equal(t, tt.want, a.written[0]) + if tc.wantErr != "" { + require.EqualError(t, err, tc.wantErr) + assert.Empty(t, a.written) + } else { + require.NoError(t, err) + assert.Len(t, a.written, 1) + assert.Equal(t, pin, a.written[0].pin) + assert.Equal(t, tc.wantWritten, a.written[0].val) + } }) } } -func TestAnalogActuatorDriverStart(t *testing.T) { +func TestAnalogActuatorWriteRaw_AnalogWriteNotSupported(t *testing.T) { + // arrange d := NewAnalogActuatorDriver(newAioTestAdaptor(), "1") - require.NoError(t, d.Start()) + d.connection = &aioTestBareAdaptor{} + // act & assert + require.EqualError(t, d.WriteRaw(3), "AnalogWrite is not supported by the platform 'bare'") } -func TestAnalogActuatorDriverHalt(t *testing.T) { - d := NewAnalogActuatorDriver(newAioTestAdaptor(), "1") - require.NoError(t, d.Halt()) -} +func TestAnalogActuatorWrite_SetScaler(t *testing.T) { + tests := map[string]struct { + fromMin float64 + fromMax float64 + input float64 + wantWritten int + }{ + "byte_range_min": {fromMin: 0, fromMax: 255, input: 0, wantWritten: 0}, + "byte_range_max": {fromMin: 0, fromMax: 255, input: 255, wantWritten: 255}, + "signed_percent_range_min": {fromMin: -100, fromMax: 100, input: -100, wantWritten: 0}, + "signed_percent_range_mid": {fromMin: -100, fromMax: 100, input: 0, wantWritten: 127}, + "signed_percent_range_max": {fromMin: -100, fromMax: 100, input: 100, wantWritten: 255}, + "voltage_range_min": {fromMin: 0, fromMax: 5.1, input: 0, wantWritten: 0}, + "voltage_range_nearmin": {fromMin: 0, fromMax: 5.1, input: 0.02, wantWritten: 1}, + "voltage_range_mid": {fromMin: 0, fromMax: 5.1, input: 2.55, wantWritten: 127}, + "voltage_range_nearmax": {fromMin: 0, fromMax: 5.1, input: 5.08, wantWritten: 254}, + "voltage_range_max": {fromMin: 0, fromMax: 5.1, input: 5.1, wantWritten: 255}, + "upscale": {fromMin: 0, fromMax: 24, input: 12, wantWritten: 127}, + "below_min": {fromMin: -10, fromMax: 10, input: -11, wantWritten: 0}, + "exceed_max": {fromMin: 0, fromMax: 20, input: 21, wantWritten: 255}, + } -func TestAnalogActuatorDriverDefaultName(t *testing.T) { - d := NewAnalogActuatorDriver(newAioTestAdaptor(), "1") - assert.True(t, strings.HasPrefix(d.Name(), "AnalogActuator")) + const pin = "7" + a := newAioTestAdaptor() + d := NewAnalogActuatorDriver(a, pin) + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + d.SetScaler(AnalogActuatorLinearScaler(tc.fromMin, tc.fromMax, 0, 255)) + a.written = nil // reset previous writes + // act + err := d.Write(tc.input) + // assert + require.NoError(t, err) + assert.Len(t, a.written, 1) + assert.Equal(t, pin, a.written[0].pin) + assert.Equal(t, tc.wantWritten, a.written[0].val) + }) + } } -func TestAnalogActuatorDriverSetName(t *testing.T) { - d := NewAnalogActuatorDriver(newAioTestAdaptor(), "1") - d.SetName("mybot") - assert.Equal(t, "mybot", d.Name()) +func TestAnalogActuatorCommands_WithActuatorScaler(t *testing.T) { + // arrange + const pin = "8" + a := newAioTestAdaptor() + d := NewAnalogActuatorDriver(a, pin, WithActuatorScaler(func(input float64) int { return int((input + 3) / 2.5) })) + a.written = nil // reset previous writes + // act & assert: WriteRaw + err := d.Command("WriteRaw")(map[string]interface{}{"val": "100"}) + assert.Nil(t, err) + assert.Len(t, a.written, 1) + assert.Equal(t, pin, a.written[0].pin) + assert.Equal(t, 100, a.written[0].val) + assert.Equal(t, 100, d.RawValue()) + assert.InDelta(t, 0.0, d.Value(), 0.0) + // act & assert: Write + err = d.Command("Write")(map[string]interface{}{"val": "247.0"}) + assert.Nil(t, err) + assert.Len(t, a.written, 2) + assert.Equal(t, pin, a.written[1].pin) + assert.Equal(t, 100, a.written[1].val) + assert.Equal(t, 100, d.RawValue()) + assert.InDelta(t, 247.0, d.Value(), 0.0) + // arrange & act & assert: Write with error + a.simulateWriteError = true + err = d.Command("Write")(map[string]interface{}{"val": "247.0"}) + require.EqualError(t, err.(error), "write error") } diff --git a/drivers/aio/analog_sensor_driver.go b/drivers/aio/analog_sensor_driver.go index 7e9059ad3..37a7fefbb 100644 --- a/drivers/aio/analog_sensor_driver.go +++ b/drivers/aio/analog_sensor_driver.go @@ -1,56 +1,79 @@ package aio import ( - "log" - "sync" + "fmt" "time" "gobot.io/x/gobot/v2" ) +// sensorOptionApplier needs to be implemented by each configurable option type +type sensorOptionApplier interface { + apply(cfg *sensorConfiguration) +} + +// sensorConfiguration contains all changeable attributes of the driver. +type sensorConfiguration struct { + readInterval time.Duration + scale func(input int) (value float64) +} + +// sensorReadIntervalOption is the type for applying another read interval to the configuration +type sensorReadIntervalOption time.Duration + +// sensorScaleOption is the type for applying another scaler to the configuration +type sensorScaleOption struct { + scaler func(input int) (value float64) +} + // AnalogSensorDriver represents an Analog Sensor type AnalogSensorDriver struct { - name string - pin string - halt chan bool - interval time.Duration - connection AnalogReader + *driver + sensorCfg *sensorConfiguration + pin string + halt chan bool gobot.Eventer - gobot.Commander - rawValue int - value float64 - scale func(input int) (value float64) - mutex *sync.Mutex // to prevent data race between cyclic and single shot write/read to values and scaler + lastRawValue int + lastValue float64 } -// NewAnalogSensorDriver returns a new AnalogSensorDriver with a polling interval of -// 10 Milliseconds given an AnalogReader and pin. -// The driver supports customizable scaling from read int value to returned float64. +// NewAnalogSensorDriver returns a new driver for analog sensors, given an AnalogReader and pin. +// The driver supports cyclic reading and customizable scaling from read int value to returned float64. // The default scaling is 1:1. An adjustable linear scaler is provided by the driver. // -// Optionally accepts: +// Supported options: // -// time.Duration: Interval at which the AnalogSensor is polled for new information +// "WithName" +// "WithSensorCyclicRead" +// "WithSensorScaler" // // Adds the following API Commands: // // "Read" - See AnalogDriverSensor.Read // "ReadRaw" - See AnalogDriverSensor.ReadRaw -func NewAnalogSensorDriver(a AnalogReader, pin string, v ...time.Duration) *AnalogSensorDriver { +func NewAnalogSensorDriver(a AnalogReader, pin string, opts ...interface{}) *AnalogSensorDriver { d := &AnalogSensorDriver{ - name: gobot.DefaultName("AnalogSensor"), - connection: a, - pin: pin, - Eventer: gobot.NewEventer(), - Commander: gobot.NewCommander(), - interval: 10 * time.Millisecond, - halt: make(chan bool), - scale: func(input int) float64 { return float64(input) }, - mutex: &sync.Mutex{}, + driver: newDriver(a, "AnalogSensor"), + sensorCfg: &sensorConfiguration{scale: func(input int) float64 { return float64(input) }}, + pin: pin, + Eventer: gobot.NewEventer(), + halt: make(chan bool), } - - if len(v) > 0 { - d.interval = v[0] + d.afterStart = d.initialize + d.beforeHalt = d.shutdown + + for _, opt := range opts { + switch o := opt.(type) { + case optionApplier: + o.apply(d.driverCfg) + case sensorOptionApplier: + o.apply(d.sensorCfg) + case time.Duration: + // TODO this is only for backward compatibility and will be removed after version 2.x + d.sensorCfg.readInterval = o + default: + panic(fmt.Sprintf("'%s' can not be applied on '%s'", opt, d.driverCfg.name)) + } } d.AddEvent(Data) @@ -70,21 +93,40 @@ func NewAnalogSensorDriver(a AnalogReader, pin string, v ...time.Duration) *Anal return d } -// Start starts the AnalogSensorDriver and reads the sensor at the given interval. +// WithSensorCyclicRead add a asynchronous cyclic reading functionality to the sensor with the given read interval. +func WithSensorCyclicRead(interval time.Duration) sensorOptionApplier { + return sensorReadIntervalOption(interval) +} + +// WithSensorScaler substitute the default 1:1 return value function by a new scaling function +func WithSensorScaler(scaler func(input int) (value float64)) sensorOptionApplier { + return sensorScaleOption{scaler: scaler} +} + +// SetScaler substitute the default 1:1 return value function by a new scaling function +// If the scaler is not changed after initialization, prefer to use [aio.WithSensorScaler] instead. +func (a *AnalogSensorDriver) SetScaler(scaler func(int) float64) { + a.mutex.Lock() + defer a.mutex.Unlock() + + WithSensorScaler(scaler).apply(a.sensorCfg) +} + +// initialize the AnalogSensorDriver and if the cyclic reading is active, reads the sensor at the given interval. // Emits the Events: // // Data int - Event is emitted on change and represents the current raw reading from the sensor. // Value float64 - Event is emitted on change and represents the current reading from the sensor. // Error error - Event is emitted on error reading from the sensor. -func (a *AnalogSensorDriver) Start() error { - if a.interval == 0 { +func (a *AnalogSensorDriver) initialize() error { + if a.sensorCfg.readInterval == 0 { // cyclic reading deactivated return nil } oldRawValue := 0 oldValue := 0.0 go func() { - timer := time.NewTimer(a.interval) + timer := time.NewTimer(a.sensorCfg.readInterval) timer.Stop() for { rawValue, value, err := a.analogRead() @@ -101,7 +143,7 @@ func (a *AnalogSensorDriver) Start() error { } } - timer.Reset(a.interval) + timer.Reset(a.sensorCfg.readInterval) select { case <-timer.C: case <-a.halt: @@ -113,9 +155,9 @@ func (a *AnalogSensorDriver) Start() error { return nil } -// Halt stops polling the analog sensor for new information -func (a *AnalogSensorDriver) Halt() error { - if a.interval == 0 { +// shutdown stops polling the analog sensor for new information +func (a *AnalogSensorDriver) shutdown() error { + if a.sensorCfg.readInterval == 0 { // cyclic reading deactivated return nil } @@ -123,25 +165,9 @@ func (a *AnalogSensorDriver) Halt() error { return nil } -// Name returns the AnalogSensorDrivers name -func (a *AnalogSensorDriver) Name() string { return a.name } - -// SetName sets the AnalogSensorDrivers name -func (a *AnalogSensorDriver) SetName(n string) { a.name = n } - // Pin returns the AnalogSensorDrivers pin func (a *AnalogSensorDriver) Pin() string { return a.pin } -// Connection returns the AnalogSensorDrivers Connection -func (a *AnalogSensorDriver) Connection() gobot.Connection { - if conn, ok := a.connection.(gobot.Connection); ok { - return conn - } - - log.Printf("%s has no gobot connection\n", a.name) - return nil -} - // Read returns the current reading from the sensor, scaled by the current scaler func (a *AnalogSensorDriver) Read() (float64, error) { _, value, err := a.analogRead() @@ -154,20 +180,12 @@ func (a *AnalogSensorDriver) ReadRaw() (int, error) { return rawValue, err } -// SetScaler substitute the default 1:1 return value function by a new scaling function -func (a *AnalogSensorDriver) SetScaler(scaler func(int) float64) { - a.mutex.Lock() - defer a.mutex.Unlock() - - a.scale = scaler -} - // Value returns the last read value from the sensor func (a *AnalogSensorDriver) Value() float64 { a.mutex.Lock() defer a.mutex.Unlock() - return a.value + return a.lastValue } // RawValue returns the last read raw value from the sensor @@ -175,7 +193,7 @@ func (a *AnalogSensorDriver) RawValue() int { a.mutex.Lock() defer a.mutex.Unlock() - return a.rawValue + return a.lastRawValue } // analogRead performs an reading from the sensor and sets the internal attributes and returns the raw and scaled value @@ -183,14 +201,35 @@ func (a *AnalogSensorDriver) analogRead() (int, float64, error) { a.mutex.Lock() defer a.mutex.Unlock() - rawValue, err := a.connection.AnalogRead(a.Pin()) + reader, ok := a.connection.(AnalogReader) + if !ok { + return 0, 0, fmt.Errorf("AnalogRead is not supported by the platform '%s'", a.Connection().Name()) + } + + rawValue, err := reader.AnalogRead(a.Pin()) if err != nil { return 0, 0, err } - a.rawValue = rawValue - a.value = a.scale(a.rawValue) - return a.rawValue, a.value, nil + a.lastRawValue = rawValue + a.lastValue = a.sensorCfg.scale(a.lastRawValue) + return a.lastRawValue, a.lastValue, nil +} + +func (o sensorReadIntervalOption) String() string { + return "read interval option for analog sensors" +} + +func (o sensorScaleOption) String() string { + return "scaler option for analog sensors" +} + +func (o sensorReadIntervalOption) apply(cfg *sensorConfiguration) { + cfg.readInterval = time.Duration(o) +} + +func (o sensorScaleOption) apply(cfg *sensorConfiguration) { + cfg.scale = o.scaler } // AnalogSensorLinearScaler creates a linear scaler function from the given values. diff --git a/drivers/aio/analog_sensor_driver_test.go b/drivers/aio/analog_sensor_driver_test.go index 3f782e3b9..ddb004984 100644 --- a/drivers/aio/analog_sensor_driver_test.go +++ b/drivers/aio/analog_sensor_driver_test.go @@ -2,7 +2,7 @@ package aio import ( - "errors" + "fmt" "strings" "testing" "time" @@ -15,48 +15,104 @@ import ( var _ gobot.Driver = (*AnalogSensorDriver)(nil) -func TestAnalogSensorDriver(t *testing.T) { +func TestNewAnalogSensorDriver(t *testing.T) { + // arrange + const pin = "5" a := newAioTestAdaptor() - d := NewAnalogSensorDriver(a, "1") - assert.NotNil(t, d.Connection()) - - // default interval - assert.Equal(t, 10*time.Millisecond, d.interval) - - // commands - a = newAioTestAdaptor() - d = NewAnalogSensorDriver(a, "42", 30*time.Second) - d.SetScaler(func(input int) float64 { return 2.5*float64(input) - 3 }) - assert.Equal(t, "42", d.Pin()) - assert.Equal(t, 30*time.Second, d.interval) + // act + d := NewAnalogSensorDriver(a, pin) + // assert: driver attributes + assert.IsType(t, &AnalogSensorDriver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), "AnalogSensor")) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) + // assert: sensor attributes + assert.Equal(t, pin, d.Pin()) + assert.InDelta(t, 0.0, d.lastValue, 0, 0) + assert.Equal(t, 0, d.lastRawValue) + assert.NotNil(t, d.halt) + assert.NotNil(t, d.Eventer) + require.NotNil(t, d.sensorCfg) + assert.Equal(t, time.Duration(0), d.sensorCfg.readInterval) + assert.NotNil(t, d.sensorCfg.scale) +} - a.analogReadFunc = func() (int, error) { - return 100, nil +func TestNewAnalogSensorDriver_options(t *testing.T) { + // This is a general test, that options are applied in constructor by using the common WithName() option, least one + // option of this driver and one of another driver (which should lead to panic). Further tests for options can also + // be done by call of "WithOption(val).apply(cfg)". + // arrange + const ( + myName = "voltage 1" + cycReadDur = 10 * time.Millisecond + ) + panicFunc := func() { + NewAnalogSensorDriver(newAioTestAdaptor(), "1", WithName("crazy"), WithActuatorScaler(func(float64) int { return 0 })) } + // act + d := NewAnalogSensorDriver(newAioTestAdaptor(), "1", WithName(myName), WithSensorCyclicRead(cycReadDur)) + // assert + assert.Equal(t, cycReadDur, d.sensorCfg.readInterval) + assert.Equal(t, myName, d.Name()) + assert.PanicsWithValue(t, "'scaler option for analog actuators' can not be applied on 'crazy'", panicFunc) +} - ret := d.Command("ReadRaw")(nil).(map[string]interface{}) - assert.Equal(t, 100, ret["val"].(int)) - assert.Nil(t, ret["err"]) - - ret = d.Command("Read")(nil).(map[string]interface{}) - assert.InDelta(t, 247.0, ret["val"].(float64), 0.0) - assert.Nil(t, ret["err"]) +func TestAnalogSensor_WithSensorScaler(t *testing.T) { + // arrange + myScaler := func(input int) float64 { return float64(input) / 2 } + cfg := sensorConfiguration{} + // act + WithSensorScaler(myScaler).apply(&cfg) + // assert + assert.InDelta(t, 1.5, cfg.scale(3), 0.0) +} - // refresh value on read - a = newAioTestAdaptor() - d = NewAnalogSensorDriver(a, "3") - a.analogReadFunc = func() (int, error) { - return 150, nil +func TestAnalogSensorDriverReadRaw(t *testing.T) { + tests := map[string]struct { + simulateReadErr bool + wantVal int + wantErr string + }{ + "read_raw": {wantVal: analogReadReturnValue}, + "error_read": {wantVal: 0, simulateReadErr: true, wantErr: "read error"}, } - assert.InDelta(t, 0.0, d.Value(), 0.0) - val, err := d.Read() - require.NoError(t, err) - assert.InDelta(t, 150.0, val, 0.0) - assert.InDelta(t, 150.0, d.Value(), 0.0) - assert.Equal(t, 150, d.RawValue()) + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + const pin = "47" + a := newAioTestAdaptor() + d := NewAnalogSensorDriver(a, pin) + a.simulateReadError = tc.simulateReadErr + a.written = nil // reset previous writes + // act + got, err := d.ReadRaw() + // assert + if tc.wantErr != "" { + require.EqualError(t, err, tc.wantErr) + assert.Empty(t, a.written) + } else { + require.NoError(t, err) + } + assert.Equal(t, tc.wantVal, got) + }) + } +} + +func TestAnalogSensorDriverReadRaw_AnalogWriteNotSupported(t *testing.T) { + // arrange + d := NewAnalogSensorDriver(newAioTestAdaptor(), "1") + d.connection = &aioTestBareAdaptor{} + // act & assert + got, err := d.ReadRaw() + require.EqualError(t, err, "AnalogRead is not supported by the platform 'bare'") + assert.Equal(t, 0, got) } -func TestAnalogSensorDriverWithLinearScaler(t *testing.T) { +func TestAnalogSensorRead_SetScaler(t *testing.T) { // the input scales per default from 0...255 tests := map[string]struct { toMin float64 @@ -76,98 +132,113 @@ func TestAnalogSensorDriverWithLinearScaler(t *testing.T) { } a := newAioTestAdaptor() d := NewAnalogSensorDriver(a, "7") - for name, tt := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange - d.SetScaler(AnalogSensorLinearScaler(0, 255, tt.toMin, tt.toMax)) + d.SetScaler(AnalogSensorLinearScaler(0, 255, tc.toMin, tc.toMax)) a.analogReadFunc = func() (int, error) { - return tt.input, nil + return tc.input, nil } // act got, err := d.Read() // assert require.NoError(t, err) - assert.InDelta(t, tt.want, got, 0.0) + assert.InDelta(t, tc.want, got, 0.0) }) } } -func TestAnalogSensorDriverStart(t *testing.T) { - sem := make(chan bool, 1) +func TestAnalogSensor_WithSensorCyclicRead(t *testing.T) { + // arrange a := newAioTestAdaptor() - d := NewAnalogSensorDriver(a, "1") + d := NewAnalogSensorDriver(a, "1", WithSensorCyclicRead(10*time.Millisecond)) d.SetScaler(func(input int) float64 { return float64(input * input) }) + semData := make(chan bool) + semDone := make(chan bool) + nextVal := make(chan int) + readTimeout := 1 * time.Second + a.analogReadFunc = func() (int, error) { + val := 100 + var err error + select { + case val = <-nextVal: + if val < 0 { + err = fmt.Errorf("analog read error") + } + return val, err + default: + return val, nil + } + } - // expect data to be received + // arrange: expect raw value to be received _ = d.Once(d.Event(Data), func(data interface{}) { assert.Equal(t, 100, data.(int)) - sem <- true + semData <- true }) - _ = d.Once(d.Event(Value), func(data interface{}) { - assert.InDelta(t, 10000.0, data.(float64), 0.0) - sem <- true + // arrange: expect scaled value to be received + _ = d.Once(d.Event(Value), func(value interface{}) { + assert.InDelta(t, 10000.0, value.(float64), 0.0) + <-semData // wait for data is finished + semDone <- true + nextVal <- -1 // arrange: error in read function }) - // send data - a.analogReadFunc = func() (int, error) { - return 100, nil - } - + // act (start cyclic reading) require.NoError(t, d.Start()) + // assert: both events within timeout select { - case <-sem: - case <-time.After(1 * time.Second): + case <-semDone: + case <-time.After(readTimeout): t.Errorf("AnalogSensor Event \"Data\" was not published") } - // expect error to be received - _ = d.Once(d.Event(Error), func(data interface{}) { - assert.Equal(t, "read error", data.(error).Error()) - sem <- true + // arrange: for error to be received + _ = d.Once(d.Event(Error), func(err interface{}) { + assert.Equal(t, "analog read error", err.(error).Error()) + semDone <- true }) - // send error - a.analogReadFunc = func() (int, error) { - return 0, errors.New("read error") - } - + // assert: error select { - case <-sem: - case <-time.After(1 * time.Second): + case <-semDone: + case <-time.After(readTimeout): t.Errorf("AnalogSensor Event \"Error\" was not published") } - // send a halt message + // arrange: for halt message _ = d.Once(d.Event(Data), func(data interface{}) { - sem <- true + semData <- true }) - _ = d.Once(d.Event(Value), func(data interface{}) { - sem <- true + _ = d.Once(d.Event(Value), func(value interface{}) { + semDone <- true }) - a.analogReadFunc = func() (int, error) { - return 200, nil - } - + // act: send a halt message d.halt <- true + // assert: no event select { - case <-sem: - t.Errorf("AnalogSensor Event should not published") - case <-time.After(1 * time.Second): + case <-semData: + t.Errorf("AnalogSensor Event for data should not published") + case <-semDone: + t.Errorf("AnalogSensor Event for value should not published") + case <-time.After(readTimeout): } } -func TestAnalogSensorDriverHalt(t *testing.T) { - d := NewAnalogSensorDriver(newAioTestAdaptor(), "1") +func TestAnalogSensorHalt_WithSensorCyclicRead(t *testing.T) { + // arrange + d := NewAnalogSensorDriver(newAioTestAdaptor(), "1", WithSensorCyclicRead(10*time.Millisecond)) done := make(chan struct{}) go func() { <-d.halt close(done) }() + // act & assert require.NoError(t, d.Halt()) select { case <-done: @@ -176,13 +247,25 @@ func TestAnalogSensorDriverHalt(t *testing.T) { } } -func TestAnalogSensorDriverDefaultName(t *testing.T) { - d := NewAnalogSensorDriver(newAioTestAdaptor(), "1") - assert.True(t, strings.HasPrefix(d.Name(), "AnalogSensor")) -} - -func TestAnalogSensorDriverSetName(t *testing.T) { - d := NewAnalogSensorDriver(newAioTestAdaptor(), "1") - d.SetName("mybot") - assert.Equal(t, "mybot", d.Name()) +func TestAnalogSensorCommands_WithSensorScaler(t *testing.T) { + // arrange + a := newAioTestAdaptor() + d := NewAnalogSensorDriver(a, "42", WithSensorScaler(func(input int) float64 { return 2.5*float64(input) - 3 })) + var readReturn int + a.analogReadFunc = func() (int, error) { + readReturn += 100 + return readReturn, nil + } + // act & assert: ReadRaw + ret := d.Command("ReadRaw")(nil).(map[string]interface{}) + assert.Equal(t, 100, ret["val"].(int)) + assert.Nil(t, ret["err"]) + assert.Equal(t, 100, d.RawValue()) + assert.InDelta(t, 247.0, d.Value(), 0.0) + // act & assert: Read + ret = d.Command("Read")(nil).(map[string]interface{}) + assert.InDelta(t, 497.0, ret["val"].(float64), 0.0) + assert.Nil(t, ret["err"]) + assert.Equal(t, 200, d.RawValue()) + assert.InDelta(t, 497.0, d.Value(), 0.0) } diff --git a/drivers/aio/grove_drivers.go b/drivers/aio/grove_drivers.go index 8d4783420..70feb84e9 100644 --- a/drivers/aio/grove_drivers.go +++ b/drivers/aio/grove_drivers.go @@ -1,28 +1,23 @@ package aio -import ( - "time" -) +import "gobot.io/x/gobot/v2" // GroveRotaryDriver represents an analog rotary dial with a Grove connector type GroveRotaryDriver struct { *AnalogSensorDriver } -// NewGroveRotaryDriver returns a new GroveRotaryDriver with a polling interval of -// 10 Milliseconds given an AnalogReader and pin. +// NewGroveRotaryDriver returns a new driver for grove rotary dial, given an AnalogReader and pin. // -// Optionally accepts: -// -// time.Duration: Interval at which the AnalogSensor is polled for new information -// -// Adds the following API Commands: -// -// "Read" - See AnalogSensor.Read -func NewGroveRotaryDriver(a AnalogReader, pin string, v ...time.Duration) *GroveRotaryDriver { - return &GroveRotaryDriver{ - AnalogSensorDriver: NewAnalogSensorDriver(a, pin, v...), +// Supported options: see [aio.NewAnalogSensorDriver] +// Adds the following API Commands: see [aio.NewAnalogSensorDriver] +func NewGroveRotaryDriver(a AnalogReader, pin string, opts ...interface{}) *GroveRotaryDriver { + d := GroveRotaryDriver{ + AnalogSensorDriver: NewAnalogSensorDriver(a, pin, opts...), } + d.driverCfg.name = gobot.DefaultName("GroveRotary") + + return &d } // GroveLightSensorDriver represents an analog light sensor @@ -31,78 +26,66 @@ type GroveLightSensorDriver struct { *AnalogSensorDriver } -// NewGroveLightSensorDriver returns a new GroveLightSensorDriver with a polling interval of -// 10 Milliseconds given an AnalogReader and pin. -// -// Optionally accepts: +// NewGroveLightSensorDriver returns a new driver for grove light sensor, given an AnalogReader and pin. // -// time.Duration: Interval at which the AnalogSensor is polled for new information -// -// Adds the following API Commands: -// -// "Read" - See AnalogSensor.Read -func NewGroveLightSensorDriver(a AnalogReader, pin string, v ...time.Duration) *GroveLightSensorDriver { - return &GroveLightSensorDriver{ - AnalogSensorDriver: NewAnalogSensorDriver(a, pin, v...), +// Supported options: see [aio.NewAnalogSensorDriver] +// Adds the following API Commands: see [aio.NewAnalogSensorDriver] +func NewGroveLightSensorDriver(a AnalogReader, pin string, opts ...interface{}) *GroveLightSensorDriver { + d := GroveLightSensorDriver{ + AnalogSensorDriver: NewAnalogSensorDriver(a, pin, opts...), } + d.driverCfg.name = gobot.DefaultName("GroveLightSensor") + + return &d } -// GrovePiezoVibrationSensorDriver represents an analog vibration sensor -// with a Grove connector +// GrovePiezoVibrationSensorDriver represents an analog vibration sensor with a Grove connector type GrovePiezoVibrationSensorDriver struct { *AnalogSensorDriver } -// NewGrovePiezoVibrationSensorDriver returns a new GrovePiezoVibrationSensorDriver with a polling interval of -// 10 Milliseconds given an AnalogReader and pin. +// NewGrovePiezoVibrationSensorDriver returns a new driver for grove piezo vibration sensor, given an AnalogReader +// and pin. // -// Optionally accepts: -// -// time.Duration: Interval at which the AnalogSensor is polled for new information -// -// Adds the following API Commands: -// -// "Read" - See AnalogSensor.Read +// Supported options: see [aio.NewAnalogSensorDriver] +// Adds the following API Commands: see [aio.NewAnalogSensorDriver] func NewGrovePiezoVibrationSensorDriver( a AnalogReader, pin string, - v ...time.Duration, + opts ...interface{}, ) *GrovePiezoVibrationSensorDriver { - sensor := &GrovePiezoVibrationSensorDriver{ - AnalogSensorDriver: NewAnalogSensorDriver(a, pin, v...), + d := &GrovePiezoVibrationSensorDriver{ + AnalogSensorDriver: NewAnalogSensorDriver(a, pin, opts...), } + d.driverCfg.name = gobot.DefaultName("GrovePiezoVibrationSensor") - sensor.AddEvent(Vibration) + d.AddEvent(Vibration) - if err := sensor.On(sensor.Event(Data), func(data interface{}) { + if err := d.On(d.Event(Data), func(data interface{}) { if data.(int) > 1000 { //nolint:forcetypeassert // no error return value, so there is no better way - sensor.Publish(sensor.Event(Vibration), data) + d.Publish(d.Event(Vibration), data) } }); err != nil { panic(err) } - return sensor + return d } -// GroveSoundSensorDriver represents a analog sound sensor -// with a Grove connector +// GroveSoundSensorDriver represents a analog sound sensor with a Grove connector type GroveSoundSensorDriver struct { *AnalogSensorDriver } -// NewGroveSoundSensorDriver returns a new GroveSoundSensorDriver with a polling interval of -// 10 Milliseconds given an AnalogReader and pin. +// NewGroveSoundSensorDriver returns a new driver for grove sound sensor, given an AnalogReader and pin. // -// Optionally accepts: -// -// time.Duration: Interval at which the AnalogSensor is polled for new information -// -// Adds the following API Commands: -// -// "Read" - See AnalogSensor.Read -func NewGroveSoundSensorDriver(a AnalogReader, pin string, v ...time.Duration) *GroveSoundSensorDriver { - return &GroveSoundSensorDriver{ - AnalogSensorDriver: NewAnalogSensorDriver(a, pin, v...), +// Supported options: see [aio.NewAnalogSensorDriver] +// Adds the following API Commands: see [aio.NewAnalogSensorDriver] +func NewGroveSoundSensorDriver(a AnalogReader, pin string, opts ...interface{}) *GroveSoundSensorDriver { + d := GroveSoundSensorDriver{ + AnalogSensorDriver: NewAnalogSensorDriver(a, pin, opts...), } + d.driverCfg.name = gobot.DefaultName("GroveSoundSensor") + + return &d } diff --git a/drivers/aio/grove_drivers_test.go b/drivers/aio/grove_drivers_test.go index a58502fd1..69cf35c93 100644 --- a/drivers/aio/grove_drivers_test.go +++ b/drivers/aio/grove_drivers_test.go @@ -4,6 +4,7 @@ package aio import ( "errors" "reflect" + "strings" "sync/atomic" "testing" "time" @@ -14,42 +15,125 @@ import ( "gobot.io/x/gobot/v2" ) -type DriverAndPinner interface { +type groveDriverTestDriverAndEventer interface { gobot.Driver - gobot.Pinner + gobot.Eventer } -type DriverAndEventer interface { - gobot.Driver - gobot.Eventer +func TestNewGroveRotaryDriver(t *testing.T) { + // arrange + a := newAioTestAdaptor() + pin := "456" + // act + d := NewGroveRotaryDriver(a, pin) + // assert: driver attributes + assert.IsType(t, &GroveRotaryDriver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), "GroveRotary")) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) + // assert: sensor attributes + assert.Equal(t, pin, d.Pin()) + assert.InDelta(t, 0.0, d.lastValue, 0, 0) + assert.Equal(t, 0, d.lastRawValue) + assert.NotNil(t, d.halt) + assert.NotNil(t, d.Eventer) + require.NotNil(t, d.sensorCfg) + assert.Equal(t, time.Duration(0), d.sensorCfg.readInterval) + assert.NotNil(t, d.sensorCfg.scale) } -func TestDriverDefaults(t *testing.T) { - testAdaptor := newAioTestAdaptor() +func TestNewGroveLightSensorDriver(t *testing.T) { + // arrange + a := newAioTestAdaptor() pin := "456" + // act + d := NewGroveLightSensorDriver(a, pin) + // assert: driver attributes + assert.IsType(t, &GroveLightSensorDriver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), "GroveLightSensor")) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) + // assert: sensor attributes + assert.Equal(t, pin, d.Pin()) + assert.InDelta(t, 0.0, d.lastValue, 0, 0) + assert.Equal(t, 0, d.lastRawValue) + assert.NotNil(t, d.halt) + assert.NotNil(t, d.Eventer) + require.NotNil(t, d.sensorCfg) + assert.Equal(t, time.Duration(0), d.sensorCfg.readInterval) + assert.NotNil(t, d.sensorCfg.scale) +} - drivers := []DriverAndPinner{ - NewGroveSoundSensorDriver(testAdaptor, pin), - NewGroveLightSensorDriver(testAdaptor, pin), - NewGrovePiezoVibrationSensorDriver(testAdaptor, pin), - NewGroveRotaryDriver(testAdaptor, pin), - } +func TestNewGrovePiezoVibrationSensorDriver(t *testing.T) { + // arrange + a := newAioTestAdaptor() + pin := "456" + // act + d := NewGrovePiezoVibrationSensorDriver(a, pin) + // assert: driver attributes + assert.IsType(t, &GrovePiezoVibrationSensorDriver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), "GrovePiezoVibrationSensor")) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) + // assert: sensor attributes + assert.Equal(t, pin, d.Pin()) + assert.InDelta(t, 0.0, d.lastValue, 0, 0) + assert.Equal(t, 0, d.lastRawValue) + assert.NotNil(t, d.halt) + assert.NotNil(t, d.Eventer) + require.NotNil(t, d.sensorCfg) + assert.Equal(t, time.Duration(0), d.sensorCfg.readInterval) + assert.NotNil(t, d.sensorCfg.scale) +} - for _, driver := range drivers { - assert.Equal(t, testAdaptor, driver.Connection()) - assert.Equal(t, pin, driver.Pin()) - } +func TestNewGroveSoundSensorDriver(t *testing.T) { + // arrange + a := newAioTestAdaptor() + pin := "456" + // act + d := NewGroveSoundSensorDriver(a, pin) + // assert: driver attributes + assert.IsType(t, &GroveSoundSensorDriver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), "GroveSoundSensor")) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) + // assert: sensor attributes + assert.Equal(t, pin, d.Pin()) + assert.InDelta(t, 0.0, d.lastValue, 0, 0) + assert.Equal(t, 0, d.lastRawValue) + assert.NotNil(t, d.halt) + assert.NotNil(t, d.Eventer) + require.NotNil(t, d.sensorCfg) + assert.Equal(t, time.Duration(0), d.sensorCfg.readInterval) + assert.NotNil(t, d.sensorCfg.scale) } -func TestAnalogDriverHalt(t *testing.T) { +func TestGroveDriverHalt_WithSensorCyclicRead(t *testing.T) { + // arrange testAdaptor := newAioTestAdaptor() pin := "456" - drivers := []DriverAndEventer{ - NewGroveSoundSensorDriver(testAdaptor, pin), - NewGroveLightSensorDriver(testAdaptor, pin), - NewGrovePiezoVibrationSensorDriver(testAdaptor, pin), - NewGroveRotaryDriver(testAdaptor, pin), + drivers := []groveDriverTestDriverAndEventer{ + NewGroveSoundSensorDriver(testAdaptor, pin, WithSensorCyclicRead(10*time.Millisecond)), + NewGroveLightSensorDriver(testAdaptor, pin, WithSensorCyclicRead(10*time.Millisecond)), + NewGrovePiezoVibrationSensorDriver(testAdaptor, pin, WithSensorCyclicRead(10*time.Millisecond)), + NewGroveRotaryDriver(testAdaptor, pin, WithSensorCyclicRead(10*time.Millisecond)), } for _, driver := range drivers { @@ -73,15 +157,16 @@ func TestAnalogDriverHalt(t *testing.T) { } } -func TestDriverPublishesError(t *testing.T) { +func TestGroveDriverWithSensorCyclicReadPublishesError(t *testing.T) { + // arrange testAdaptor := newAioTestAdaptor() pin := "456" - drivers := []DriverAndEventer{ - NewGroveSoundSensorDriver(testAdaptor, pin), - NewGroveLightSensorDriver(testAdaptor, pin), - NewGrovePiezoVibrationSensorDriver(testAdaptor, pin), - NewGroveRotaryDriver(testAdaptor, pin), + drivers := []groveDriverTestDriverAndEventer{ + NewGroveSoundSensorDriver(testAdaptor, pin, WithSensorCyclicRead(10*time.Millisecond)), + NewGroveLightSensorDriver(testAdaptor, pin, WithSensorCyclicRead(10*time.Millisecond)), + NewGrovePiezoVibrationSensorDriver(testAdaptor, pin, WithSensorCyclicRead(10*time.Millisecond)), + NewGroveRotaryDriver(testAdaptor, pin, WithSensorCyclicRead(10*time.Millisecond)), } for _, driver := range drivers { @@ -102,7 +187,7 @@ func TestDriverPublishesError(t *testing.T) { select { case <-sem: case <-time.After(time.Second): - t.Errorf("%s Event \"Error\" was not published", getType(driver)) + t.Errorf("%s Event \"Error\" was not published", groveGetType(driver)) } // Cleanup @@ -110,7 +195,7 @@ func TestDriverPublishesError(t *testing.T) { } } -func getType(driver interface{}) string { +func groveGetType(driver interface{}) string { d := reflect.TypeOf(driver) if d.Kind() == reflect.Ptr { diff --git a/drivers/aio/grove_temperature_sensor_driver.go b/drivers/aio/grove_temperature_sensor_driver.go index f12e5132d..d30675906 100644 --- a/drivers/aio/grove_temperature_sensor_driver.go +++ b/drivers/aio/grove_temperature_sensor_driver.go @@ -1,39 +1,45 @@ package aio import ( + "fmt" "time" "gobot.io/x/gobot/v2" ) -var _ gobot.Driver = (*GroveTemperatureSensorDriver)(nil) - // GroveTemperatureSensorDriver represents a temperature sensor // The temperature is reported in degree Celsius type GroveTemperatureSensorDriver struct { *TemperatureSensorDriver } -// NewGroveTemperatureSensorDriver returns a new GroveTemperatureSensorDriver with a polling interval of -// 10 Milliseconds given an AnalogReader and pin. -// -// Optionally accepts: -// -// time.Duration: Interval at which the sensor is polled for new information (given 0 switch the polling off) +// NewGroveTemperatureSensorDriver returns a new driver for grove temperature sensor, given an AnalogReader and pin. // -// Adds the following API Commands: -// -// "Read" - See AnalogDriverSensor.Read -// "ReadValue" - See AnalogDriverSensor.ReadValue -func NewGroveTemperatureSensorDriver(a AnalogReader, pin string, v ...time.Duration) *GroveTemperatureSensorDriver { - t := NewTemperatureSensorDriver(a, pin, v...) +// Supported options: see [aio.NewAnalogSensorDriver] +// Adds the following API Commands: see [aio.NewAnalogSensorDriver] +func NewGroveTemperatureSensorDriver(a AnalogReader, pin string, opts ...interface{}) *GroveTemperatureSensorDriver { + t := NewTemperatureSensorDriver(a, pin, opts...) ntc := TemperatureSensorNtcConf{TC0: 25, R0: 10000.0, B: 3975} // Ohm, R25=10k t.SetNtcScaler(1023, 10000, false, ntc) // Ohm, reference value: 1023, series R: 10k d := &GroveTemperatureSensorDriver{ TemperatureSensorDriver: t, } - d.SetName(gobot.DefaultName("GroveTemperatureSensor")) + d.driverCfg.name = gobot.DefaultName("GroveTemperatureSensor") + + for _, opt := range opts { + switch o := opt.(type) { + case optionApplier: + o.apply(d.driverCfg) + case sensorOptionApplier: + o.apply(d.sensorCfg) + case time.Duration: + // TODO this is only for backward compatibility and will be removed after version 2.x + d.sensorCfg.readInterval = o + default: + panic(fmt.Sprintf("'%s' can not be applied on '%s'", opt, d.driverCfg.name)) + } + } return d } diff --git a/drivers/aio/grove_temperature_sensor_driver_test.go b/drivers/aio/grove_temperature_sensor_driver_test.go index ebe27a278..1559d4f54 100644 --- a/drivers/aio/grove_temperature_sensor_driver_test.go +++ b/drivers/aio/grove_temperature_sensor_driver_test.go @@ -15,15 +15,54 @@ import ( var _ gobot.Driver = (*GroveTemperatureSensorDriver)(nil) -func TestGroveTemperatureSensorDriver(t *testing.T) { - testAdaptor := newAioTestAdaptor() - d := NewGroveTemperatureSensorDriver(testAdaptor, "123") - assert.Equal(t, testAdaptor, d.Connection()) - assert.Equal(t, "123", d.Pin()) - assert.Equal(t, 10*time.Millisecond, d.interval) +func TestNewGroveTemperatureSensorDriver(t *testing.T) { + // arrange + const pin = "123" + a := newAioTestAdaptor() + // act + d := NewGroveTemperatureSensorDriver(a, pin) + // assert: driver attributes + assert.IsType(t, &GroveTemperatureSensorDriver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), "GroveTemperatureSensor")) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) + // assert: sensor attributes + assert.Equal(t, pin, d.Pin()) + assert.InDelta(t, 0.0, d.lastValue, 0, 0) + assert.Equal(t, 0, d.lastRawValue) + assert.NotNil(t, d.halt) + assert.NotNil(t, d.Eventer) + require.NotNil(t, d.sensorCfg) + assert.Equal(t, time.Duration(0), d.sensorCfg.readInterval) + assert.NotNil(t, d.sensorCfg.scale) +} + +func TestNewGroveTemperatureSensorDriver_options(t *testing.T) { + // This is a general test, that options are applied in constructor by using the common WithName() option, least one + // option of this driver and one of another driver (which should lead to panic). Further tests for options can also + // be done by call of "WithOption(val).apply(cfg)". + // arrange + const ( + myName = "inlet temperature" + cycReadDur = 10 * time.Millisecond + ) + panicFunc := func() { + NewGroveTemperatureSensorDriver(newAioTestAdaptor(), "1", WithName("crazy"), + WithActuatorScaler(func(float64) int { return 0 })) + } + // act + d := NewGroveTemperatureSensorDriver(newAioTestAdaptor(), "1", WithName(myName), WithSensorCyclicRead(cycReadDur)) + // assert + assert.Equal(t, cycReadDur, d.sensorCfg.readInterval) + assert.Equal(t, myName, d.Name()) + assert.PanicsWithValue(t, "'scaler option for analog actuators' can not be applied on 'crazy'", panicFunc) } -func TestGroveTemperatureSensorDriverScaling(t *testing.T) { +func TestGroveTemperatureSensorRead_scaler(t *testing.T) { tests := map[string]struct { input int want float64 @@ -40,25 +79,26 @@ func TestGroveTemperatureSensorDriverScaling(t *testing.T) { } a := newAioTestAdaptor() d := NewGroveTemperatureSensorDriver(a, "54") - for name, tt := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange a.analogReadFunc = func() (int, error) { - return tt.input, nil + return tc.input, nil } // act got, err := d.Read() // assert require.NoError(t, err) - assert.InDelta(t, tt.want, got, 0.0) + assert.InDelta(t, tc.want, got, 0.0) }) } } -func TestGroveTempSensorPublishesTemperatureInCelsius(t *testing.T) { - sem := make(chan bool, 1) +func TestGroveTemperatureSensor_publishesTemperatureInCelsius(t *testing.T) { + // arrange + sem := make(chan bool) a := newAioTestAdaptor() - d := NewGroveTemperatureSensorDriver(a, "1") + d := NewGroveTemperatureSensorDriver(a, "1", WithSensorCyclicRead(10*time.Millisecond)) a.analogReadFunc = func() (int, error) { return 585, nil @@ -67,18 +107,16 @@ func TestGroveTempSensorPublishesTemperatureInCelsius(t *testing.T) { assert.Equal(t, "31.62", fmt.Sprintf("%.2f", data.(float64))) sem <- true }) + + // act: start cyclic reading require.NoError(t, d.Start()) + // assert: value was published select { case <-sem: case <-time.After(1 * time.Second): - t.Errorf("Grove Temperature Sensor Event \"Data\" was not published") + t.Errorf("Grove Temperature Sensor Event \"Value\" was not published") } assert.InDelta(t, 31.61532462352477, d.Temperature(), 0.0) } - -func TestGroveTempDriverDefaultName(t *testing.T) { - d := NewGroveTemperatureSensorDriver(newAioTestAdaptor(), "1") - assert.True(t, strings.HasPrefix(d.Name(), "GroveTemperatureSensor")) -} diff --git a/drivers/aio/helpers_test.go b/drivers/aio/helpers_test.go index af7013332..a91029dd5 100644 --- a/drivers/aio/helpers_test.go +++ b/drivers/aio/helpers_test.go @@ -1,14 +1,33 @@ package aio -import "sync" +import ( + "fmt" + "sync" +) + +const analogReadReturnValue = 99 + +type aioTestBareAdaptor struct{} + +func (t *aioTestBareAdaptor) Connect() error { return nil } +func (t *aioTestBareAdaptor) Finalize() error { return nil } +func (t *aioTestBareAdaptor) Name() string { return "bare" } +func (t *aioTestBareAdaptor) SetName(n string) {} + +type aioTestWritten struct { + pin string + val int +} type aioTestAdaptor struct { - name string - port string - mtx sync.Mutex - analogReadFunc func() (val int, err error) - analogWriteFunc func(val int) error - written []int + name string + written []aioTestWritten + simulateWriteError bool + simulateReadError bool + port string + mtx sync.Mutex + analogReadFunc func() (val int, err error) + analogWriteFunc func(val int) error } func newAioTestAdaptor() *aioTestAdaptor { @@ -16,7 +35,7 @@ func newAioTestAdaptor() *aioTestAdaptor { name: "aio_test_adaptor", port: "/dev/null", analogReadFunc: func() (int, error) { - return 99, nil + return analogReadReturnValue, nil }, analogWriteFunc: func(val int) error { return nil @@ -30,6 +49,11 @@ func newAioTestAdaptor() *aioTestAdaptor { func (t *aioTestAdaptor) AnalogRead(pin string) (int, error) { t.mtx.Lock() defer t.mtx.Unlock() + + if t.simulateReadError { + return 0, fmt.Errorf("read error") + } + return t.analogReadFunc() } @@ -37,7 +61,13 @@ func (t *aioTestAdaptor) AnalogRead(pin string) (int, error) { func (t *aioTestAdaptor) AnalogWrite(pin string, val int) error { t.mtx.Lock() defer t.mtx.Unlock() - t.written = append(t.written, val) + + if t.simulateWriteError { + return fmt.Errorf("write error") + } + + w := aioTestWritten{pin: pin, val: val} + t.written = append(t.written, w) return t.analogWriteFunc(val) } diff --git a/drivers/aio/temperature_sensor_driver.go b/drivers/aio/temperature_sensor_driver.go index 15953a04e..f415caa1d 100644 --- a/drivers/aio/temperature_sensor_driver.go +++ b/drivers/aio/temperature_sensor_driver.go @@ -1,6 +1,7 @@ package aio import ( + "fmt" "math" "time" @@ -25,24 +26,28 @@ type TemperatureSensorDriver struct { *AnalogSensorDriver } -// NewTemperatureSensorDriver is a gobot driver for analog temperature sensors -// with a polling interval 10 Milliseconds given an AnalogReader and pin. -// For further details please refer to AnalogSensorDriver. +// NewTemperatureSensorDriver is a driver for analog temperature sensors, given an AnalogReader and pin. // Linear scaling and NTC scaling is supported. // -// Optionally accepts: -// -// time.Duration: Interval at which the sensor is polled for new information (given 0 switch the polling off) -// -// Adds the following API Commands: -// -// "Read" - See AnalogDriverSensor.Read -// "ReadValue" - See AnalogDriverSensor.ReadValue -func NewTemperatureSensorDriver(a AnalogReader, pin string, v ...time.Duration) *TemperatureSensorDriver { - ad := NewAnalogSensorDriver(a, pin, v...) - - d := &TemperatureSensorDriver{AnalogSensorDriver: ad} - d.SetName(gobot.DefaultName("TemperatureSensor")) +// Supported options: see [aio.NewAnalogSensorDriver] +// Adds the following API Commands: see [aio.NewAnalogSensorDriver] +func NewTemperatureSensorDriver(a AnalogReader, pin string, opts ...interface{}) *TemperatureSensorDriver { + d := &TemperatureSensorDriver{AnalogSensorDriver: NewAnalogSensorDriver(a, pin)} + d.driverCfg.name = gobot.DefaultName("TemperatureSensor") + + for _, opt := range opts { + switch o := opt.(type) { + case optionApplier: + o.apply(d.driverCfg) + case sensorOptionApplier: + o.apply(d.sensorCfg) + case time.Duration: + // TODO this is only for backward compatibility and will be removed after version 2.x + d.sensorCfg.readInterval = o + default: + panic(fmt.Sprintf("'%s' can not be applied on '%s'", opt, d.driverCfg.name)) + } + } return d } @@ -52,6 +57,7 @@ func NewTemperatureSensorDriver(a AnalogReader, pin string, v ...time.Duration) // If the thermistor is connected to ground, the reverse flag must be set to true. // This means the voltage decreases when temperature gets higher. // Currently no negative values for voltage are supported. +// If the scaler is not changed after initialization, prefer to use [aio.WithSensorScaler] instead. func (t *TemperatureSensorDriver) SetNtcScaler(vRef uint, rOhm uint, reverse bool, ntc TemperatureSensorNtcConf) { t.SetScaler(TemperatureSensorNtcScaler(vRef, rOhm, reverse, ntc)) } @@ -59,6 +65,7 @@ func (t *TemperatureSensorDriver) SetNtcScaler(vRef uint, rOhm uint, reverse boo // SetLinearScaler sets a function for linear scaling the read value. // This can be applied for some silicon based PTC sensors or e.g. PT100, // and in a small temperature range also for NTC. +// If the scaler is not changed after initialization, prefer to use [aio.WithSensorScaler] instead. func (t *TemperatureSensorDriver) SetLinearScaler(fromMin, fromMax int, toMin, toMax float64) { t.SetScaler(AnalogSensorLinearScaler(fromMin, fromMax, toMin, toMax)) } diff --git a/drivers/aio/temperature_sensor_driver_test.go b/drivers/aio/temperature_sensor_driver_test.go index f0977562a..1d2a30069 100644 --- a/drivers/aio/temperature_sensor_driver_test.go +++ b/drivers/aio/temperature_sensor_driver_test.go @@ -12,15 +12,54 @@ import ( "github.com/stretchr/testify/require" ) -func TestTemperatureSensorDriver(t *testing.T) { - testAdaptor := newAioTestAdaptor() - d := NewTemperatureSensorDriver(testAdaptor, "123") - assert.Equal(t, testAdaptor, d.Connection()) - assert.Equal(t, "123", d.Pin()) - assert.Equal(t, 10*time.Millisecond, d.interval) +func TestNewTemperatureSensorDriver(t *testing.T) { + // arrange + const pin = "123" + a := newAioTestAdaptor() + // act + d := NewTemperatureSensorDriver(a, pin) + // assert: driver attributes + assert.IsType(t, &TemperatureSensorDriver{}, d) + assert.NotNil(t, d.driverCfg) + assert.True(t, strings.HasPrefix(d.Name(), "TemperatureSensor")) + assert.Equal(t, a, d.Connection()) + require.NoError(t, d.afterStart()) + require.NoError(t, d.beforeHalt()) + assert.NotNil(t, d.Commander) + assert.NotNil(t, d.mutex) + // assert: sensor attributes + assert.Equal(t, pin, d.Pin()) + assert.InDelta(t, 0.0, d.lastValue, 0, 0) + assert.Equal(t, 0, d.lastRawValue) + assert.NotNil(t, d.halt) + assert.NotNil(t, d.Eventer) + require.NotNil(t, d.sensorCfg) + assert.Equal(t, time.Duration(0), d.sensorCfg.readInterval) + assert.NotNil(t, d.sensorCfg.scale) +} + +func TestNewTemperatureSensorDriver_options(t *testing.T) { + // This is a general test, that options are applied in constructor by using the common WithName() option, least one + // option of this driver and one of another driver (which should lead to panic). Further tests for options can also + // be done by call of "WithOption(val).apply(cfg)". + // arrange + const ( + myName = "outlet temperature" + cycReadDur = 10 * time.Millisecond + ) + panicFunc := func() { + NewTemperatureSensorDriver(newAioTestAdaptor(), "1", WithName("crazy"), + WithActuatorScaler(func(float64) int { return 0 })) + } + // act + d := NewTemperatureSensorDriver(newAioTestAdaptor(), "1", WithName(myName), WithSensorCyclicRead(cycReadDur)) + // assert + assert.Equal(t, cycReadDur, d.sensorCfg.readInterval) + assert.Equal(t, myName, d.Name()) + assert.PanicsWithValue(t, "'scaler option for analog actuators' can not be applied on 'crazy'", panicFunc) } -func TestTemperatureSensorDriverNtcScaling(t *testing.T) { +func TestTemperatureSensorRead_NtcScaler(t *testing.T) { tests := map[string]struct { input int want float64 @@ -40,22 +79,22 @@ func TestTemperatureSensorDriverNtcScaling(t *testing.T) { d := NewTemperatureSensorDriver(a, "4") ntc1 := TemperatureSensorNtcConf{TC0: 25, R0: 10000.0, B: 3950} // Ohm, R25=10k, B=3950 d.SetNtcScaler(255, 1000, true, ntc1) // Ohm, reference value: 3300, series R: 1k - for name, tt := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange a.analogReadFunc = func() (int, error) { - return tt.input, nil + return tc.input, nil } // act got, err := d.Read() // assert require.NoError(t, err) - assert.InDelta(t, tt.want, got, 0.0) + assert.InDelta(t, tc.want, got, 0.0) }) } } -func TestTemperatureSensorDriverLinearScaling(t *testing.T) { +func TestTemperatureSensorDriver_LinearScaler(t *testing.T) { tests := map[string]struct { input int want float64 @@ -74,25 +113,26 @@ func TestTemperatureSensorDriverLinearScaling(t *testing.T) { a := newAioTestAdaptor() d := NewTemperatureSensorDriver(a, "4") d.SetLinearScaler(-128, 127, -40, 100) - for name, tt := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange a.analogReadFunc = func() (int, error) { - return tt.input, nil + return tc.input, nil } // act got, err := d.Read() // assert require.NoError(t, err) - assert.InDelta(t, tt.want, got, 0.0) + assert.InDelta(t, tc.want, got, 0.0) }) } } -func TestTempSensorPublishesTemperatureInCelsius(t *testing.T) { - sem := make(chan bool, 1) +func TestTemperatureSensorPublishesTemperatureInCelsius(t *testing.T) { + // arrange + sem := make(chan bool) a := newAioTestAdaptor() - d := NewTemperatureSensorDriver(a, "1") + d := NewTemperatureSensorDriver(a, "1", WithSensorCyclicRead(10*time.Millisecond)) ntc := TemperatureSensorNtcConf{TC0: 25, R0: 10000.0, B: 3975} // Ohm, R25=10k d.SetNtcScaler(1023, 10000, false, ntc) // Ohm, reference value: 1023, series R: 10k @@ -114,10 +154,11 @@ func TestTempSensorPublishesTemperatureInCelsius(t *testing.T) { assert.InDelta(t, 31.61532462352477, d.Value(), 0.0) } -func TestTempSensorPublishesError(t *testing.T) { - sem := make(chan bool, 1) +func TestTemperatureSensorWithSensorCyclicReadPublishesError(t *testing.T) { + // arrange + sem := make(chan bool) a := newAioTestAdaptor() - d := NewTemperatureSensorDriver(a, "1") + d := NewTemperatureSensorDriver(a, "1", WithSensorCyclicRead(10*time.Millisecond)) // send error a.analogReadFunc = func() (int, error) { @@ -139,13 +180,15 @@ func TestTempSensorPublishesError(t *testing.T) { } } -func TestTempSensorHalt(t *testing.T) { - d := NewTemperatureSensorDriver(newAioTestAdaptor(), "1") +func TestTemperatureSensorHalt_WithSensorCyclicRead(t *testing.T) { + // arrange + d := NewTemperatureSensorDriver(newAioTestAdaptor(), "1", WithSensorCyclicRead(10*time.Millisecond)) done := make(chan struct{}) go func() { <-d.halt close(done) }() + // act & assert require.NoError(t, d.Halt()) select { case <-done: @@ -154,17 +197,6 @@ func TestTempSensorHalt(t *testing.T) { } } -func TestTempDriverDefaultName(t *testing.T) { - d := NewTemperatureSensorDriver(newAioTestAdaptor(), "1") - assert.True(t, strings.HasPrefix(d.Name(), "TemperatureSensor")) -} - -func TestTempDriverSetName(t *testing.T) { - d := NewTemperatureSensorDriver(newAioTestAdaptor(), "1") - d.SetName("mybot") - assert.Equal(t, "mybot", d.Name()) -} - func TestTempDriver_initialize(t *testing.T) { tests := map[string]struct { input TemperatureSensorNtcConf @@ -203,14 +235,14 @@ func TestTempDriver_initialize(t *testing.T) { }, }, } - for name, tt := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { // arrange - ntc := tt.input + ntc := tc.input // act ntc.initialize() // assert - assert.Equal(t, tt.want, ntc) + assert.Equal(t, tc.want, ntc) }) } } diff --git a/drivers/gpio/buzzer_driver_test.go b/drivers/gpio/buzzer_driver_test.go index f3626a185..f1486ff1a 100644 --- a/drivers/gpio/buzzer_driver_test.go +++ b/drivers/gpio/buzzer_driver_test.go @@ -59,7 +59,7 @@ func TestBuzzerDriverOnError(t *testing.T) { return errors.New("write error") } - require.ErrorContains(t, d.On(), "write error") + require.EqualError(t, d.On(), "write error") } func TestBuzzerDriverOffError(t *testing.T) { @@ -69,7 +69,7 @@ func TestBuzzerDriverOffError(t *testing.T) { return errors.New("write error") } - require.ErrorContains(t, d.Off(), "write error") + require.EqualError(t, d.Off(), "write error") } func TestBuzzerDriverToneError(t *testing.T) { @@ -79,5 +79,5 @@ func TestBuzzerDriverToneError(t *testing.T) { return errors.New("write error") } - require.ErrorContains(t, d.Tone(100, 0.01), "write error") + require.EqualError(t, d.Tone(100, 0.01), "write error") } diff --git a/drivers/gpio/direct_pin_driver_test.go b/drivers/gpio/direct_pin_driver_test.go index 1dccd9224..9512467f9 100644 --- a/drivers/gpio/direct_pin_driver_test.go +++ b/drivers/gpio/direct_pin_driver_test.go @@ -45,13 +45,13 @@ func TestDirectPinDriver(t *testing.T) { assert.Nil(t, ret["err"]) err = d.Command("DigitalWrite")(map[string]interface{}{"level": "1"}) - require.ErrorContains(t, err.(error), "write error") + require.EqualError(t, err.(error), "write error") err = d.Command("PwmWrite")(map[string]interface{}{"level": "1"}) - require.ErrorContains(t, err.(error), "write error") + require.EqualError(t, err.(error), "write error") err = d.Command("ServoWrite")(map[string]interface{}{"level": "1"}) - require.ErrorContains(t, err.(error), "write error") + require.EqualError(t, err.(error), "write error") } func TestDirectPinDriverStart(t *testing.T) { @@ -76,7 +76,7 @@ func TestDirectPinDriverOff(t *testing.T) { func TestDirectPinDriverOffNotSupported(t *testing.T) { a := &gpioTestBareAdaptor{} d := NewDirectPinDriver(a, "1") - require.ErrorContains(t, d.Off(), "DigitalWrite is not supported by this platform") + require.EqualError(t, d.Off(), "DigitalWrite is not supported by this platform") } func TestDirectPinDriverOn(t *testing.T) { @@ -93,7 +93,7 @@ func TestDirectPinDriverOnError(t *testing.T) { func TestDirectPinDriverOnNotSupported(t *testing.T) { a := &gpioTestBareAdaptor{} d := NewDirectPinDriver(a, "1") - require.ErrorContains(t, d.On(), "DigitalWrite is not supported by this platform") + require.EqualError(t, d.On(), "DigitalWrite is not supported by this platform") } func TestDirectPinDriverDigitalWrite(t *testing.T) { @@ -105,7 +105,7 @@ func TestDirectPinDriverDigitalWrite(t *testing.T) { func TestDirectPinDriverDigitalWriteNotSupported(t *testing.T) { a := &gpioTestBareAdaptor{} d := NewDirectPinDriver(a, "1") - require.ErrorContains(t, d.DigitalWrite(1), "DigitalWrite is not supported by this platform") + require.EqualError(t, d.DigitalWrite(1), "DigitalWrite is not supported by this platform") } func TestDirectPinDriverDigitalWriteError(t *testing.T) { @@ -124,7 +124,7 @@ func TestDirectPinDriverDigitalReadNotSupported(t *testing.T) { a := &gpioTestBareAdaptor{} d := NewDirectPinDriver(a, "1") _, e := d.DigitalRead() - require.ErrorContains(t, e, "DigitalRead is not supported by this platform") + require.EqualError(t, e, "DigitalRead is not supported by this platform") } func TestDirectPinDriverPwmWrite(t *testing.T) { @@ -136,7 +136,7 @@ func TestDirectPinDriverPwmWrite(t *testing.T) { func TestDirectPinDriverPwmWriteNotSupported(t *testing.T) { a := &gpioTestBareAdaptor{} d := NewDirectPinDriver(a, "1") - require.ErrorContains(t, d.PwmWrite(1), "PwmWrite is not supported by this platform") + require.EqualError(t, d.PwmWrite(1), "PwmWrite is not supported by this platform") } func TestDirectPinDriverPwmWriteError(t *testing.T) { @@ -153,7 +153,7 @@ func TestDirectPinDriverServoWrite(t *testing.T) { func TestDirectPinDriverServoWriteNotSupported(t *testing.T) { a := &gpioTestBareAdaptor{} d := NewDirectPinDriver(a, "1") - require.ErrorContains(t, d.ServoWrite(1), "ServoWrite is not supported by this platform") + require.EqualError(t, d.ServoWrite(1), "ServoWrite is not supported by this platform") } func TestDirectPinDriverServoWriteError(t *testing.T) { diff --git a/drivers/gpio/gpio_driver_test.go b/drivers/gpio/gpio_driver_test.go index 7511a3caf..753c1fd55 100644 --- a/drivers/gpio/gpio_driver_test.go +++ b/drivers/gpio/gpio_driver_test.go @@ -62,7 +62,7 @@ func TestStart(t *testing.T) { // arrange after start function d.afterStart = func() error { return fmt.Errorf("after start error") } // act, assert - require.ErrorContains(t, d.Start(), "after start error") + require.EqualError(t, d.Start(), "after start error") } func TestHalt(t *testing.T) { @@ -73,5 +73,5 @@ func TestHalt(t *testing.T) { // arrange after start function d.beforeHalt = func() error { return fmt.Errorf("before halt error") } // act, assert - require.ErrorContains(t, d.Halt(), "before halt error") + require.EqualError(t, d.Halt(), "before halt error") } diff --git a/drivers/gpio/hd44780_driver_test.go b/drivers/gpio/hd44780_driver_test.go index a43778118..3fbd13f77 100644 --- a/drivers/gpio/hd44780_driver_test.go +++ b/drivers/gpio/hd44780_driver_test.go @@ -94,7 +94,7 @@ func TestHD44780DriverStartError(t *testing.T) { D7: "", } d = NewHD44780Driver(a, 2, 16, HD44780_4BITMODE, "13", "15", pins) - require.ErrorContains(t, d.Start(), "Initialization error") + require.EqualError(t, d.Start(), "Initialization error") pins = HD44780DataPin{ D0: "31", @@ -107,7 +107,7 @@ func TestHD44780DriverStartError(t *testing.T) { D7: "", } d = NewHD44780Driver(a, 2, 16, HD44780_8BITMODE, "13", "15", pins) - require.ErrorContains(t, d.Start(), "Initialization error") + require.EqualError(t, d.Start(), "Initialization error") } func TestHD44780DriverWrite(t *testing.T) { @@ -131,14 +131,14 @@ func TestHD44780DriverWriteError(t *testing.T) { return errors.New("write error") } _ = d.Start() - require.ErrorContains(t, d.Write("hello gobot"), "write error") + require.EqualError(t, d.Write("hello gobot"), "write error") d, a = initTestHD44780Driver8BitModeWithStubbedAdaptor() a.digitalWriteFunc = func(string, byte) error { return errors.New("write error") } _ = d.Start() - require.ErrorContains(t, d.Write("hello gobot"), "write error") + require.EqualError(t, d.Write("hello gobot"), "write error") } func TestHD44780DriverClear(t *testing.T) { @@ -159,10 +159,10 @@ func TestHD44780DriverSetCursor(t *testing.T) { func TestHD44780DriverSetCursorInvalid(t *testing.T) { d := initTestHD44780Driver() - require.ErrorContains(t, d.SetCursor(-1, 3), "Invalid position value (-1, 3), range (1, 15)") - require.ErrorContains(t, d.SetCursor(2, 3), "Invalid position value (2, 3), range (1, 15)") - require.ErrorContains(t, d.SetCursor(0, -1), "Invalid position value (0, -1), range (1, 15)") - require.ErrorContains(t, d.SetCursor(0, 16), "Invalid position value (0, 16), range (1, 15)") + require.EqualError(t, d.SetCursor(-1, 3), "Invalid position value (-1, 3), range (1, 15)") + require.EqualError(t, d.SetCursor(2, 3), "Invalid position value (2, 3), range (1, 15)") + require.EqualError(t, d.SetCursor(0, -1), "Invalid position value (0, -1), range (1, 15)") + require.EqualError(t, d.SetCursor(0, 16), "Invalid position value (0, 16), range (1, 15)") } func TestHD44780DriverDisplayOn(t *testing.T) { @@ -234,5 +234,5 @@ func TestHD44780DriverCreateChar(t *testing.T) { func TestHD44780DriverCreateCharError(t *testing.T) { d := initTestHD44780Driver() charMap := [8]byte{1, 2, 3, 4, 5, 6, 7, 8} - require.ErrorContains(t, d.CreateChar(8, charMap), "can't set a custom character at a position greater than 7") + require.EqualError(t, d.CreateChar(8, charMap), "can't set a custom character at a position greater than 7") } diff --git a/drivers/gpio/led_driver_test.go b/drivers/gpio/led_driver_test.go index 0fef94b11..56bda0efa 100644 --- a/drivers/gpio/led_driver_test.go +++ b/drivers/gpio/led_driver_test.go @@ -41,16 +41,16 @@ func TestLedDriver(t *testing.T) { } err = d.Command("Toggle")(nil) - require.ErrorContains(t, err.(error), "write error") + require.EqualError(t, err.(error), "write error") err = d.Command("On")(nil) - require.ErrorContains(t, err.(error), "write error") + require.EqualError(t, err.(error), "write error") err = d.Command("Off")(nil) - require.ErrorContains(t, err.(error), "write error") + require.EqualError(t, err.(error), "write error") err = d.Command("Brightness")(map[string]interface{}{"level": 100.0}) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") } func TestLedDriverStart(t *testing.T) { @@ -78,7 +78,7 @@ func TestLedDriverBrightness(t *testing.T) { a.pwmWriteFunc = func(string, byte) error { return errors.New("pwm error") } - require.ErrorContains(t, d.Brightness(150), "pwm error") + require.EqualError(t, d.Brightness(150), "pwm error") } func TestLEDDriverDefaultName(t *testing.T) { diff --git a/drivers/gpio/rgb_led_driver_test.go b/drivers/gpio/rgb_led_driver_test.go index b4b057986..27d9870d1 100644 --- a/drivers/gpio/rgb_led_driver_test.go +++ b/drivers/gpio/rgb_led_driver_test.go @@ -45,16 +45,16 @@ func TestRgbLedDriver(t *testing.T) { } err = d.Command("Toggle")(nil) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") err = d.Command("On")(nil) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") err = d.Command("Off")(nil) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") err = d.Command("SetRGB")(map[string]interface{}{"r": 0xff, "g": 0xff, "b": 0xff}) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") } func TestRgbLedDriverStart(t *testing.T) { @@ -85,7 +85,7 @@ func TestRgbLedDriverSetLevel(t *testing.T) { a.pwmWriteFunc = func(string, byte) error { return errors.New("pwm error") } - require.ErrorContains(t, d.SetLevel("1", 150), "pwm error") + require.EqualError(t, d.SetLevel("1", 150), "pwm error") } func TestRgbLedDriverDefaultName(t *testing.T) { diff --git a/drivers/gpio/servo_driver_test.go b/drivers/gpio/servo_driver_test.go index ee05e932f..8efb5d751 100644 --- a/drivers/gpio/servo_driver_test.go +++ b/drivers/gpio/servo_driver_test.go @@ -32,16 +32,16 @@ func TestServoDriver(t *testing.T) { } err = d.Command("Min")(nil) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") err = d.Command("Center")(nil) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") err = d.Command("Max")(nil) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") err = d.Command("Move")(map[string]interface{}{"angle": 100.0}) - require.ErrorContains(t, err.(error), "pwm error") + require.EqualError(t, err.(error), "pwm error") } func TestServoDriverStart(t *testing.T) { diff --git a/drivers/i2c/ads1x15_driver_1015_test.go b/drivers/i2c/ads1x15_driver_1015_test.go index 5640a27aa..c06018a96 100644 --- a/drivers/i2c/ads1x15_driver_1015_test.go +++ b/drivers/i2c/ads1x15_driver_1015_test.go @@ -253,13 +253,13 @@ func TestADS1015_rawRead(t *testing.T) { // arrange channel := 0 channelOffset := 1 - for name, tt := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { a.written = []byte{} // reset writes of Start() and former test // arrange reads conversion := []uint8{0x00, 0x00} // a conversion is in progress noConversion := []uint8{0x80, 0x00} // no conversion in progress - returnRead := [3][]uint8{conversion, noConversion, tt.input} + returnRead := [3][]uint8{conversion, noConversion, tc.input} numCallsRead := 0 a.i2cReadImpl = func(b []byte) (int, error) { numCallsRead++ @@ -268,15 +268,15 @@ func TestADS1015_rawRead(t *testing.T) { return len(b), nil } // act - got, err := d.rawRead(channel, channelOffset, tt.gain, tt.dataRate) + got, err := d.rawRead(channel, channelOffset, tc.gain, tc.dataRate) // assert require.NoError(t, err) - assert.Equal(t, tt.want, got) + assert.Equal(t, tc.want, got) assert.Equal(t, 3, numCallsRead) assert.Len(t, a.written, 6) assert.Equal(t, uint8(ads1x15PointerConfig), a.written[0]) - assert.Equal(t, tt.wantConfig[0], a.written[1]) // MSByte: OS, MUX, PGA, MODE - assert.Equal(t, tt.wantConfig[1], a.written[2]) // LSByte: DR, COMP_* + assert.Equal(t, tc.wantConfig[0], a.written[1]) // MSByte: OS, MUX, PGA, MODE + assert.Equal(t, tc.wantConfig[1], a.written[2]) // LSByte: DR, COMP_* assert.Equal(t, uint8(ads1x15PointerConfig), a.written[3]) // first check for no conversion assert.Equal(t, uint8(ads1x15PointerConfig), a.written[4]) // second check for no conversion assert.Equal(t, uint8(ads1x15PointerConversion), a.written[5]) diff --git a/drivers/i2c/ads1x15_driver_1115_test.go b/drivers/i2c/ads1x15_driver_1115_test.go index bae7ced2e..ade8579fe 100644 --- a/drivers/i2c/ads1x15_driver_1115_test.go +++ b/drivers/i2c/ads1x15_driver_1115_test.go @@ -253,13 +253,13 @@ func TestADS1115_rawRead(t *testing.T) { // arrange channel := 0 channelOffset := 1 - for name, tt := range tests { + for name, tc := range tests { t.Run(name, func(t *testing.T) { a.written = []byte{} // reset writes of Start() and former test // arrange reads conversion := []uint8{0x00, 0x00} // a conversion is in progress noConversion := []uint8{0x80, 0x00} // no conversion in progress - returnRead := [3][]uint8{conversion, noConversion, tt.input} + returnRead := [3][]uint8{conversion, noConversion, tc.input} numCallsRead := 0 a.i2cReadImpl = func(b []byte) (int, error) { numCallsRead++ @@ -268,15 +268,15 @@ func TestADS1115_rawRead(t *testing.T) { return len(b), nil } // act - got, err := d.rawRead(channel, channelOffset, tt.gain, tt.dataRate) + got, err := d.rawRead(channel, channelOffset, tc.gain, tc.dataRate) // assert require.NoError(t, err) - assert.Equal(t, tt.want, got) + assert.Equal(t, tc.want, got) assert.Equal(t, 3, numCallsRead) assert.Len(t, a.written, 6) assert.Equal(t, uint8(ads1x15PointerConfig), a.written[0]) - assert.Equal(t, tt.wantConfig[0], a.written[1]) // MSByte: OS, MUX, PGA, MODE - assert.Equal(t, tt.wantConfig[1], a.written[2]) // LSByte: DR, COMP_* + assert.Equal(t, tc.wantConfig[0], a.written[1]) // MSByte: OS, MUX, PGA, MODE + assert.Equal(t, tc.wantConfig[1], a.written[2]) // LSByte: DR, COMP_* assert.Equal(t, uint8(ads1x15PointerConfig), a.written[3]) // first check for no conversion assert.Equal(t, uint8(ads1x15PointerConfig), a.written[4]) // second check for no conversion assert.Equal(t, uint8(ads1x15PointerConversion), a.written[5]) diff --git a/drivers/i2c/yl40_driver.go b/drivers/i2c/yl40_driver.go index b740150fe..f4a9addf3 100644 --- a/drivers/i2c/yl40_driver.go +++ b/drivers/i2c/yl40_driver.go @@ -127,20 +127,20 @@ func NewYL40Driver(a Connector, options ...func(Config)) *YL40Driver { } // initialize analog drivers - y.aBri = aio.NewAnalogSensorDriver(pcf, yl40Pins[YL40Bri], y.conf.sensors[YL40Bri].interval) - y.aTemp = aio.NewTemperatureSensorDriver(pcf, yl40Pins[YL40Temp], y.conf.sensors[YL40Temp].interval) - y.aAIN2 = aio.NewAnalogSensorDriver(pcf, yl40Pins[YL40AIN2], y.conf.sensors[YL40AIN2].interval) - y.aPoti = aio.NewAnalogSensorDriver(pcf, yl40Pins[YL40Poti], y.conf.sensors[YL40Poti].interval) - y.aOut = aio.NewAnalogActuatorDriver(pcf, yl40Pins[YL40AOUT]) - - // set input scalers - y.aBri.SetScaler(y.conf.sensors[YL40Bri].scaler) - y.aTemp.SetScaler(y.conf.sensors[YL40Temp].scaler) - y.aAIN2.SetScaler(y.conf.sensors[YL40AIN2].scaler) - y.aPoti.SetScaler(y.conf.sensors[YL40Poti].scaler) - - // set output scaler - y.aOut.SetScaler(y.conf.aOutScaler) + y.aBri = aio.NewAnalogSensorDriver(pcf, yl40Pins[YL40Bri], + aio.WithSensorCyclicRead(y.conf.sensors[YL40Bri].interval), + aio.WithSensorScaler(y.conf.sensors[YL40Bri].scaler)) + y.aTemp = aio.NewTemperatureSensorDriver(pcf, yl40Pins[YL40Temp], + aio.WithSensorCyclicRead(y.conf.sensors[YL40Temp].interval), + aio.WithSensorScaler(y.conf.sensors[YL40Temp].scaler)) + y.aAIN2 = aio.NewAnalogSensorDriver(pcf, yl40Pins[YL40AIN2], + aio.WithSensorCyclicRead(y.conf.sensors[YL40AIN2].interval), + aio.WithSensorScaler(y.conf.sensors[YL40AIN2].scaler)) + y.aPoti = aio.NewAnalogSensorDriver(pcf, yl40Pins[YL40Poti], + aio.WithSensorCyclicRead(y.conf.sensors[YL40Poti].interval), + aio.WithSensorScaler(y.conf.sensors[YL40Poti].scaler)) + y.aOut = aio.NewAnalogActuatorDriver(pcf, yl40Pins[YL40AOUT], + aio.WithActuatorScaler(y.conf.aOutScaler)) return y } diff --git a/examples/beaglebone_led_brightness_with_analog_input.go b/examples/beaglebone_led_brightness_with_analog_input.go index c284c74b7..ddd8598fb 100644 --- a/examples/beaglebone_led_brightness_with_analog_input.go +++ b/examples/beaglebone_led_brightness_with_analog_input.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -17,7 +18,7 @@ import ( func main() { beagleboneAdaptor := beaglebone.NewAdaptor() - sensor := aio.NewAnalogSensorDriver(beagleboneAdaptor, "P9_33") + sensor := aio.NewAnalogSensorDriver(beagleboneAdaptor, "P9_33", aio.WithSensorCyclicRead(500*time.Millisecond)) led := gpio.NewLedDriver(beagleboneAdaptor, "P9_14") work := func() { diff --git a/examples/edison_grove_light_sensor.go b/examples/edison_grove_light_sensor.go index 4497b0647..68f08784f 100644 --- a/examples/edison_grove_light_sensor.go +++ b/examples/edison_grove_light_sensor.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -16,7 +17,7 @@ import ( func main() { board := edison.NewAdaptor() - sensor := aio.NewGroveLightSensorDriver(board, "0") + sensor := aio.NewGroveLightSensorDriver(board, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(sensor.Event("data"), func(data interface{}) { diff --git a/examples/edison_grove_piezo_vibration.go b/examples/edison_grove_piezo_vibration.go index 635d19b83..ea30c7e5b 100644 --- a/examples/edison_grove_piezo_vibration.go +++ b/examples/edison_grove_piezo_vibration.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -16,7 +17,7 @@ import ( func main() { board := edison.NewAdaptor() - sensor := aio.NewGrovePiezoVibrationSensorDriver(board, "0") + sensor := aio.NewGrovePiezoVibrationSensorDriver(board, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(aio.Vibration, func(data interface{}) { diff --git a/examples/edison_grove_rotary_sensor.go b/examples/edison_grove_rotary_sensor.go index 2b50db7e1..6fbde9e86 100644 --- a/examples/edison_grove_rotary_sensor.go +++ b/examples/edison_grove_rotary_sensor.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -16,7 +17,7 @@ import ( func main() { board := edison.NewAdaptor() - sensor := aio.NewGroveRotaryDriver(board, "0") + sensor := aio.NewGroveRotaryDriver(board, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(aio.Data, func(data interface{}) { diff --git a/examples/edison_grove_sound_sensor.go b/examples/edison_grove_sound_sensor.go index 002e7f5c6..794355297 100644 --- a/examples/edison_grove_sound_sensor.go +++ b/examples/edison_grove_sound_sensor.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -16,7 +17,7 @@ import ( func main() { board := edison.NewAdaptor() - sensor := aio.NewGroveSoundSensorDriver(board, "0") + sensor := aio.NewGroveSoundSensorDriver(board, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(aio.Data, func(data interface{}) { diff --git a/examples/edison_grove_temperature_sensor.go b/examples/edison_grove_temperature_sensor.go index 721b93e14..6886f6093 100644 --- a/examples/edison_grove_temperature_sensor.go +++ b/examples/edison_grove_temperature_sensor.go @@ -17,7 +17,7 @@ import ( func main() { board := edison.NewAdaptor() - sensor := aio.NewGroveTemperatureSensorDriver(board, "0") + sensor := aio.NewGroveTemperatureSensorDriver(board, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { gobot.Every(500*time.Millisecond, func() { diff --git a/examples/edison_led_brightness_with_analog_input.go b/examples/edison_led_brightness_with_analog_input.go index edba45aab..9b82fefae 100644 --- a/examples/edison_led_brightness_with_analog_input.go +++ b/examples/edison_led_brightness_with_analog_input.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -17,7 +18,7 @@ import ( func main() { e := edison.NewAdaptor() - sensor := aio.NewAnalogSensorDriver(e, "0") + sensor := aio.NewAnalogSensorDriver(e, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) led := gpio.NewLedDriver(e, "3") work := func() { diff --git a/examples/firmata_grove_sound_sensor.go b/examples/firmata_grove_sound_sensor.go index d0e12ab60..d118c7a52 100644 --- a/examples/firmata_grove_sound_sensor.go +++ b/examples/firmata_grove_sound_sensor.go @@ -9,6 +9,7 @@ package main import ( "fmt" "os" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -17,7 +18,7 @@ import ( func main() { board := firmata.NewAdaptor(os.Args[1]) - sensor := aio.NewGroveSoundSensorDriver(board, "3") + sensor := aio.NewGroveSoundSensorDriver(board, "3", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(aio.Data, func(data interface{}) { diff --git a/examples/firmata_integration.go b/examples/firmata_integration.go index d4006ff50..eafbddbc9 100644 --- a/examples/firmata_integration.go +++ b/examples/firmata_integration.go @@ -29,7 +29,7 @@ func main() { led1 := gpio.NewLedDriver(firmataAdaptor, "3") led2 := gpio.NewLedDriver(firmataAdaptor, "4") button := gpio.NewButtonDriver(firmataAdaptor, "2") - sensor := aio.NewAnalogSensorDriver(firmataAdaptor, "0") + sensor := aio.NewAnalogSensorDriver(firmataAdaptor, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { gobot.Every(1*time.Second, func() { diff --git a/examples/firmata_led_brightness_with_analog_input.go b/examples/firmata_led_brightness_with_analog_input.go index a9c6b4c34..3c86ae375 100644 --- a/examples/firmata_led_brightness_with_analog_input.go +++ b/examples/firmata_led_brightness_with_analog_input.go @@ -16,6 +16,7 @@ package main import ( "fmt" "os" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -25,7 +26,7 @@ import ( func main() { firmataAdaptor := firmata.NewAdaptor(os.Args[1]) - sensor := aio.NewAnalogSensorDriver(firmataAdaptor, "0") + sensor := aio.NewAnalogSensorDriver(firmataAdaptor, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) led := gpio.NewLedDriver(firmataAdaptor, "3") work := func() { diff --git a/examples/gopigo3_grove_light_sensor.go b/examples/gopigo3_grove_light_sensor.go index e9472e345..8454f96fc 100644 --- a/examples/gopigo3_grove_light_sensor.go +++ b/examples/gopigo3_grove_light_sensor.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -18,7 +19,7 @@ import ( func main() { raspiAdaptor := raspi.NewAdaptor() gpg3 := gopigo3.NewDriver(raspiAdaptor) - sensor := aio.NewGroveLightSensorDriver(gpg3, "AD_1_1") + sensor := aio.NewGroveLightSensorDriver(gpg3, "AD_1_1", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(sensor.Event("data"), func(data interface{}) { diff --git a/examples/joule_grove_rotary_sensor.go b/examples/joule_grove_rotary_sensor.go index 84a16c2f9..bd1e7cbc6 100644 --- a/examples/joule_grove_rotary_sensor.go +++ b/examples/joule_grove_rotary_sensor.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -18,7 +19,7 @@ import ( func main() { board := joule.NewAdaptor() ads1015 := i2c.NewADS1015Driver(board) - sensor := aio.NewGroveRotaryDriver(ads1015, "0") + sensor := aio.NewGroveRotaryDriver(ads1015, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(aio.Data, func(data interface{}) { diff --git a/examples/joule_led_brightness_with_analog_input.go b/examples/joule_led_brightness_with_analog_input.go index 3bef27334..167e3210a 100644 --- a/examples/joule_led_brightness_with_analog_input.go +++ b/examples/joule_led_brightness_with_analog_input.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -19,7 +20,7 @@ import ( func main() { e := joule.NewAdaptor() ads1015 := i2c.NewADS1015Driver(e) - sensor := aio.NewAnalogSensorDriver(ads1015, "0") + sensor := aio.NewAnalogSensorDriver(ads1015, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) led := gpio.NewLedDriver(e, "J12_26") work := func() { diff --git a/examples/raspi_grove_pi_rotary.go b/examples/raspi_grove_pi_rotary.go index c73ba961f..afecd5eff 100644 --- a/examples/raspi_grove_pi_rotary.go +++ b/examples/raspi_grove_pi_rotary.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -18,7 +19,7 @@ import ( func main() { board := raspi.NewAdaptor() gp := i2c.NewGrovePiDriver(board) - sensor := aio.NewGroveRotaryDriver(gp, "A1") + sensor := aio.NewGroveRotaryDriver(gp, "A1", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(aio.Data, func(data interface{}) { diff --git a/examples/raspi_grove_rotary_sensor.go b/examples/raspi_grove_rotary_sensor.go index 438798901..f9960ce7f 100644 --- a/examples/raspi_grove_rotary_sensor.go +++ b/examples/raspi_grove_rotary_sensor.go @@ -8,6 +8,7 @@ package main import ( "fmt" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -18,7 +19,7 @@ import ( func main() { board := raspi.NewAdaptor() ads1015 := i2c.NewADS1015Driver(board) - sensor := aio.NewGroveRotaryDriver(ads1015, "0") + sensor := aio.NewGroveRotaryDriver(ads1015, "0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(aio.Data, func(data interface{}) { diff --git a/examples/wifi_firmata_analog_input.go b/examples/wifi_firmata_analog_input.go index 912f5d6dc..924d81e35 100644 --- a/examples/wifi_firmata_analog_input.go +++ b/examples/wifi_firmata_analog_input.go @@ -9,6 +9,7 @@ package main import ( "fmt" "os" + "time" "gobot.io/x/gobot/v2" "gobot.io/x/gobot/v2/drivers/aio" @@ -17,7 +18,7 @@ import ( func main() { firmataAdaptor := firmata.NewTCPAdaptor(os.Args[1]) - sensor := aio.NewAnalogSensorDriver(firmataAdaptor, "A0") + sensor := aio.NewAnalogSensorDriver(firmataAdaptor, "A0", aio.WithSensorCyclicRead(500*time.Millisecond)) work := func() { sensor.On(aio.Data, func(data interface{}) {