1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-04-24 13:48:49 +08:00

core: Refactor events to use channels all the way down. Allows 'metal' development using Gobot libs.

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram 2016-08-30 13:27:50 +02:00
parent 22aeb498f4
commit 0e25f29a1b
14 changed files with 153 additions and 189 deletions

View File

@ -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}
}

View File

@ -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
}

View File

@ -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))
}

30
examples/metal_button.go Normal file
View File

@ -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()
}
}
}
}

View File

@ -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)

View File

@ -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):

View File

@ -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
})

View File

@ -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)
}
}

View File

@ -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
})

View File

@ -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
}

View File

@ -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):

View File

@ -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 {

View File

@ -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)))

View File

@ -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)
}