goatd documentation¶
Contents
Introduction¶
Goatd is designed to be the manager for a goat control system, granting graceful startup, telemetry, logging and a built in simulator.
There are two main components of a system written using goatd:
- the driver interfaces with the particular set of hardware in the goat.
- the behaviour performs a set of actions to make the goat do a particular task. The API available for these scripts is supposed to be declarative, with the idea that for any goat with a driver written, any behaviour script will work.
Installing¶
Goatd is currently tested and supported on Python 2.7 and 3.4. Support for python 2 may be dropped in the near future.
Installing in a virtualenv from PyPi (recommended)¶
This installs goatd in a virtualenv, keeping it separate from the rest of the system. First, create a new virtualenv:
$ virtualenv env
Activate this virtualenv:
$ source env/bin/activate
Install goatd and its dependencies from the latest published stable release:
$ pip install goatd
Installing with Docker¶
$ docker build -t goatd .
Installing for development¶
First, clone the repository and change to the directory:
$ git clone https://github.com/goatd/goatd.git
$ cd goatd
Create a new virtualenv:
$ virtualenv goatd-dev-env
Activate this virtualenv:
$ source goatd-dev-env/bin/activate
Install goatd in editable mode from the local copy:
$ pip install --editable .
Installing when you don’t care and live life on the edge (system wide installation)¶
First install dependencies:
On any Debian based distribution (Debian, Ubuntu, Mint etc):
$ apt-get install python-yaml
On Red Hat systems (Fedora, CentOS, etc):
$ dnf install PyYAML
Then clone the repository and change to the directory:
$ git clone https://github.com/goatd/goatd.git
$ cd goatd
Run the installer:
$ sudo python setup.py install
Running goatd¶
Running with Docker:¶
Assumming you have built the docker image locally:
Quick-start:
$ docker run -d -p 2222:2222 goatd
$ curl localhost:2222
{"goatd": {"version": 1.3}}
By default, the image uses the example configuration, drivers and behaviours.
There are three major ways to develop using this Docker image.
- Modify the configuration and mount the custom configuration.
- Simply mount a directory (e.g. for plugins, drivers or behaviours).
- A combination of the above.
For example, to use a custom driver:
Running locally:¶
$ goatd --help
usage: goatd [-h] [--version] [CONFIG FILE]
Experimental robotic sailing goat daemon.
positional arguments:
CONFIG FILE a path to a configuration file
optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
After you have installed goatd, it can be run with $ goatd.
You will need to create a configuration file. It should look something like:
goatd:
port: 2222
interface: 127.0.0.1
plugin_directory: null
plugins:
- logger:
period: 10
filename: logs/gps_trace
driver:
file: example/basic_driver.py
behaviours:
- example:
file: example/basic_behaviour.py
The example config file (goatd-config.yaml.example) can be modified for
your goat.
Output will be similar to:
$ goatd
[15:43:55] loaded function heading as "heading"
[15:43:55] loaded function get_wind as "wind_direction"
[15:43:55] loaded function get_wind_speed as "wind_speed"
[15:43:55] loaded function position as "position"
[15:43:55] loaded function rudder as "rudder"
[15:43:55] loaded function sail as "sail"
[15:43:55] loaded driver from example/basic_driver.py
If you would like to use a different config file in a different location, pass
the path as an argument to goatd. For example, $ goatd /etc/goatd/fancy-conf.yaml.
Using the goatd API¶
Goatd’s main method of interaction is via the JSON API.
/¶
GETReturns the current status and version of goatd. Example output:
{ "goatd": { "version": 1.1 } }
/goat¶
GETReturns attributes about the current state of the goat. Example output:
{ "active": false, "position": [2.343443, null], "heading": 2.43, "wind": { "direction": 8.42, "speed": 25 } }
/wind¶
GETReturns properties of the wind. Example output:
{ "direction": 8.42, "speed": 25 }
/waypoints¶
GETReturns the active set of waypoints
{ "current": [1.0, 1.0], "home": [0.0, 0.0], "waypoints": [ [0.0, 0.0], [1.0, 1.0], [2.0, 2.0] ] }
POSTAdd to the current set of waypoints
{ "waypoints": [ [0.0, 0.0], [1.0, 1.0], [2.0, 2.0] ] }
/behaviours¶
GETReturns data about available and current behaviours. Example output:
{ "current": null, "behaviours": { "basic": { "filename": "example/basic_behaviour.py", "running": false } } }
POSTChange the currently running behaviour. Setting the current behaviour to
nullwill cause no behaviour to be run.Examples:
{ "current": null }
{ "current": "basic" }
Drivers¶
Driver basics¶
Goatd drivers are implemented as a simple user defined class in a loadable python module. When a behaviour script requires information about the current state of the goat or needs to send a command to some hardware, goatd runs one of the methods in the driver.
To write a driver, a python module should be created that contains an object
named driver. This object must be an instance of a class inheriting from
and implementing the interface defined in BaseGoatdDriver:
Note that the driver instance must be named driver, otherwise goatd
won’t know where to find it.
Example driver¶
An example:
import goatd
class MyFancyGoatDriver(goatd.BaseGoatdDriver):
def __init__(self):
# initialize some things here
pass
def heading(self):
return 30.0
def wind_direction(self):
return 45.0
def wind_speed(self):
return 4.0
def position(self):
return (0, 0)
def rudder(self, angle):
print('moving rudder to', angle)
def sail(self, angle):
print('moving sail to', angle)
def reconnect(self):
pass
# create an instance of the driver class
driver = MyFancyGoatDriver()
Configuring goatd to use a driver¶
Once you’ve written a driver, you can tell goatd to load it as the active
driver by setting scripts.driver in your configuration file. Eg:
scripts:
driver: example/driver.py
This can be a relative path, as with the example above. It can also be
absolute. goatd will also expand ~ to your home directory:
scripts:
driver: ~/git/sails-goatd-driver/driver.py
Plugins¶
Plugins are loadable python modules that run in a separate thread inside goatd. They have access to the current data about the goat.
Plugins are enabled with the main goatd configuration file. Each plugin may
have a few extra parameters, but all have the enabled parameter to enable
or disable it.
Example:
plugins:
- some_plugin_name:
enabled: true
Bundled plugins¶
Goatd comes with a few plugins preinstalled. These are:
loggerThis logs data about the current state of the goat to a file periodically.
Configuration parameters:
period- the time in seconds between each logged linefilename- the path to the file logs will be written to
Example:
plugins: - logger: enabled: true period: 10 filename: logs/log_trace
gpx_loggerThis logs data about the current state of the goat to a GPX formatted file periodically.
Configuration parameters:
period- the time in seconds between each logged linefilename- the path to the file logs will be written to, the filename
will be appended with a timestamp
Example:
plugins: - gpx_logger: enabled: true period: 1 filename: logs/gpx_log
mavlinkThis allows goatd to communicate using a subset of the mavlink protocol.
Configuration parameters:
device- the serial port to usebaud- baud rate to use with the serial port
Example:
plugins: - mavlink: enabled: true device: /dev/ttyUSB0 baud: 115200
Writing new plugins¶
To implement a plugin, a class must be implemented that conforms to a certain interface (similar to how drivers are defined). The interface is simple:
An example implementation would be:
from goatd import BasePlugin
class ExamplePlugin(BasePlugin):
def main(self):
while self.running:
position = self.goatd.goat.position()
print('logging some stuff ', position)
plugin = LoggerPlugin
Some things to note:
- You automatically get access to an object called
self.goatd. This contains agoatattribute which you can use to interact with the live goat. self.runningcan be used to check if the plugin should end. When the plugin is started by goatd, this will be set toTrue. When goatd is about to quit or plugins need to be stopped for some other reason, it will be set toFalse.
python-goatdclient¶
Goatd has a client library written for python. It contains a python wrapper module and a command line client.
You can install python-goatdclient from PyPi by running:
$ pip install python-goatdclient
Goatdclient includes the following user facing classes:
-
class
goatdclient.Goat(goatd=None, auto_update=True)[source]¶ A goat controlled by goatd
Parameters: auto_update – automatically update properties when they are requested. -
heading¶ Return the current heading of the goat in degrees.
Returns: current bearing Return type: Bearing
-
set_rudder(angle)[source]¶ Set the angle of the rudder to be angle degrees.
Parameters: angle (float between -90 and 90) – rudder angle
-
set_sail(angle)[source]¶ Set the angle of the sail to angle degrees
Parameters: angle (float between -90 and 90) – sail angle
-
target_rudder_angle¶ Return the current target rudder angle in degrees.
Returns: rudder angle Return type: float
-
target_sail_angle¶ Return the current target sail angle in degrees.
Returns: sail angle Return type: float
-
wind¶ Return the direction of the wind in degrees.
Returns: wind object containing direction bearing and speed Return type: Wind
-
-
class
goatdclient.Behaviour(goatd=None)[source]¶
Goat returns and uses special classes for bearings and latitude longitude
points. These contain some common functionality.
-
class
goatdclient.Point(latitude, longitude)[source]¶ A point on the face of the earth
-
bearing_to(point)[source]¶ Return the bearing to another point.
Parameters: point (Point) – Point to measure bearing to Returns: The bearing to the other point Return type: Bearing
-
cross_track_distance(start_point, end_point)[source]¶ Return the cross track distance from this point to the line between two points:
* end_point / / / * this point / / * start_point
Parameters: Returns: The perpendicular distance to the line between
start_pointandend_point, where distance on the right ofstart_pointis positive and distance on the left is negativeReturn type: float
-
distance_to(point)[source]¶ Return the distance between this point and another point in meters.
Parameters: point (Point) – Point to measure distance to Returns: The distance to the other point Return type: float
-
classmethod
from_radians(lat_radians, long_radians)[source]¶ Return a new instance of Point from a pair of coordinates in radians.
-
lat¶ Return the latitude in degrees
-
lat_radians¶ Return the latitude in radians
-
long¶ Return the longitude in degrees
-
long_radians¶ Return the longitude in radians
-
relative_point(bearing_to_point, distance)[source]¶ Return a waypoint at a location described relative to the current point
Parameters: - bearing_to_point (Bearing) – Relative bearing from the current waypoint
- distance (float) – Distance from the current waypoint
Returns: The point described by the parameters
-
-
class
goatdclient.Bearing(degrees)[source]¶ An angle between 0 and 360 degrees
Examples:
>>> Bearing(100) <Bearing (100.00 degrees clockwise from north) at 0x7f25e22b3710> >>> Bearing(100) + Bearing(100) <Bearing (200.00 degrees clockwise from north) at 0x7f25e22b3940> >>> Bearing(100) + Bearing(300) <Bearing (40.00 degrees clockwise from north) at 0x7f25e22b37b8> >>> Bearing(0) - Bearing(100) <Bearing (260.00 degrees clockwise from north) at 0x7f25e22b3940> >>> import math >>> Bearing.from_radians(math.pi) <Bearing (180.00 degrees clockwise from north) at 0x7f25e22b3828> >>> int(Bearing(120.4)) 120 >>> float(Bearing(120.4)) 120.4
-
delta(other)[source]¶ Return the error between this and another bearing. This will be an angle in degrees, positive or negative depending on the direction of the error.
- self other
- /
- /
- __/
- / <- angle will be +ve
- other self
- /
- /
- __/
- / <- angle will be -ve
Parameters: other (Bearing) – bearing to compare to Returns: error angle Return type: float
-
Testing¶
To run tests, install tox
$ pip install tox
and run tox. If all the tests pass, the output should be similar to:
$ tox
GLOB sdist-make: /home/louis/git/goatd/setup.py
py27 inst-nodeps: /home/louis/git/goatd/.tox/dist/goatd-1.1.3.zip
py27 installed: goatd==1.1.3,coverage==4.0.2,coveralls==1.1,docopt==0.6.2,p
luggy==0.3.1,py==1.4.30,pytest==2.8.2,pytest-cov==2.2.0,PyYAML==3.11,reques
ts==2.8.1,tox==2.2.1,virtualenv==13.1.2,wheel==0.24.0
py27 runtests: PYTHONHASHSEED='2985615961'
py27 runtests: commands[0] | py.test -v --cov goatd goatd
========================= test session starts ==========================
platform linux2 -- Python 2.7.10, pytest-2.8.2, py-1.4.30, pluggy-0.3.1 --
/home/louis/git/goatd/.tox/py27/bin/python2.7
cachedir: .cache
rootdir: /home/louis/git/goatd, inifile:
plugins: cov-2.2.0
collected 50 items
goatd/tests/test_api.py::TestAPI::test_GET PASSED
goatd/tests/test_api.py::TestAPI::test_content_type PASSED
... snipped
====================== 50 passed in 1.39 seconds =======================
_______________________________ summary ________________________________
py27: commands succeeded
py34: commands succeeded
pypy: commands succeeded
flake8: commands succeeded
congratulations :)
This will run all test environments. To run an individual environment, run
tox -e py27, or more generally tox -e <env>, replacing env with
py27, py34, pypy or flake8 (style checks).
The current test results from the head of the master branch can be found
here.
