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  }