diff --git a/adaptor.go b/adaptor.go index 7781ce00..6a6d072a 100644 --- a/adaptor.go +++ b/adaptor.go @@ -9,6 +9,7 @@ type Adaptor struct { adaptorType string } +// AdaptorInterface defines behaviour expected for a Gobot Adaptor type AdaptorInterface interface { Finalize() bool Connect() bool @@ -22,6 +23,7 @@ type AdaptorInterface interface { 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))) @@ -43,34 +45,42 @@ func NewAdaptor(name string, adaptorType string, v ...interface{}) *Adaptor { 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(), diff --git a/api/api.go b/api/api.go index 3ecfb24b..66ac4782 100644 --- a/api/api.go +++ b/api/api.go @@ -12,8 +12,6 @@ import ( "github.com/hybridgroup/gobot/api/robeaux" ) -// Optional restful API through Gobot has access -// all the robots. type api struct { gobot *gobot.Gobot router *pat.PatternServeMux @@ -25,6 +23,8 @@ type api struct { start func(*api) } +// newAPI returns a gobot api instance +// and starts a http server using configuration options func NewAPI(g *gobot.Gobot) *api { return &api{ gobot: g, @@ -47,6 +47,7 @@ func NewAPI(g *gobot.Gobot) *api { } } +// ServeHTTP calls api handlers and then serves request using api router func (a *api) ServeHTTP(res http.ResponseWriter, req *http.Request) { for _, handler := range a.handlers { handler(res, req) @@ -54,38 +55,43 @@ func (a *api) ServeHTTP(res http.ResponseWriter, req *http.Request) { a.router.ServeHTTP(res, req) } +// Post wraps api router Post call func (a *api) Post(path string, f func(http.ResponseWriter, *http.Request)) { a.router.Post(path, http.HandlerFunc(f)) } +// Put wraps api router Put call func (a *api) Put(path string, f func(http.ResponseWriter, *http.Request)) { a.router.Put(path, http.HandlerFunc(f)) } +// Delete wraps api router Delete call func (a *api) Delete(path string, f func(http.ResponseWriter, *http.Request)) { a.router.Del(path, http.HandlerFunc(f)) } +// Options wraps api router Options call func (a *api) Options(path string, f func(http.ResponseWriter, *http.Request)) { a.router.Options(path, http.HandlerFunc(f)) } +// Get wraps api router Get call func (a *api) Get(path string, f func(http.ResponseWriter, *http.Request)) { a.router.Get(path, http.HandlerFunc(f)) } +// Head wraps api router Head call func (a *api) Head(path string, f func(http.ResponseWriter, *http.Request)) { a.router.Head(path, http.HandlerFunc(f)) } +// AddHandler appends handler to api handlers func (a *api) AddHandler(f func(http.ResponseWriter, *http.Request)) { a.handlers = append(a.handlers, f) } -// start starts the api using the start function -// sets on the API on initialization. +// Start initializes the api by setting up c3pio routes and robeaux func (a *api) Start() { - // api mcpCommandRoute := "/api/commands/:command" deviceCommandRoute := "/api/robots/:robot/devices/:device/commands/:command" robotCommandRoute := "/api/robots/:robot/commands/:command" @@ -108,7 +114,6 @@ func (a *api) Start() { a.Get("/api/robots/:robot/connections/:connection", a.robotConnection) a.Get("/api/", a.mcp) - // robeaux a.Get("/", func(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, "/index.html", http.StatusMovedPermanently) }) @@ -125,6 +130,8 @@ func (a *api) Start() { a.start(a) } +// robeaux returns handler for robeaux routes. +// Writes asset in response and sets correct header func (a *api) robeaux(res http.ResponseWriter, req *http.Request) { path := req.URL.Path buf, err := robeaux.Asset(path[1:]) @@ -141,14 +148,20 @@ func (a *api) robeaux(res http.ResponseWriter, req *http.Request) { res.Write(buf) } +// mcp returns MCP route handler. +// Writes JSON with gobot representation func (a *api) mcp(res http.ResponseWriter, req *http.Request) { a.writeJSON(map[string]interface{}{"MCP": a.gobot.ToJSON()}, res) } +// mcpCommands returns commands route handler. +// Writes JSON with global commands representation func (a *api) mcpCommands(res http.ResponseWriter, req *http.Request) { a.writeJSON(map[string]interface{}{"commands": a.gobot.ToJSON().Commands}, res) } +// robots returns route handler. +// Writes JSON with robots representation func (a *api) robots(res http.ResponseWriter, req *http.Request) { jsonRobots := []*gobot.JSONRobot{} a.gobot.Robots().Each(func(r *gobot.Robot) { @@ -157,14 +170,20 @@ func (a *api) robots(res http.ResponseWriter, req *http.Request) { a.writeJSON(map[string]interface{}{"robots": jsonRobots}, res) } +// robot returns route handler. +// Writes JSON with robot representation func (a *api) robot(res http.ResponseWriter, req *http.Request) { a.writeJSON(map[string]interface{}{"robot": a.gobot.Robot(req.URL.Query().Get(":robot")).ToJSON()}, res) } +// robotCommands returns commands route handler +// Writes JSON with robot commands representation func (a *api) robotCommands(res http.ResponseWriter, req *http.Request) { a.writeJSON(map[string]interface{}{"commands": a.gobot.Robot(req.URL.Query().Get(":robot")).ToJSON().Commands}, res) } +// robotDevices returns devices route handler. +// Writes JSON with robot devices representation func (a *api) robotDevices(res http.ResponseWriter, req *http.Request) { jsonDevices := []*gobot.JSONDevice{} a.gobot.Robot(req.URL.Query().Get(":robot")).Devices().Each(func(d gobot.Device) { @@ -173,6 +192,8 @@ func (a *api) robotDevices(res http.ResponseWriter, req *http.Request) { a.writeJSON(map[string]interface{}{"devices": jsonDevices}, res) } +// robotDevice returns device route handler. +// Writes JSON with robot device representation func (a *api) robotDevice(res http.ResponseWriter, req *http.Request) { a.writeJSON( map[string]interface{}{"device": a.gobot.Robot(req.URL.Query().Get(":robot")). @@ -180,6 +201,9 @@ func (a *api) robotDevice(res http.ResponseWriter, req *http.Request) { ) } +// robotDeviceEvent returns device event route handler. +// Creates an event stream connection +// and queries event data to be written when received func (a *api) robotDeviceEvent(res http.ResponseWriter, req *http.Request) { f, _ := res.(http.Flusher) c, _ := res.(http.CloseNotifier) @@ -210,6 +234,8 @@ func (a *api) robotDeviceEvent(res http.ResponseWriter, req *http.Request) { } } +// robotDeviceCommands returns device commands route handler +// writes JSON with robot device commands representation func (a *api) robotDeviceCommands(res http.ResponseWriter, req *http.Request) { a.writeJSON( map[string]interface{}{"commands": a.gobot.Robot(req.URL.Query().Get(":robot")). @@ -217,6 +243,8 @@ func (a *api) robotDeviceCommands(res http.ResponseWriter, req *http.Request) { ) } +// robotConnections returns connections route handler +// writes JSON with robot connections representation func (a *api) robotConnections(res http.ResponseWriter, req *http.Request) { jsonConnections := []*gobot.JSONConnection{} a.gobot.Robot(req.URL.Query().Get(":robot")).Connections().Each(func(c gobot.Connection) { @@ -225,6 +253,8 @@ func (a *api) robotConnections(res http.ResponseWriter, req *http.Request) { a.writeJSON(map[string]interface{}{"connections": jsonConnections}, res) } +// robotConnection returns connection route handler +// writes JSON with robot connection representation func (a *api) robotConnection(res http.ResponseWriter, req *http.Request) { a.writeJSON( map[string]interface{}{"connection": a.gobot.Robot(req.URL.Query().Get(":robot")). @@ -233,6 +263,7 @@ func (a *api) robotConnection(res http.ResponseWriter, req *http.Request) { ) } +// executeMcpCommand calls a global command asociated to requested route func (a *api) executeMcpCommand(res http.ResponseWriter, req *http.Request) { a.executeCommand(a.gobot.Command(req.URL.Query().Get(":command")), res, @@ -240,6 +271,7 @@ func (a *api) executeMcpCommand(res http.ResponseWriter, req *http.Request) { ) } +// executeDeviceCommand calls a device command asociated to requested route func (a *api) executeDeviceCommand(res http.ResponseWriter, req *http.Request) { a.executeCommand( a.gobot.Robot(req.URL.Query().Get(":robot")). @@ -250,6 +282,7 @@ func (a *api) executeDeviceCommand(res http.ResponseWriter, req *http.Request) { ) } +// executeRobotCommand calls a robot command asociated to requested route func (a *api) executeRobotCommand(res http.ResponseWriter, req *http.Request) { a.executeCommand( a.gobot.Robot(req.URL.Query().Get(":robot")). @@ -259,6 +292,7 @@ func (a *api) executeRobotCommand(res http.ResponseWriter, req *http.Request) { ) } +// executeCommand writes JSON response with `f` returned value. func (a *api) executeCommand(f func(map[string]interface{}) interface{}, res http.ResponseWriter, req *http.Request, @@ -274,12 +308,14 @@ func (a *api) executeCommand(f func(map[string]interface{}) interface{}, } } +// writeJSON writes `j` as JSON in response func (a *api) writeJSON(j interface{}, res http.ResponseWriter) { data, _ := json.Marshal(j) res.Header().Set("Content-Type", "application/json; charset=utf-8") res.Write(data) } +// Debug add handler to api that prints each request func (a *api) Debug() { a.AddHandler(func(res http.ResponseWriter, req *http.Request) { log.Println(req) diff --git a/api/basic_auth.go b/api/basic_auth.go index a86c7a7c..5aa08ffd 100644 --- a/api/basic_auth.go +++ b/api/basic_auth.go @@ -6,9 +6,9 @@ import ( "net/http" ) +// BasicAuth returns basic auth handler. +// Inspired by https://github.com/codegangsta/martini-contrib/blob/master/auth/ func BasicAuth(username, password string) http.HandlerFunc { - // basic auth inspired by - // https://github.com/codegangsta/martini-contrib/blob/master/auth/ return func(res http.ResponseWriter, req *http.Request) { if !secureCompare(req.Header.Get("Authorization"), "Basic "+base64.StdEncoding.EncodeToString([]byte(username+":"+password)), diff --git a/api/cors.go b/api/cors.go index 81ef2bd5..f5dec73c 100644 --- a/api/cors.go +++ b/api/cors.go @@ -14,6 +14,7 @@ type CORS struct { allowOriginPatterns []string } +// AllowRequestFrom returns handler to verify that requests come from allowedOrigins func AllowRequestsFrom(allowedOrigins ...string) http.HandlerFunc { c := &CORS{ AllowOrigins: allowedOrigins, @@ -35,6 +36,7 @@ func AllowRequestsFrom(allowedOrigins ...string) http.HandlerFunc { } } +// isOriginAllowed returns true if origin matches an allowed origin pattern. func (c *CORS) isOriginAllowed(origin string) (allowed bool) { for _, allowedOriginPattern := range c.allowOriginPatterns { allowed, _ = regexp.MatchString(allowedOriginPattern, origin) @@ -45,6 +47,7 @@ func (c *CORS) isOriginAllowed(origin string) (allowed bool) { return } +// generatePatterns generates regex expresions for AllowOrigins func (c *CORS) generatePatterns() { if c.AllowOrigins != nil { for _, origin := range c.AllowOrigins { @@ -56,10 +59,12 @@ func (c *CORS) generatePatterns() { } } +// AllowedHeaders returns allowed headers in a string func (c *CORS) AllowedHeaders() string { return strings.Join(c.AllowHeaders, ",") } +// AllowedMethods returns allowed http methods in a string func (c *CORS) AllowedMethods() string { return strings.Join(c.AllowMethods, ",") } diff --git a/api/doc.go b/api/doc.go new file mode 100644 index 00000000..dbe0981d --- /dev/null +++ b/api/doc.go @@ -0,0 +1,40 @@ +/* +Package api provides functionally to expose your gobot programs +to other by using starting a web server and adding commands. + +Example: + + package main + + import ( + "fmt" + + "github.com/hybridgroup/gobot" + "github.com/hybridgroup/gobot/api" + ) + + func main() { + gbot := gobot.NewGobot() + + // Starts the API server on default port 3000 + api.NewAPI(gbot).Start() + + // Accessible via http://localhost:3000/api/commands/say_hello + gbot.AddCommand("say_hello", func(params map[string]interface{}) interface{} { + return "Master says hello!" + }) + + hello := gbot.AddRobot(gobot.NewRobot("Eve")) + + // Accessible via http://localhost:3000/robots/Eve/commands/say_hello + hello.AddCommand("say_hello", func(params map[string]interface{}) interface{} { + return fmt.Sprintf("%v says hello!", hello.Name) + }) + + gbot.Start() + } + +It follows Common Protocol for Programming Physical Input and Output (CPPP-IO) spec: +https://github.com/hybridgroup/cppp-io +*/ +package api diff --git a/connection.go b/connection.go index c95e2304..75d5689c 100644 --- a/connection.go +++ b/connection.go @@ -5,6 +5,7 @@ import ( "log" ) +// JSONConnection holds a JSON representation of a connection. type JSONConnection struct { Name string `json:"name"` Adaptor string `json:"adaptor"` @@ -14,17 +15,19 @@ type Connection AdaptorInterface type connections []Connection +// Len returns connections length func (c *connections) Len() int { return len(*c) } +// Each calls function for each connection func (c *connections) Each(f func(Connection)) { for _, connection := range *c { f(connection) } } -// Start() starts all the connections. +// Start initializes all the connections. func (c *connections) Start() error { var err error log.Println("Starting connections...") @@ -42,7 +45,7 @@ func (c *connections) Start() error { return err } -// Finalize() finalizes all the connections. +// Finalize finishes all the connections. func (c *connections) Finalize() { for _, connection := range *c { connection.Finalize() diff --git a/device.go b/device.go index febfa2df..15f925bf 100644 --- a/device.go +++ b/device.go @@ -5,6 +5,7 @@ import ( "log" ) +// JSONDevice is a JSON representation of a Gobot Device. type JSONDevice struct { Name string `json:"name"` Driver string `json:"driver"` @@ -16,17 +17,19 @@ type Device DriverInterface type devices []Device +// Len returns devices length func (d *devices) Len() int { return len(*d) } +// Each calls `f` function each device func (d *devices) Each(f func(Device)) { for _, device := range *d { f(device) } } -// Start() starts all the devices. +// Start starts all the devices. func (d *devices) Start() error { var err error log.Println("Starting devices...") @@ -44,7 +47,7 @@ func (d *devices) Start() error { return err } -// Halt() stop all the devices. +// Halt stop all the devices. func (d *devices) Halt() { for _, device := range *d { device.Halt() diff --git a/driver.go b/driver.go index 6eda0702..36002753 100644 --- a/driver.go +++ b/driver.go @@ -5,6 +5,7 @@ import ( "time" ) +// DriverInterface defines Driver expected behaviour type DriverInterface interface { Start() bool Halt() bool @@ -35,6 +36,8 @@ type Driver struct { 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))) @@ -64,42 +67,52 @@ func NewDriver(name string, driverType string, v ...interface{}) *Driver { 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 { @@ -109,22 +122,27 @@ func (d *Driver) Event(name string) *Event { } } +// 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(), diff --git a/event.go b/event.go index 71e051bc..c2dfaa16 100644 --- a/event.go +++ b/event.go @@ -10,6 +10,8 @@ type Event struct { Callbacks []callback } +// NewEvent generates a new event by making a channel +// and start reading from it func NewEvent() *Event { e := &Event{ Chan: make(chan interface{}, 1), @@ -23,6 +25,7 @@ func NewEvent() *Event { return e } +// Writes sends event data to channel func (e *Event) Write(data interface{}) { select { case e.Chan <- data: @@ -30,6 +33,8 @@ func (e *Event) Write(data interface{}) { } } +// Read waits data from channel and execute callbacks +// for each event when received func (e *Event) Read() { for s := range e.Chan { tmp := []callback{} diff --git a/utils.go b/utils.go index 0cf02c42..631c605d 100644 --- a/utils.go +++ b/utils.go @@ -10,13 +10,10 @@ import ( // Every triggers f every `t` time until the end of days. func Every(t time.Duration, f func()) { c := time.Tick(t) - // start a go routine to not bloc the function + go func() { for { - // wait for the ticker to tell us to run <-c - // run the passed function in another go routine - // so we don't slow down the loop. go f() } }() @@ -27,27 +24,35 @@ func After(t time.Duration, f func()) { time.AfterFunc(t, f) } +// Publish emits an event by writting value func Publish(e *Event, val interface{}) { e.Write(val) } +// 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}) } +// 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}) } +// Rand generates random int lower than max func Rand(max int) int { i, _ := rand.Int(rand.Reader, big.NewInt(int64(max))) return int(i.Int64()) } +// FromScale creates a scale using min and max values +// to be used in combination with ToScale func FromScale(input, min, max float64) float64 { return (input - math.Min(min, max)) / (math.Max(min, max) - math.Min(min, max)) } +// ToScale is used with FromScale to return input converted to new scale func ToScale(input, min, max float64) float64 { i := input*(math.Max(min, max)-math.Min(min, max)) + math.Min(min, max) if i < math.Min(min, max) {