From 0e25f29a1b613573c3f9df4ce016084264fc7d97 Mon Sep 17 00:00:00 2001 From: deadprogram Date: Tue, 30 Aug 2016 13:27:50 +0200 Subject: [PATCH] core: Refactor events to use channels all the way down. Allows 'metal' development using Gobot libs. Signed-off-by: deadprogram --- event.go | 36 ++---- eventer.go | 108 +++++++++++++++--- eventer_test.go | 10 +- examples/metal_button.go | 30 +++++ examples_test.go | 24 ---- platforms/gpio/analog_sensor_driver.go | 4 +- platforms/gpio/analog_sensor_driver_test.go | 6 +- platforms/gpio/button_driver.go | 9 +- platforms/gpio/button_driver_test.go | 8 +- platforms/gpio/grove_drivers.go | 10 +- .../gpio/grove_temperature_sensor_driver.go | 4 +- platforms/gpio/makey_button_driver.go | 6 +- utils.go | 27 ----- utils_test.go | 60 ---------- 14 files changed, 153 insertions(+), 189 deletions(-) create mode 100644 examples/metal_button.go diff --git a/event.go b/event.go index 11f7be557..dee8aa646 100644 --- a/event.go +++ b/event.go @@ -1,35 +1,13 @@ package gobot -import "sync" - -type callback struct { - f func(interface{}) - once bool -} - -// Event executes the list of Callbacks when Chan is written to. +// Event represents when something asyncronous happens in a Driver +// or Adaptor type Event struct { - sync.Mutex - Callbacks []callback + Name string + Data interface{} } -// NewEvent returns a new Event which is now listening for data. -func NewEvent() *Event { - return &Event{} -} - -// Write writes data to the Event, it will not block and will not buffer if there -// are no active subscribers to the Event. -func (e *Event) Write(data interface{}) { - e.Lock() - defer e.Unlock() - - tmp := []callback{} - for _, cb := range e.Callbacks { - go cb.f(data) - if !cb.once { - tmp = append(tmp, cb) - } - } - e.Callbacks = tmp +// NewEvent returns a new Event and its associated data. +func NewEvent(name string, data interface{}) *Event { + return &Event{Name: name, Data: data} } diff --git a/eventer.go b/eventer.go index 3b19a6950..651fe8b72 100644 --- a/eventer.go +++ b/eventer.go @@ -1,36 +1,112 @@ package gobot +type eventChannel chan *Event + type eventer struct { - events map[string]*Event + // map of valid Event names + eventnames map[string]string + + // new events get put in to the event channel + in eventChannel + + // slice of out channels used by subscribers + outs []eventChannel } -// Eventer is the interface which describes behaviour for a Driver or Adaptor -// which uses events. +// Eventer is the interface which describes how a Driver or Adaptor +// handles events. type Eventer interface { - // Events returns the Event map. - Events() (events map[string]*Event) - // Event returns an Event by name. Returns nil if the Event is not found. - Event(name string) (event *Event) - // AddEvent adds a new Event given a name. + // Events returns the map of valid Event names. + Events() (eventnames map[string]string) + // Event returns the map of valid Event names. + Event(name string) string + // AddEvent registers a new Event name. AddEvent(name string) + // Publish new events to anyone listening + Publish(name string, data interface{}) + // Subscribe to any events from this eventer + Subscribe() (events eventChannel) } // NewEventer returns a new Eventer. func NewEventer() Eventer { - return &eventer{ - events: make(map[string]*Event), + evtr := &eventer{ + eventnames: make(map[string]string), + in: make(eventChannel, 1), + outs: make([]eventChannel, 1), } + + // goroutine to cascade in events to all out event channels + go func() { + for { + select { + case evt := <-evtr.in: + for _, out := range evtr.outs[1:] { + out <- evt + } + } + } + }() + + return evtr } -func (e *eventer) Events() map[string]*Event { - return e.events +func (e *eventer) Events() map[string]string { + return e.eventnames } -func (e *eventer) Event(name string) (event *Event) { - event, _ = e.events[name] - return +func (e *eventer) Event(name string) string { + return e.eventnames[name] } func (e *eventer) AddEvent(name string) { - e.events[name] = NewEvent() + e.eventnames[name] = name +} + +func (e *eventer) Publish(name string, data interface{}) { + evt := NewEvent(name, data) + e.in <- evt +} + +func (e *eventer) Subscribe() eventChannel { + out := make(eventChannel) + e.outs = append(e.outs, out) + return out +} + +// On executes f when e is Published to. Returns ErrUnknownEvent if Event +// does not exist. +func (e *eventer) On(n string, f func(s interface{})) (err error) { + out := e.Subscribe() + go func() { + for { + select { + case evt := <-out: + if evt.Name == n { + f(evt.Data) + } + } + } + }() + + return +} + +// Once is similar to On except that it only executes f one time. Returns +//ErrUnknownEvent if Event does not exist. +func (e *eventer) Once(n string, f func(s interface{})) (err error) { + out := e.Subscribe() + go func() { + for { + select { + case evt := <-out: + if evt.Name == n { + f(evt.Data) + break + } + } + } + }() + + return } diff --git a/eventer_test.go b/eventer_test.go index 9052eccde..26d2ce12f 100644 --- a/eventer_test.go +++ b/eventer_test.go @@ -2,8 +2,6 @@ package gobot import ( "testing" - - "github.com/hybridgroup/gobot/gobottest" ) func TestEventer(t *testing.T) { @@ -11,12 +9,6 @@ func TestEventer(t *testing.T) { e.AddEvent("test") if _, ok := e.Events()["test"]; !ok { - t.Errorf("Could not add event to list of Events") + t.Errorf("Could not add event to list of Event names") } - - event := e.Event("test") - gobottest.Refute(t, event, nil) - - event = e.Event("booyeah") - gobottest.Assert(t, event, (*Event)(nil)) } diff --git a/examples/metal_button.go b/examples/metal_button.go new file mode 100644 index 000000000..15cce7107 --- /dev/null +++ b/examples/metal_button.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "github.com/hybridgroup/gobot/platforms/gpio" + "github.com/hybridgroup/gobot/platforms/intel-iot/edison" +) + +func main() { + e := edison.NewEdisonAdaptor("edison") + led := gpio.NewLedDriver(e, "led", "13") + button := gpio.NewButtonDriver(e, "button", "5") + + e.Connect() + led.Start() + button.Start() + + led.Off() + + buttonEvents := button.Subscribe() + for { + select { + case event := <-buttonEvents: + fmt.Println("Event:", event.Name, event.Data) + if event.Name == "push" { + led.Toggle() + } + } + } +} diff --git a/examples_test.go b/examples_test.go index b1cd5be5f..e736ab990 100644 --- a/examples_test.go +++ b/examples_test.go @@ -21,30 +21,6 @@ func ExampleAfter() { }) } -func ExamplePublish() { - e := gobot.NewEvent() - gobot.Publish(e, 100) -} - -func ExampleOn() { - e := gobot.NewEvent() - gobot.On(e, func(s interface{}) { - fmt.Println(s) - }) - gobot.Publish(e, 100) - gobot.Publish(e, 200) -} - -func ExampleOnce() { - e := gobot.NewEvent() - gobot.Once(e, func(s interface{}) { - fmt.Println(s) - fmt.Println("I will no longer respond to events") - }) - gobot.Publish(e, 100) - gobot.Publish(e, 200) -} - func ExampleRand() { i := gobot.Rand(100) fmt.Printf("%v is > 0 && < 100", i) diff --git a/platforms/gpio/analog_sensor_driver.go b/platforms/gpio/analog_sensor_driver.go index 9da5d6a4e..ad9d20af7 100644 --- a/platforms/gpio/analog_sensor_driver.go +++ b/platforms/gpio/analog_sensor_driver.go @@ -63,10 +63,10 @@ func (a *AnalogSensorDriver) Start() (errs []error) { for { newValue, err := a.Read() if err != nil { - gobot.Publish(a.Event(Error), err) + a.Publish(a.Event(Error), err) } else if newValue != value && newValue != -1 { value = newValue - gobot.Publish(a.Event(Data), value) + a.Publish(a.Event(Data), value) } select { case <-time.After(a.interval): diff --git a/platforms/gpio/analog_sensor_driver_test.go b/platforms/gpio/analog_sensor_driver_test.go index 138a88603..844c3a138 100644 --- a/platforms/gpio/analog_sensor_driver_test.go +++ b/platforms/gpio/analog_sensor_driver_test.go @@ -39,7 +39,7 @@ func TestAnalogSensorDriverStart(t *testing.T) { gobottest.Assert(t, len(d.Start()), 0) // data was received - gobot.Once(d.Event(Data), func(data interface{}) { + d.Once(d.Event(Data), func(data interface{}) { gobottest.Assert(t, data.(int), 100) sem <- true }) @@ -56,7 +56,7 @@ func TestAnalogSensorDriverStart(t *testing.T) { } // read error - gobot.Once(d.Event(Error), func(data interface{}) { + d.Once(d.Event(Error), func(data interface{}) { gobottest.Assert(t, data.(error).Error(), "read error") sem <- true }) @@ -73,7 +73,7 @@ func TestAnalogSensorDriverStart(t *testing.T) { } // send a halt message - gobot.Once(d.Event(Data), func(data interface{}) { + d.Once(d.Event(Data), func(data interface{}) { sem <- true }) diff --git a/platforms/gpio/button_driver.go b/platforms/gpio/button_driver.go index c471e4565..0bde97eeb 100644 --- a/platforms/gpio/button_driver.go +++ b/platforms/gpio/button_driver.go @@ -1,9 +1,8 @@ package gpio import ( - "time" - "github.com/hybridgroup/gobot" + "time" ) var _ gobot.Driver = (*ButtonDriver)(nil) @@ -58,7 +57,7 @@ func (b *ButtonDriver) Start() (errs []error) { for { newValue, err := b.connection.DigitalRead(b.Pin()) if err != nil { - gobot.Publish(b.Event(Error), err) + b.Publish(b.Event(Error), err) } else if newValue != state && newValue != -1 { state = newValue b.update(newValue) @@ -91,9 +90,9 @@ func (b *ButtonDriver) Connection() gobot.Connection { return b.connection.(gobo func (b *ButtonDriver) update(newValue int) { if newValue == 1 { b.Active = true - gobot.Publish(b.Event(Push), newValue) + b.Publish(b.Event(Push), newValue) } else { b.Active = false - gobot.Publish(b.Event(Release), newValue) + b.Publish(b.Event(Release), newValue) } } diff --git a/platforms/gpio/button_driver_test.go b/platforms/gpio/button_driver_test.go index f40a5dbb9..c86b3acee 100644 --- a/platforms/gpio/button_driver_test.go +++ b/platforms/gpio/button_driver_test.go @@ -42,7 +42,7 @@ func TestButtonDriverStart(t *testing.T) { return } - gobot.Once(d.Event(Push), func(data interface{}) { + d.Once(d.Event(Push), func(data interface{}) { gobottest.Assert(t, d.Active, true) sem <- true }) @@ -58,7 +58,7 @@ func TestButtonDriverStart(t *testing.T) { return } - gobot.Once(d.Event(Release), func(data interface{}) { + d.Once(d.Event(Release), func(data interface{}) { gobottest.Assert(t, d.Active, false) sem <- true }) @@ -74,7 +74,7 @@ func TestButtonDriverStart(t *testing.T) { return } - gobot.Once(d.Event(Error), func(data interface{}) { + d.Once(d.Event(Error), func(data interface{}) { sem <- true }) @@ -89,7 +89,7 @@ func TestButtonDriverStart(t *testing.T) { return } - gobot.Once(d.Event(Push), func(data interface{}) { + d.Once(d.Event(Push), func(data interface{}) { sem <- true }) diff --git a/platforms/gpio/grove_drivers.go b/platforms/gpio/grove_drivers.go index e7c41d614..276e99684 100644 --- a/platforms/gpio/grove_drivers.go +++ b/platforms/gpio/grove_drivers.go @@ -111,11 +111,11 @@ func NewGrovePiezoVibrationSensorDriver(a AnalogReader, name string, pin string, sensor.AddEvent(Vibration) - gobot.On(sensor.Event(Data), func(data interface{}) { - if data.(int) > 1000 { - gobot.Publish(sensor.Event(Vibration), data) - } - }) + // sensor.On(sensor.Event(Data), func(data interface{}) { + // if data.(int) > 1000 { + // sensor.Publish(sensor.Event(Vibration), data) + // } + // }) return sensor } diff --git a/platforms/gpio/grove_temperature_sensor_driver.go b/platforms/gpio/grove_temperature_sensor_driver.go index 29201e40d..a7d2c2ba1 100644 --- a/platforms/gpio/grove_temperature_sensor_driver.go +++ b/platforms/gpio/grove_temperature_sensor_driver.go @@ -64,10 +64,10 @@ func (a *GroveTemperatureSensorDriver) Start() (errs []error) { newValue := 1/(math.Log(resistance/10000.0)/thermistor+1/298.15) - 273.15 if err != nil { - gobot.Publish(a.Event(Error), err) + a.Publish(a.Event(Error), err) } else if newValue != a.temperature && newValue != -1 { a.temperature = newValue - gobot.Publish(a.Event(Data), a.temperature) + a.Publish(a.Event(Data), a.temperature) } select { case <-time.After(a.interval): diff --git a/platforms/gpio/makey_button_driver.go b/platforms/gpio/makey_button_driver.go index 0c1193516..3ed6e2125 100644 --- a/platforms/gpio/makey_button_driver.go +++ b/platforms/gpio/makey_button_driver.go @@ -67,15 +67,15 @@ func (b *MakeyButtonDriver) Start() (errs []error) { for { newValue, err := b.connection.DigitalRead(b.Pin()) if err != nil { - gobot.Publish(b.Event(Error), err) + b.Publish(b.Event(Error), err) } else if newValue != state && newValue != -1 { state = newValue if newValue == 0 { b.Active = true - gobot.Publish(b.Event(Push), newValue) + b.Publish(b.Event(Push), newValue) } else { b.Active = false - gobot.Publish(b.Event(Release), newValue) + b.Publish(b.Event(Release), newValue) } } select { diff --git a/utils.go b/utils.go index be573cf43..31ee64656 100644 --- a/utils.go +++ b/utils.go @@ -51,33 +51,6 @@ func After(t time.Duration, f func()) { time.AfterFunc(t, f) } -// Publish emits val to all subscribers of e. Returns ErrUnknownEvent if Event -// does not exist. -func Publish(e *Event, val interface{}) (err error) { - if err = eventError(e); err == nil { - e.Write(val) - } - return -} - -// On executes f when e is Published to. Returns ErrUnknownEvent if Event -// does not exist. -func On(e *Event, f func(s interface{})) (err error) { - if err = eventError(e); err == nil { - e.Callbacks = append(e.Callbacks, callback{f, false}) - } - return -} - -// Once is similar to On except that it only executes f one time. Returns -//ErrUnknownEvent if Event does not exist. -func Once(e *Event, f func(s interface{})) (err error) { - if err = eventError(e); err == nil { - e.Callbacks = append(e.Callbacks, callback{f, true}) - } - return -} - // Rand returns a positive random int up to max func Rand(max int) int { i, _ := rand.Int(rand.Reader, big.NewInt(int64(max))) diff --git a/utils_test.go b/utils_test.go index 245e98291..36d1dc9fd 100644 --- a/utils_test.go +++ b/utils_test.go @@ -46,66 +46,6 @@ func TestAfter(t *testing.T) { gobottest.Assert(t, i, 1) } -func TestPublish(t *testing.T) { - c := make(chan interface{}, 1) - - cb := callback{ - f: func(val interface{}) { - c <- val - }, - } - - e := &Event{Callbacks: []callback{cb}} - Publish(e, 1) - <-time.After(10 * time.Millisecond) - Publish(e, 2) - Publish(e, 3) - Publish(e, 4) - i := <-c - gobottest.Assert(t, i, 1) - - var e1 = (*Event)(nil) - gobottest.Assert(t, Publish(e1, 4), ErrUnknownEvent) -} - -func TestOn(t *testing.T) { - var i int - e := NewEvent() - On(e, func(data interface{}) { - i = data.(int) - }) - Publish(e, 10) - <-time.After(1 * time.Millisecond) - gobottest.Assert(t, i, 10) - - var e1 = (*Event)(nil) - err := On(e1, func(data interface{}) { - i = data.(int) - }) - gobottest.Assert(t, err, ErrUnknownEvent) -} -func TestOnce(t *testing.T) { - i := 0 - e := NewEvent() - Once(e, func(data interface{}) { - i += data.(int) - }) - On(e, func(data interface{}) { - i += data.(int) - }) - Publish(e, 10) - <-time.After(1 * time.Millisecond) - Publish(e, 10) - <-time.After(1 * time.Millisecond) - gobottest.Assert(t, i, 30) - - var e1 = (*Event)(nil) - err := Once(e1, func(data interface{}) { - i = data.(int) - }) - gobottest.Assert(t, err, ErrUnknownEvent) -} - func TestFromScale(t *testing.T) { gobottest.Assert(t, FromScale(5, 0, 10), 0.5) }