diff --git a/README.md b/README.md index e8731413..51eec88e 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,20 @@ func main() { robot.Start() } ``` +## API: + +Gobot includes a RESTful API to query the status of any robot running within a group, including the connection and device status, and execute device commands. + +To activate the API, use the `Api` command like this: + +```go + master := gobot.GobotMaster() + gobot.Api(master) +``` +To specify the api port run your Gobot program with the `PORT` environment variable +``` + $ PORT=8080 go run gobotProgram.go +``` ## Hardware Support Gobot has a extensible system for connecting to hardware devices. The following robotics and physical computing platforms are currently supported: diff --git a/api.go b/api.go new file mode 100644 index 00000000..73cfe3fd --- /dev/null +++ b/api.go @@ -0,0 +1,60 @@ +package gobot + +import ( + "encoding/json" + "github.com/codegangsta/martini" + "net/http" +) + +type api struct{} + +func Api(bot *Master) { + a := new(api) + m := martini.Classic() + + m.Get("/robots", func() string { + return toJson(bot.Robots) + }) + + m.Get("/robots/:robotname", func(params martini.Params) string { + return toJson(bot.FindRobot(params["robotname"])) + }) + + m.Get("/robots/:robotname/devices", func(params martini.Params) string { + return toJson(bot.FindRobot(params["robotname"]).GetDevices()) + }) + + m.Get("/robots/:robotname/devices/:devicename", func(params martini.Params) string { + return toJson(bot.FindRobotDevice(params["robotname"], params["devicename"])) + }) + + m.Get("/robots/:robotname/devices/:devicename/commands", func(params martini.Params) string { + return toJson(bot.FindRobotDevice(params["robotname"], params["devicename"]).Commands()) + }) + + command_route := "/robots/:robotname/devices/:devicename/commands/:command" + + m.Get(command_route, func(params martini.Params, res http.ResponseWriter, req *http.Request) string { + return a.executeCommand(bot, params, res, req) + }) + m.Post(command_route, func(params martini.Params, res http.ResponseWriter, req *http.Request) string { + return a.executeCommand(bot, params, res, req) + }) + + go m.Run() +} + +func (a *api) executeCommand(bot *Master, params martini.Params, res http.ResponseWriter, req *http.Request) string { + decoder := json.NewDecoder(req.Body) + var body map[string]interface{} + decoder.Decode(&body) + robot := bot.FindRobotDevice(params["robotname"], params["devicename"]) + commands := robot.Commands().([]string) + for command := range commands { + if commands[command] == params["command"] { + ret := Call(robot.Driver, params["command"], body) + return toJson(map[string]interface{}{"results": ret}) + } + } + return toJson(map[string]interface{}{"results": "Unknown Command"}) +} diff --git a/connection.go b/connection.go index eaaad42b..8f9b3380 100644 --- a/connection.go +++ b/connection.go @@ -9,7 +9,7 @@ type Connection struct { Name string Adaptor interface{} Port string - Robot *Robot + Robot *Robot `json:"-"` Params map[string]string } diff --git a/device.go b/device.go index 6c0babbe..c5f57061 100644 --- a/device.go +++ b/device.go @@ -8,7 +8,7 @@ import ( type Device struct { Name string Interval string - Robot *Robot + Robot *Robot `json:"-"` Driver interface{} Params map[string]string } @@ -29,6 +29,6 @@ func (d *Device) Start() { } } -func (d *Device) Command(method_name string, arguments []string) { - //dt.Driver.Command(method_name, arguments) +func (d *Device) Commands() interface{} { + return reflect.ValueOf(d.Driver).Elem().FieldByName("Commands").Interface() } diff --git a/driver.go b/driver.go index 4ab5039a..978c6895 100644 --- a/driver.go +++ b/driver.go @@ -7,7 +7,8 @@ type Driver struct { Pin string Name string Params map[string]string - Events map[string]chan interface{} + Commands []string + Events map[string]chan interface{} `json:"-"` } func NewDriver(d Driver) Driver { diff --git a/examples/sphero_api.go b/examples/sphero_api.go new file mode 100644 index 00000000..e06c66f8 --- /dev/null +++ b/examples/sphero_api.go @@ -0,0 +1,38 @@ +package main + +import ( + "github.com/hybridgroup/gobot" + "github.com/hybridgroup/gobot-sphero" +) + +func main() { + master := gobot.GobotMaster() + gobot.Api(master) + + spheros := map[string]string{ + "Sphero-BPO": "127.0.0.1:4560", + } + + for name, port := range spheros { + spheroAdaptor := new(gobotSphero.SpheroAdaptor) + spheroAdaptor.Name = "sphero" + spheroAdaptor.Port = port + + sphero := gobotSphero.NewSphero(spheroAdaptor) + sphero.Name = "sphero" + sphero.Interval = "0.5s" + + work := func() { + sphero.SetRGB(uint8(255), uint8(0), uint8(0)) + } + + master.Robots = append(master.Robots, gobot.Robot{ + Name: name, + Connections: []interface{}{spheroAdaptor}, + Devices: []interface{}{sphero}, + Work: work, + }) + } + + master.Start() +} diff --git a/examples/sphero_master.go b/examples/sphero_master.go new file mode 100644 index 00000000..1c3af0e0 --- /dev/null +++ b/examples/sphero_master.go @@ -0,0 +1,46 @@ +package main + +import ( + "github.com/hybridgroup/gobot" + "github.com/hybridgroup/gobot-sphero" +) + +func main() { + master := gobot.GobotMaster() + + spheros := map[string]string{ + "Sphero-BPO": "127.0.0.1:4560", + } + + for name, port := range spheros { + spheroAdaptor := new(gobotSphero.SpheroAdaptor) + spheroAdaptor.Name = "sphero" + spheroAdaptor.Port = port + + sphero := gobotSphero.NewSphero(spheroAdaptor) + sphero.Name = "sphero" + sphero.Interval = "0.5s" + + work := func() { + sphero.SetRGB(uint8(255), uint8(0), uint8(0)) + } + + master.Robots = append(master.Robots, gobot.Robot{ + Name: name, + Connections: []interface{}{spheroAdaptor}, + Devices: []interface{}{sphero}, + Work: work, + }) + } + + master.Robots = append(master.Robots, gobot.Robot{ + Work: func() { + sphero := master.FindRobot("Sphero-BPO") + gobot.Every("1s", func() { + gobot.Call(sphero.GetDevice("sphero").Driver, "SetRGB", uint8(gobot.Rand(255)), uint8(gobot.Rand(255)), uint8(gobot.Rand(255))) + }) + }, + }) + + master.Start() +} diff --git a/master.go b/master.go new file mode 100644 index 00000000..c87fa52a --- /dev/null +++ b/master.go @@ -0,0 +1,55 @@ +package gobot + +import "time" + +type Master struct { + Robots []Robot +} + +func GobotMaster() *Master { + m := new(Master) + return m +} + +func (m *Master) Start() { + for s := range m.Robots { + go m.Robots[s].Start() + } + + for { + time.Sleep(10 * time.Millisecond) + } +} + +func (m *Master) FindRobot(name string) *Robot { + for s := range m.Robots { + if m.Robots[s].Name == name { + return &m.Robots[s] + } + } + return nil +} +func (m *Master) FindRobotDevice(name string, device string) *Device { + for r := range m.Robots { + if m.Robots[r].Name == name { + for d := range m.Robots[r].devices { + if m.Robots[r].devices[d].Name == device { + return m.Robots[r].devices[d] + } + } + } + } + return nil +} +func (m *Master) FindRobotConnection(name string, connection string) *Connection { + for r := range m.Robots { + if m.Robots[r].Name == name { + for c := range m.Robots[r].connections { + if m.Robots[r].connections[c].Name == connection { + return m.Robots[r].connections[c] + } + } + } + } + return nil +} diff --git a/robot.go b/robot.go index 54f3a44f..97e16674 100644 --- a/robot.go +++ b/robot.go @@ -11,9 +11,9 @@ type Robot struct { Connections []interface{} Devices []interface{} Name string - Work func() - connections []*Connection - devices []*Device + Work func() `json:"-"` + connections []*Connection `json:"-"` + devices []*Device `json:"-"` } func (r *Robot) Start() { @@ -65,3 +65,16 @@ func (r *Robot) startDevices() { r.devices[i].Start() } } + +func (r *Robot) GetDevices() []*Device { + return r.devices +} + +func (r *Robot) GetDevice(name string) *Device { + for i := range r.devices { + if r.devices[i].Name == name { + return r.devices[i] + } + } + return nil +} diff --git a/gobot.go b/utils.go similarity index 62% rename from gobot.go rename to utils.go index 5573e8be..8dfaa3c9 100644 --- a/gobot.go +++ b/utils.go @@ -1,8 +1,10 @@ package gobot import ( + "encoding/json" "math/rand" "net" + "reflect" "time" ) @@ -25,9 +27,6 @@ func After(t string, f func()) { } func parseDuration(t string) time.Duration { - return ParseDuration(t) -} -func ParseDuration(t string) time.Duration { dur, err := time.ParseDuration(t) if err != nil { panic(err) @@ -35,9 +34,9 @@ func ParseDuration(t string) time.Duration { return dur } -func Random(min int, max int) int { +func Rand(max int) int { rand.Seed(time.Now().UTC().UnixNano()) - return rand.Intn(max-min) + min + return rand.Intn(max) } func On(cs chan interface{}) interface{} { @@ -47,15 +46,6 @@ func On(cs chan interface{}) interface{} { return nil } -func Work(robots []Robot) { - for s := range robots { - go robots[s].Start() - } - for { - time.Sleep(10 * time.Millisecond) - } -} - func ConnectTo(port string) net.Conn { tcpPort, err := net.Dial("tcp", port) if err != nil { @@ -63,3 +53,16 @@ func ConnectTo(port string) net.Conn { } return tcpPort } + +func Call(thing interface{}, method string, params ...interface{}) []reflect.Value { + in := make([]reflect.Value, len(params)) + for k, param := range params { + in[k] = reflect.ValueOf(param) + } + return reflect.ValueOf(thing).MethodByName(method).Call(in) +} + +func toJson(obj interface{}) string { + b, _ := json.Marshal(obj) + return string(b) +}