From 8851344da1c8300b16f04e637c6d12d8f1b83d7d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 20 Dec 2016 12:06:45 +0100 Subject: [PATCH] core: some WIP on using digitalread and analogread event subscriptions for all gpio drivers Signed-off-by: deadprogram --- drivers/gpio/analog_sensor_driver.go | 27 ++++----- drivers/gpio/gpio.go | 14 +++++ drivers/gpio/grove_drivers.go | 8 +-- eventer.go | 20 +++---- examples/beaglebone_analog.go | 33 +++++++++++ platforms/beaglebone/beaglebone_adaptor.go | 69 ++++++++++++++++++++++ 6 files changed, 140 insertions(+), 31 deletions(-) create mode 100644 examples/beaglebone_analog.go diff --git a/drivers/gpio/analog_sensor_driver.go b/drivers/gpio/analog_sensor_driver.go index 5b0fc483..2a4c1c11 100644 --- a/drivers/gpio/analog_sensor_driver.go +++ b/drivers/gpio/analog_sensor_driver.go @@ -12,7 +12,7 @@ type AnalogSensorDriver struct { pin string halt chan bool interval time.Duration - connection AnalogReader + connection AnalogReadEventer gobot.Eventer gobot.Commander } @@ -25,7 +25,7 @@ type AnalogSensorDriver struct { // // Adds the following API Commands: // "Read" - See AnalogSensor.Read -func NewAnalogSensorDriver(a AnalogReader, pin string, v ...time.Duration) *AnalogSensorDriver { +func NewAnalogSensorDriver(a AnalogReadEventer, pin string, v ...time.Duration) *AnalogSensorDriver { d := &AnalogSensorDriver{ name: "AnalogSensor", connection: a, @@ -56,25 +56,18 @@ func NewAnalogSensorDriver(a AnalogReader, pin string, v ...time.Duration) *Anal // Data int - 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() (err error) { - value := 0 - go func() { - timer := time.NewTimer(a.interval) - timer.Stop() - for { - newValue, err := a.Read() - if err != nil { - a.Publish(a.Event(Error), err) - } else if newValue != value && newValue != -1 { - value = newValue - a.Publish(a.Event(Data), value) - } + evts := a.connection.SubscribeAnalogRead(a.Pin()) - timer.Reset(a.interval) + go func() { + analogread := "analogread-" + a.Pin() + for { select { - case <-timer.C: case <-a.halt: - timer.Stop() return + case evt := <-evts: + if evt.Name == analogread { + a.Publish(Data, evt.Data) + } } } }() diff --git a/drivers/gpio/gpio.go b/drivers/gpio/gpio.go index b9e5e4bd..c137237c 100644 --- a/drivers/gpio/gpio.go +++ b/drivers/gpio/gpio.go @@ -73,3 +73,17 @@ type DigitalReader interface { gobot.Adaptor DigitalRead(string) (val int, err error) } + +// DigitalReadEventer interface represents an Adaptor which has SubscribeDigitalRead capabilities +type DigitalReadEventer interface { + gobot.Eventer + DigitalReader + SubscribeDigitalRead(string) (gobot.EventChannel) +} + +// AnalogReadEventer interface represents an Adaptor which has SubscribeAnalogRead capabilities +type AnalogReadEventer interface { + gobot.Eventer + AnalogReader + SubscribeAnalogRead(string) (gobot.EventChannel) +} diff --git a/drivers/gpio/grove_drivers.go b/drivers/gpio/grove_drivers.go index 94c2d2c9..f6ec5e4c 100644 --- a/drivers/gpio/grove_drivers.go +++ b/drivers/gpio/grove_drivers.go @@ -34,7 +34,7 @@ type GroveRotaryDriver struct { // // Adds the following API Commands: // "Read" - See AnalogSensor.Read -func NewGroveRotaryDriver(a AnalogReader, pin string, v ...time.Duration) *GroveRotaryDriver { +func NewGroveRotaryDriver(a AnalogReadEventer, pin string, v ...time.Duration) *GroveRotaryDriver { return &GroveRotaryDriver{ AnalogSensorDriver: NewAnalogSensorDriver(a, pin, v...), } @@ -72,7 +72,7 @@ type GroveLightSensorDriver struct { // // Adds the following API Commands: // "Read" - See AnalogSensor.Read -func NewGroveLightSensorDriver(a AnalogReader, pin string, v ...time.Duration) *GroveLightSensorDriver { +func NewGroveLightSensorDriver(a AnalogReadEventer, pin string, v ...time.Duration) *GroveLightSensorDriver { return &GroveLightSensorDriver{ AnalogSensorDriver: NewAnalogSensorDriver(a, pin, v...), } @@ -92,7 +92,7 @@ type GrovePiezoVibrationSensorDriver struct { // // Adds the following API Commands: // "Read" - See AnalogSensor.Read -func NewGrovePiezoVibrationSensorDriver(a AnalogReader, pin string, v ...time.Duration) *GrovePiezoVibrationSensorDriver { +func NewGrovePiezoVibrationSensorDriver(a AnalogReadEventer, pin string, v ...time.Duration) *GrovePiezoVibrationSensorDriver { sensor := &GrovePiezoVibrationSensorDriver{ AnalogSensorDriver: NewAnalogSensorDriver(a, pin, v...), } @@ -152,7 +152,7 @@ type GroveSoundSensorDriver struct { // // Adds the following API Commands: // "Read" - See AnalogSensor.Read -func NewGroveSoundSensorDriver(a AnalogReader, pin string, v ...time.Duration) *GroveSoundSensorDriver { +func NewGroveSoundSensorDriver(a AnalogReadEventer, pin string, v ...time.Duration) *GroveSoundSensorDriver { return &GroveSoundSensorDriver{ AnalogSensorDriver: NewAnalogSensorDriver(a, pin, v...), } diff --git a/eventer.go b/eventer.go index bf54cfd4..c1e20529 100644 --- a/eventer.go +++ b/eventer.go @@ -2,17 +2,17 @@ package gobot import "sync" -type eventChannel chan *Event +type EventChannel chan *Event type eventer struct { // map of valid Event names eventnames map[string]string // new events get put in to the event channel - in eventChannel + in EventChannel // map of out channels used by subscribers - outs map[eventChannel]eventChannel + outs map[EventChannel]EventChannel // mutex to protect the eventChannel map eventsMutex sync.Mutex @@ -38,10 +38,10 @@ type Eventer interface { Publish(name string, data interface{}) // Subscribe to events - Subscribe() (events eventChannel) + Subscribe() (events EventChannel) // Unsubscribe from an event channel - Unsubscribe(events eventChannel) + Unsubscribe(events EventChannel) // Event handler On(name string, f func(s interface{})) (err error) @@ -54,8 +54,8 @@ type Eventer interface { func NewEventer() Eventer { evtr := &eventer{ eventnames: make(map[string]string), - in: make(eventChannel, 1), - outs: make(map[eventChannel]eventChannel), + in: make(EventChannel, 1), + outs: make(map[EventChannel]EventChannel), } // goroutine to cascade "in" events to all "out" event channels @@ -103,16 +103,16 @@ func (e *eventer) Publish(name string, data interface{}) { } // Subscribe to any events from this eventer -func (e *eventer) Subscribe() eventChannel { +func (e *eventer) Subscribe() EventChannel { e.eventsMutex.Lock() defer e.eventsMutex.Unlock() - out := make(eventChannel) + out := make(EventChannel) e.outs[out] = out return out } // Unsubscribe from the event channel -func (e *eventer) Unsubscribe(events eventChannel) { +func (e *eventer) Unsubscribe(events EventChannel) { e.eventsMutex.Lock() defer e.eventsMutex.Unlock() delete(e.outs, events) diff --git a/examples/beaglebone_analog.go b/examples/beaglebone_analog.go new file mode 100644 index 00000000..148ba32d --- /dev/null +++ b/examples/beaglebone_analog.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/drivers/gpio" + "gobot.io/x/gobot/platforms/beaglebone" +) + +func main() { + a := beaglebone.NewAdaptor() + sensor := gpio.NewAnalogSensorDriver(a, "P9_39") + + work := func() { + sensor.On(gpio.Data, func(data interface{}) { + voltage := (float64(data.(int)) * 1.8) / 1024 // BBB uses 1.8V + tempC := (voltage - 0.5) * 100 + tempF := (tempC * 9 / 5) + 32 + + fmt.Printf("%.2f°C\n", tempC) + fmt.Printf("%.2f°F\n", tempF) + }) + } + + robot := gobot.NewRobot("sensorBot", + []gobot.Connection{a}, + []gobot.Device{sensor}, + work, + ) + + robot.Start() +} diff --git a/platforms/beaglebone/beaglebone_adaptor.go b/platforms/beaglebone/beaglebone_adaptor.go index 675ccbf7..40164a25 100644 --- a/platforms/beaglebone/beaglebone_adaptor.go +++ b/platforms/beaglebone/beaglebone_adaptor.go @@ -9,10 +9,12 @@ import ( "path/filepath" "strconv" "strings" + "time" multierror "github.com/hashicorp/go-multierror" "gobot.io/x/gobot" "gobot.io/x/gobot/sysfs" + "gobot.io/x/gobot/drivers/gpio" ) var glob = func(pattern string) (matches []string, err error) { @@ -31,6 +33,9 @@ type Adaptor struct { analogPath string analogPinMap map[string]string slots string + interval time.Duration + halt chan bool + gobot.Eventer } // NewAdaptor returns a new Beaglebone Adaptor @@ -39,6 +44,8 @@ func NewAdaptor() *Adaptor { name: "Beaglebone", digitalPins: make([]sysfs.DigitalPin, 120), pwmPins: make(map[string]*pwmPin), + interval: 10 * time.Millisecond, + halt: make(chan bool), } b.setSlots() @@ -195,6 +202,68 @@ func (b *Adaptor) AnalogRead(pin string) (val int, err error) { return } +// SubscribeDigitalRead starts reading from the specified pin, +// and publishes events on the returned event channel when changes occur +func (b *Adaptor) SubscribeDigitalRead(pin string) (gobot.EventChannel) { + // TODO: replace with epoll based implementation + b.AddEvent("digitalread-" + pin) + value := 0 + go func() { + timer := time.NewTimer(b.interval) + timer.Stop() + for { + newValue, err := b.DigitalRead(pin) + if err != nil { + b.Publish(b.Event(gpio.Error), err) + } else if newValue != value && newValue != -1 { + value = newValue + b.Publish(b.Event("digitalread-" + pin), value) + } + + timer.Reset(b.interval) + select { + case <-timer.C: + case <-b.halt: + timer.Stop() + return + } + } + }() + + return b.Subscribe() +} + +// SubscribeAnalogRead starts reading from the specified pin, +// and publishes events on the returned event channel when changes occur +func (b *Adaptor) SubscribeAnalogRead(pin string) (gobot.EventChannel) { + // TODO: replace with epoll based implementation + b.AddEvent("analogread-" + pin) + value := 0 + go func() { + timer := time.NewTimer(b.interval) + timer.Stop() + for { + newValue, err := b.AnalogRead(pin) + if err != nil { + b.Publish(b.Event(gpio.Error), err) + } else if newValue != value && newValue != -1 { + value = newValue + b.Publish(b.Event("analogread-" + pin), value) + } + + timer.Reset(b.interval) + select { + case <-timer.C: + case <-b.halt: + timer.Stop() + return + } + } + }() + + return b.Subscribe() +} + // I2cStart starts a i2c device in specified address on i2c bus /dev/i2c-1 func (b *Adaptor) I2cStart(address int) (err error) { if b.i2cDevice == nil {