gobot.io/x/gobot/v2@v2.1.0/robot.go (about) 1 package gobot 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "os/signal" 8 "sync/atomic" 9 10 "sync" 11 12 multierror "github.com/hashicorp/go-multierror" 13 ) 14 15 // JSONRobot a JSON representation of a Robot. 16 type JSONRobot struct { 17 Name string `json:"name"` 18 Commands []string `json:"commands"` 19 Connections []*JSONConnection `json:"connections"` 20 Devices []*JSONDevice `json:"devices"` 21 } 22 23 // NewJSONRobot returns a JSONRobot given a Robot. 24 func NewJSONRobot(robot *Robot) *JSONRobot { 25 jsonRobot := &JSONRobot{ 26 Name: robot.Name, 27 Commands: []string{}, 28 Connections: []*JSONConnection{}, 29 Devices: []*JSONDevice{}, 30 } 31 32 for command := range robot.Commands() { 33 jsonRobot.Commands = append(jsonRobot.Commands, command) 34 } 35 36 robot.Devices().Each(func(device Device) { 37 jsonDevice := NewJSONDevice(device) 38 jsonRobot.Connections = append(jsonRobot.Connections, NewJSONConnection(robot.Connection(jsonDevice.Connection))) 39 jsonRobot.Devices = append(jsonRobot.Devices, jsonDevice) 40 }) 41 return jsonRobot 42 } 43 44 // Robot is a named entity that manages a collection of connections and devices. 45 // It contains its own work routine and a collection of 46 // custom commands to control a robot remotely via the Gobot api. 47 type Robot struct { 48 Name string 49 Work func() 50 connections *Connections 51 devices *Devices 52 trap func(chan os.Signal) 53 AutoRun bool 54 running atomic.Value 55 done chan bool 56 workRegistry *RobotWorkRegistry 57 WorkEveryWaitGroup *sync.WaitGroup 58 WorkAfterWaitGroup *sync.WaitGroup 59 Commander 60 Eventer 61 } 62 63 // Robots is a collection of Robot 64 type Robots []*Robot 65 66 // Len returns the amount of Robots in the collection. 67 func (r *Robots) Len() int { 68 return len(*r) 69 } 70 71 // Start calls the Start method of each Robot in the collection 72 func (r *Robots) Start(args ...interface{}) (err error) { 73 autoRun := true 74 if args[0] != nil { 75 autoRun = args[0].(bool) 76 } 77 for _, robot := range *r { 78 if rerr := robot.Start(autoRun); rerr != nil { 79 err = multierror.Append(err, rerr) 80 return 81 } 82 } 83 return 84 } 85 86 // Stop calls the Stop method of each Robot in the collection 87 func (r *Robots) Stop() (err error) { 88 for _, robot := range *r { 89 if rerr := robot.Stop(); rerr != nil { 90 err = multierror.Append(err, rerr) 91 return 92 } 93 } 94 return 95 } 96 97 // Each enumerates through the Robots and calls specified callback function. 98 func (r *Robots) Each(f func(*Robot)) { 99 for _, robot := range *r { 100 f(robot) 101 } 102 } 103 104 // NewRobot returns a new Robot. It supports the following optional params: 105 // 106 // name: string with the name of the Robot. A name will be automatically generated if no name is supplied. 107 // []Connection: Connections which are automatically started and stopped with the robot 108 // []Device: Devices which are automatically started and stopped with the robot 109 // func(): The work routine the robot will execute once all devices and connections have been initialized and started 110 // 111 func NewRobot(v ...interface{}) *Robot { 112 r := &Robot{ 113 Name: fmt.Sprintf("%X", Rand(int(^uint(0)>>1))), 114 connections: &Connections{}, 115 devices: &Devices{}, 116 done: make(chan bool, 1), 117 trap: func(c chan os.Signal) { 118 signal.Notify(c, os.Interrupt) 119 }, 120 AutoRun: true, 121 Work: nil, 122 Eventer: NewEventer(), 123 Commander: NewCommander(), 124 } 125 126 for i := range v { 127 switch v[i].(type) { 128 case string: 129 r.Name = v[i].(string) 130 case []Connection: 131 log.Println("Initializing connections...") 132 for _, connection := range v[i].([]Connection) { 133 c := r.AddConnection(connection) 134 log.Println("Initializing connection", c.Name(), "...") 135 } 136 case []Device: 137 log.Println("Initializing devices...") 138 for _, device := range v[i].([]Device) { 139 d := r.AddDevice(device) 140 log.Println("Initializing device", d.Name(), "...") 141 } 142 case func(): 143 r.Work = v[i].(func()) 144 } 145 } 146 147 r.workRegistry = &RobotWorkRegistry{ 148 r: make(map[string]*RobotWork), 149 } 150 r.WorkAfterWaitGroup = &sync.WaitGroup{} 151 r.WorkEveryWaitGroup = &sync.WaitGroup{} 152 153 r.running.Store(false) 154 log.Println("Robot", r.Name, "initialized.") 155 156 return r 157 } 158 159 // Start a Robot's Connections, Devices, and work. 160 func (r *Robot) Start(args ...interface{}) (err error) { 161 if len(args) > 0 && args[0] != nil { 162 r.AutoRun = args[0].(bool) 163 } 164 log.Println("Starting Robot", r.Name, "...") 165 if cerr := r.Connections().Start(); cerr != nil { 166 err = multierror.Append(err, cerr) 167 log.Println(err) 168 return 169 } 170 if derr := r.Devices().Start(); derr != nil { 171 err = multierror.Append(err, derr) 172 log.Println(err) 173 return 174 } 175 if r.Work == nil { 176 r.Work = func() {} 177 } 178 179 log.Println("Starting work...") 180 go func() { 181 r.Work() 182 <-r.done 183 }() 184 185 r.running.Store(true) 186 if r.AutoRun { 187 c := make(chan os.Signal, 1) 188 r.trap(c) 189 190 // waiting for interrupt coming on the channel 191 <-c 192 193 // Stop calls the Stop method on itself, if we are "auto-running". 194 r.Stop() 195 } 196 197 return 198 } 199 200 // Stop stops a Robot's connections and Devices 201 func (r *Robot) Stop() error { 202 var result error 203 log.Println("Stopping Robot", r.Name, "...") 204 err := r.Devices().Halt() 205 if err != nil { 206 result = multierror.Append(result, err) 207 } 208 err = r.Connections().Finalize() 209 if err != nil { 210 result = multierror.Append(result, err) 211 } 212 213 r.done <- true 214 r.running.Store(false) 215 return result 216 } 217 218 // Running returns if the Robot is currently started or not 219 func (r *Robot) Running() bool { 220 return r.running.Load().(bool) 221 } 222 223 // Devices returns all devices associated with this Robot. 224 func (r *Robot) Devices() *Devices { 225 return r.devices 226 } 227 228 // AddDevice adds a new Device to the robots collection of devices. Returns the 229 // added device. 230 func (r *Robot) AddDevice(d Device) Device { 231 *r.devices = append(*r.Devices(), d) 232 return d 233 } 234 235 // Device returns a device given a name. Returns nil if the Device does not exist. 236 func (r *Robot) Device(name string) Device { 237 if r == nil { 238 return nil 239 } 240 for _, device := range *r.devices { 241 if device.Name() == name { 242 return device 243 } 244 } 245 return nil 246 } 247 248 // Connections returns all connections associated with this robot. 249 func (r *Robot) Connections() *Connections { 250 return r.connections 251 } 252 253 // AddConnection adds a new connection to the robots collection of connections. 254 // Returns the added connection. 255 func (r *Robot) AddConnection(c Connection) Connection { 256 *r.connections = append(*r.Connections(), c) 257 return c 258 } 259 260 // Connection returns a connection given a name. Returns nil if the Connection 261 // does not exist. 262 func (r *Robot) Connection(name string) Connection { 263 if r == nil { 264 return nil 265 } 266 for _, connection := range *r.connections { 267 if connection.Name() == name { 268 return connection 269 } 270 } 271 return nil 272 }