From 7c63f215934a2a49458b8896033fb89aedfd81df Mon Sep 17 00:00:00 2001 From: Adrian Zankich Date: Thu, 20 Nov 2014 18:00:32 -0800 Subject: [PATCH] Completely overhaul driver and adaptor interfaces --- adaptor.go | 85 +------------------------- adaptor_test.go | 18 ------ commander.go | 39 ++++++++++++ connection.go | 2 +- device.go | 2 +- driver.go | 158 +----------------------------------------------- driver_test.go | 57 ----------------- eventer.go | 33 ++++++++++ gobot.go | 37 +++--------- loopback.go | 20 ++++++ pinger.go | 40 ++++++++++++ robot.go | 21 ++----- test_helper.go | 113 ++++++++-------------------------- utils.go | 56 +++++++++++++++-- 14 files changed, 225 insertions(+), 456 deletions(-) delete mode 100644 adaptor_test.go create mode 100644 commander.go delete mode 100644 driver_test.go create mode 100644 eventer.go create mode 100644 loopback.go create mode 100644 pinger.go diff --git a/adaptor.go b/adaptor.go index 60c14e47f..d944265ad 100644 --- a/adaptor.go +++ b/adaptor.go @@ -1,89 +1,10 @@ package gobot -import "fmt" - -type Adaptor struct { - name string - port string - connected bool - adaptorType string -} - -// AdaptorInterface defines behaviour expected for a Gobot Adaptor -type AdaptorInterface interface { +type Adaptor interface { Finalize() []error Connect() []error - Port() string Name() string - Type() string - Connected() bool - SetConnected(bool) - SetName(string) - SetPort(string) + Port() string + String() string ToJSON() *JSONConnection } - -// NewAdaptor returns a new Gobot Adaptor -func NewAdaptor(name string, adaptorType string, v ...interface{}) *Adaptor { - if name == "" { - name = fmt.Sprintf("%X", Rand(int(^uint(0)>>1))) - } - - a := &Adaptor{ - adaptorType: adaptorType, - name: name, - port: "", - } - - for i := range v { - switch v[i].(type) { - case string: - a.port = v[i].(string) - } - } - - return a -} - -// Port returns adaptor port -func (a *Adaptor) Port() string { - return a.port -} - -// SetPort sets adaptor port -func (a *Adaptor) SetPort(s string) { - a.port = s -} - -// Name returns adaptor name -func (a *Adaptor) Name() string { - return a.name -} - -// SetName sets adaptor name -func (a *Adaptor) SetName(s string) { - a.name = s -} - -// Type returns adaptor type -func (a *Adaptor) Type() string { - return a.adaptorType -} - -// Connected returns true if adaptor is connected -func (a *Adaptor) Connected() bool { - return a.connected -} - -// SetConnected sets adaptor as connected/disconnected -func (a *Adaptor) SetConnected(b bool) { - a.connected = b -} - -// ToJSON returns a json representation of adaptor -func (a *Adaptor) ToJSON() *JSONConnection { - return &JSONConnection{ - Name: a.Name(), - Adaptor: a.Type(), - } -} diff --git a/adaptor_test.go b/adaptor_test.go deleted file mode 100644 index 22ca59ea6..000000000 --- a/adaptor_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package gobot - -import "testing" - -func TestAdaptor(t *testing.T) { - a := NewAdaptor("", "testBot", "/dev/null") - Refute(t, a.Name(), "") - a.SetPort("/dev/null1") - Assert(t, a.Port(), "/dev/null1") - a.SetName("myAdaptor") - Assert(t, a.Name(), "myAdaptor") - - a.SetConnected(true) - Assert(t, a.Connected(), true) - - a.SetConnected(false) - Assert(t, a.Connected(), false) -} diff --git a/commander.go b/commander.go new file mode 100644 index 000000000..28487fb0f --- /dev/null +++ b/commander.go @@ -0,0 +1,39 @@ +package gobot + +import "errors" + +type commander struct { + commands map[string]func(map[string]interface{}) interface{} +} + +type Commander interface { + Command(string) (command func(map[string]interface{}) interface{}, err error) + Commands() (commands map[string]func(map[string]interface{}) interface{}) + AddCommand(name string, command func(map[string]interface{}) interface{}) +} + +func NewCommander() Commander { + return &commander{ + commands: make(map[string]func(map[string]interface{}) interface{}), + } +} + +// Command retrieves a command by name +func (c *commander) Command(name string) (command func(map[string]interface{}) interface{}, err error) { + command, ok := c.commands[name] + if ok { + return + } + err = errors.New("Unknown Command") + return +} + +// Commands returns a map of driver commands +func (c *commander) Commands() map[string]func(map[string]interface{}) interface{} { + return c.commands +} + +// AddCommand links specified command name to `f` +func (c *commander) AddCommand(name string, command func(map[string]interface{}) interface{}) { + c.commands[name] = command +} diff --git a/connection.go b/connection.go index 59183e0bf..b5620af80 100644 --- a/connection.go +++ b/connection.go @@ -12,7 +12,7 @@ type JSONConnection struct { Adaptor string `json:"adaptor"` } -type Connection AdaptorInterface +type Connection Adaptor type connections []Connection diff --git a/device.go b/device.go index ef738b6e2..bf5986cf8 100644 --- a/device.go +++ b/device.go @@ -14,7 +14,7 @@ type JSONDevice struct { Commands []string `json:"commands"` } -type Device DriverInterface +type Device Driver type devices []Device diff --git a/driver.go b/driver.go index 1b355ea7a..bb655f221 100644 --- a/driver.go +++ b/driver.go @@ -1,163 +1,11 @@ package gobot -import ( - "fmt" - "time" -) - -// DriverInterface defines Driver expected behaviour -type DriverInterface interface { +type Driver interface { Start() []error Halt() []error - Adaptor() AdaptorInterface - SetInterval(time.Duration) - Interval() time.Duration - SetName(string) Name() string Pin() string - SetPin(string) - Command(string) func(map[string]interface{}) interface{} - Commands() map[string]func(map[string]interface{}) interface{} - AddCommand(string, func(map[string]interface{}) interface{}) - Events() map[string]*Event - Event(string) *Event - AddEvent(string) - Type() string + String() string + Connection() Connection ToJSON() *JSONDevice } - -type Driver struct { - adaptor AdaptorInterface - interval time.Duration - pin string - name string - commands map[string]func(map[string]interface{}) interface{} - events map[string]*Event - driverType string -} - -// NewDriver returns a Driver with specified parameters -// and sets driver pin, adaptor and interval -func NewDriver(name string, driverType string, v ...interface{}) *Driver { - if name == "" { - name = fmt.Sprintf("%X", Rand(int(^uint(0)>>1))) - } - - d := &Driver{ - driverType: driverType, - name: name, - interval: 10 * time.Millisecond, - commands: make(map[string]func(map[string]interface{}) interface{}), - events: make(map[string]*Event), - adaptor: nil, - pin: "", - } - - for i := range v { - switch v[i].(type) { - case string: - d.pin = v[i].(string) - case AdaptorInterface: - d.adaptor = v[i].(AdaptorInterface) - case time.Duration: - d.interval = v[i].(time.Duration) - } - } - - return d -} - -// Adaptor returns driver adaptor -func (d *Driver) Adaptor() AdaptorInterface { - return d.adaptor -} - -// SetInterval defines driver interval duration. -func (d *Driver) SetInterval(t time.Duration) { - d.interval = t -} - -// Interval current driver interval duration -func (d *Driver) Interval() time.Duration { - return d.interval -} - -// SetName sets driver name. -func (d *Driver) SetName(s string) { - d.name = s -} - -// Name returns driver name. -func (d *Driver) Name() string { - return d.name -} - -// Pin returns driver pin -func (d *Driver) Pin() string { - return d.pin -} - -// SetPin defines driver pin -func (d *Driver) SetPin(pin string) { - d.pin = pin -} - -// Type returns driver type -func (d *Driver) Type() string { - return d.driverType -} - -// Events returns driver events map -func (d *Driver) Events() map[string]*Event { - return d.events -} - -// Event returns an event by name if exists -func (d *Driver) Event(name string) *Event { - e, ok := d.events[name] - if ok { - return e - } else { - panic(fmt.Sprintf("Unknown Driver Event: %v", name)) - } -} - -// AddEvents adds a new event by name -func (d *Driver) AddEvent(name string) { - d.events[name] = NewEvent() -} - -// Command retrieves a command by name -func (d *Driver) Command(name string) func(map[string]interface{}) interface{} { - return d.commands[name] -} - -// Commands returns a map of driver commands -func (d *Driver) Commands() map[string]func(map[string]interface{}) interface{} { - return d.commands -} - -// AddCommand links specified command name to `f` -func (d *Driver) AddCommand(name string, f func(map[string]interface{}) interface{}) { - d.commands[name] = f -} - -// ToJSON returns JSON Driver represnentation including adaptor and commands -func (d *Driver) ToJSON() *JSONDevice { - jsonDevice := &JSONDevice{ - Name: d.Name(), - Driver: d.Type(), - Commands: []string{}, - Connection: "", - } - - if d.Adaptor() != nil { - jsonDevice.Connection = d.Adaptor().ToJSON().Name - } - - for command := range d.Commands() { - jsonDevice.Commands = append(jsonDevice.Commands, command) - } - - return jsonDevice -} diff --git a/driver_test.go b/driver_test.go deleted file mode 100644 index bc0c72844..000000000 --- a/driver_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package gobot - -import ( - "fmt" - "testing" - "time" -) - -func TestDriver(t *testing.T) { - a := NewTestAdaptor("testAdaptor") - d := NewDriver("", - "testDriver", - a, - "1", - 5*time.Second, - ) - - Refute(t, d.Name(), "") - Assert(t, d.Type(), "testDriver") - Assert(t, d.Interval(), 5*time.Second) - Assert(t, d.Pin(), "1") - Assert(t, d.Adaptor(), a) - - d.SetPin("10") - Assert(t, d.Pin(), "10") - - d.SetName("myDriver") - Assert(t, d.Name(), "myDriver") - - d.SetInterval(100 * time.Second) - Assert(t, d.Interval(), 100*time.Second) - - Assert(t, len(d.Commands()), 0) - d.AddCommand("cmd1", func(params map[string]interface{}) interface{} { - return fmt.Sprintf("hello from %v", params["name"]) - }) - Assert(t, len(d.Commands()), 1) - Assert(t, - d.Command("cmd1")(map[string]interface{}{"name": d.Name()}).(string), - "hello from "+d.Name(), - ) - - Assert(t, len(d.Events()), 0) - d.AddEvent("event1") - Assert(t, len(d.Events()), 1) - Refute(t, d.Event("event1"), nil) - - defer func() { - r := recover() - if r != nil { - Assert(t, "Unknown Driver Event: event2", r) - } else { - t.Errorf("Did not return Unknown Event error") - } - }() - d.Event("event2") -} diff --git a/eventer.go b/eventer.go new file mode 100644 index 000000000..e15485c9a --- /dev/null +++ b/eventer.go @@ -0,0 +1,33 @@ +package gobot + +type eventer struct { + events map[string]*Event +} + +type Eventer interface { + Events() (events map[string]*Event) + Event(name string) (event *Event) + AddEvent(name string) +} + +func NewEventer() Eventer { + return &eventer{ + events: make(map[string]*Event), + } +} + +// Events returns driver events map +func (e *eventer) Events() map[string]*Event { + return e.events +} + +// Event returns an event by name if exists +func (e *eventer) Event(name string) (event *Event) { + event, _ = e.events[name] + return +} + +// AddEvents adds a new event by name +func (e *eventer) AddEvent(name string) { + e.events[name] = NewEvent() +} diff --git a/gobot.go b/gobot.go index db0af5608..0a039addc 100644 --- a/gobot.go +++ b/gobot.go @@ -14,47 +14,24 @@ type JSONGobot struct { // Gobot is a container composed of one or more robots type Gobot struct { - robots *robots - commands map[string]func(map[string]interface{}) interface{} - trap func(chan os.Signal) + robots *robots + trap func(chan os.Signal) + Commander + Eventer } // NewGobot instantiates a new Gobot func NewGobot() *Gobot { return &Gobot{ - robots: &robots{}, - commands: make(map[string]func(map[string]interface{}) interface{}), + robots: &robots{}, trap: func(c chan os.Signal) { signal.Notify(c, os.Interrupt) }, + Commander: NewCommander(), + Eventer: NewEventer(), } } -/* -AddCommand creates a new command and adds it to the Gobot. This command -will be available via HTTP using '/commands/name' - -Example: - gbot.AddCommand( 'rollover', func( params map[string]interface{}) interface{} { - fmt.Println( "Rolling over - Stand by...") - }) - - With the api package setup, you can now get your Gobot to rollover using: http://localhost:3000/commands/rollover -*/ -func (g *Gobot) AddCommand(name string, f func(map[string]interface{}) interface{}) { - g.commands[name] = f -} - -// Commands lists all available commands on this Gobot instance. -func (g *Gobot) Commands() map[string]func(map[string]interface{}) interface{} { - return g.commands -} - -// Command fetch the associated command using the given command name -func (g *Gobot) Command(name string) func(map[string]interface{}) interface{} { - return g.commands[name] -} - // Start runs the main Gobot event loop func (g *Gobot) Start() (errs []error) { if rerrs := g.robots.Start(); len(rerrs) > 0 { diff --git a/loopback.go b/loopback.go new file mode 100644 index 000000000..e44a2fce0 --- /dev/null +++ b/loopback.go @@ -0,0 +1,20 @@ +package gobot + +type loopbackAdaptor struct { + name string + port string +} + +func (t *loopbackAdaptor) Finalize() (errs []error) { return } +func (t *loopbackAdaptor) Connect() (errs []error) { return } +func (t *loopbackAdaptor) Name() string { return t.name } +func (t *loopbackAdaptor) Port() string { return t.port } +func (t *loopbackAdaptor) String() string { return "loopbackAdaptor" } +func (t *loopbackAdaptor) ToJSON() *JSONConnection { return &JSONConnection{} } + +func NewLoopbackAdaptor(name string) *loopbackAdaptor { + return &loopbackAdaptor{ + name: name, + port: "", + } +} diff --git a/pinger.go b/pinger.go new file mode 100644 index 000000000..f5e3d9e09 --- /dev/null +++ b/pinger.go @@ -0,0 +1,40 @@ +package gobot + +type pingDriver struct { + name string + pin string + connection Connection + Eventer + Commander +} + +func (t *pingDriver) Start() (errs []error) { return } +func (t *pingDriver) Halt() (errs []error) { return } +func (t *pingDriver) Name() string { return t.name } +func (t *pingDriver) Pin() string { return t.pin } +func (t *pingDriver) String() string { return "pingDriver" } +func (t *pingDriver) Connection() Connection { return t.connection } +func (t *pingDriver) ToJSON() *JSONDevice { return &JSONDevice{} } + +func NewPingDriver(adaptor *loopbackAdaptor, name string) *pingDriver { + t := &pingDriver{ + name: name, + connection: adaptor, + pin: "", + Eventer: NewEventer(), + Commander: NewCommander(), + } + + t.AddEvent("ping") + + t.AddCommand("ping", func(params map[string]interface{}) interface{} { + return t.Ping() + }) + + return t +} + +func (t *pingDriver) Ping() string { + Publish(t.Event("ping"), "ping") + return "pong" +} diff --git a/robot.go b/robot.go index 143bb3c28..11517003c 100644 --- a/robot.go +++ b/robot.go @@ -19,10 +19,11 @@ type JSONRobot struct { // a user can specificy custom commands to control a robot remotely. type Robot struct { Name string - commands map[string]func(map[string]interface{}) interface{} Work func() connections *connections devices *devices + Commander + Eventer } type robots []*Robot @@ -62,10 +63,11 @@ func NewRobot(name string, v ...interface{}) *Robot { r := &Robot{ Name: name, - commands: make(map[string]func(map[string]interface{}) interface{}), connections: &connections{}, devices: &devices{}, Work: nil, + Eventer: NewEventer(), + Commander: NewCommander(), } log.Println("Initializing Robot", r.Name, "...") @@ -94,21 +96,6 @@ func NewRobot(name string, v ...interface{}) *Robot { return r } -// AddCommand setup a new command that we be made available via the REST api. -func (r *Robot) AddCommand(name string, f func(map[string]interface{}) interface{}) { - r.commands[name] = f -} - -// Commands lists out all available commands on this robot. -func (r *Robot) Commands() map[string]func(map[string]interface{}) interface{} { - return r.commands -} - -// Command fetch a named command on this robot. -func (r *Robot) Command(name string) func(map[string]interface{}) interface{} { - return r.commands[name] -} - // Start a robot instance and runs it's work function if any. You should not // need to manually start a robot if already part of a Gobot application as the // robot will be automatically started for you. diff --git a/test_helper.go b/test_helper.go index 67935c3a9..fc7608258 100644 --- a/test_helper.go +++ b/test_helper.go @@ -1,32 +1,6 @@ package gobot -import ( - "fmt" - "reflect" - "runtime" - "strings" - "testing" - "time" -) - -func logFailure(t *testing.T, message string) { - _, file, line, _ := runtime.Caller(2) - s := strings.Split(file, "/") - t.Errorf("%v:%v: %v", s[len(s)-1], line, message) -} -func Assert(t *testing.T, a interface{}, b interface{}) { - if !reflect.DeepEqual(a, b) { - logFailure(t, fmt.Sprintf("%v - \"%v\", should equal, %v - \"%v\"", - a, reflect.TypeOf(a), b, reflect.TypeOf(b))) - } -} - -func Refute(t *testing.T, a interface{}, b interface{}) { - if reflect.DeepEqual(a, b) { - logFailure(t, fmt.Sprintf("%v - \"%v\", should not equal, %v - \"%v\"", - a, reflect.TypeOf(a), b, reflect.TypeOf(b))) - } -} +import "fmt" type testStruct struct { i int @@ -59,21 +33,26 @@ func (NullReadWriteCloser) Close() error { } type testDriver struct { - Driver + name string + pin string + connection Connection + Commander } -func (t *testDriver) Start() (errs []error) { return } -func (t *testDriver) Halt() (errs []error) { return } +func (t *testDriver) Start() (errs []error) { return } +func (t *testDriver) Halt() (errs []error) { return } +func (t *testDriver) Name() string { return t.name } +func (t *testDriver) Pin() string { return t.pin } +func (t *testDriver) String() string { return "testDriver" } +func (t *testDriver) Connection() Connection { return t.connection } +func (t *testDriver) ToJSON() *JSONDevice { return &JSONDevice{} } func NewTestDriver(name string, adaptor *testAdaptor) *testDriver { t := &testDriver{ - Driver: *NewDriver( - name, - "TestDriver", - adaptor, - "1", - 100*time.Millisecond, - ), + name: name, + connection: adaptor, + pin: "1", + Commander: NewCommander(), } t.AddCommand("TestDriverCommand", func(params map[string]interface{}) interface{} { @@ -90,19 +69,21 @@ func NewTestDriver(name string, adaptor *testAdaptor) *testDriver { } type testAdaptor struct { - Adaptor + name string + port string } func (t *testAdaptor) Finalize() (errs []error) { return } func (t *testAdaptor) Connect() (errs []error) { return } +func (t *testAdaptor) Name() string { return t.name } +func (t *testAdaptor) Port() string { return t.port } +func (t *testAdaptor) String() string { return "testAdaptor" } +func (t *testAdaptor) ToJSON() *JSONConnection { return &JSONConnection{} } func NewTestAdaptor(name string) *testAdaptor { return &testAdaptor{ - Adaptor: *NewAdaptor( - name, - "TestAdaptor", - "/dev/null", - ), + name: name, + port: "/dev/null", } } @@ -126,49 +107,3 @@ func NewTestRobot(name string) *Robot { }) return r } - -type loopbackAdaptor struct { - Adaptor -} - -func (t *loopbackAdaptor) Finalize() (errs []error) { return } -func (t *loopbackAdaptor) Connect() (errs []error) { return } - -func NewLoopbackAdaptor(name string) *loopbackAdaptor { - return &loopbackAdaptor{ - Adaptor: *NewAdaptor( - name, - "Loopback", - ), - } -} - -type pingDriver struct { - Driver -} - -func (t *pingDriver) Start() (errs []error) { return } -func (t *pingDriver) Halt() (errs []error) { return } - -func NewPingDriver(adaptor *loopbackAdaptor, name string) *pingDriver { - t := &pingDriver{ - Driver: *NewDriver( - name, - "Ping", - adaptor, - ), - } - - t.AddEvent("ping") - - t.AddCommand("ping", func(params map[string]interface{}) interface{} { - return t.Ping() - }) - - return t -} - -func (t *pingDriver) Ping() string { - Publish(t.Event("ping"), "ping") - return "pong" -} diff --git a/utils.go b/utils.go index 631c605d2..a5249d245 100644 --- a/utils.go +++ b/utils.go @@ -2,11 +2,46 @@ package gobot import ( "crypto/rand" + "errors" + "fmt" + "log" "math" "math/big" + "reflect" + "runtime" + "strings" + "testing" "time" ) +var eventError = func(e *Event) (err error) { + if e == nil { + err = errors.New("Event does not exist") + log.Println(err.Error()) + return + } + return +} + +func logFailure(t *testing.T, message string) { + _, file, line, _ := runtime.Caller(2) + s := strings.Split(file, "/") + t.Errorf("%v:%v: %v", s[len(s)-1], line, message) +} +func Assert(t *testing.T, a interface{}, b interface{}) { + if !reflect.DeepEqual(a, b) { + logFailure(t, fmt.Sprintf("%v - \"%v\", should equal, %v - \"%v\"", + a, reflect.TypeOf(a), b, reflect.TypeOf(b))) + } +} + +func Refute(t *testing.T, a interface{}, b interface{}) { + if reflect.DeepEqual(a, b) { + logFailure(t, fmt.Sprintf("%v - \"%v\", should not equal, %v - \"%v\"", + a, reflect.TypeOf(a), b, reflect.TypeOf(b))) + } +} + // Every triggers f every `t` time until the end of days. func Every(t time.Duration, f func()) { c := time.Tick(t) @@ -25,19 +60,28 @@ func After(t time.Duration, f func()) { } // Publish emits an event by writting value -func Publish(e *Event, val interface{}) { - e.Write(val) +func Publish(e *Event, val interface{}) (err error) { + if err = eventError(e); err == nil { + e.Write(val) + } + return } // On adds `f` to callbacks that are executed on specified event -func On(e *Event, f func(s interface{})) { - e.Callbacks = append(e.Callbacks, callback{f, false}) +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 adds `f` to callbacks that are executed on specified event // and sets flag to be called only once -func Once(e *Event, f func(s interface{})) { - e.Callbacks = append(e.Callbacks, callback{f, true}) +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 generates random int lower than max