├── .catkin.options
├── dependencies.rosinstall
├── utilities
├── map_bg_images
│ └── .empty
├── docker
│ ├── term-in-container.sh
│ ├── Dockerfile_indigo-ros-tutorials
│ ├── ssh-x-to-container.sh
│ ├── Dockerfile_indigo-ros-desktop-catkin
│ ├── build-all.sh
│ ├── start-container.sh
│ ├── Dockerfile_indigo-sailing-robot
│ └── Dockerfile_indigo-ros-base-catkin
├── .run_pigpiod.sh
├── setup_scripts
│ ├── pigpio.service
│ ├── Setup_SailingRobot_WS.sh
│ ├── Install_needed_packages.sh
│ ├── Setup_ALL.sh
│ ├── Setup_basic_configuration.sh
│ ├── Install_needed_packages_Raspi.sh
│ └── Setup_ROS.sh
├── postprocessing
│ ├── scrap_empty_gps_traces
│ ├── adjust.py
│ └── analyse_area_scanning
├── read_pwm.py
├── servo_wiggle.py
├── waypoint_generator
│ ├── waypoint_generator_startline.py
│ └── waypoint_generator_round_buoy.py
├── gen_map_image.py
├── setclock
└── send_ublx_gps_msg.py
├── src
├── sailing_robot
│ ├── tests
│ │ ├── __init__.py
│ │ ├── test_angle_average.py
│ │ ├── test_sail_table.py
│ │ ├── test_gps_utils.py
│ │ ├── test_tasks.py
│ │ ├── test_navigation.py
│ │ └── test_heading_planning_laylines.py
│ ├── src
│ │ └── sailing_robot
│ │ │ ├── __init__.py
│ │ │ ├── taskbase.py
│ │ │ ├── tack_control.py
│ │ │ ├── pid_data.py
│ │ │ ├── timeout.py
│ │ │ ├── heading_planning_dumb.py
│ │ │ ├── obstacle_waypoints.py
│ │ │ └── sail_table.py
│ ├── rosdoc.yaml
│ ├── doc
│ │ ├── modules.rst
│ │ ├── index.rst
│ │ └── sailing_robot.rst
│ ├── msg
│ │ ├── Velocity.msg
│ │ ├── gpswtime.msg
│ │ ├── NaVSOL.msg
│ │ └── BatteryState.msg
│ ├── launch
│ │ ├── parameters
│ │ │ ├── calibration_laser.yaml
│ │ │ ├── servos_laser.yaml
│ │ │ ├── servos_blackpython.yaml
│ │ │ ├── archive
│ │ │ │ ├── Viana
│ │ │ │ │ ├── viana_area_scanning_waypoints.yaml
│ │ │ │ │ ├── viana_obstacle_avoidance_waypoints.yaml
│ │ │ │ │ ├── viana_test_waypoints.yaml
│ │ │ │ │ ├── viana_fleetrace_waypoints.yaml
│ │ │ │ │ └── viana_position_keeping_waypoints.yaml
│ │ │ │ ├── Horten
│ │ │ │ │ ├── horten_wp
│ │ │ │ │ │ ├── obstacle_avoidance2.yaml
│ │ │ │ │ │ ├── obstacle_avoidance.yaml
│ │ │ │ │ │ ├── area_scanning.yaml
│ │ │ │ │ │ ├── obstacle_avoidance2_gen_obstacle.yaml
│ │ │ │ │ │ └── obstacle_avoidance_gen_obstacle.yaml
│ │ │ │ │ ├── horten_position_keeping_waypoints.yaml
│ │ │ │ │ └── horten_fleetrace_waypoints.yaml
│ │ │ │ └── Calshot
│ │ │ │ │ ├── calshot_station_keeping.yaml
│ │ │ │ │ ├── calshot_obstacle_avoidance_2.yaml
│ │ │ │ │ └── calshot_obstacle_avoidance_1.yaml
│ │ │ ├── calibration_111.yaml
│ │ │ ├── calibration_blackpython.yaml
│ │ │ ├── boldrewood_waypoints.yaml
│ │ │ ├── sailingClub_waypoints_obs.yaml
│ │ │ ├── Calshot_TriangleRace.yaml
│ │ │ ├── sailsettings_laser.yaml
│ │ │ ├── simulation_waypoints.yaml
│ │ │ ├── sailingClub_waypoints_area_scanning.yaml
│ │ │ ├── sailsettings_blackpython_rigA.yaml
│ │ │ ├── sailsettings_blackpython.yaml
│ │ │ ├── eastleigh_waypoints.yaml
│ │ │ ├── sailingClub_waypoints.yaml
│ │ │ └── simulator.yaml
│ │ ├── navsat_test.launch
│ │ ├── imu_fusion_test.launch
│ │ ├── archive
│ │ │ ├── milestone-1-dryland
│ │ │ │ └── sail-accross-boldrewood.launch
│ │ │ ├── 25_june__forDebugging.launch
│ │ │ ├── publish_highlevel_sailing_demand.launch
│ │ │ ├── test-all-systems.launch
│ │ │ ├── june_eastleigh_only_rpi.launch
│ │ │ ├── with-real-hardware.launch
│ │ │ ├── eastleigh-jul-03.launch
│ │ │ ├── 25_june.launch
│ │ │ ├── eastleigh-jul-17.launch
│ │ │ ├── sailingClub-jul-27.launch
│ │ │ ├── sailingClub-aug-23.launch
│ │ │ ├── sailingClub-aug-25.launch
│ │ │ ├── eastleigh-jul-24.launch
│ │ │ ├── Viana
│ │ │ │ ├── test-viana.launch
│ │ │ │ └── viana.launch
│ │ │ └── eastleigh-april-22.launch
│ │ ├── test-imu.launch
│ │ ├── test-gps.launch
│ │ ├── standby.launch
│ │ ├── test-wind-direction.launch
│ │ ├── rviz-bag-visualisation.launch
│ │ ├── test-calibration.launch
│ │ ├── dual_ekf_navsat_example.launch
│ │ ├── simulator.launch
│ │ └── use-shell-launch.sh
│ ├── rostests
│ │ └── test-1.test
│ ├── setup.py
│ ├── cfg
│ │ └── TackVoting.cfg
│ ├── scripts
│ │ ├── debugging_dump_params
│ │ ├── dummy_heading
│ │ ├── dummy_apparent_wind_direction
│ │ ├── tack
│ │ ├── actuator_demand_sail
│ │ ├── debugging_gps_log
│ │ ├── sensor_driver_battery
│ │ ├── dummy_position
│ │ ├── debugging_blink_on_sailing_state
│ │ ├── simulation_gps_fix
│ │ ├── actuator_driver_sail
│ │ ├── sensor_driver_multiplexer
│ │ ├── sensor_service_imu
│ │ ├── sensor_processed_wind_direction
│ │ ├── sensor_driver_imu_fusion
│ │ ├── actuator_demand_rudder
│ │ ├── wave_position
│ │ ├── force_jibe_tack
│ │ ├── actuator_driver_rudder
│ │ └── simulation_heading
│ └── package.xml
├── xsens_driver
│ ├── .gitignore
│ ├── README
│ ├── AUTHORS
│ ├── package.xml
│ ├── launch
│ │ └── xsens_driver.launch
│ ├── LICENCE
│ └── CHANGELOG.rst
└── CMakeLists.txt
├── dashboard
├── .gitignore
└── src
│ ├── res
│ ├── main.scss
│ └── base.scss
│ ├── jslib
│ └── fp.js
│ ├── main.css.map
│ └── main.css
├── piaccess
├── magellan
├── zhenghe
├── sftp
├── ssh
├── pullfrompi.sh
├── gitpull.sh
├── live-2D-plot.sh
├── push2pi.sh
├── usbip-stop-export.sh
├── setup_repo.sh
├── README.md
├── usbip-export.sh
├── time2pi.sh
└── usbip-bind.sh
├── recorded_data
├── notes
│ ├── 2017-06-17T17.20.49Z.json
│ ├── 2017-08-06T12.36.12Z.json
│ ├── 2018-05-13T12.40.14Z.json
│ ├── 2017-06-17T17.11.34Z.json
│ ├── 2017-08-06T11.00.29Z.json
│ ├── 2017-08-06T12.21.48Z.json
│ ├── 2017-08-06T10.43.39Z.json
│ ├── 2017-08-06T10.53.47Z.json
│ ├── 2017-06-17T17.12.44Z.json
│ ├── 2017-06-17T17.28.21Z.json
│ └── log_timed_note.py
└── marker-icon.png
├── notes
├── node-overview.pdf
├── soton-sail-club-waypoints.png
├── test-eastleigh-2016-07-03
├── roll_calibration
│ └── successful compensation.png
├── conventions
├── horten_waypoints
│ ├── obstacle_avoidance2.csv
│ ├── obstacle_avoidance.csv
│ └── area_scanning_micro.csv
├── setup_of_RPi
├── WindPolar
├── test-sailclub-2016-08-06
├── WRSC2016-collision-avoidance-points.txt
├── weather-conditions
├── test-eastleigh-2016-07-17
├── test-sailclub-2016-08-25
├── WRSC2016-area-scan-final-points.txt
├── usbip.txt
├── ComponentList.md
├── setup-list
├── packing-list
└── systems-checklist.md
├── calibration
├── README.md
├── monitor_imu.py
└── monitor_battery_voltage.py
├── LICENCE.txt
├── .gitignore
└── node_docstrings.py
/.catkin.options:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dependencies.rosinstall:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/utilities/map_bg_images/.empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/sailing_robot/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dashboard/.gitignore:
--------------------------------------------------------------------------------
1 | .sass-cache/
2 | build.sh
--------------------------------------------------------------------------------
/src/sailing_robot/src/sailing_robot/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/piaccess/magellan:
--------------------------------------------------------------------------------
1 | export SAIL_PI_IP=192.168.42.1
2 |
--------------------------------------------------------------------------------
/piaccess/zhenghe:
--------------------------------------------------------------------------------
1 | export SAIL_PI_IP=10.42.0.1
2 |
3 |
--------------------------------------------------------------------------------
/src/xsens_driver/.gitignore:
--------------------------------------------------------------------------------
1 | .*.sw?
2 | *~
3 | *.pyc
4 |
--------------------------------------------------------------------------------
/src/sailing_robot/rosdoc.yaml:
--------------------------------------------------------------------------------
1 | - builder: sphinx
2 | sphinx_root_dir: doc
3 |
--------------------------------------------------------------------------------
/recorded_data/notes/2017-06-17T17.20.49Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "Disabled jibe forcing"
3 | }
--------------------------------------------------------------------------------
/recorded_data/notes/2017-08-06T12.36.12Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "needs more condom"
3 | }
--------------------------------------------------------------------------------
/recorded_data/notes/2018-05-13T12.40.14Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "imu died several times today"
3 | }
--------------------------------------------------------------------------------
/recorded_data/notes/2017-06-17T17.11.34Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "Tack voting increased to 20 seconds"
3 | }
--------------------------------------------------------------------------------
/recorded_data/notes/2017-08-06T11.00.29Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "Redo wiring with heatshrink in radio box"
3 | }
--------------------------------------------------------------------------------
/recorded_data/notes/2017-08-06T12.21.48Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "in, remote controlled, to record accel data"
3 | }
--------------------------------------------------------------------------------
/src/sailing_robot/doc/modules.rst:
--------------------------------------------------------------------------------
1 | src
2 | ===
3 |
4 | .. toctree::
5 | :maxdepth: 4
6 |
7 | sailing_robot
8 |
--------------------------------------------------------------------------------
/recorded_data/notes/2017-08-06T10.43.39Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "Calibration 003 is in use. Will test with sails on."
3 | }
--------------------------------------------------------------------------------
/src/xsens_driver/README:
--------------------------------------------------------------------------------
1 | ROS Driver for XSens MT/MTi/MTi-G devices.
2 |
3 | See: http://ros.org/wiki/ethzasl_xsens_driver
4 |
--------------------------------------------------------------------------------
/notes/node-overview.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maritime-Robotics-Student-Society/sailing-robot/HEAD/notes/node-overview.pdf
--------------------------------------------------------------------------------
/piaccess/sftp:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "SFTP-ing to the pi at ${SAIL_PI_IP:=192.168.12.1}..."
5 | sftp pi@$SAIL_PI_IP
6 |
--------------------------------------------------------------------------------
/piaccess/ssh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "SSH-ing to the pi at ${SAIL_PI_IP:=192.168.12.1}..."
5 | ssh -X pi@$SAIL_PI_IP
6 |
--------------------------------------------------------------------------------
/recorded_data/marker-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maritime-Robotics-Student-Society/sailing-robot/HEAD/recorded_data/marker-icon.png
--------------------------------------------------------------------------------
/recorded_data/notes/2017-08-06T10.53.47Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "Take measurements of all ropes so we know how to fix them if needed"
3 | }
--------------------------------------------------------------------------------
/src/sailing_robot/msg/Velocity.msg:
--------------------------------------------------------------------------------
1 | # message for publishing GPS velocity
2 | float32 speed # [m/s]
3 | float32 heading # [degrees]
4 |
5 |
--------------------------------------------------------------------------------
/notes/soton-sail-club-waypoints.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maritime-Robotics-Student-Society/sailing-robot/HEAD/notes/soton-sail-club-waypoints.png
--------------------------------------------------------------------------------
/recorded_data/notes/2017-06-17T17.12.44Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "Investigate jibe recovery - it always seems to go opposite to the tack that timed out."
3 | }
--------------------------------------------------------------------------------
/src/sailing_robot/msg/gpswtime.msg:
--------------------------------------------------------------------------------
1 | # Message for GPS fix with time
2 | sensor_msgs/NavSatFix fix
3 | uint16 time_h
4 | uint16 time_m
5 | uint16 time_s
6 |
--------------------------------------------------------------------------------
/notes/test-eastleigh-2016-07-03:
--------------------------------------------------------------------------------
1 | Wifi connection dodgy - antenna too low in boat?
2 |
3 | Servos not responding promptly - fixed with change to servo_control node
4 |
--------------------------------------------------------------------------------
/notes/roll_calibration/successful compensation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Maritime-Robotics-Student-Society/sailing-robot/HEAD/notes/roll_calibration/successful compensation.png
--------------------------------------------------------------------------------
/recorded_data/notes/2017-06-17T17.28.21Z.json:
--------------------------------------------------------------------------------
1 | {
2 | "message": "What's going on with coming to waypoint 4? It heads in a beautiful straight line, but not to the waypoint."
3 | }
4 |
--------------------------------------------------------------------------------
/notes/conventions:
--------------------------------------------------------------------------------
1 | simulation of actuators and simulators is in the same package as actuators and simulators
2 | naming: description-partnumber
3 | e.g.
4 | gps-ublox7
5 | gps-simulated
6 |
7 |
--------------------------------------------------------------------------------
/notes/horten_waypoints/obstacle_avoidance2.csv:
--------------------------------------------------------------------------------
1 | New waypoints for anti-collision ,
2 | ,
3 | 1,"59.426883, 10.470271"
4 | 2,"59.427039, 10.470378"
5 | 3,"59.427353, 10.46878"
6 | 4,"59.427189, 10.468619"
7 |
--------------------------------------------------------------------------------
/notes/setup_of_RPi:
--------------------------------------------------------------------------------
1 | Raspberry Pi Setup for Robot Operate System
2 |
3 | Hardware version of Raspberry Pi: RASPBERRY PI 2 MODEL B
4 |
5 | Operate system: Raspbian
6 |
7 | Robot Operate System version: Indigo
8 |
9 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/calibration_laser.yaml:
--------------------------------------------------------------------------------
1 | compass: {XOFFSET: -1140, XSCALE: 2809, YOFFSET: 34, YSCALE: 2998}
2 | wind_dir: {ANGLEOFFSET: 0, XOFFSET: -472, XSCALE: 54710, YOFFSET: -472, YSCALE: 51222}
3 |
--------------------------------------------------------------------------------
/notes/WindPolar:
--------------------------------------------------------------------------------
1 | (windirection angle, wind speed): pwm setting
2 |
3 |
4 | (36, 2):965
5 | (40,2):1000
6 | (53,2):1025
7 | (85,2):1115
8 | (105,2):1265
9 | (160,2):1475
10 | (178,2):1640
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/utilities/docker/term-in-container.sh:
--------------------------------------------------------------------------------
1 | # Open another terminal to an existing sailing-robot docker container.
2 | # This works when you have run start-container.sh to start the docker container.
3 | docker exec -it sailing-robot bash
4 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/servos_laser.yaml:
--------------------------------------------------------------------------------
1 | rudder/PWMoffset: 85
2 | rudder/servolowerlimits: 1140
3 | rudder/servohigherlimits: 1900
4 |
5 | sail/PWMoffset: 0
6 | sail/servolowerlimits: 1100
7 | sail/servohigherlimits: 1900
8 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/servos_blackpython.yaml:
--------------------------------------------------------------------------------
1 | rudder/pin: 13
2 | rudder/PWMoffset: 85
3 | rudder/servolowerlimits: 1000
4 | rudder/servohigherlimits: 2000
5 |
6 | sail/pin: 24
7 | # Sail PWM limits moved to sailsettings config files
8 |
--------------------------------------------------------------------------------
/utilities/docker/Dockerfile_indigo-ros-tutorials:
--------------------------------------------------------------------------------
1 | FROM sotonsailbot/ros:indigo-desktop-catkin
2 | Maintainer Martin Biggs
3 |
4 | RUN sudo apt-get update \
5 | && sudo apt-get install --assume-yes ros-indigo-ros-tutorials \
6 | && sudo rm -fr /var/lib/apt/lists
7 |
--------------------------------------------------------------------------------
/utilities/.run_pigpiod.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # check if pigpiod is running and run it if it is not
3 |
4 | if ps ax | grep -v grep | grep pigpiod > /dev/null
5 | then
6 | echo pigpiod already running
7 | else
8 | echo launching pigpiod
9 | sudo pigpiod
10 | fi
11 |
--------------------------------------------------------------------------------
/utilities/setup_scripts/pigpio.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Daemon required to control GPIO pins via pigpio
3 | [Service]
4 | ExecStart=/usr/bin/pigpiod -l
5 | ExecStop=/bin/systemctl kill -s SIGKILL pigpiod
6 | Type=forking
7 | [Install]
8 | WantedBy=multi-user.target
9 |
--------------------------------------------------------------------------------
/piaccess/pullfrompi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "On ${SAIL_PI_IP:=192.168.12.1}, pushing to bareclone..."
5 | ssh pi@$SAIL_PI_IP 'cd ~/sailing-robot; git push bareclone master'
6 |
7 | echo "Pulling from the pi..."
8 | git pull pi@$SAIL_PI_IP:sailing-robot-bare master
9 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Viana/viana_area_scanning_waypoints.yaml:
--------------------------------------------------------------------------------
1 | wp/table: {"A": [41.6908721, -8.8231420],
2 | "B": [41.6904579, -8.8226779],
3 | "D": [41.6900449, -8.8222149],
4 | "E": [41.6915660, -8.8220369],
5 | }
6 |
--------------------------------------------------------------------------------
/piaccess/gitpull.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Pushing to bareclone on the pi at ${SAIL_PI_IP:=192.168.12.1}..."
5 | ssh pi@$SAIL_PI_IP 'cd ~/sailing-robot; git push bareclone master'
6 |
7 | echo "Pulling from the pi..."
8 | git pull pi@$SAIL_PI_IP:sailing-robot-bare master
9 |
--------------------------------------------------------------------------------
/piaccess/live-2D-plot.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Connecting to ROS at ${SAIL_PI_IP:=192.168.12.1}, launching 2D plot node locally..."
5 | export ROS_IP=$SAIL_PI_IP
6 | export ROS_MASTER_URI=http://$SAIL_PI_IP:11311
7 |
8 | rosrun sailing_robot debugging_2D_plot_matplot
9 |
--------------------------------------------------------------------------------
/utilities/docker/ssh-x-to-container.sh:
--------------------------------------------------------------------------------
1 | # SSH to the running docker container with X forwarding.
2 | # This works when you have run start-container.sh to start the docker container,
3 | # and allows you to run graphical programs such as rviz. Probably Linux-only.
4 | ssh 172.17.0.2 -l pi -X
5 |
--------------------------------------------------------------------------------
/piaccess/push2pi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | echo "Pushing to the pi at ${SAIL_PI_IP:=192.168.12.1}..."
5 | git push pi@$SAIL_PI_IP:sailing-robot-bare
6 |
7 |
8 | echo "Pulling from bareclone on the pi..."
9 | ssh pi@$SAIL_PI_IP 'cd ~/sailing-robot; git pull bareclone master'
10 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/calibration_111.yaml:
--------------------------------------------------------------------------------
1 | compass: {XOFFSET: -1102, XSCALE: 1990, YOFFSET: 348, YSCALE: 2174, ZOFFSET: 2028.563290580735,
2 | ZSCALE: 389.26594613530216}
3 | wind_dir: {ANGLEOFFSET: 93.46941600491634, XOFFSET: -55, XSCALE: 51501, YOFFSET: -55,
4 | YSCALE: 47610}
5 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Horten/horten_wp/obstacle_avoidance2.yaml:
--------------------------------------------------------------------------------
1 | # Changing wind necessitated a new course
2 |
3 | wp/table: {
4 | "wp1": [59.426883, 10.470271],
5 | "wp4": [59.427039, 10.470378],
6 | "wp3": [59.427353, 10.46878],
7 | "wp2": [59.427189, 10.468619],
8 | }
9 |
--------------------------------------------------------------------------------
/notes/test-sailclub-2016-08-06:
--------------------------------------------------------------------------------
1 | Calibration before setting sails?
2 |
3 | Couldn't get a sensible reading from the wind sensor - where's the script?
4 |
5 | Our own screwdriver
6 |
7 | Can't tack in moderate waves - boat has too little momentum, speed dies quickly
8 |
9 | Water ingress! Control lost
10 |
--------------------------------------------------------------------------------
/utilities/docker/Dockerfile_indigo-ros-desktop-catkin:
--------------------------------------------------------------------------------
1 | FROM sotonsailbot/ros:indigo-base-catkin
2 | Maintainer Martin Biggs
3 |
4 | USER root
5 | RUN apt-get update \
6 | && apt-get upgrade --assume-yes \
7 | # TODO narrow down what ROS packages I need
8 | && apt-get install --assume-yes ros-indigo-desktop
9 | USER pi
10 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/calibration_blackpython.yaml:
--------------------------------------------------------------------------------
1 | compass:
2 | XOFFSET: -1102
3 | XSCALE: 1990
4 | YOFFSET: 348
5 | YSCALE: 2174
6 | ZOFFSET: 2028.563290580735
7 | ZSCALE: 389.26594613530216
8 | wind_dir:
9 | ANGLEOFFSET: 15.484221017173432
10 | XOFFSET: -693
11 | XSCALE: 21734
12 | YOFFSET: 896
13 | YSCALE: 20931
14 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Horten/horten_wp/obstacle_avoidance.yaml:
--------------------------------------------------------------------------------
1 |
2 | wp/table: {
3 | "start1": [59.426911, 10.469397000000072],
4 | "start2": [59.427009, 10.469669999999951],
5 | "wp1": [59.426911, 10.469397000000072],
6 | "wp4": [59.427009, 10.469669999999951],
7 | "wp3": [59.428095, 10.468119999999999],
8 | "wp2": [59.427999, 10.467841000000021],
9 | }
10 |
--------------------------------------------------------------------------------
/piaccess/usbip-stop-export.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Stop exporting the Xsens USB device over IP.
3 | # This is called from usbip-bind.sh on Tabarly.
4 | set -e
5 |
6 | USBIP_DIR=~/raspi-linux/linux-raspberrypi-kernel_1.20161215-1/tools/usb/usbip/src
7 |
8 | BUSID=$(cat /tmp/xsens_busid)
9 | echo "Unbinding bus ID: $BUSID"
10 | sudo $USBIP_DIR/usbip unbind -b $BUSID
11 | rm -f /tmp/xsens_busid
12 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Viana/viana_obstacle_avoidance_waypoints.yaml:
--------------------------------------------------------------------------------
1 | wp/table: {"wp1": [41.6903040, -8.8225033],
2 | "wp2": [41.6911709, -8.8214719],
3 | "wp3": [41.6910329, -8.8213570],
4 | "wp4": [41.6901650, -8.8223889],
5 | "green1": [41.6903000, -8.8231199],
6 | "green2": [41.6914597, -8.8211700],
7 | }
8 |
--------------------------------------------------------------------------------
/notes/WRSC2016-collision-avoidance-points.txt:
--------------------------------------------------------------------------------
1 |
2 | 0, 41.6903000, -8.8231199, "boia verde 11");
3 | 1, 41.6914597, -8.8211700, "boia verde 13");
4 |
5 | -----------------------------------------
6 | Collision avoidance:
7 | 4, 41.6903040, -8.8225033 West
8 | 5, 41.6911709, -8.8214719 North
9 | 6, 41.6910329, -8.8213570 East
10 | 7, 41.6901650, -8.8223889 South
--------------------------------------------------------------------------------
/utilities/docker/build-all.sh:
--------------------------------------------------------------------------------
1 | docker build -t sotonsailbot/ros:indigo-base-catkin -f ./Dockerfile_indigo-ros-base-catkin ../ && \
2 | docker build -t sotonsailbot/ros:indigo-desktop-catkin -f ./Dockerfile_indigo-ros-desktop-catkin ../ && \
3 | docker build -t sotonsailbot/ros:indigo-ros-tutorials -f ./Dockerfile_indigo-ros-tutorials ../
4 | docker build -t sotonsailbot/ros:indigo -f ./Dockerfile_indigo-sailing-robot ../
5 |
--------------------------------------------------------------------------------
/notes/weather-conditions:
--------------------------------------------------------------------------------
1 | 2017-09-04
2 | Horten
3 | 10 kts gusting 15 kts
4 | north-east wind, strong waves, no white horses
5 | used: small sails, tack-jibing
6 |
7 | 2017-09-05
8 | forecast:
9 | 5 kts, gusting 8, increasing to gusting 11 between 12h and 15h
10 | north-east wind, changing to east between 12h and 15h
11 |
12 | plan: use small sails, tack jibing; only reason for sail change would be strong current
13 |
14 |
--------------------------------------------------------------------------------
/notes/horten_waypoints/obstacle_avoidance.csv:
--------------------------------------------------------------------------------
1 | Anti-Collision all boats ,,,,
2 | Line,CollisionStart,WRSC 2017 Anti-Collision,59.426911_59.427009,10.469397000000072_10.469669999999951
3 | Point,CollisionFirst,WRSC 2017 Anti-Collision,59.426911,10.469397000000072
4 | Point,CollisionSecond,WRSC 2017 Anti-Collision,59.427009,10.469669999999951
5 | Point,CollisonThird,WRSC 2017 Anti-Collision,59.428095,10.468119999999999
6 | Point,Collison4,WRSC 2017 Anti-Collision,59.427999,10.467841000000021
7 |
--------------------------------------------------------------------------------
/utilities/postprocessing/scrap_empty_gps_traces:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from __future__ import print_function
3 | import glob
4 | import os
5 |
6 | target_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7 | print("Looking for files in", target_dir)
8 |
9 | for file in glob.glob(os.path.join(target_dir, 'gps-trace-*.csv')):
10 | if os.stat(file).st_size == 0:
11 | print("Removing empty file", os.path.basename(file))
12 | os.unlink(file)
13 |
--------------------------------------------------------------------------------
/src/sailing_robot/src/sailing_robot/taskbase.py:
--------------------------------------------------------------------------------
1 | """Base class for task classes.
2 |
3 | This has almost no implementation; the debugging methods are injected by
4 | tasks_ros. This allows us to test task classes outside ROS.
5 | """
6 |
7 | class TaskBase(object):
8 | debug_topics = []
9 |
10 | def debug_pub(self, topic, value):
11 | pass
12 |
13 | def log(self, level, msg, *values):
14 | print(msg % values)
15 |
16 | def init_ros(self):
17 | pass
18 |
--------------------------------------------------------------------------------
/piaccess/setup_repo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Set up the repository clones on a blank raspberry pi.
4 | # Prepare the directories expected by push2pi and pullfrompi.
5 | # This requires an internet connection to clone from Github.
6 |
7 | ssh pi@$SAIL_PI_IP <<'ENDSSH'
8 | git clone https://github.com/Maritime-Robotics-Student-Society/sailing-robot.git sailing-robot
9 | git clone --bare sailing-robot sailing-robot-bare
10 | cd sailing-robot
11 | git remote add bareclone ~/sailing-robot-bare
12 | ENDSSH
13 |
--------------------------------------------------------------------------------
/src/sailing_robot/rostests/test-1.test:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Horten/horten_wp/area_scanning.yaml:
--------------------------------------------------------------------------------
1 | wp/table: {
2 | "start1" : [59.426946, 10.471205000000054],
3 | "start2" : [59.427093, 10.469810000000052],
4 | "finish1" :[59.427246, 10.46841500000005],
5 | "finish2" :[59.427164, 10.469155],
6 | "p1" : [59.426946, 10.4712050000001],
7 | "p2" : [59.428332, 10.471762],
8 | "p3" : [59.428621, 10.469016],
9 | "p4" : [59.427928, 10.468715],
10 | "p5" : [59.427759, 10.47011],
11 | "p6" : [59.427093, 10.4698100000001]
12 | }
13 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Calshot/calshot_station_keeping.yaml:
--------------------------------------------------------------------------------
1 |
2 | wp/tasks: [
3 | {"kind": "to_waypoint", "waypoint": "mark"},
4 | {"kind": "keep_station", "waypoint": "mark", "linger": 30000, "wind_angle": 45, "radius": 1.5},
5 | ]
6 |
7 | # distance within waypoint is accepted, in [m]
8 | wp/acceptRadius: 2
9 | wp/tackVotingRadius: 20
10 |
11 |
12 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
13 | #
14 | wp/table: {"mark" : [50.82046666, -1.328633333],
15 | }
16 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Horten/horten_position_keeping_waypoints.yaml:
--------------------------------------------------------------------------------
1 |
2 | wp/tasks: [
3 | {"kind": "to_waypoint", "waypoint": "mark"},
4 | {"kind": "keep_station", "waypoint": "mark", "linger": 3000, "wind_angle": 45, "radius": 1.5},
5 | ]
6 |
7 | # distance within waypoint is accepted, in [m]
8 | wp/acceptRadius: 2
9 | wp/tackVotingRadius: 20
10 |
11 |
12 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
13 | #
14 | wp/table: {"mark" : [59.425784, 10.4731249],
15 | }
16 |
--------------------------------------------------------------------------------
/piaccess/README.md:
--------------------------------------------------------------------------------
1 | This folder contains scripts for communicating with the raspis we use.
2 |
3 | First, source one of the pi identity files, which set $SAIL_PI_IP:
4 |
5 | source piaccess/magellan
6 | # OR
7 | source piaccess/zhenghe
8 |
9 | Then run:
10 |
11 | - `piaccess/ssh` : SSH to the raspi
12 | - `piaccess/time2pi.sh` : Set the raspi's clock from your computer
13 | - `piaccess/push2pi.sh` : Push this git repository to the raspi
14 | - `piaccess/check_compass_calib.py` : Plot the latest compass calibration data
15 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/navsat_test.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/imu_fusion_test.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/sailing_robot/setup.py:
--------------------------------------------------------------------------------
1 | ## ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD
2 | ## http://docs.ros.org/api/catkin/html/howto/format2/installing_python.html## pdf download of the page in sources folder:
3 | ## docs-ros_installing_python.pdf
4 | from distutils.core import setup
5 | from catkin_pkg.python_setup import generate_distutils_setup
6 |
7 | # fetch values from package.xml
8 | setup_args = generate_distutils_setup(
9 | packages=['sailing_robot'],
10 | package_dir={'': 'src'})
11 |
12 | setup(**setup_args)
13 |
14 |
--------------------------------------------------------------------------------
/utilities/setup_scripts/Setup_SailingRobot_WS.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | cd ~
4 |
5 | # Clone from git
6 | git clone https://github.com/Maritime-Robotics-Student-Society/sailing-robot.git
7 |
8 | # Resolve dependencies with rosdep
9 | cd ~/sailing-robot
10 | rosdep install --from-paths src --ignore-src --rosdistro indigo -y -r --os=debian:jessie
11 |
12 | # Build catkin workspace
13 | catkin_make
14 |
15 | # Source new installation
16 | source devel/setup.bash
17 | echo "source /home/pi/sailing-robot/devel/setup.bash" >> ~/.bashrc
18 |
19 | cd ~
20 |
--------------------------------------------------------------------------------
/src/sailing_robot/src/sailing_robot/tack_control.py:
--------------------------------------------------------------------------------
1 | class Tacking(object):
2 | # This doesn't yet have any state, but it may in the future
3 | def calculate_sail_and_rudder(self, state):
4 | if state in ('switch_to_port_tack', ):
5 | # Turning right
6 | return 0, -90
7 | elif state in ('switch_to_stbd_tack', ):
8 | # Turning left
9 | return 0, 90
10 | else:
11 | # Normal sailing - it should use sail and rudder angles from elsewhere
12 | return 0, 0
13 |
--------------------------------------------------------------------------------
/src/sailing_robot/cfg/TackVoting.cfg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | PACKAGE = "sailing_robot"
3 |
4 | from dynamic_reconfigure.parameter_generator_catkin import *
5 |
6 | gen = ParameterGenerator()
7 |
8 | gen.add("radius", double_t, 0, "radius within which the tack voting begins", 2, 0, 20)
9 | gen.add("samples", double_t, 0, "number of samples to be taken into account for vote", 50, 1, 100)
10 | gen.add("threshold", double_t, 0, "percentage of samples that need to be above threshold", 0.8, 0, 1)
11 |
12 | exit(gen.generate(PACKAGE, "tasks", "TackVoting"))
13 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/boldrewood_waypoints.yaml:
--------------------------------------------------------------------------------
1 |
2 | # for simplified entry of path, simply add list of waypoint names
3 | # starts with list entry 0, continues until end is reached
4 | wp/list: ["1", "2", "3", "1"]
5 |
6 |
7 | # distance within waypoint is accepted, in [m]
8 | wp/acceptRadius: 1.5
9 |
10 |
11 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
12 | #
13 | wp/table: {"1" : [50.936981, -1.405315],
14 | "2" : [50.93716, -1.405683],
15 | "3" : [50.936908, -1.406061],
16 | }
17 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/debugging_dump_params:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Dump all parameters, to match up with our rosbag files.
3 | """
4 | from datetime import datetime
5 | import json
6 | import os.path
7 | import rospy
8 |
9 | FILENAME_BASE = "~/sailing-robot/params-dump_{}_{}.json"
10 |
11 | params = rospy.get_param('/')
12 |
13 | filename = FILENAME_BASE.format(params.get('log_name', ''),
14 | datetime.now().strftime('%Y-%m-%dT%H.%M.%S'))
15 | filename = os.path.expanduser(filename)
16 |
17 | with open(filename, 'w') as f:
18 | json.dump(params, f, indent=2)
19 |
--------------------------------------------------------------------------------
/src/sailing_robot/tests/test_angle_average.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from sailing_robot.navigation import angle_average
4 |
5 | class AngleAverageTests(unittest.TestCase):
6 | def test_realValues(self):
7 | self.assertAlmostEqual(angle_average([1,2,3,4,5]), 3)
8 | self.assertAlmostEqual(angle_average([10,90]), 50)
9 | self.assertAlmostEqual(angle_average([11,351]), 1)
10 |
11 | def test_extreme(self):
12 | angle_average([])
13 |
14 | angle_average([1,1,1])
15 |
16 | angle_average([1,359])
17 |
18 | angle_average([90,270])
19 |
--------------------------------------------------------------------------------
/utilities/docker/start-container.sh:
--------------------------------------------------------------------------------
1 | # Start the ROS docker container, with this repository mounted inside it as
2 | # $HOME/sailing-robot
3 |
4 | # This script lives in utilities/docker/
5 | # The sailing-robot repo is two levels up.
6 | this_file=$(readlink -f "$0")
7 | docker=$(dirname "$this_file")
8 | utils=$(dirname "$docker")
9 | repo=$(dirname "$utils")
10 |
11 | echo "Starting docker container with $repo mounted at ~/sailing-robot"
12 |
13 | docker run --name sailing-robot -h sailing-robot -v "$repo:/home/pi/sailing-robot"\
14 | -p 8448:8448 --rm -it sotonsailbot/ros:indigo
15 |
--------------------------------------------------------------------------------
/dashboard/src/res/main.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | // variables.less
4 | $theme-primary: /*#33CCFF*/#336699;
5 | $theme-secondary: #33CC99;
6 | $theme-light: /*white*/#FFFF33;
7 | // $theme-dark: #222;
8 | $theme-dark: /*#336666*/#003333;
9 | // Mine variables
10 | $header-background-color: rgba(0, 0, 0, 0.7);
11 | $header-padding: 50px 15px;
12 | $sponsor-anchors: #33CCFF;
13 |
14 | $log-level-debug: rgb(2, 117, 216);
15 | $log-level-info: rgb(91, 192, 222);
16 | $log-level-warn: rgb(240, 173, 78);
17 | $log-level-error: rgb(217, 83, 79);
18 | $log-level-fatal: black;
19 |
20 | @import "base";
--------------------------------------------------------------------------------
/src/xsens_driver/AUTHORS:
--------------------------------------------------------------------------------
1 | Francis Colas
2 |
3 | Stéphane Magnenat
4 | Nikolaus Demmel
5 | Benjamin Hitov
6 | Enrique Fernandez
7 | Sam Pfeiffer
8 | Paul Mathieu
9 | François Pomerleau
10 | João Sousa
11 | Vincent Rousseau
12 | Martin Pecka
13 | Konstantinos Chatzilygeroudis
14 |
--------------------------------------------------------------------------------
/src/sailing_robot/doc/index.rst:
--------------------------------------------------------------------------------
1 | .. Soton Pirate RoBoat documentation master file, created by
2 | sphinx-quickstart on Wed Jul 6 20:40:44 2016.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to Soton Pirate RoBoat's documentation!
7 | ===============================================
8 |
9 | Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 | nodes
15 |
16 |
17 |
18 | Indices and tables
19 | ==================
20 |
21 | * :ref:`genindex`
22 | * :ref:`modindex`
23 | * :ref:`search`
24 |
25 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Viana/viana_test_waypoints.yaml:
--------------------------------------------------------------------------------
1 |
2 | wp/tasks: [
3 | {"kind": "to_waypoint", "waypoint": "1"},
4 | {"kind": "to_waypoint", "waypoint": "2"},
5 | {"kind": "to_waypoint", "waypoint": "3", "jump_label": "last_point"},
6 | ]
7 |
8 | # distance within waypoint is accepted, in [m]
9 | wp/acceptRadius: 1.5
10 | wp/tackVotingRadius: 20
11 |
12 |
13 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
14 | #
15 | wp/table: {"1" : [41.6929383, -8.8217432],
16 | "2" : [41.6926909, -8.8218753],
17 | "3" : [41.6927538, -8.8222401]
18 | }
19 |
--------------------------------------------------------------------------------
/dashboard/src/jslib/fp.js:
--------------------------------------------------------------------------------
1 | const fp = {
2 | range(start, count) {
3 | return Array(...Array(count)).map((_, i) => start + i);
4 | },
5 |
6 | curry(uncurried, ...outerArgs) {
7 | if (outerArgs.length === 0) {
8 | return uncurried;
9 | }
10 | return function (...innerArgs) {
11 | uncurried(...outerArgs.concat(innerArgs));
12 | }
13 | },
14 |
15 | compose(...fxns) {
16 | if (fxs.length === 1) {
17 | return function (x) {
18 | return fxns[0](x);
19 | }
20 | }
21 | return function (x) {
22 | return fxns[0](compose(...fxns.slice(1))(0));
23 | }
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/milestone-1-dryland/sail-accross-boldrewood.launch:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/notes/test-eastleigh-2016-07-17:
--------------------------------------------------------------------------------
1 | utilities script to check for gps fix
2 | Run something to keep reading GPS, to maintain the fix once we've got it
3 |
4 | Check before leaving that we have the latitude
5 |
6 | More double sided tape
7 | test laylines with simulator
8 | keep voting in
9 | see how close we can get to the Wind
10 | wind speed dependent: closes angle to wind; sail sheet table
11 |
12 | Dump params on launch to match rosbags
13 |
14 | Make sure batteries are charged beforehand!
15 |
16 | Notes of what happened:
17 |
18 | Wind direction averaging time decreased from 2s to .5s, c. 7.30pm
19 | Course changed from 11, 9, 10 to 11, 8, 7, 12, c. 8pm
20 |
--------------------------------------------------------------------------------
/calibration/README.md:
--------------------------------------------------------------------------------
1 | The scripts in this directory calibrate sensors on the boat.
2 |
3 | - `run_calibration.sh`: calibrates compass and wind direction sensors,
4 | dumps ROS parameters to a YAML file.
5 | - `compasscalib_roll`: calibrates the MinIMU compass, including roll compensation
6 | - `wind_direction_calib`: calibrates the wind direction sensor
7 | - See also `piaccess/check_compass_calib.py` to visualise the results of a
8 | compass calibration.
9 | - `camera_detection_calibration.py`: calibrate the object recognition code
10 | using either a webcam or image files.
11 |
12 | The Xsens compass has to be calibrated separately using a laptop.
13 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/sailingClub_waypoints_obs.yaml:
--------------------------------------------------------------------------------
1 | # CAUTION:
2 | # Some of these waypoints may not be suitable in all tide conditions!!
3 | # for simplified entry of path, simply add list of waypoint names
4 | # starts with list entry 0, continues until end is reached
5 | wp/list: [ "wp1"]
6 |
7 | # distance within waypoint is accepted, in [m]
8 | wp/acceptRadius: 3.0
9 | wp/tackVotingRadius: 10
10 |
11 |
12 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
13 | #
14 | wp/table: {"wp1" : [50.879044, -1.367864],
15 | "wp2" : [50.879101, -1.364584],
16 | "wp4" : [50.878658, -1.367824]
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/Calshot_TriangleRace.yaml:
--------------------------------------------------------------------------------
1 | # CAUTION:
2 | # Some of these waypoints may not be suitable in all tide conditions!!
3 | # for simplified entry of path, simply add list of waypoint names
4 | # starts with list entry 0, continues until end is reached
5 |
6 | wp/list: ["3", "1", "2","3"]
7 |
8 |
9 | # distance within waypoint is accepted, in [m]
10 | wp/acceptRadius: 3.5
11 | wp/tackVotingRadius: 20
12 |
13 |
14 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
15 | #
16 | wp/table: { "1" : [50.822000,-1.31188333],
17 | "2" : [50.821183,-1.3117666],
18 | "3" : [50.821116,-1.310633]
19 | }
20 |
--------------------------------------------------------------------------------
/piaccess/usbip-export.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Exports the Xsens USB device over IP.
3 | # This is called from usbip-bind.sh on Tabarly.
4 | set -e
5 |
6 | echo $USER
7 |
8 | USBIP_DIR=~/raspi-linux/linux-raspberrypi-kernel_1.20161215-1/tools/usb/usbip/src
9 | XSENS_USBID="2639:0300"
10 |
11 | # Get the bus ID for the Xsens
12 | BUSID=$($USBIP_DIR/usbip list --parsable --local | awk -F# "\$2 ~/$XSENS_USBID/ { sub(/busid=/, \"\"); print \$1}")
13 | echo "Bus ID: $BUSID"
14 |
15 | # Start the daemon
16 | sudo $USBIP_DIR/usbipd -D
17 | sleep 0.2 # Give daemon a chance to start
18 |
19 | # Bind the device
20 | sudo $USBIP_DIR/usbip bind -b $BUSID
21 | echo $BUSID > /tmp/xsens_busid
22 |
--------------------------------------------------------------------------------
/utilities/read_pwm.py:
--------------------------------------------------------------------------------
1 | """Experimentation with reading PWM signal on Raspberry Pi.
2 |
3 | This technique is used in sensor_driver_multiplexer
4 | """
5 | import pigpio
6 | import time
7 |
8 | GPIO = 22
9 |
10 | last_tick = 0
11 | last_interval = 0
12 |
13 | def tick(gpio, level, tick):
14 | global last_tick
15 | last_tick = tick
16 |
17 | def tock(gpio, level, tick):
18 | global last_interval
19 | last_interval = tick - last_tick
20 |
21 | pi = pigpio.pi()
22 | pi.set_mode(GPIO, pigpio.INPUT)
23 |
24 | pi.callback(GPIO, pigpio.RISING_EDGE, tick)
25 | pi.callback(GPIO, pigpio.FALLING_EDGE, tock)
26 |
27 | while True:
28 | time.sleep(1)
29 | print(last_interval)
30 |
--------------------------------------------------------------------------------
/piaccess/time2pi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Send the time from this computer to the raspi
4 |
5 | # This is not suitable for setting a precise time: it makes no effort to account
6 | # for the delay between reading the local clock and setting it on the raspi.
7 | # But when the raspi is powered on, its clock can easily be days out, so even
8 | # setting an approximate time is important.
9 |
10 | # See also utilities/setclock, which sets the clock from a GPS signal
11 |
12 | set -e
13 |
14 | echo "Sending time to the pi at ${SAIL_PI_IP:=192.168.12.1}..."
15 | TIMESTAMP=$(date --utc +%Y-%m-%dT%H:%M:%S)
16 |
17 | set -x
18 | ssh -t pi@$SAIL_PI_IP "sudo date --utc +%Y-%m-%dT%H:%M:%S -s $TIMESTAMP"
19 |
--------------------------------------------------------------------------------
/notes/test-sailclub-2016-08-25:
--------------------------------------------------------------------------------
1 | bag 1: remote controlled
2 | bag 2: mostly autonomous; when trying to rech second waypoint, boat increases distance to wp until it drifts ashore
3 | bag 3: position keeping one wp
4 |
5 |
6 | 19:45: position keeping
7 | manually add wind sensor offset -10 degrees
8 |
9 | tacking works for a bit, then it fails on the other tack
10 | -> We need to catch this! try to tack for 10 sec (or a wind speed dependant time!), then jibe!
11 |
12 | 19:59 pier takes control over boat again
13 | wind almost died off
14 | tide start going out quickly
15 |
16 | 20:05 start area scanning
17 | jib is stuck, nana goes in to make it move
18 |
19 | 20:11 abort: rain, no more wind, no more light
20 |
--------------------------------------------------------------------------------
/utilities/setup_scripts/Install_needed_packages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | # Install:
5 | # Geos headers (for shapely)
6 | # scipy (for compass calibration)
7 | # pip (Python packages)
8 | # vim
9 | # bc (calculator)
10 | # opencv (vision analisys for obstacle avoidance)
11 | echo "Installing apt packages..."
12 | sudo apt-get --assume-yes install libgeos-dev python-scipy python-pip vim bc python-opencv
13 |
14 |
15 | # Install:
16 | # Latlon, shapely, pyproj (navigation)
17 | # pynmea2 (reading GPS)
18 | # tornado (web server for HTML dashboard)
19 | echo "Installing Python packages..."
20 | yes | sudo pip install --upgrade pip
21 | yes | sudo pip install Latlon shapely pyproj==1.9.6 pynmea2 spidev tornado
22 |
23 |
--------------------------------------------------------------------------------
/notes/WRSC2016-area-scan-final-points.txt:
--------------------------------------------------------------------------------
1 |
2 | Green buoys:
3 | 0, 41.6903000, -8.8231199
4 | 1, 41.6914597, -8.8211700
5 |
6 | Area scan small:
7 | 2, 41.6908721, -8.8231420
8 | 3, 41.6915660, -8.8220369
9 | 4, 41.6907390, -8.8211099
10 | 5, 41.6900449, -8.8222149
11 | 6, 41.6904579, -8.8226779
12 | 7, 41.6908049, -8.8221249
13 | 10, 41.6903920, -8.8216619
14 |
15 | Area scan large:
16 | 11, 41.6890799, -8.8262499
17 | 12, 41.6900049, -8.8247769
18 | 13, 41.6889019, -8.8235410
19 | 14, 41.6879759, -8.8250139
20 | 15, 41.6885279, -8.8256309
21 | 16, 41.6889910, -8.8248940
22 | 19, 41.6884390, -8.8242770
23 |
--------------------------------------------------------------------------------
/utilities/docker/Dockerfile_indigo-sailing-robot:
--------------------------------------------------------------------------------
1 | FROM sotonsailbot/ros:indigo-desktop-catkin
2 | Maintainer Martin Biggs
3 |
4 | USER root
5 | RUN apt-get --assume-yes install libgeos-dev python-scipy python-pip \
6 | && pip install --upgrade pip \
7 | && pip install Latlon shapely pynmea2 spidev wiringpi2 jinja2 \
8 | folium "tornado<5" pandas
9 | # Fix for Mac's looking for python in /usr/local/bin
10 | RUN ln -s /usr/bin/python /usr/local/bin/python
11 |
12 | USER pi
13 | RUN bash -c "echo 'source /home/pi/sailing-robot/devel/setup.bash' >> ~/.bashrc"
14 | RUN bash -c "echo 'cd ~' >> ~/.bashrc"
15 | RUN bash -c "echo 'sudo service ssh start' > ~/start_ssh_server"
16 | RUN bash -c "chmod u+x ~/start_ssh_server"
17 |
--------------------------------------------------------------------------------
/notes/horten_waypoints/area_scanning_micro.csv:
--------------------------------------------------------------------------------
1 | Area scanning micro,,,,
2 | Line,MicroAreaFinish,WRSC 2017 Area scanning micro,59.427246_59.427164,10.46841500000005_10.469155
3 | Line,MicroAreaStart,WRSC 2017 Area scanning micro,59.426946_59.427093,10.471205000000054_10.469810000000052
4 | Point,MicroArea1,WRSC 2017 Area scanning micro,59.426946,10.471205000000054
5 | Point,MicroArea2,WRSC 2017 Area scanning micro,59.428332,10.471762000000012
6 | Point,MicroArea3,WRSC 2017 Area scanning micro,59.428621,10.46901600000001
7 | Point,MircoArea4,WRSC 2017 Area scanning micro,59.427928,10.468714999999975
8 | Point,MicroArea5,WRSC 2017 Area scanning micro,59.427759,10.470109999999977
9 | Point,MicroArea6,WRSC 2017 Area scanning micro,59.427093,10.469810000000052
10 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/sailsettings_laser.yaml:
--------------------------------------------------------------------------------
1 | # format: wind angle (as read from wind sensor), wind speed (m/s), scaled servo PWM
2 | # scaled servo PWM: 0 = sail fully sheeted in, min PWM; 1 = sail fully out, max PWM
3 | # linearly scaled inbetween
4 | # CAUTION: include 0 angle and 180 angle, since those are the initial values in the lookup script
5 |
6 | sailsettings/table: {'0': 0,
7 | '36': 0,
8 | '40': 0.0519,
9 | '53': 0.0889,
10 | '85': 0.2222,
11 | '105': 0.4444,
12 | '160': 0.7555,
13 | '178': 1,
14 | '180': 1,
15 | }
16 |
17 | sailsettings/windSpeed: 2
18 |
--------------------------------------------------------------------------------
/piaccess/usbip-bind.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Connect to the Xsens on the Raspi using USB-over-IP.
3 | # Run this from Tabarly while connected to the raspi by wifi.
4 | set -e
5 |
6 | sudo modprobe vhci-hcd
7 | echo "SSH-ing to Pi to set up usbip export..."
8 | ssh -t pi@${SAIL_PI_IP:=192.168.12.1} bash sailing-robot/piaccess/usbip-export.sh
9 | BUSID=$(ssh pi@${SAIL_PI_IP} cat /tmp/xsens_busid)
10 |
11 | echo "Attaching [-r $SAIL_PI_IP -b $BUSID]"
12 | sudo usbip attach -r $SAIL_PI_IP -b $BUSID
13 |
14 | echo
15 | echo "Now use Xsens on this computer. Press return here to disconnect."
16 | read
17 |
18 | echo "Detaching"
19 | sudo usbip detach -p 0
20 |
21 | echo "SSHing to Pi to unbind Xsens"
22 | ssh -t pi@${SAIL_PI_IP} bash sailing-robot/piaccess/usbip-stop-export.sh
23 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/25_june__forDebugging.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Horten/horten_fleetrace_waypoints.yaml:
--------------------------------------------------------------------------------
1 | # Order to go to waypoints
2 | wp/tasks: [
3 | {"kind": "to_waypoint", "waypoint": "start_c"},
4 | {"kind": "to_waypoint", "waypoint": "wp1"},
5 | {"kind": "to_waypoint", "waypoint": "wp2"},
6 | {"kind": "to_waypoint", "waypoint": "start_c"},
7 | ]
8 |
9 | # distance within waypoint is accepted, in [m]
10 | wp/acceptRadius: 2
11 | wp/tackVotingRadius: 20
12 |
13 |
14 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
15 | #
16 | wp/table: {"start_c" : [59.427706, 10.468471],
17 | "wp2" : [59.429058, 10.470314],
18 | "wp1" : [59.429385, 10.46864],
19 | "start_l" : [ 59.427722, 10.468278],
20 | "start_r" : [ 59.427667, 10.468694],
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/notes/usbip.txt:
--------------------------------------------------------------------------------
1 | Outdated tutorials:
2 | https://www.howtoforge.com/how-to-set-up-a-usb-over-ip-server-and-client-with-ubuntu-10.04
3 | https://raspberrypi.stackexchange.com/questions/4433/how-do-i-make-my-raspberry-pi-act-as-a-wireless-usb-controller
4 |
5 | Don't use the usbip package:
6 | https://bugs.launchpad.net/ubuntu/+source/linux/+bug/900384
7 |
8 | # Raspi
9 |
10 | - Load module usbip-host
11 | - Compile usbip in Linux source tree matching kernel
12 |
13 | lsusb
14 | ./usbip list -l # -l = local
15 | sudo ./usbipd
16 | # bus id looks like 1-1.4
17 | sudo ./usbip bind -b 1-1.4
18 |
19 | # On Tabarly
20 | sudo usbip attach -r 192.168.12.1 -b 1-1.4
21 | #...use usb device
22 | sudo usbip detach -p 00 # this uses a 'port', not the bus id
23 |
24 | # Back on raspi
25 | sudo ./usbip unbind -b 1-1.4
26 |
27 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/simulation_waypoints.yaml:
--------------------------------------------------------------------------------
1 |
2 | # for simplified entry of path, simply add list of waypoint names
3 | # starts with list entry 0, continues until end is reached
4 | wp/list: ["1", "2", "3",
5 | "1", "2", "3",
6 | "1", "2", "3",
7 | "1", "2", "3",
8 | "1", "2", "3",
9 | "1", "2", "3",
10 | "1", "2", "3",
11 | "1", "2", "3",
12 | "1", "2", "3",
13 | "1", "2", "3",
14 | ]
15 |
16 |
17 | # distance within waypoint is accepted, in [m]
18 | wp/acceptRadius: 1.5
19 |
20 |
21 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
22 | #
23 | wp/table: {"1" : [50.936981, -1.405315],
24 | "2" : [50.93716, -1.405683],
25 | "3" : [50.936908, -1.406061],
26 | }
27 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/dummy_heading:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #
3 | # Node publishes apparent wind direction
4 | # DUMMY NODE, at the moment it just keeps publishing a constant wind direction
5 | # as specified in the parameters file
6 |
7 | import rospy
8 | from std_msgs.msg import Float32
9 |
10 | currentHeading = rospy.get_param("dummy/heading")
11 |
12 | def heading_publisher():
13 |
14 | rate = rospy.Rate(rospy.get_param("config/rate"))
15 |
16 | while not rospy.is_shutdown():
17 | heading_pub.publish(currentHeading)
18 | rate.sleep()
19 |
20 |
21 | if __name__ == '__main__':
22 | try:
23 | heading_pub = rospy.Publisher('heading', Float32, queue_size=10)
24 | rospy.init_node("publish_dummy_heading_data", anonymous=True)
25 | heading_publisher()
26 | except rospy.ROSInterruptException:
27 | pass
28 |
--------------------------------------------------------------------------------
/src/sailing_robot/tests/test_sail_table.py:
--------------------------------------------------------------------------------
1 | from nose.tools import assert_equal, assert_almost_equal
2 | from sailing_robot.sail_table import SailTable, SailData
3 |
4 | SAMPLE_SAIL_TABLE = {
5 | '0': 0,
6 | '30': 0,
7 | '90': 0.5,
8 | '180': 0.9,
9 | }
10 |
11 | def test_sail_table():
12 | st = SailTable(SAMPLE_SAIL_TABLE)
13 | assert_almost_equal(st.interpolate_sail_setting(20), 0)
14 | assert_almost_equal(st.interpolate_sail_setting(60), 0.25)
15 | assert_almost_equal(st.interpolate_sail_setting(90), 0.5)
16 | assert_almost_equal(st.interpolate_sail_setting(135), 0.7)
17 | assert_almost_equal(st.interpolate_sail_setting(190), 0.9)
18 |
19 | def test_sail_data():
20 | st = SailTable(SAMPLE_SAIL_TABLE)
21 | sd = SailData(st)
22 | sd.wind_direction_apparent = 90
23 | assert_almost_equal(sd.calculate_sheet_setting(), 0.5)
24 |
--------------------------------------------------------------------------------
/src/sailing_robot/tests/test_gps_utils.py:
--------------------------------------------------------------------------------
1 | from nose.tools import assert_equal
2 | from sailing_robot.gps_utils import ubx_checksum
3 |
4 | def test_ubx_checksum():
5 | assert_equal(ubx_checksum(b'\x06\x01\x08\x00\xF0\x02\x00\x00\x00\x00\x00\x01'),
6 | b'\x02\x32')
7 | assert_equal(ubx_checksum(b'\x06\x01\x08\x00\xF0\x03\x00\x00\x00\x00\x00\x01'),
8 | b'\x03\x39')
9 | assert_equal(ubx_checksum(b'\x06\x01\x08\x00\xF0\x04\x00\x00\x00\x00\x00\x01'),
10 | b'\x04\x40')
11 | assert_equal(ubx_checksum(b'\x06\x01\x08\x00\xF0\x05\x00\x00\x00\x00\x00\x01'),
12 | b'\x05\x47')
13 | assert_equal(ubx_checksum(b'\x06\x01\x08\x00\xF0\x01\x00\x00\x00\x00\x00\x01'),
14 | b'\x01\x2B')
15 | assert_equal(ubx_checksum(b'\x06\x08\x06\x00\xC8\x00\x01\x00\x01\x00'),
16 | b'\xDE\x6A')
17 |
--------------------------------------------------------------------------------
/src/xsens_driver/package.xml:
--------------------------------------------------------------------------------
1 |
2 | xsens_driver
3 | 2.1.0
4 |
5 | ROS Driver for XSens MT/MTi/MTi-G devices.
6 |
7 | Francis Colas
8 | BSD
9 |
10 | catkin
11 |
12 | rospy
13 | std_msgs
14 | tf
15 | sensor_msgs
16 | geometry_msgs
17 | diagnostic_msgs
18 |
19 | rospy
20 | std_msgs
21 | tf
22 | sensor_msgs
23 | geometry_msgs
24 | diagnostic_msgs
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/sailingClub_waypoints_area_scanning.yaml:
--------------------------------------------------------------------------------
1 | # CAUTION:
2 | # Some of these waypoints may not be suitable in all tide conditions!!
3 | # for simplified entry of path, simply add list of waypoint names
4 | # starts with list entry 0, continues until end is reached
5 | #wp/list: ["1", "2", "3"]
6 | # triangle: 4, 5, 6
7 | # position keeping: 4
8 | # position keeping: 4, 5
9 | # downwind: 6, 7
10 | wp/list: ["4", "5", "6"]
11 |
12 |
13 | # distance within waypoint is accepted, in [m]
14 | wp/acceptRadius: 5
15 | wp/tackVotingRadius: 0.1
16 |
17 |
18 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
19 | #
20 | wp/table: {"A" : [50.889436, -1.383421],
21 | "B" : [50.890024, -1.38395],
22 | "C" : [50.8896643333, -1.38345933333], # C and D are dummy, but not used
23 | "D" : [ 50.8896095, -1.383619],
24 | }
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/utilities/servo_wiggle.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | '''Servo testing script.
3 |
4 | Run this as:
5 | ./servo_wiggle.py
6 | '''
7 |
8 | # 2015-04-10
9 | # Public Domain
10 |
11 | from __future__ import print_function
12 |
13 | import time
14 | import pigpio
15 |
16 | RUDDER_PIN = 13
17 | SAIL_PIN = 24
18 |
19 | MIN_PW = 1000
20 | MID_PW = 1500
21 | MAX_PW = 2000
22 |
23 | STEP = 50 # Change pw by STEP
24 | SLEEP = 0.1 # every SLEEP seconds
25 |
26 | pi = pigpio.pi()
27 |
28 | pw = MIN_PW
29 | rising = True
30 |
31 | while True:
32 | time.sleep(SLEEP)
33 | if pw >= MAX_PW:
34 | rising = False
35 | print('max', end=' ')
36 | elif pw <= MIN_PW:
37 | rising = True
38 | print('min', end=' ')
39 |
40 | if rising:
41 | pw += STEP
42 | else:
43 | pw -= STEP
44 |
45 | pi.set_servo_pulsewidth(RUDDER_PIN, pw)
46 | pi.set_servo_pulsewidth(SAIL_PIN, pw)
47 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/dummy_apparent_wind_direction:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #
3 | # Node publishes apparent wind direction
4 | # DUMMY NODE, at the moment it just keeps publishing a constant wind direction
5 | # as specified in the parameters file
6 |
7 | import rospy
8 | from std_msgs.msg import Float64
9 |
10 | windDirection = rospy.get_param("dummy/wind_direction_apparent")
11 |
12 | def wind_direction_apparent_publisher():
13 |
14 | rate = rospy.Rate(rospy.get_param("config/rate"))
15 |
16 | while not rospy.is_shutdown():
17 | wind_pub.publish(windDirection)
18 | rate.sleep()
19 |
20 |
21 | if __name__ == '__main__':
22 | try:
23 | wind_pub = rospy.Publisher('wind_direction_apparent', Float64, queue_size=10)
24 | rospy.init_node("publish_dummy_wind_data", anonymous=True)
25 | wind_direction_apparent_publisher()
26 | except rospy.ROSInterruptException:
27 | pass
28 |
--------------------------------------------------------------------------------
/notes/ComponentList.md:
--------------------------------------------------------------------------------
1 | #Sensors
2 |
3 |
4 | | Name |Model | Document | Library | Node |
5 | |---|---|---|---|---|
6 | | Accelerometer & Compass | LSM303D | https://www.pololu.com/file/0J703/LSM303D.pdf | https://github.com/pololu/lsm303-arduino |Wind_direction Heading |
7 | | Gyro | L3GD20H | https://www.pololu.com/file/0J731/L3GD20H.pdf | https://github.com/pololu/l3g-arduino | Heading |
8 | | GPS | Ublox MAX M8Q | https://www.u-blox.com/sites/default/files/MAX-M8_DataSheet_(UBX-13004644).pdf | |
9 |
10 |
11 | #Actuator
12 | | Name |Model | Document | Library | Node |
13 | |---|---|---|---|---|
14 | | Rudder servo | TBC | | Arudino::Servo() | Rudder control |
15 | | Sail servo | HiTec 785 HB | https://drive.google.com/file/d/0B0FZNysk4zLXREdoMUFxT0JMZkE/view | https://github.com/Maritime-Robotics-Student-Society/sailing-robot/blob/master/ArduinoCode/Rosserial/Rosserial.ino | Sail control |
16 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/sailsettings_blackpython_rigA.yaml:
--------------------------------------------------------------------------------
1 | # Sail position settings for sail set 1 (largest sails)
2 |
3 | # format: wind angle (as read from wind sensor), wind speed (m/s), scaled servo PWM
4 | # scaled servo PWM: 0 = sail fully sheeted in, min PWM; 1 = sail fully out, max PWM
5 | # linearly scaled inbetween
6 | # CAUTION: include 0 angle and 180 angle, since those are the initial values in the lookup script
7 |
8 |
9 |
10 | sailsettings/table: {'0': 0,
11 | '35': 0,
12 | '45':0,
13 | '55':0.0833,
14 | '60':0.2833,
15 | '75':0.3833,
16 | '90':0.5667,
17 | '135':0.9333,
18 | '180':1,}
19 |
20 | # Servo PWM limits in microseconds
21 | sail/servolowerlimits: 1095
22 | sail/servohigherlimits: 1395
23 | sail/PWMoffset: 0
24 |
25 | sailsettings/windSpeed: 6
26 |
27 | sailsettings/sheet_out_to_jibe: true
28 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/sailsettings_blackpython.yaml:
--------------------------------------------------------------------------------
1 | # Sail position settings for sail sets 2 and 3 (medium & smaller sails)
2 |
3 | # format: wind angle (as read from wind sensor), wind speed (m/s), scaled servo PWM
4 | # scaled servo PWM: 0 = sail fully sheeted in, min PWM; 1 = sail fully out, max PWM
5 | # linearly scaled inbetween
6 | # CAUTION: include 0 angle and 180 angle, since those are the initial values in the lookup script
7 |
8 | sailsettings/table: {'0': 0,
9 | '36': 0,
10 | '40': 0.0519,
11 | '53': 0.0889,
12 | '85': 0.2222,
13 | '105': 0.4444,
14 | '160': 0.7555,
15 | '178': 1,
16 | '180': 1,
17 | }
18 |
19 | # Servo PWM limits in microseconds
20 | sail/servolowerlimits: 1100
21 | sail/servohigherlimits: 1380
22 | sail/PWMoffset: 0
23 |
24 | sailsettings/windSpeed: 2
25 |
--------------------------------------------------------------------------------
/src/sailing_robot/src/sailing_robot/pid_data.py:
--------------------------------------------------------------------------------
1 | import rospy
2 |
3 |
4 | class PID_Data:
5 | def __init__(self):
6 | self.tack_rudder = 0.
7 | self.sailing_state = 'normal'
8 | self.goal_heading = 0.
9 | self.heading = 0.
10 |
11 | def update_goal_heading(self, msg):
12 | """
13 | Update goal heading data from higher level controller for PID controller
14 | :param msg:
15 | """
16 | self.goal_heading = msg.data
17 |
18 | def update_sailing_state(self, msg):
19 | """
20 | Update sailing state data from higher level controller
21 | """
22 | self.sailing_state = msg.data
23 |
24 | def update_heading(self, msg):
25 | """
26 | Get continuous update of current heading from sensors
27 | :param msg:
28 |
29 | """
30 | self.heading = msg.data
31 |
32 | def update_tack_rudder(self,msg):
33 | self.tack_rudder = msg.data
34 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/test-imu.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/tack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Node receives sailing_state from heading_control
4 | # Outputs tack_sail and tack_rudder to override normal direction control
5 |
6 | import rospy
7 | from std_msgs.msg import Float32
8 | from std_msgs.msg import String
9 |
10 | from sailing_robot.tack_control import Tacking
11 |
12 | if __name__ == '__main__':
13 | try:
14 | sail_pub = rospy.Publisher('tack_sail', Float32, queue_size=10)
15 | rudder_pub = rospy.Publisher('tack_rudder', Float32, queue_size=10)
16 | tacking = Tacking()
17 | def recv(msg):
18 | sail, rudder = tacking.calculate_sail_and_rudder(msg.data)
19 | sail_pub.publish(sail)
20 | rudder_pub.publish(rudder)
21 |
22 | rospy.init_node("publish_tack_data", anonymous=True)
23 | rospy.Subscriber('sailing_state', String, recv)
24 | rospy.spin() # Wait for shutdown
25 | except rospy.ROSInterruptException:
26 | pass
27 |
--------------------------------------------------------------------------------
/dashboard/src/main.css.map:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "mappings": "AAAA,IAAK;EACH,WAAW,EAAE,IAAI;EACjB,YAAY,EAAE,IAAI;EAClB,WAAW,EAAE,mBAAmB;EAChC,WAAW,EAAE,GAAG;EAChB,UAAU,EAAE,KAAK;;AAMnB,YAAa;EACX,MAAM,EAAE,MAAM;EACd,MAAM,EAAE,cAAc;EACtB,eAAe,EAAE,QAAQ;EACzB,kBAAM;IACJ,gBAAgB,ECTI,OAAO;IDU3B,KAAK,EAAE,KAAK;EAGZ,oCAAkB;IAChB,gBAAgB,ECjBJ,OAAO;EDmBrB,qBAAG;IACD,KAAK,ECnBY,OAAO;EDqB1B,qBAAG;IACD,SAAS,EAAE,GAAG;IACd,OAAO,EAAE,KAAK;;AAMlB,iBAAG;EACD,UAAU,EAAE,MAAM;;AAItB,aAAc;EACZ,MAAM,EAAE,KAAK;EACb,gBAAgB,EAAE,aAAa;;AAM3B,wCAAe;EACb,UAAU,EAAE,IAAI;AAElB,uCAAc;EACZ,UAAU,EAAE,MAAM;AAEpB,uCAAc;EACZ,UAAU,EAAE,MAAM;AAEpB,wCAAe;EACb,UAAU,EAAE,GAAG;AAEjB,wCAAe;EACb,UAAU,EAAE,UAAU;AAI5B,uBAAgB;EACd,MAAM,EAAE,KAAK;EACb,UAAU,EAAE,MAAM;EAClB,KAAK,EAAE,KAAK;EACZ,OAAO,EAAE,GAAG;EAEV,+BAAQ;IACN,KAAK,EC1DK,OAAgB;ED4D5B,8BAAO;IACL,KAAK,EC5DI,OAAiB;ED8D5B,8BAAO;IACL,KAAK,EC9DI,OAAiB;EDgE5B,+BAAQ;IACN,KAAK,EChEK,OAAgB;EDkE5B,+BAAQ;IACN,KAAK,EClEK,KAAK",
4 | "sources": ["res/base.scss","res/main.scss"],
5 | "names": [],
6 | "file": "main.css"
7 | }
--------------------------------------------------------------------------------
/utilities/setup_scripts/Setup_ALL.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | if [ "$1" = '' ]
5 | then
6 | echo -e "use with: \n $0 raspi \n or \n $0 workstation"
7 | exit 1
8 | fi
9 |
10 |
11 | setup () {
12 | echo
13 | echo "============== " $1 " =============="
14 | echo "Proceeding? [Y/n]"
15 | read ans
16 | if [ "$ans" = "y" ] || [ "$ans" = "Y" ] || [ "$ans" = '' ]
17 | then
18 | echo "== Running " $2
19 | ./$2
20 | else
21 | echo "== Skipping"
22 | fi
23 | }
24 |
25 |
26 |
27 |
28 | setup "Install needed packages" Install_needed_packages.sh
29 |
30 | if [ "$1" = "raspi" ]
31 | then
32 | setup "Access Point" Setup_Access_Point.sh
33 | fi
34 |
35 | if [ "$1" = "raspi" ]
36 | then
37 | setup "Raspberry pi setup" Install_needed_packages_Raspi.sh
38 | fi
39 |
40 | setup "Setting up ROS (ubuntu)" Setup_ROS.sh
41 |
42 | if [ "$1" = "raspi" ]
43 | then
44 | setup "Basic configuration (vim/bash)" Setup_basic_configuration.sh
45 | fi
46 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/publish_highlevel_sailing_demand.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/utilities/docker/Dockerfile_indigo-ros-base-catkin:
--------------------------------------------------------------------------------
1 | FROM ros:indigo-ros-base
2 | Maintainer Martin Biggs
3 |
4 | USER root
5 | RUN apt-get update \
6 | && apt-get upgrade --assume-yes \
7 | && apt-get install --assume-yes g++ vim openssh-server \
8 | && mkdir /var/run/shhd \
9 | && useradd --user-group --create-home --shell /bin/bash pi \
10 | && echo 'pi:raspberry' | chpasswd \
11 | && echo "pi ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/pi \
12 | && rm -fr /var/lib/apt/lists
13 |
14 | USER pi
15 | RUN bash -c "\
16 | # Fix for Tab AutoCompletion not working in ROS
17 | echo 'export LC_ALL=\"C\"' >> ~/.bashrc \
18 | # Fix 'WARNING: Terminal is not fully functional'
19 | && echo 'export TERM=xterm' >> ~/.bashrc \
20 | && echo 'source /opt/ros/indigo/setup.bash' >> ~/.bashrc \
21 | && source /opt/ros/indigo/setup.bash \
22 | && mkdir -p ~/catkin_ws/src \
23 | && cd ~/catkin_ws/src \
24 | && catkin_init_workspace \
25 | && cd ~/catkin_ws/ \
26 | && catkin_make \
27 | && rosdep update \
28 | && echo 'source /home/pi/catkin_ws/devel/setup.bash' >> ~/.bashrc"
29 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/test-all-systems.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Viana/viana_fleetrace_waypoints.yaml:
--------------------------------------------------------------------------------
1 |
2 | wp/tasks: [
3 | {"kind": "to_waypoint", "waypoint": "5"},
4 | {"kind": "to_waypoint", "waypoint": "6"},
5 | {"kind": "to_waypoint", "waypoint": "4"},
6 | {"kind": "to_waypoint", "waypoint": "f1"},
7 | {"kind": "to_waypoint", "waypoint": "f2"},
8 | ]
9 |
10 | # distance within waypoint is accepted, in [m]
11 | wp/acceptRadius: 8
12 | wp/tackVotingRadius: 20
13 |
14 |
15 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
16 | #
17 | wp/table: {"0" : [41.6912384, -8.8227519], # RIB, LHS of start/finish line
18 | "1" : [41.6910000, -8.8225000],
19 | "5" : [41.6897354, -8.8244113],
20 | "6" : [41.6892661, -8.8238736],
21 | "4" : [41.6905097, -8.8219375],
22 | f1: [41.69117512169704, -8.822531747768949],
23 | f2: [41.691063278364496, -8.82272015160325],
24 | }
25 |
26 | navigation/safety_zone_ll: [
27 | [41.68883, -8.82683],
28 | [41.68767, -8.82533],
29 | [41.69050, -8.82100],
30 | [41.69167, -8.82217],
31 | ]
32 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Viana/viana_position_keeping_waypoints.yaml:
--------------------------------------------------------------------------------
1 |
2 | wp/tasks: [
3 | {"kind": "to_waypoint", "waypoint": "course1"},
4 | {"kind": "keep_station", "waypoint": "course1", "linger": 3000, "wind_angle": 45, "radius": 2.5},
5 | ]
6 |
7 | # distance within waypoint is accepted, in [m]
8 | wp/acceptRadius: 2
9 | wp/tackVotingRadius: 20
10 |
11 |
12 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
13 | #
14 | wp/table: {"0" : [41.6912384, -8.8227519], # RIB, LHS of start/finish line
15 | "1" : [41.6910000, -8.8225000],
16 | "5" : [41.6897354, -8.8244113],
17 | "6" : [41.6892661, -8.8238736],
18 | "4" : [41.6905097, -8.8219375],
19 | f1: [41.69117512169704, -8.822531747768949],
20 | f2: [41.691063278364496, -8.82272015160325],
21 | course1: [ 41.68846, -8.82469],
22 | course2: [ 41.68785, -8.82535],
23 |
24 | }
25 |
26 | navigation/safety_zone_ll: []
27 | # [41.68883, -8.82683],
28 | #[41.68767, -8.82533],
29 | #[41.69050, -8.82100],
30 | #[41.69167, -8.82217],
31 | #]
32 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/eastleigh_waypoints.yaml:
--------------------------------------------------------------------------------
1 |
2 | # for simplified entry of path, simply add list of waypoint names
3 | # starts with list entry 0, continues until end is reached
4 | wp/list: ["13", "12", "11"]
5 |
6 |
7 | # distance within waypoint is accepted, in [m]
8 | wp/acceptRadius: 1.5
9 | wp/tackVotingRadius: 6
10 |
11 |
12 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
13 | #
14 | wp/table: {"1" : [50.957567, -1.367289],
15 | "2" : [50.957444, -1.366680],
16 | "3" : [50.957391, -1.367093],
17 | "4" : [50.957302, -1.366713],
18 | "5" : [50.957205, -1.367262],
19 | "6" : [50.957075, -1.367324],
20 | "7" : [50.957018, -1.366974],
21 | "8" : [50.956933, -1.367378],
22 | "9" : [50.956880, -1.366988],
23 | "10": [50.956785, -1.367338],
24 | "11": [50.956702, -1.367013],
25 | "12": [50.956645, -1.367629],
26 | "13": [50.956371, -1.367142],
27 | "P1": [50.957021, -1.366734],
28 | "P2": [50.956466, -1.366816],
29 | }
30 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/actuator_demand_sail:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import rospy
4 | from std_msgs.msg import Float64, Float32, String
5 |
6 | from sailing_robot.sail_table import SailTable, SailData
7 | import sailing_robot.pid_control as _PID
8 |
9 | sail_table_dict = rospy.get_param('sailsettings/table')
10 | sheet_out_to_jibe = rospy.get_param('sailsettings/sheet_out_to_jibe', False)
11 | sail_table = SailTable(sail_table_dict)
12 | sail_data = SailData(sail_table)
13 |
14 | def node_publisher():
15 | pub = rospy.Publisher('sailsheet_normalized', Float32, queue_size=10)
16 | rospy.init_node('actuator_demand_sail', anonymous=True)
17 |
18 | rate = rospy.Rate(10)
19 | while not rospy.is_shutdown():
20 | sheet_normalized = sail_data.calculate_sheet_setting()
21 | pub.publish(sheet_normalized)
22 | rate.sleep()
23 |
24 |
25 | if __name__ == '__main__':
26 | try:
27 | rospy.Subscriber('wind_direction_apparent', Float64, sail_data.update_wind)
28 | rospy.Subscriber('sailing_state', String, sail_data.update_sailing_state)
29 | node_publisher()
30 | except rospy.ROSInterruptException:
31 | pass
32 |
--------------------------------------------------------------------------------
/utilities/postprocessing/adjust.py:
--------------------------------------------------------------------------------
1 | """Adjust the timestamps of recorded data files.
2 |
3 | For when we've done a test with the clock on the raspi set incorrectly.
4 | """
5 |
6 | from datetime import datetime, timedelta
7 | from pathlib import Path
8 | import re
9 |
10 | # CORRECTION = timedelta(10, 60496)
11 | CORRECTION = timedelta(seconds=(1535393272-1529482132)) # epoch from good - raspi
12 | TIMESTAMP_RE = r'(\d{4})-(\d{2})-(\d{2})-?T?(\d{2})[-.](\d{2})[-.](\d{2})'
13 |
14 | #for path in sorted(Path.cwd().glob('*_2018-06-20-*')):
15 | for path in sorted(Path.cwd().glob('*_triangle*')):
16 | print('--')
17 | print(path.name)
18 | m = re.search(TIMESTAMP_RE, path.name)
19 | old_ts = datetime(*[int(g) for g in m.groups()])
20 | new_ts = old_ts + CORRECTION
21 | if "T" in path.name:
22 | new_name = path.name[:m.start()] + new_ts.strftime("%Y-%m-%dT%H.%M.%S") \
23 | + path.name[m.end():]
24 | else:
25 | new_name = path.name[:m.start()] + new_ts.strftime("%Y-%m-%d-%H-%M-%S") \
26 | + path.name[m.end():]
27 | print(new_name)
28 | new_file = path.parent / new_name
29 | path.rename(new_file)
30 |
31 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/test-gps.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/utilities/setup_scripts/Setup_basic_configuration.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # This script set some basic bash aliases and vim comfiguration to feel at home
3 | # when using the sailrobot
4 | # It only includes widely used configurations
5 |
6 | cat <> ~/.bashrc
7 |
8 | # Some useful aliases
9 | alias ls='ls --color=auto'
10 | alias ll='ls -lh'
11 | alias la='ls -Ah'
12 | alias l='ls -CF'
13 | alias sl='ls'
14 | alias lt='ls -tr'
15 | alias llt='ls -ltrh'
16 | alias vi='vim'
17 | alias mv='mv -i'
18 | alias cp='cp -i'
19 |
20 | EOF
21 |
22 |
23 |
24 | cat <> ~/.vimrc
25 |
26 | syntax enable " enable syntax processing
27 | set background=dark
28 | set smartcase " case insensitive smart
29 | set number " show line numbers
30 | set showcmd " show command in bottom bar
31 | set cursorline " highlight current line
32 | set expandtab " tabs are spaces
33 | set lazyredraw " redraw only when we need to.
34 | EOF
35 |
36 |
37 |
38 | cat <> ~/.inputrc
39 |
40 | $include /etc/inputrc
41 |
42 | set completion-ignore-case on
43 |
44 | set show-all-if-ambiguous on
45 |
46 | TAB:menu-complete
47 | # arrow up
48 | "\e[A":history-search-backward
49 |
50 | # arow down
51 | "\e[B":history-search-forward
52 |
53 | EOF
54 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/standby.launch:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/calibration/monitor_imu.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | """Monitor the compass MinIMU and display readings using curses.
3 | """
4 | import curses
5 | import os
6 | import sys
7 | import time
8 |
9 | my_dir = os.path.dirname(__file__)
10 | robot_src_dir = os.path.abspath(os.path.join(my_dir, '../src/sailing_robot/src'))
11 | sys.path.append(robot_src_dir)
12 |
13 | from sailing_robot.imu_utils import ImuReader
14 | from curses_imu import IMUDisplay, pitch_roll
15 |
16 | IMU_BUS = 1
17 | LGD = 0x6a #Device I2C slave address
18 | LSM = 0x1e #Device I2C slave address
19 |
20 | imu = ImuReader(IMU_BUS, LSM, LGD)
21 | imu.check_status()
22 | imu.configure_for_reading()
23 |
24 |
25 | def monitor(stdscr):
26 | display = IMUDisplay(stdscr)
27 | stdscr.addstr(curses.LINES-1, 0, 'Press Ctrl-C to quit')
28 |
29 | while True:
30 | time.sleep(0.1)
31 |
32 | magx, magy, magz = imu.read_mag_field()
33 | accx, accy, accz = imu.read_acceleration()
34 |
35 | display.update_mag(magx, magy, magz)
36 | display.update_acc(accx, accy, accz)
37 | display.update_pitch_roll(*pitch_roll(accx, accy, accz))
38 | stdscr.refresh()
39 |
40 | try:
41 | curses.wrapper(monitor)
42 | except KeyboardInterrupt:
43 | print('Stopped')
44 |
--------------------------------------------------------------------------------
/LICENCE.txt:
--------------------------------------------------------------------------------
1 | Unless specified otherwise directly in the file, all files in this repository are licenced as follows:
2 |
3 | MIT License
4 |
5 | Copyright (c) [2017] [Southampton Sailing Robot Team]
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
25 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/debugging_gps_log:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Log GPS data in the CSV format required by the rules
3 |
4 | import csv
5 | from datetime import datetime
6 | import os.path
7 | import rospy
8 | from sensor_msgs.msg import NavSatFix
9 | from sailing_robot.msg import gpswtime
10 |
11 | RECORDS_DIR = os.path.expanduser('~/sailing-robot')
12 | log_name = rospy.get_param('/log_name')
13 |
14 | def record():
15 | filename = 'gps-trace_{}_{}.csv'.format(log_name,
16 | datetime.now().strftime("%Y-%m-%dT%H.%M.%S"))
17 | day_of_month = datetime.now().day
18 | with open(os.path.join(RECORDS_DIR, filename), 'w', 0) as f:
19 | csvw = csv.writer(f)
20 | def write(msg):
21 | ts = '%02d%02d%02d%02d' % (msg.time_h, msg.time_m, msg.time_s,
22 | day_of_month)
23 | lat = int(msg.fix.latitude * 1e7)
24 | lon = int(msg.fix.longitude * 1e7)
25 | csvw.writerow([ts, lat, lon])
26 |
27 | rospy.Subscriber('gps_fix', gpswtime, write)
28 | rospy.spin()
29 |
30 | if __name__ == '__main__':
31 | try:
32 | rospy.init_node("debugging_gps_log", anonymous=True)
33 | record()
34 | except rospy.ROSInterruptException:
35 | pass
36 |
--------------------------------------------------------------------------------
/src/sailing_robot/tests/test_tasks.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from sailing_robot.tasks import TasksRunner
4 | from sailing_robot.navigation import Navigation
5 | from sailing_robot.heading_planning_laylines import HeadingPlan
6 | from sailing_robot.station_keeping2 import StationKeeping
7 |
8 | tasks_def_1 = [
9 | {'kind': 'to_waypoint',
10 | 'waypoint_ll': [50.936981, -1.405315]
11 | },
12 | {'kind': 'keep_station',
13 | 'marker_ll': [50.8, 1.01],
14 | 'linger': 90,
15 | },
16 | ]
17 |
18 | tasks_bad = tasks_def_1 + [
19 | {'kind': 'test_bad'},
20 | ]
21 |
22 | class TasksTests(unittest.TestCase):
23 | def test_load(self):
24 | tr = TasksRunner(tasks_def_1, Navigation(utm_zone=30))
25 | self.assertIsInstance(tr.tasks[0], HeadingPlan)
26 | self.assertIsInstance(tr.tasks[1], StationKeeping)
27 |
28 | def test_load_bad(self):
29 | with self.assertRaises(ValueError):
30 | tr = TasksRunner(tasks_bad, Navigation(utm_zone=30))
31 |
32 | def test_step(self):
33 | tr = TasksRunner(tasks_def_1, Navigation(utm_zone=30))
34 | tr.start_next_task()
35 | self.assertIsInstance(tr.active_task, HeadingPlan)
36 | tr.start_next_task()
37 | self.assertIsInstance(tr.active_task, StationKeeping)
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | install/
2 | .idea/
3 | build/
4 | bin/
5 | lib/
6 | msg_gen/
7 | srv_gen/
8 | msg/*Action.msg
9 | msg/*ActionFeedback.msg
10 | msg/*ActionGoal.msg
11 | msg/*ActionResult.msg
12 | msg/*Feedback.msg
13 | msg/*Goal.msg
14 | msg/*Result.msg
15 | msg/_*.py
16 | /devel/
17 | /local/
18 |
19 | # Generated by dynamic reconfigure
20 | *.cfgc
21 | /cfg/cpp/
22 | /cfg/*.py
23 |
24 | # Ignore generated docs
25 | *.dox
26 | *.wikidoc
27 |
28 | # eclipse stuff
29 | .project
30 | .cproject
31 |
32 | # Jupyter notebook stuff
33 | .ipynb_checkpoints/
34 |
35 | # Dumped parameter
36 | *.json
37 | gps-raw*
38 |
39 | # qcreator stuff
40 | CMakeLists.txt.user
41 |
42 | srv/_*.py
43 | *.pcd
44 | *.pyc
45 | qtcreator-*
46 | *.user
47 |
48 | /planning/cfg
49 | /planning/docs
50 | /planning/src
51 |
52 | *~
53 |
54 | # Emacs
55 | .#*
56 | # Vim
57 | *.swp
58 |
59 | # Catkin custom files
60 | CATKIN_IGNORE
61 | .catkin_workspace
62 |
63 | # ignore logs: rosbags and csv files
64 | *.csv
65 | *.bag
66 | *.bag.active
67 |
68 | # ignore automatically generated docs files
69 | /src/sailing_robot/doc/*
70 |
71 | # Ignore recorded data from tests
72 | /recorded_data/*.*
73 | /recorded_data/shared_pics/
74 | /recorded_data/sync_sailing_data
75 | /recorded_data/Experiment 1/
76 |
77 | # ignore mac DS files
78 | *.DS_Store
79 |
--------------------------------------------------------------------------------
/recorded_data/notes/log_timed_note.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Record a new timed note to associate with logs from the robot.
3 |
4 | Symlink this script to a location on $PATH, e.g.:
5 |
6 | ln -s "$PWD/log_timed_note.py" ~/.local/bin/sailnote
7 |
8 | Then run it when something weird happens that we want to be able to investigate
9 | later. It will create files in this directory, which can be picked up by the
10 | indexing tool later. These will be small, and may be committed to git.
11 | """
12 | from __future__ import print_function
13 | from datetime import datetime
14 | import json
15 | import os.path
16 | import sys
17 |
18 | if sys.version_info[0] < 3:
19 | input = raw_input
20 |
21 | # Not using .isoformat() because we don't need milliseconds
22 | now = datetime.utcnow().strftime("%Y-%m-%dT%H.%M.%SZ")
23 | print("Logging note at", now)
24 |
25 | this_dir = os.path.dirname(os.path.realpath(__file__))
26 | filename = os.path.join(this_dir, now + '.json')
27 | #print("Saving to:", filename)
28 |
29 | msg = input('message: ')
30 |
31 | # JSON may seem pointless for this, but if we think of extra information to add,
32 | # we have space to put it.
33 | with open(filename, 'w') as f:
34 | json.dump({'message': msg}, f, indent=2)
35 |
36 | print('Saved! Make a git commit when you have a chance.')
37 |
--------------------------------------------------------------------------------
/src/xsens_driver/launch/xsens_driver.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/xsens_driver/LICENCE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011--2016, Francis Colas.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/sensor_driver_battery:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 |
4 | from sailing_robot.msg import BatteryState
5 | from ina219 import INA219
6 | import rospy
7 |
8 |
9 |
10 |
11 | class Battery():
12 | """
13 | Node that publishes current and voltage from the ina219 module
14 | """
15 | def __init__(self):
16 | # initialize the library
17 | SHUNT_OHMS = 0.1
18 | MAX_EXPECTED_AMPS = 1
19 | self.ina = INA219(SHUNT_OHMS, MAX_EXPECTED_AMPS)
20 | self.ina.configure(self.ina.RANGE_16V, self.ina.GAIN_AUTO)
21 | self.ina.sleep()
22 |
23 |
24 | self.battery_pub = rospy.Publisher('battery', BatteryState, queue_size=10)
25 | rospy.init_node("battery", anonymous=True)
26 |
27 | self.rate = rospy.Rate(rospy.get_param("config/battery_rate"))
28 | self.battery_publisher()
29 |
30 |
31 | def battery_publisher(self):
32 |
33 | while not rospy.is_shutdown():
34 | msg = BatteryState()
35 | self.ina.wake()
36 | msg.voltage = self.ina.voltage()
37 | msg.current = -self.ina.current()
38 | msg.power = -msg.voltage * msg.current
39 | self.ina.sleep()
40 | self.battery_pub.publish(msg)
41 | self.rate.sleep()
42 |
43 |
44 | if __name__ == '__main__':
45 | try:
46 | Battery()
47 | except rospy.ROSInterruptException:
48 | pass
49 |
50 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/test-wind-direction.launch:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/node_docstrings.py:
--------------------------------------------------------------------------------
1 | """Extract module level docstrings from the node launcher scripts"""
2 | import ast
3 | import os.path
4 |
5 | SCRIPTS_DIR = os.path.join('src', 'sailing_robot', 'scripts')
6 |
7 | def get_docstring(filename):
8 | with open(filename) as f:
9 | print(filename)
10 | try:
11 | m = ast.parse(f.read())
12 | except:
13 | pass
14 |
15 | n = m.body[0]
16 | if isinstance(n, ast.Expr) and isinstance(n.value, ast.Str):
17 | return n.value.s
18 | else:
19 | return ''
20 |
21 | # write header of node overview file
22 | nodeOverview = open('src/sailing_robot/doc/nodes.rst', 'w')
23 | nodeOverview.write("Available ROS Nodes\n")
24 | nodeOverview.write("===================\n")
25 | nodeOverview.write("\n")
26 | nodeOverview.write(".. toctree::\n")
27 | nodeOverview.write(" :maxdepth: 2\n")
28 | nodeOverview.write("\n")
29 | nodeOverview.close()
30 |
31 |
32 | for nodename in os.listdir(SCRIPTS_DIR):
33 | ds = get_docstring(os.path.join(SCRIPTS_DIR, nodename))
34 | nodeOverview = open('src/sailing_robot/doc/nodes.rst', 'a')
35 | nodeOverview.write(" " + nodename + '\n')
36 | nodeOverview.close()
37 |
38 |
39 | nodeFile = open('src/sailing_robot/doc/' + nodename +'.rst', 'w')
40 | nodeFile.write(nodename + '\n')
41 | underline = len(nodename) * '='
42 | nodeFile.write(underline + '\n')
43 | nodeFile.write(ds)
44 | nodeFile.close()
45 |
46 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Calshot/calshot_obstacle_avoidance_2.yaml:
--------------------------------------------------------------------------------
1 | # distance within waypoint is accepted, in [m]
2 | wp/acceptRadius: 5
3 | wp/tackVotingRadius: 20
4 |
5 | wp/table:
6 | A: [50.8208148975909, -1.3137864720799257]
7 | B: [50.821110724829616, -1.3136170482685259]
8 | B1: [50.8209880710378, -1.313083318737597]
9 | C: [50.82132202982804, -1.3134960299252896]
10 | C1: [50.8211993754666, -1.3129622983283866]
11 | D: [50.821533334683, -1.3133750104694286]
12 | D1: [50.82141067975195, -1.31284127680658]
13 | E: [50.821744639394545, -1.313253989900943]
14 | E1: [50.82162198389388, -1.312720254172092]
15 | F: [50.821955943962635, -1.3131329682198327]
16 | F1: [50.82183328789233, -1.3125992304249792]
17 | G: [50.822251770117006, -1.3129635359969711]
18 | wp/tasks:
19 | - {kind: to_waypoint, waypoint: A}
20 | - {kind: to_waypoint, waypoint: B}
21 | - {kind: obstacle_waypoints, normal: C, obstacle: B1}
22 | - {kind: obstacle_waypoints, normal: D, obstacle: C1}
23 | - {kind: obstacle_waypoints, normal: E, obstacle: D1}
24 | - {kind: obstacle_waypoints, normal: F, obstacle: E1}
25 | - {kind: to_waypoint, waypoint: G}
26 | - {kind: to_waypoint, waypoint: F}
27 | - {kind: obstacle_waypoints, normal: E, obstacle: F1}
28 | - {kind: obstacle_waypoints, normal: D, obstacle: E1}
29 | - {kind: obstacle_waypoints, normal: C, obstacle: D1}
30 | - {kind: obstacle_waypoints, normal: B, obstacle: C1}
31 | - {kind: to_waypoint, waypoint: A}
32 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Calshot/calshot_obstacle_avoidance_1.yaml:
--------------------------------------------------------------------------------
1 |
2 | # distance within waypoint is accepted, in [m]
3 | wp/acceptRadius: 5
4 | wp/tackVotingRadius: 20
5 |
6 | wp/table:
7 | A: [50.82074310137166, -1.311355532456389]
8 | B: [50.82071849569362, -1.3118508044530302]
9 | B1: [50.82107704317086, -1.3118951998268642]
10 | C: [50.82070091891867, -1.3122045698767693]
11 | C1: [50.82105946628823, -1.3122489679504952]
12 | D: [50.8206833410682, -1.3125583350604018]
13 | D1: [50.821041888330065, -1.3126027358339911]
14 | E: [50.82066576214217, -1.3129121000038992]
15 | E1: [50.821024309296334, -1.3129565034773236]
16 | F: [50.82064818214063, -1.3132658647072049]
17 | F1: [50.82100672918706, -1.3133102708804643]
18 | G: [50.82062356833157, -1.3137611348882956]
19 | wp/tasks:
20 | - {kind: to_waypoint, waypoint: A}
21 | - {kind: to_waypoint, waypoint: B}
22 | - {kind: obstacle_waypoints, normal: C, obstacle: B1}
23 | - {kind: obstacle_waypoints, normal: D, obstacle: C1}
24 | - {kind: obstacle_waypoints, normal: E, obstacle: D1}
25 | - {kind: obstacle_waypoints, normal: F, obstacle: E1}
26 | - {kind: to_waypoint, waypoint: G}
27 | - {kind: to_waypoint, waypoint: F}
28 | - {kind: obstacle_waypoints, normal: E, obstacle: F1}
29 | - {kind: obstacle_waypoints, normal: D, obstacle: E1}
30 | - {kind: obstacle_waypoints, normal: C, obstacle: D1}
31 | - {kind: obstacle_waypoints, normal: B, obstacle: C1}
32 | - {kind: to_waypoint, waypoint: A}
33 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/dummy_position:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #
3 | # Node publishes apparent wind direction
4 | # DUMMY NODE: keeps publishing a constant wind direction as specified in the
5 | # parameters file. See 'gps' for our real gps node.
6 |
7 | from datetime import datetime
8 | import rospy
9 |
10 | from sensor_msgs.msg import NavSatFix
11 | from sailing_robot.msg import gpswtime
12 |
13 | currentLatitude = rospy.get_param("dummy/latitude")
14 | currentLongitude = rospy.get_param("dummy/longitude")
15 |
16 | position = NavSatFix()
17 |
18 | def position_publisher():
19 | rate = rospy.Rate(rospy.get_param("config/rate"))
20 |
21 | position.latitude = currentLatitude
22 | position.longitude = currentLongitude
23 |
24 | while not rospy.is_shutdown():
25 | position_pub.publish(position)
26 |
27 | now = datetime.utcnow()
28 | wtime = gpswtime()
29 | wtime.fix = position
30 | wtime.time_h = now.hour
31 | wtime.time_m = now.minute
32 | wtime.time_s = now.second
33 | gps_pub.publish(wtime)
34 | rate.sleep()
35 |
36 |
37 | if __name__ == '__main__':
38 | try:
39 | position_pub = rospy.Publisher('position', NavSatFix, queue_size=10)
40 | gps_pub = rospy.Publisher('gps_fix', gpswtime, queue_size=10)
41 | rospy.init_node("publish_dummy_position_data", anonymous=True)
42 |
43 | position_publisher()
44 | except rospy.ROSInterruptException:
45 | pass
46 |
--------------------------------------------------------------------------------
/src/sailing_robot/src/sailing_robot/timeout.py:
--------------------------------------------------------------------------------
1 | """Run a timer while other tasks run"""
2 | from threading import Timer
3 |
4 | from .taskbase import TaskBase
5 |
6 | class StartTimer(TaskBase):
7 | def __init__(self, nav, seconds, jump_to, jump_callback):
8 | """Machinery to jump to a waypoint after a set amount of time.
9 |
10 | nav : a Navigation object for common machinery.
11 | seconds : time in seconds to jump after task is started
12 | jump_to : Waypoint ID to jump to.
13 | jump_callback : Function to do jump. Provided in tasks.py
14 | """
15 | self.nav = nav
16 | self.seconds = seconds
17 | self.jump_to = jump_to
18 | self.jump_callback = jump_callback
19 |
20 | def start(self):
21 | # Create the timer here so that the task instance can be used more than
22 | # once.
23 | self.timer = Timer(self.seconds, self.jump_callback, args=[self.jump_to])
24 | # If the tasks process dies, the timer thread should die
25 | self.timer.daemon = True
26 | self.timer.start()
27 |
28 | def check_end_condition(self):
29 | "Are we done yet?"
30 | return True # Go straight to next task
31 |
32 | def calculate_state_and_goal(self):
33 | """Work out what we want the boat to do
34 | """
35 | # Dummy, should only be used for 0.1s, as it goes immediately to next
36 | # task
37 | return 'normal', self.nav.heading
38 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/debugging_blink_on_sailing_state:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # READY FOR MIT
3 | """
4 | Blink LEDs to indicate sailing state
5 | \n
6 | Publishes: messages that encode a colour in an Int32
7 | \n
8 | Subscribes: sailing state
9 | """
10 |
11 | import rospy
12 | from std_msgs.msg import Int32, String
13 |
14 |
15 | class Blink_on_sailing_state():
16 | def __init__(self):
17 | self.led_blink = rospy.Publisher('led_blink', Int32, queue_size=10)
18 |
19 | rospy.init_node("debugging_blink_on_sailing_state", anonymous=True)
20 | rospy.Subscriber('sailing_state', String, self.update_sailing_state)
21 | self.sailing_state = "normal"
22 | self.rate = rospy.Rate(rospy.get_param("config/rate"))
23 | self.blink_publisher()
24 |
25 |
26 | def update_sailing_state(self, msg):
27 | self.sailing_state = msg.data
28 |
29 |
30 | def blink_publisher(self):
31 |
32 | while not rospy.is_shutdown():
33 |
34 | if self.sailing_state == "switch_to_port_tack":
35 | color = 255*300*300 # red
36 | self.led_blink.publish(color)
37 |
38 | elif self.sailing_state == "switch_to_stbd_tack":
39 | color = 255*300 # green
40 | self.led_blink.publish(color)
41 |
42 | self.rate.sleep()
43 |
44 |
45 | if __name__ == '__main__':
46 | try:
47 | Blink_on_sailing_state()
48 | except rospy.ROSInterruptException:
49 | pass
50 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/sailingClub_waypoints.yaml:
--------------------------------------------------------------------------------
1 | # CAUTION:
2 | # Some of these waypoints may not be suitable in all tide conditions!!
3 | # for simplified entry of path, simply add list of waypoint names
4 | # starts with list entry 0, continues until end is reached
5 | #wp/list: ["1", "2", "3"]
6 | wp/list: ["9", "13"]
7 | # triangle: 4, 5, 6
8 | # position keeping: 4
9 | # position keeping: 4, 5
10 | # downwind: 6, 7
11 | #wp/tasks: [
12 | # {"kind": "to_waypoint", "waypoint": "4"},
13 | # {"kind": "to_waypoint", "waypoint": "5"},
14 | # {"kind": "keep_station", "waypoint": "5", linger: 60},
15 | # {"kind": "to_waypoint", "waypoint": "6"},
16 | #]
17 |
18 | # distance within waypoint is accepted, in [m]
19 | wp/acceptRadius: 1.5
20 | wp/tackVotingRadius: 20
21 |
22 |
23 | # Dictionary for looking up waypoints, using [lat, lon] format of NavSatFix
24 | #
25 | wp/table: {"1" : [50.8896095, -1.3836603333333333],
26 | "2" : [50.889349, -1.3836305],
27 | "3" : [50.8896643333, -1.38345933333],
28 | "4" : [ 50.8896095, -1.383619],
29 | "5" : [ 50.8896095, -1.383701666667],
30 | "6" : [50.889478, -1.383536],
31 | "7" : [50.889942, -1.384113],
32 | "8" : [50.888966, -1.383207],
33 | "9" : [50.8889141, -1.383332],
34 | "10": [50.888969, -1.383817],
35 | "11": [50.888836, -1.383762],
36 | "12": [50.888733, -1.383626],
37 | "13": [50.888851, -1.38308]
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/notes/setup-list:
--------------------------------------------------------------------------------
1 | # Setup list for water tests
2 | # if one person is absent, their responsibility is shared amongst the rest, but it is clear what tasks have to be done by someone else now
3 |
4 |
5 | PIER + TONY
6 | - assemble boat
7 | - attach sails
8 | - check remote control works
9 | - tell Nana and Sophia/Thomas when RPi is online;
10 | - waterproof
11 | - get ready for calibration
12 | - set up gopro if we use it
13 |
14 | NANA
15 | - set up wifi
16 | - check wifi laptop can ssh onto boat
17 | - set up wind sensor
18 |
19 | SOPHIA + THOMAS
20 |
21 | ======= Software setup
22 | - git pull
23 | - check if there is a more recent launch file
24 | - git push if needed
25 | - set time (if script doesnt work: sudo date -s 2016-08-25T17:30:45 )
26 | - run roscore
27 | - run calibration script (compass + wind direction)
28 | - run launch file
29 | check if data from wind direction and compass make sens
30 | - apply a heeling angle to the boat and see if the heading changes (topic 'heading_comp')
31 | - rotate the wind vane in various direction and check the topic 'wind_direction_apparent'
32 | - rotate the boat and keep the wind vane in a fixed position and check if the topic 'wind_direction_apparent' fluctuates (it shouldn't)
33 | - check if sail servo is moving in relation to the wind direction
34 | - monitor boat position with rviz
35 | - put the boat in water and check if it actually floats, if not use a buoyancy aid or use autosub code instead of sailing boat one
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/rviz-bag-visualisation.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/notes/packing-list:
--------------------------------------------------------------------------------
1 | ========= tools
2 | - dremel
3 | - soldering iron
4 | - power chord with UK connectors
5 | - adapter for Portugal (european socket in portugal)
6 | - screw drivers
7 | - screws
8 | - pliers
9 | - scissors
10 | - glue
11 | - epoxy
12 | - tape
13 | - electronic tape
14 | - multimeter
15 |
16 |
17 | ========= onboard electronic
18 | - 1 imu
19 | - 1 gps + gps antenna
20 | - 2 power banks (+ usb cables)
21 | - X AA batteries
22 | - X wifi dongles
23 | - 1 raspberry pi + sd card
24 | - 1 multiplexer
25 | - remote control + receiver
26 | - usb webcam
27 |
28 |
29 | ========= spare parts
30 | - 1 rudder from other boat as spare rudder
31 | - 1 wind direction sensor
32 | - 1 multiplexer
33 | - 1 raspberry pi + sd card (ready to use)
34 | - wires of different length and color
35 | - do we have a spare keel?
36 |
37 |
38 | ========= computer related
39 | - sailbot laptop
40 | - sailbot laptop charger
41 |
42 |
43 | ========= boat related
44 | - wifi extender
45 | - extender pole
46 | - chair for boat
47 | - wind speed sensor + long usb cable
48 |
49 |
50 | ========= boat
51 | - hull
52 | - mast (2 parts)
53 | - rudder
54 | - sails (3 sets of 2 sails)
55 |
56 |
57 | ========= misc
58 | - towel
59 | - buoyancy aids
60 | - water bottle for cleaning boat
61 | - silicone for waterproofing
62 |
63 |
64 | ========= paper
65 | - printed version of the wiki
66 | - printed version of the rules
67 | - blank papers
68 | - pens
69 |
70 |
71 | ========= buy on arrival
72 | - de-ionized water
73 | - nail polish remover (acetone)
74 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Horten/horten_wp/obstacle_avoidance2_gen_obstacle.yaml:
--------------------------------------------------------------------------------
1 | wp/acceptRadius: 8
2 | wp/tackVotingRadius: 12
3 |
4 | wp/table:
5 | A: [59.42693032012717, 10.470490123255757]
6 | B: [59.427012001570425, 10.470049168082966]
7 | B1: [59.42670000092252, 10.46983517226954]
8 | C: [59.42706300255131, 10.469773835454163]
9 | C1: [59.42675100144588, 10.469559841838787]
10 | D: [59.42711400295451, 10.469498501989335]
11 | D1: [59.42680200139158, 10.46928451057201]
12 | E: [59.427165002780086, 10.469223167688426]
13 | E1: [59.42685300075961, 10.469009178469207]
14 | F: [59.42721600202796, 10.468947832551521]
15 | F1: [59.42690399954999, 10.468733845530409]
16 | G: [59.42729767828894, 10.468506869878524]
17 | wp1: [59.426883, 10.470271]
18 | wp2: [59.427189, 10.468619]
19 | wp3: [59.427353, 10.46878]
20 | wp4: [59.427039, 10.470378]
21 | wp/tasks:
22 | - {kind: to_waypoint, waypoint: A}
23 | - {kind: to_waypoint, waypoint: B}
24 | - {kind: obstacle_waypoints, normal: C, obstacle: B1}
25 | - {kind: obstacle_waypoints, normal: D, obstacle: C1}
26 | - {kind: obstacle_waypoints, normal: E, obstacle: D1}
27 | - {kind: obstacle_waypoints, normal: F, obstacle: E1}
28 | - {kind: to_waypoint, waypoint: G}
29 | - {kind: to_waypoint, waypoint: F}
30 | - {kind: obstacle_waypoints, normal: E, obstacle: F1}
31 | - {kind: obstacle_waypoints, normal: D, obstacle: E1}
32 | - {kind: obstacle_waypoints, normal: C, obstacle: D1}
33 | - {kind: obstacle_waypoints, normal: B, obstacle: C1}
34 | - {kind: to_waypoint, waypoint: A}
35 |
--------------------------------------------------------------------------------
/utilities/setup_scripts/Install_needed_packages_Raspi.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | ## Swap file for the pi
5 | # Install needed packages
6 | sudo apt-get --assume-yes install dphys-swapfile
7 |
8 | # Copy default dphys-swapfile
9 | sudo cp /etc/dphys-swapfile /etc/dphys-swapfile.old
10 |
11 | # Delete old and create new dphys-swapfile
12 | sudo rm /etc/dphys-swapfile
13 | sudo touch /etc/dphys-swapfile
14 |
15 | # Contents of dphys-swapfile
16 | sudo echo "CONF_SWAPSIZE=1024" >> /etc/dphys-swapfile
17 |
18 | # Increase swap size
19 | sudo dphys-swapfile setup
20 | sudo dphys-swapfile swapon
21 |
22 |
23 |
24 | # i2c-tools (for i2cdetect)
25 | # gpsd-clients (simple GPS debug)
26 | echo "Installing apt packages..."
27 | sudo apt-get --assume-yes install i2c-tools gpsd-clients
28 |
29 | # spidev (Needed for serial)
30 | # ina219 library (voltmeter/currentmeter)
31 | yes | sudo pip install spidev pi_ina219
32 |
33 |
34 | # Increase UART frequency
35 | echo "Setting UART frequency..."
36 | echo "enable_uart=1" | sudo tee -a /boot/config.txt > /dev/null
37 |
38 | # Set time zone to england (it is the same for portugal)
39 | echo "Setting timezone..."
40 | sudo timedatectl set-timezone Europe/London
41 |
42 | # pigpio - for talking to GPIO pins (including daemon service)
43 | echo "Installing pigpio from apt..."
44 | sudo apt-get --assume-yes install pigpio python-pigpio
45 | echo "Installing and starting pigpio service..."
46 | sudo cp pigpio.service /lib/systemd/system
47 | sudo systemctl enable pigpio
48 | sudo systemctl start pigpio
49 | echo "Done."
50 |
--------------------------------------------------------------------------------
/utilities/waypoint_generator/waypoint_generator_startline.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python2
2 | # READY FOR MIT
3 | # Script to generate waypoints for the obstacle avoidance challenge
4 | # man:
5 | # waypoint_generator path/to/waypoint.yaml
6 |
7 | from __future__ import print_function
8 |
9 | import sys
10 | import yaml
11 | import numpy as np
12 | from sailing_robot.navigation import Navigation
13 |
14 | # Load yaml file given in argument
15 | input_file = sys.argv[1]
16 | with open(input_file, 'r') as f:
17 | yaml_data = yaml.safe_load(f)
18 |
19 | output_file = input_file[:-5] + "_gen_obstacle.yaml"
20 |
21 | margin = 10 # [m]
22 |
23 | wp1 = yaml_data['wp/table']['1']
24 | wp0 = yaml_data['wp/table']['0']
25 | #wp3 = yaml_data['wp/table']['wp3']
26 |
27 |
28 | nav = Navigation()
29 | wp1_utm = nav.latlon_to_utm(wp1[0], wp1[1])
30 | wp0_utm = nav.latlon_to_utm(wp0[0], wp0[1])
31 |
32 |
33 | # Unit vector 10
34 | v10 = np.array([wp0_utm[0] - wp1_utm[0], wp0_utm[1] - wp1_utm[1]])
35 | d10 = np.linalg.norm(v10)
36 | v10_unit = v10 / d10
37 | print(d10)
38 |
39 |
40 | # Unit vector orth to 10
41 | v10_orth = np.array([-v10_unit[1], v10_unit[0]])
42 |
43 |
44 |
45 | # Coordinates of waypoints (see scheme in the wiki
46 | f1 = wp1_utm + v10_unit*d10/2 - v10_orth*margin
47 | f2 = wp1_utm + v10_unit*d10/2 + v10_orth*margin
48 |
49 | def to_wp(wp):
50 | latlon = nav.utm_to_latlon(wp[0], wp[1])
51 | return [float(latlon.lat), float(latlon.lon) ]
52 |
53 |
54 | f1 = to_wp(f1)
55 | f2 = to_wp(f2)
56 |
57 | print("f1:", f1, ",")
58 | print("f2:", f2, ",")
59 |
--------------------------------------------------------------------------------
/dashboard/src/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin-left: auto;
3 | margin-right: auto;
4 | font-family: 'Menlo', sans-serif;
5 | font-weight: 200;
6 | background: white; }
7 |
8 | .zebra-table {
9 | margin: 0 auto;
10 | border: 1px solid #ddd;
11 | border-collapse: collapse; }
12 | .zebra-table thead {
13 | background-color: #003333;
14 | color: white; }
15 | .zebra-table tbody tr:nth-child(odd) {
16 | background-color: #33CC99; }
17 | .zebra-table tbody th {
18 | color: #FFFF33; }
19 | .zebra-table tbody td {
20 | min-width: 3em;
21 | padding: 0.5em; }
22 |
23 | #magic-compass td {
24 | text-align: center; }
25 |
26 | .compass-hand {
27 | height: 160px;
28 | transform-origin: bottom center; }
29 |
30 | #rosout .nav-item .nav-link.rosout-debug {
31 | background: blue; }
32 | #rosout .nav-item .nav-link.rosout-info {
33 | background: yellow; }
34 | #rosout .nav-item .nav-link.rosout-warn {
35 | background: orange; }
36 | #rosout .nav-item .nav-link.rosout-error {
37 | background: red; }
38 | #rosout .nav-item .nav-link.rosout-fatal {
39 | background: darkorchid; }
40 | #rosout #rosout-display {
41 | height: 500px;
42 | overflow-y: scroll;
43 | color: white;
44 | padding: 1em; }
45 | #rosout #rosout-display p.debug {
46 | color: #0275d8; }
47 | #rosout #rosout-display p.info {
48 | color: #5bc0de; }
49 | #rosout #rosout-display p.warn {
50 | color: #f0ad4e; }
51 | #rosout #rosout-display p.error {
52 | color: #d9534f; }
53 | #rosout #rosout-display p.fatal {
54 | color: black; }
55 |
56 | /*# sourceMappingURL=main.css.map */
57 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/june_eastleigh_only_rpi.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/simulation_gps_fix:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # READY FOR MIT
3 | # Simulator for gps_fix
4 |
5 |
6 | import rospy
7 | from sensor_msgs.msg import NavSatFix
8 | from sailing_robot.msg import gpswtime
9 | import time, math
10 | from datetime import datetime
11 |
12 |
13 |
14 | class Gps_fix_simu():
15 | def __init__(self):
16 | """
17 | Publish gps_fix topic for the logger
18 | """
19 |
20 | self.gps_fix_pub = rospy.Publisher('gps_fix', gpswtime, queue_size=10)
21 |
22 | rospy.init_node("simulation_gps_fix", anonymous=True)
23 |
24 | self.rate = rospy.Rate(1)
25 |
26 | rospy.Subscriber('position', NavSatFix, self.update_position)
27 | self.gps_fix_lock = True
28 |
29 | while self.gps_fix_lock and not rospy.is_shutdown():
30 | self.rate.sleep()
31 |
32 | rospy.loginfo("gps fix simulated")
33 | self.gps_fix_publisher()
34 |
35 |
36 | def update_position(self, msg):
37 | self.position = msg
38 | self.gps_fix_lock = False
39 |
40 | def gps_fix_publisher(self):
41 |
42 | while not rospy.is_shutdown():
43 |
44 | msg = gpswtime()
45 | msg.fix = self.position
46 | msg.time_h = datetime.now().hour
47 | msg.time_m = datetime.now().minute
48 | msg.time_s = datetime.now().second
49 |
50 | self.gps_fix_pub.publish(msg)
51 | self.rate.sleep()
52 |
53 |
54 | if __name__ == '__main__':
55 | try:
56 | Gps_fix_simu()
57 | except rospy.ROSInterruptException:
58 | pass
59 |
60 |
--------------------------------------------------------------------------------
/src/sailing_robot/tests/test_navigation.py:
--------------------------------------------------------------------------------
1 | from nose.tools import assert_equal, assert_almost_equal
2 | from sailing_robot.navigation import (angleAbsDistance, angle_subtract,
3 | Navigation,
4 | )
5 |
6 | def test_angle_abs_difference():
7 | assert_equal(angleAbsDistance(90, 50), 40)
8 | assert_equal(angleAbsDistance(50, 90), 40)
9 | assert_equal(angleAbsDistance(350, 40), 50)
10 | assert_equal(angleAbsDistance(40, 350), 50)
11 |
12 | def test_angle_subtract():
13 | assert_equal(angle_subtract(40, 10), 30)
14 | assert_equal(angle_subtract(40, 90), -50)
15 | assert_equal(angle_subtract(10, 350), 20)
16 | assert_equal(angle_subtract(340, 10), -30)
17 |
18 | def test_utm_latlon_conversion():
19 | n = Navigation(utm_zone=30)
20 | lat = 50.927482
21 | lon = -1.408787
22 | x, y = n.latlon_to_utm(lat, lon)
23 | ll = n.utm_to_latlon(x, y)
24 | assert_almost_equal(ll.lat.decimal_degree, lat)
25 | assert_almost_equal(ll.lon.decimal_degree, lon)
26 |
27 | class DummyNSFMsg(object):
28 | def __init__(self, lat, lon):
29 | self.latitude = lat
30 | self.longitude = lon
31 |
32 | def test_safety_zone():
33 | n = Navigation(safety_zone_ll=[
34 | (50.78, 1.00),
35 | (50.78, 1.04),
36 | (50.82, 1.04),
37 | (50.82, 1.00),
38 | ])
39 | n.update_position(DummyNSFMsg(50.8, 1.02))
40 | assert_equal(n.check_safety_zone(), 0)
41 |
42 | n.update_position(DummyNSFMsg(50.78001, 1.02))
43 | assert_equal(n.check_safety_zone(), 1)
44 |
45 | n.update_position(DummyNSFMsg(50.75, 1.02))
46 | assert_equal(n.check_safety_zone(), 2)
47 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/archive/Horten/horten_wp/obstacle_avoidance_gen_obstacle.yaml:
--------------------------------------------------------------------------------
1 | wp/acceptRadius: 8
2 | wp/tackVotingRadius: 12
3 |
4 | wp/table:
5 | A: [59.42688742927379, 10.469637281941829]
6 | B: [59.427141334946505, 10.469274173751813]
7 | B1: [59.426945332716336, 10.468728175446557]
8 | C: [59.42732266931173, 10.469014844932985]
9 | C1: [59.42712666600814, 10.4684688451743]
10 | D: [59.42750400316797, 10.468755513346792]
11 | D1: [59.427307998790944, 10.468209512134678]
12 | E: [59.42768533651517, 10.46849617899315]
13 | E1: [59.4274893310647, 10.467950176327633]
14 | F: [59.427866669353335, 10.468236841872027]
15 | F1: [59.42767066282942, 10.467690837753139]
16 | G: [59.42812057117717, 10.467873712756386]
17 | start1: [59.426911, 10.469397000000072]
18 | start2: [59.427009, 10.469669999999951]
19 | wp1: [59.426911, 10.469397000000072]
20 | wp2: [59.427999, 10.467841000000021]
21 | wp3: [59.428095, 10.468119999999999]
22 | wp4: [59.427009, 10.469669999999951]
23 | wp/tasks:
24 | - {kind: to_waypoint, waypoint: A}
25 | - {kind: to_waypoint, waypoint: B}
26 | - {kind: obstacle_waypoints, normal: C, obstacle: B1}
27 | - {kind: obstacle_waypoints, normal: D, obstacle: C1}
28 | - {kind: obstacle_waypoints, normal: E, obstacle: D1}
29 | - {kind: obstacle_waypoints, normal: F, obstacle: E1}
30 | - {kind: to_waypoint, waypoint: G}
31 | - {kind: to_waypoint, waypoint: F}
32 | - {kind: obstacle_waypoints, normal: E, obstacle: F1}
33 | - {kind: obstacle_waypoints, normal: D, obstacle: E1}
34 | - {kind: obstacle_waypoints, normal: C, obstacle: D1}
35 | - {kind: obstacle_waypoints, normal: B, obstacle: C1}
36 | - {kind: to_waypoint, waypoint: A}
37 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/parameters/simulator.yaml:
--------------------------------------------------------------------------------
1 |
2 | # Wind conditions:
3 | # wind speed in m/s (1m/s = 1.9kts)
4 | simulation/wind/speed: 5
5 |
6 | # wind direction relative to north
7 | simulation/wind/direction: 317
8 |
9 | # A gaussian noise is generated on both wind speed and wind direction, use a value of 0 to remove the noise generation.
10 | # These parameters are the 'width' of the gaussian distribution
11 | simulation/wind/noise_direction_range: 0 # [degree]
12 | simulation/wind/noise_speed_range: 0 # [m/s]
13 |
14 |
15 | # Boat velocity
16 | # velocity of the boat at 90° from the wind in with 1 m/s wind speed
17 | # for example in a 10kts wind, if the boat goes at 3kts, the coefficient is 0.3
18 | simulation/velocity/coefficient: 0.4
19 |
20 | # Minimum velocity of the boat (in m/s)
21 | simulation/velocity/minimum: 0.5
22 |
23 | # Time after a tack the boat velocity is reduced [s]
24 | simulation/velocity/tacking_punishment_time: 3 # [s]
25 |
26 | # the velocity will be multiplied by the following coefficient after each tack
27 | simulation/velocity/tacking_punishment_coefficient: 0.2
28 |
29 | # simulate a constant water stream
30 | simulation/velocity/water_stream_speed: 0.0 # [m/s]
31 | simulation/velocity/water_stream_direction: 180 # [degree]
32 |
33 | # sailsheet error normalized (sailsheet_error_norm) in simulator is multiplied by this coef. to simulate influence of not-ideal setting of sailsheet
34 | simulation/vecolity/coef_sailsheet_error: 1
35 |
36 |
37 | simulation/heading_init: 270.0
38 |
39 |
40 | # Heading coefficient
41 | # just a multiplicator coefficient for the heading control in case of need
42 | simulation/heading/coefficient: 1.0
43 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/actuator_driver_sail:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """
3 | Node looks up correct sail setting from table
4 | Subscribes: wind direction apparent
5 | Sets sail actuator to correct PWM value
6 | """
7 | import time
8 | import rospy
9 | from std_msgs.msg import Float64, Float32, String
10 | import pigpio
11 | from sailing_robot.sail_table import SailTable
12 |
13 | SAILGPIO = rospy.get_param('sail/pin')
14 |
15 | # get dictionary for the boat specific sail PWM settings
16 | minPWM = rospy.get_param('sail/servolowerlimits')
17 | maxPWM = rospy.get_param('sail/servohigherlimits')
18 |
19 | def sail_servo_update(msg):
20 | sheet_normalized = msg.data
21 |
22 | # calculate actual PWM value from limits
23 | sheetPWM = (sheet_normalized * (maxPWM-minPWM)) + minPWM
24 | debug_pub_pwm.publish(sheetPWM)
25 | piPWM.set_servo_pulsewidth(SAILGPIO, sheetPWM)
26 |
27 | def post():
28 | '''Power-On Self Test'''
29 | if not rospy.get_param('do_post', False):
30 | pass
31 |
32 | rospy.logwarn('sail test: sheet in')
33 | piPWM.set_servo_pulsewidth(SAILGPIO, minPWM)
34 | time.sleep(3)
35 | rospy.logwarn('sail test: sheet out')
36 | piPWM.set_servo_pulsewidth(SAILGPIO, maxPWM)
37 | time.sleep(3)
38 |
39 | if __name__ == '__main__':
40 | piPWM = pigpio.pi();
41 | piPWM.set_mode(SAILGPIO, pigpio.OUTPUT) # GPIO 24/RPi PIN 18 as sail servo pin
42 | post()
43 | try:
44 | debug_pub_pwm = rospy.Publisher('debug_sailsheet_pwm', Float32, queue_size=10)
45 | rospy.init_node('actuator_driver_sail', anonymous=True)
46 | rospy.Subscriber('sailsheet_normalized', Float32, sail_servo_update)
47 |
48 | rospy.spin()
49 |
50 | except rospy.ROSInterruptException:
51 | pass
52 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/sensor_driver_multiplexer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Read whether the servos are being remotely control.
3 |
4 | Publishes: remote_control (Bool)
5 | """
6 | import rospy
7 | from std_msgs.msg import Bool
8 | import pigpio
9 | import time
10 |
11 | # GPIO 22/RPi PIN 15 as multiplexer connection
12 | GPIO = rospy.get_param('multiplexer/gpio', default=22)
13 |
14 | # Reading PWMs: we set up callbacks for the edges of the pulse (tick and tock).
15 | # The time between when they are called, in microseconds, is the pulse width.
16 |
17 | last_tick = 0
18 | last_interval = 0
19 | last_pulse_time = 0
20 |
21 | def tick(gpio, level, tick):
22 | global last_tick
23 | last_tick = tick
24 |
25 | def tock(gpio, level, tick):
26 | global last_interval, last_pulse_time
27 | last_interval = tick - last_tick
28 | last_pulse_time = time.time()
29 |
30 | if __name__ == '__main__':
31 | pi = pigpio.pi();
32 | pi.set_mode(GPIO, pigpio.INPUT)
33 | try:
34 | rospy.init_node("sensor_driver_multiplexer", anonymous=True)
35 | pub = rospy.Publisher('remote_control', Bool, queue_size=10)
36 | rate = rospy.Rate(rospy.get_param("config/rate"))
37 |
38 | pi.callback(GPIO, pigpio.RISING_EDGE, tick)
39 | pi.callback(GPIO, pigpio.FALLING_EDGE, tock)
40 |
41 | while not rospy.is_shutdown():
42 | if time.time() - last_pulse_time > 0.1:
43 | # No recent pulse: RC off or connection lost
44 | pub.publish(False)
45 | else:
46 | # Long pulse means RC overrides raspberry pi. 1500 us cutoff
47 | pub.publish(last_interval > 1500)
48 | rate.sleep()
49 |
50 | except rospy.ROSInterruptException:
51 | pass
52 |
--------------------------------------------------------------------------------
/notes/systems-checklist.md:
--------------------------------------------------------------------------------
1 | # Systems checklist
2 |
3 | To ensure that things are running with real hardware. Setup steps:
4 |
5 | 1. Ensure RasPi and Arduinos have correct code loaded from Github, and the RasPi
6 | code is built (`catkin_make` in sailing-robot directory).
7 | 2. Connect RasPi to power source, and Arduinos to RasPi.
8 | 3. Plug in all sensors and actuators
9 | 4. On the RasPi, run `source devel/setup.bash`, and then
10 | `roslaunch sailing_robot with-real-hardware.launch`
11 |
12 | Check that:
13 |
14 | - All nodes come up successfully
15 | - Values 0-360 are being published on `/heading`
16 | - `/heading` changes appropriately when the IMU is turned
17 | - Values 0-360 are being published on `/wind_direction_apparent`
18 | - `/wind_direction_apparent` changes when the windvane is manually moved
19 | - `/wind_speed_apparent` falls to 0 when anemometer still, rises when manually moved
20 | - TODO: smoothing of wind direction (and speed?)
21 | - `/position` gives a sensible latitude and longitude
22 | - `/position` changes if the setup is moved some distance
23 | - `/sailing_state` is being published: should change between 'normal',
24 | 'tack_to_port_tack' and 'tack_to_stbd_tack' depending on heading, wind
25 | direction and next waypoint.
26 | - `/goal_heading` is being published: points to next waypoint or at beating
27 | angle close to wind.
28 | - `/tack_sail` is published (0)
29 | - `/tack_rudder` is published (0, 90, -90)
30 | - `/rudder_control` is published:
31 | - Same as `/tack_rudder` when `/sailing_state` is tack
32 | - To correct `/heading` towards `/goal_heading` otherwise
33 | - `/sail_servo` is published: pulls sail in close to the wind, on both sides
34 | - `/rudder_control` is affecting rudder servo
35 | - `/sail_servo` is affecting sheet winch servo
36 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/test-calibration.launch:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/xsens_driver/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 | Changelog for package xsens_driver
3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 |
5 | 2.1.0 (2017-04-14)
6 | ------------------
7 | * Add no_rotation_duration option
8 | * Fix typo (`#39 `_)
9 | * Fix gnss pvt parsing (`#37 `_)
10 | * fix GetOptionFlags (`#34 `_)
11 | * Contributors: Andersw88, Francis Colas
12 |
13 | 2.0.1 (2016-08-16)
14 | ------------------
15 | * fix TimeReference member name
16 | * Contributors: Francis Colas
17 |
18 | 2.0.0 (2016-08-02)
19 | ------------------
20 | * support of mark iv devices (configuration and ROS node)
21 | * remove gps_common dependency (for jade and kinetic)
22 | * work in 16.04 with pyserial3
23 | * proper message types for temperature, pressure, magnetic field and time
24 | * better timeout management
25 | * various bug fixes
26 | * Contributors: CTU robot, Francis Colas, João Sousa, Konstantinos Chatzilygeroudis, Latitude OBC, Vincent Rousseau, fcolas, jotaemesousa
27 |
28 | 1.0.3 (2014-05-14)
29 | ------------------
30 | * Inclusion of launch file
31 | * Additions and fixes from PAL robotics
32 | * Add local frame conversion for calibrated imu data (acc, gyr, mag)
33 | * Contributors: Enrique Fernandez, Francis Colas, Paul Mathieu, Sam Pfeiffer
34 |
35 | 1.0.2 (2014-03-04)
36 | ------------------
37 | * catkinized
38 | * experimental support of mark 4 IMUs
39 | * fixed scaling in DOP values
40 | * adding publisher for full data as a string
41 | * relative topic names
42 |
43 | 1.0.1 (2012-08-27)
44 | ------------------
45 | * minor improvements
46 | * naming cleanup
47 | * Contributors: Benjamin Hitov, Francis Colas, Nikolaus Demmel, Stéphane Magnenat, fcolas
48 |
--------------------------------------------------------------------------------
/src/sailing_robot/msg/NaVSOL.msg:
--------------------------------------------------------------------------------
1 | # NAV-SOL (0x01 0x06)
2 | # Navigation Solution Information
3 | #
4 | # This message combines Position, velocity and time solution in ECEF, including
5 | # accuracy figures
6 | # This message has only been retained for backwards compatibility; users are
7 | # recommended to use the UBX-NAV-PVT message in preference.
8 | #
9 |
10 | uint8 CLASS_ID = 1
11 | uint8 MESSAGE_ID = 6
12 |
13 | uint32 iTOW # GPS Millisecond time of week [ms]
14 | int32 fTOW # Fractional Nanoseconds remainder of rounded
15 | # ms above, range -500000 .. 500000 [ns]
16 | int16 week # GPS week (GPS time)
17 |
18 | uint8 gpsFix # GPSfix Type, range 0..5
19 | uint8 GPS_NO_FIX = 0
20 | uint8 GPS_DEAD_RECKONING_ONLY = 1
21 | uint8 GPS_2D_FIX = 2
22 | uint8 GPS_3D_FIX = 3
23 | uint8 GPS_GPS_DEAD_RECKONING_COMBINED = 4
24 | uint8 GPS_TIME_ONLY_FIX = 5
25 |
26 | uint8 flags # Fix Status Flags
27 | uint8 FLAGS_GPS_FIX_OK = 1 # Fix within limits i.e. within DOP & ACC Masks
28 | uint8 FLAGS_DIFF_SOLN = 2 # DGPS used
29 | uint8 FLAGS_WKNSET = 4 # Week Number valid
30 | uint8 FLAGS_TOWSET = 8 # Time of Week valid
31 |
32 | int32 ecefX # ECEF X coordinate [cm]
33 | int32 ecefY # ECEF Y coordinate [cm]
34 | int32 ecefZ # ECEF Z coordinate [cm]
35 | uint32 pAcc # 3D Position Accuracy Estimate [cm]
36 | int32 ecefVX # ECEF X velocity [cm/s]
37 | int32 ecefVY # ECEF Y velocity [cm/s]
38 | int32 ecefVZ # ECEF Z velocity [cm/s]
39 | uint32 sAcc # Speed Accuracy Estimate [cm/s]
40 | uint16 pDOP # Position DOP [1 / 0.01]
41 | uint8 reserved1 # Reserved
42 | uint8 numSV # Number of SVs used in Nav Solution
43 | uint32 reserved2 # Reserved
44 |
--------------------------------------------------------------------------------
/src/sailing_robot/tests/test_heading_planning_laylines.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from nose.tools import assert_equal
3 |
4 | from shapely.geometry import Point
5 | from sailing_robot.heading_planning_laylines import HeadingPlan, LAYLINE_EXTENT
6 | from sailing_robot.navigation import Navigation
7 |
8 | class HeadingPlanTests(unittest.TestCase):
9 | def setUp(self):
10 | nav = Navigation(beating_angle=45, utm_zone=30)
11 | nav.heading = 0
12 | nav.wind_direction = 225
13 | self.hp = HeadingPlan(nav)
14 | self.hp.waypoint_xy = Point(0, 0)
15 |
16 | def test_lay_triangle(self):
17 | lt = self.hp.lay_triangle()
18 | coords = list(lt.exterior.coords)
19 | self.assertEqual(coords[0], (0, 0))
20 |
21 | self.assertAlmostEqual(coords[1][0], 0)
22 | self.assertAlmostEqual(coords[1][1], LAYLINE_EXTENT)
23 |
24 | self.assertAlmostEqual(coords[2][0], LAYLINE_EXTENT)
25 | self.assertAlmostEqual(coords[2][1], 0)
26 |
27 | def test_calculate(self):
28 | # Between the laylines
29 | self.hp.nav.position_xy = Point(50, 50)
30 | state, goal_heading = self.hp.calculate_state_and_goal()
31 | self.assertEqual(state, 'normal')
32 | self.assertAlmostEqual(goal_heading, 270)
33 |
34 | # Outside laylines, going in right direction
35 | self.hp.nav.position_xy = Point(50, -50)
36 | state, goal_heading = self.hp.calculate_state_and_goal()
37 | self.assertEqual(state, 'normal')
38 | self.assertAlmostEqual(goal_heading, 315)
39 |
40 | # Outside laylines, going in wrong direction
41 | self.hp.nav.position_xy = Point(-50, 50)
42 | state, goal_heading = self.hp.calculate_state_and_goal()
43 | self.assertEqual(state, 'switch_to_stbd_tack')
44 | self.assertAlmostEqual(goal_heading, 180)
45 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/sensor_service_imu:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Sensor service IMU is a node to
4 | # * convert quaternion orientation into heading angle with our convention
5 | # * transform NED (North East Down) reference IMU data into ENU (East North Up)
6 | #
7 | # Subscribe to: imu/data (Imu)
8 | # Published topic: heading (Float32)
9 |
10 |
11 | from __future__ import division
12 |
13 | import rospy
14 | import tf
15 | import math
16 |
17 | from std_msgs.msg import Float32
18 | from sensor_msgs.msg import Imu
19 |
20 | class heading_processing(object):
21 | def __init__(self):
22 | rospy.init_node('Heading_service')
23 | self.heading = 0
24 | self.heading_pub = rospy.Publisher('heading', Float32, queue_size=10)
25 | self.pitch_pub = rospy.Publisher('pitch', Float32, queue_size=10)
26 | self.roll_pub = rospy.Publisher('roll', Float32, queue_size=10)
27 | rospy.Subscriber('imu/data', Imu, self.heading_publisher)
28 |
29 | def heading_publisher(self, msg):
30 | imu = msg.orientation
31 | self.heading = (math.degrees(
32 | tf.transformations.euler_from_quaternion(
33 | (imu.x, imu.y,imu.z, imu.w))[2]) - 90) % 360
34 | self.pitch = math.degrees(
35 | tf.transformations.euler_from_quaternion(
36 | (imu.x, imu.y,imu.z, imu.w))[1])
37 | self.roll = math.degrees(
38 | tf.transformations.euler_from_quaternion(
39 | (imu.x, imu.y,imu.z, imu.w))[0])
40 |
41 |
42 |
43 | def run(self):
44 | r = rospy.Rate(20)
45 | while not rospy.is_shutdown():
46 | self.heading_pub.publish(self.heading)
47 | r.sleep()
48 |
49 | if __name__ == '__main__':
50 | process = heading_processing()
51 | try:
52 | process.run()
53 | except rospy.ROSInterruptException:
54 | pass
55 |
--------------------------------------------------------------------------------
/utilities/setup_scripts/Setup_ROS.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Setup ROS repositories
4 | sudo sh -c \'echo \"deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main\" > /etc/apt/sources.list.d/ros-latest.list\'
5 | wget https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -O - | sudo apt-key add -
6 |
7 | # Update Debian package index
8 | sudo apt-get update
9 | sudo apt-get --assume-yes upgrade
10 |
11 | # Install bootstrap dependencies
12 | sudo apt-get install --assume-yes ros-kinetic-desktop-full
13 | sudo apt-get install --assume-yes python-rosinstall python-rosinstall-generator python-wstool build-essential
14 |
15 | #sudo apt-get --assume-yes install python-pip python-setuptools python-yaml python-distribute python-docutils python-dateutil python-six
16 | #yes | sudo pip install rosdep rosinstall_generator wstool rosinstall
17 |
18 | # Initialize Rosdep
19 | sudo rosdep init
20 | rosdep update
21 |
22 | # Create Catkin Workspace
23 | #mkdir ~/ros_catkin_ws
24 | #cd ~/ros_catkin_ws
25 |
26 | # Fetch core packages to be built
27 | #rosinstall_generator mavros mavros_extras hector_slam hector_localization hokuyo_node ros_control joystick_drivers ros_comm geometry_msgs sensor_msgs rosserial_python rosserial_msgs diagnostic_msgs --rosdistro kinetic --deps --wet-only --exclude roslisp --tar > kinetic-ros_comm-wet.rosinstall
28 | #wstool init src kinetic-ros_comm-wet.rosinstall
29 |
30 | # Resolve dependencies with rosdep
31 | #cd ~/ros_catkin_ws
32 | #rosdep install --from-paths src --ignore-src --rosdistro kinetic -y -r --os=debian:jessie
33 |
34 | # Build catkin workspace
35 | #sudo ./src/catkin/bin/catkin_make_isolated --install -DCMAKE_BUILD_TYPE=Release --install-space /opt/ros/kinetic
36 |
37 | # Source new installation
38 | source /opt/ros/kinetic/setup.bash
39 | echo "source /opt/ros/kinetic/setup.bash" >> ~/.bashrc
40 |
41 | # Setup networking so other laptops can connect to the boat
42 | # master is the boat
43 | echo "export ROS_HOSTNAME=192.168.42.1" >> ~/.bashrc
44 |
45 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/sensor_processed_wind_direction:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # READY FOR MIT
3 |
4 | import collections
5 | import rospy
6 | from std_msgs.msg import Float32, Float64
7 | from sailing_robot.navigation import angle_average
8 |
9 |
10 |
11 | class Wind_direction_average():
12 | def __init__(self):
13 | self.wind_direction_average_pub = rospy.Publisher('wind_direction_average', Float32, queue_size=10)
14 |
15 | rospy.init_node("sensor_processed_wind_direction", anonymous=True)
16 | rospy.Subscriber('heading', Float32, self.update_heading)
17 | self.heading = 0
18 | rospy.Subscriber('wind_direction_apparent', Float64, self.update_wind_direction_apparent)
19 | self.wind_direction_apparent = 0
20 |
21 | sensor_rate = rospy.get_param("config/rate")
22 | self.rate = rospy.Rate(sensor_rate)
23 | AVE_TIME = rospy.get_param("wind/trend_average_time") # lengh of the averaging in seconds
24 | AVE_SIZE = int(AVE_TIME * sensor_rate) # size of the averaging sample
25 | self.average_list = collections.deque(maxlen = AVE_SIZE)
26 | self.wind_direction_average_publisher()
27 |
28 |
29 | def update_heading(self, msg):
30 | self.heading = msg.data
31 |
32 |
33 | def update_wind_direction_apparent(self, msg):
34 | self.wind_direction_apparent = msg.data
35 |
36 |
37 | def wind_direction_average_publisher(self):
38 |
39 | while not rospy.is_shutdown():
40 | wind_direction = (self.wind_direction_apparent + self.heading) % 360
41 |
42 | self.average_list.append(wind_direction)
43 |
44 | wind_direction_average = angle_average(list(self.average_list))
45 | self.wind_direction_average_pub.publish(wind_direction_average)
46 |
47 | self.rate.sleep()
48 |
49 |
50 | if __name__ == '__main__':
51 | try:
52 | Wind_direction_average()
53 | except rospy.ROSInterruptException:
54 | pass
55 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/with-real-hardware.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/calibration/monitor_battery_voltage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | """Display raw readings from the MinIMU in a curses terminal interface.
3 |
4 | Run this as a script to play back readings stored in a CSV file.
5 | The curses interface defined here is also used in other scripts in this folder.
6 | """
7 | import csv
8 | from curses import wrapper
9 | import math
10 | import os
11 | import sys
12 | import time
13 | from ina219 import INA219
14 |
15 | SHUNT_OHMS = 0.1
16 | MAX_AMPS = 1
17 |
18 |
19 | ina = INA219(SHUNT_OHMS, MAX_AMPS)
20 | ina.configure(ina.RANGE_16V, ina.GAIN_AUTO)
21 |
22 | class Extrema(object):
23 | def __init__(self):
24 | self.min = 99999
25 | self.max = -99999
26 |
27 | def update(self, value):
28 | value = float(value)
29 | if value < self.min:
30 | self.min = value
31 | if value > self.max:
32 | self.max = value
33 | return self.min, value, self.max
34 |
35 |
36 | class VoltDisplay(object):
37 | def __init__(self, stdscr):
38 | self.stdscr = stdscr
39 | stdscr.clear()
40 | stdscr.addstr(0, 0, ' | min | now | max ')
41 | stdscr.addstr(1, 0, 'Current (mA)|')
42 | stdscr.addstr(2, 0, 'Voltage (V) |')
43 |
44 | self.voltage_extreme = Extrema()
45 | self.current_extreme = Extrema()
46 |
47 | def update_current(self):
48 | self.stdscr.addstr(1, 14, '{:3.3f} | {:3.3f} | {:3.3f}'.format(*self.current_extreme.update(ina.current())))
49 | def update_voltage(self):
50 | self.stdscr.addstr(2, 14, '{:3.3f} | {:3.3f} | {:3.3f}'.format(*self.voltage_extreme.update(ina.voltage())))
51 |
52 |
53 | def main(stdscr):
54 | display = VoltDisplay(stdscr)
55 | while True:
56 | display.update_current()
57 | display.update_voltage()
58 | stdscr.refresh()
59 | time.sleep(0.5)
60 |
61 | stdscr.getkey()
62 |
63 | if __name__ == '__main__':
64 |
65 | try:
66 | wrapper(main)
67 | except KeyboardInterrupt:
68 | print('^C')
69 |
--------------------------------------------------------------------------------
/src/sailing_robot/src/sailing_robot/heading_planning_dumb.py:
--------------------------------------------------------------------------------
1 | import LatLon as ll
2 | import math
3 | from shapely.geometry import Point
4 |
5 | from .taskbase import TaskBase
6 |
7 | class HeadingPlan(TaskBase):
8 | def __init__(self, nav,
9 | waypoint=ll.LatLon(50.742810, 1.014469), # somewhere in the solent
10 | target_radius=2, waypoint_id=None,
11 | ):
12 | """Heading planning machinery, dumb version.
13 |
14 | For testing purposes, this believes that the boat can go in any
15 | direction, including directly upwind. It never tries to tack.
16 | """
17 | self.nav = nav
18 | self.waypoint = waypoint
19 | self.waypoint_id = waypoint_id
20 | x, y = self.nav.latlon_to_utm(waypoint.lat.decimal_degree, waypoint.lon.decimal_degree)
21 | self.waypoint_xy = Point(x, y)
22 | self.target_area = self.waypoint_xy.buffer(target_radius)
23 |
24 | def start(self):
25 | pass
26 |
27 | def check_end_condition(self):
28 | return self.nav.position_xy.within(self.target_area)
29 |
30 | def distance_heading_to_waypoint(self):
31 | dx = self.waypoint_xy.x - self.nav.position_xy.x
32 | dy = self.waypoint_xy.y - self.nav.position_xy.y
33 | d = (dx**2 + dy**2) ** 0.5
34 | h = math.degrees(math.atan2(dx, dy)) % 360
35 | return d, h
36 |
37 | debug_topics = [
38 | ('dbg_distance_to_waypoint', 'Float32'),
39 | ('dbg_heading_to_waypoint', 'Float32'),
40 | ('dbg_latest_waypoint_id', 'String'),
41 | ]
42 |
43 | def calculate_state_and_goal(self):
44 | """Work out what we want the boat to do
45 | """
46 | dwp, hwp = self.distance_heading_to_waypoint()
47 | self.debug_pub('dbg_distance_to_waypoint', dwp)
48 | self.debug_pub('dbg_heading_to_waypoint', hwp)
49 | self.debug_pub('dbg_latest_waypoint_id', self.waypoint_id)
50 | wp_heading = self.nav.position_ll.heading_initial(self.waypoint)
51 | return 'normal', wp_heading
52 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/sensor_driver_imu_fusion:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | import sys, getopt
3 |
4 | sys.path.append('.')
5 | import RTIMU
6 | import os.path
7 | import time
8 | import math
9 | import rospy
10 | from sensor_msgs.msg import Imu
11 |
12 | def imu_publisher():
13 |
14 | SETTINGS_FILE = "../launch/parameters/RTIMULib"
15 | rospy.loginfo("Using calibration profile RTIMULib.ini")
16 | if not os.path.exists(SETTINGS_FILE + ".ini"):
17 | rospy.loginfo("Calibration file does not exist, default profile created")
18 | s = RTIMU.Settings(SETTINGS_FILE)
19 | imu = RTIMU.RTIMU(s)
20 | # fusion parameters TODO:use ROS parameter server to define
21 | imu.setSlerpPower(0.02)
22 | imu.setGyroEnable(True)
23 | imu.setAccelEnable(True)
24 | imu.setCompassEnable(True)
25 |
26 | if (not imu.IMUInit()):
27 | rospy.loginfo("IMU Init failed, check connection and/or access permission")
28 |
29 | imudata = Imu() # sensor message published in ROS
30 | rate = rospy.Rate(30)
31 | while not rospy.is_shutdown():
32 | imu.IMURead()
33 | data = imu.getIMUData()
34 |
35 | imudata.orientation.x = data["fusionQPose"][0]
36 | imudata.orientation.y = data["fusionQPose"][1]
37 | imudata.orientation.z = data["fusionQPose"][2]
38 | imudata.orientation.w = data["fusionQPose"][3]
39 |
40 | imudata.linear_acceleration.x = data["accel"][0]
41 | imudata.linear_acceleration.y = data["accel"][1]
42 | imudata.linear_acceleration.z = data["accel"][2]
43 |
44 | imudata.angular_velocity.x = data["gyro"][0]
45 | imudata.angular_velocity.y = data["gyro"][1]
46 | imudata.angular_velocity.z = data["gyro"][2]
47 |
48 | imu_pub.publish(imudata)
49 | rate.sleep()
50 |
51 | if __name__ == '__main__':
52 | try:
53 | imu_pub = rospy.Publisher('imu/data', Imu, queue_size=10)
54 | rospy.init_node("publish_IMU_message", anonymous=True)
55 | imu_publisher()
56 | except rospy.ROSInterruptException:
57 | pass
58 |
--------------------------------------------------------------------------------
/dashboard/src/res/base.scss:
--------------------------------------------------------------------------------
1 | body {
2 | margin-left: auto;
3 | margin-right: auto;
4 | font-family: 'Menlo', sans-serif;
5 | font-weight: 200;
6 | background: white; // background-image: -webkit-gradient(linear, left top, left bottom, from($sponsor-anchors), to($theme-primary));
7 | // background-image: -webkit-linear-gradient(top, $sponsor-anchors, $theme-primary);
8 | // background-image: -moz-linear-gradient(top, $sponsor-anchors, $theme-primary);
9 | // background-image: linear-gradient(to bottom, $sponsor-anchors, $theme-primary);
10 | }
11 |
12 | .zebra-table {
13 | margin: 0 auto;
14 | border: 1px solid #ddd;
15 | border-collapse: collapse;
16 | thead {
17 | background-color: $theme-dark;
18 | color: white;
19 | }
20 | tbody {
21 | tr:nth-child(odd) {
22 | background-color: $theme-secondary;
23 | }
24 | th {
25 | color: $theme-light;
26 | }
27 | td {
28 | min-width: 3em;
29 | padding: 0.5em;
30 | }
31 | }
32 | }
33 |
34 | #magic-compass {
35 | td {
36 | text-align: center;
37 | }
38 | }
39 |
40 | .compass-hand {
41 | height: 160px;
42 | transform-origin: bottom center;
43 | }
44 |
45 | #rosout {
46 | .nav-item {
47 | .nav-link {
48 | &.rosout-debug {
49 | background: blue;
50 | }
51 | &.rosout-info {
52 | background: yellow;
53 | }
54 | &.rosout-warn {
55 | background: orange;
56 | }
57 | &.rosout-error {
58 | background: red;
59 | }
60 | &.rosout-fatal {
61 | background: darkorchid;
62 | }
63 | }
64 | }
65 | #rosout-display {
66 | height: 500px;
67 | overflow-y: scroll;
68 | color: white;
69 | padding: 1em;
70 | p {
71 | &.debug {
72 | color: $log-level-debug;
73 | }
74 | &.info {
75 | color: $log-level-info;
76 | }
77 | &.warn {
78 | color: $log-level-warn;
79 | }
80 | &.error {
81 | color: $log-level-error;
82 | }
83 | &.fatal {
84 | color: $log-level-fatal;
85 | }
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/eastleigh-jul-03.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/actuator_demand_rudder:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import rospy
4 | from std_msgs.msg import Float32, Int16, String
5 |
6 | from sailing_robot.pid_data import PID_Data
7 | import sailing_robot.pid_control as _PID
8 | from sailing_robot.navigation import angle_subtract
9 |
10 | data = PID_Data()
11 |
12 | rudder = rospy.get_param('rudder')
13 |
14 | controller = _PID.PID(rudder['control']['Kp'], rudder['control']['Ki'], rudder['control']['Kd'],rudder['maxAngle'], -rudder['maxAngle'])
15 |
16 |
17 | def node_publisher():
18 | """
19 | Publish rudder servo angle data (Int16) to Arduino node.
20 | Higher level tack angle was used when in TACK manoeuvre.
21 | PID controller was used in other conditions.
22 | :rtype: object
23 | """
24 | pub = rospy.Publisher('rudder_control', Int16, queue_size=10) # Use UInt 16 here to minimize the memory use
25 | rospy.init_node('actuator_demand_rudder', anonymous=True)
26 | rate = rospy.Rate(10)
27 |
28 | while not rospy.is_shutdown():
29 | rospy.loginfo("Sailing state: %r", data.sailing_state)
30 | if data.sailing_state == 'normal':
31 | rawangle = -controller.update_PID(angle_subtract(
32 | data.heading, data.goal_heading))
33 | angle = _PID.saturation(rawangle,-rudder['maxAngle'], rudder['maxAngle'])
34 | rospy.loginfo("Angle: %r", angle)
35 | else:
36 | rawangle = data.tack_rudder
37 | angle = _PID.saturation(rawangle,-rudder['maxAngle'], rudder['maxAngle'])
38 |
39 | pub.publish(int(angle))
40 |
41 | rate.sleep()
42 |
43 | print(data.goal_heading)
44 |
45 | if __name__ == '__main__':
46 | try:
47 | rospy.Subscriber('goal_heading', Float32, data.update_goal_heading)
48 | rospy.Subscriber('heading', Float32, data.update_heading)
49 | rospy.Subscriber('sailing_state', String, data.update_sailing_state)
50 | rospy.Subscriber('tack_rudder', Float32, data.update_tack_rudder)
51 | node_publisher()
52 | except rospy.ROSInterruptException:
53 | pass
54 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/dual_ekf_navsat_example.launch:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/utilities/gen_map_image.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python2
2 |
3 | import sys
4 | import os
5 | import smopy
6 | import yaml
7 |
8 | from PIL import Image
9 | import numpy as np
10 | from sailing_robot.navigation import Navigation
11 |
12 |
13 | my_dir = os.path.dirname(__file__)
14 | image_dir = os.path.abspath(os.path.join(my_dir, 'map_bg_images'))
15 |
16 | # Load yaml file given in argument
17 | input_file = sys.argv[1]
18 | with open(input_file, 'r') as f:
19 | yaml_data = yaml.safe_load(f)
20 |
21 |
22 | waypoints = np.array(yaml_data['wp/table'].values())
23 |
24 | origin = [waypoints[:,0].mean(), waypoints[:,1].mean()]
25 |
26 | side_dist=250 # half of the size of the square in m
27 |
28 | location = os.path.basename(input_file).split("_")[0]
29 |
30 | output_file_name = os.path.join(image_dir,
31 | str(origin[0]) + '_' +
32 | str(origin[1]) + "_" +
33 | str(side_dist) + "_" +
34 | location + ".png")
35 |
36 | nav = Navigation()
37 | origin_utm = nav.latlon_to_utm(origin[0], origin[1])
38 |
39 | minx = - side_dist
40 | maxx = + side_dist
41 | miny = - side_dist
42 | maxy = + side_dist
43 |
44 | SO_corner = nav.utm_to_latlon(origin_utm[0] + minx, origin_utm[1] + miny)
45 | NE_corner = nav.utm_to_latlon(origin_utm[0] + maxx, origin_utm[1] + maxy)
46 |
47 | image_map = smopy.Map((float(SO_corner.lat),
48 | float(SO_corner.lon),
49 | float(NE_corner.lat),
50 | float(NE_corner.lon)), z=18, maxtiles=302, margin=0)
51 |
52 | mapminx, mapminy = image_map.to_pixels((float(SO_corner.lat),
53 | float(SO_corner.lon)), )
54 |
55 | mapmaxx, mapmaxy = image_map.to_pixels((float(NE_corner.lat),
56 | float(NE_corner.lon)), )
57 |
58 | mapminx = int(mapminx)
59 | mapminy = int(mapminy)
60 | mapmaxx = int(mapmaxx)
61 | mapmaxy = int(mapmaxy)
62 | image = image_map.to_numpy()[mapmaxy:mapminy, mapminx:mapmaxx, :]
63 |
64 | im = Image.fromarray(image)
65 | im.save(output_file_name)
66 |
67 |
68 |
--------------------------------------------------------------------------------
/utilities/setclock:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | """Set the clock from the GPS unit.
3 |
4 | Run this at the start of a test to update the RPi's clock before starting ROS,
5 | so that timestamps are correct.
6 | """
7 | from __future__ import print_function
8 |
9 | import os
10 | import serial
11 | import subprocess
12 | import sys
13 | import time
14 | import traceback
15 |
16 | import pynmea2
17 |
18 | my_dir = os.path.dirname(__file__)
19 | robot_src_dir = os.path.abspath(os.path.join(my_dir, '../../src/sailing_robot/src'))
20 | sys.path.append(robot_src_dir)
21 |
22 | from sailing_robot.gps_utils import UBXMessage, get_port, UbxNmeaParser
23 |
24 | def get_gps_time(serial_port):
25 | # Enable GxZDA (time stamp) messages
26 | serial_port.write(UBXMessage(b'\x06\x01', payload=b'\xF0\x08\x01').serialise())
27 | time.sleep(0.1)
28 |
29 | streamreader = UbxNmeaParser()
30 | while True:
31 | line = serial_port.readline()
32 | if not line.strip():
33 | continue
34 | print(repr(line))
35 | streamreader.feed(line)
36 |
37 | try:
38 | batch = list(streamreader.get_msgs())
39 | except (pynmea2.ParseError, pynmea2.ChecksumError, UnicodeError):
40 | s = "Error parsing GPS data.\nbuffer={!r}\nline={!r}\n{}\n".format(
41 | streamreader.buf, line, traceback.format_exc()
42 | )
43 | print(s)
44 | print("Trying again...")
45 | streamreader.stream = b''
46 | continue
47 |
48 | for msg in batch:
49 | if not isinstance(msg, pynmea2.NMEASentence):
50 | continue
51 | if (msg.sentence_type == 'ZDA') and (msg.timestamp is not None):
52 | return msg.datetime
53 |
54 | serial_port = serial.Serial(get_port(), 9600, timeout=0.5)
55 | print("Getting time from GPS...")
56 | dtime = get_gps_time(serial_port)
57 |
58 | dts = dtime.strftime("%Y-%m-%dT%H:%M:%S")
59 | print("Setting clock to:", dts)
60 | print(" (may require sudo password)")
61 | subprocess.check_call(["sudo", "date", "--utc", "+%Y-%m-%dT%H:%M:%S", "-s", dts])
62 | print("Done")
63 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/25_june.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/wave_position:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import rospy
4 | from std_msgs.msg import Float32
5 | from sensor_msgs.msg import Imu
6 | from sailing_robot.wave_position import Wave_position
7 |
8 | ############################################################################
9 | ## Setting ##
10 | ############################################################################
11 |
12 | time_range = rospy.get_param('wave_position/time_range') # time window captured by the wave_position algorithm
13 | refresh_time = rospy.get_param('wave_position/refresh_time') # how often the model is re-trained
14 |
15 | """
16 | The higher the time_range, the less the algorithm is sensitive to noise.
17 | Also, the higher the time_range, the worse the algorithm reacts to change
18 | to wave period. (time_range should be small for irregular waves)
19 | After resfrest_time seconds pass, the algorithm takes last time_range
20 | seconds of the acceleration reading and uses that for prediction. This
21 | repeats every refresh_time seconds.
22 | """
23 |
24 | ############################################################################
25 |
26 |
27 | rospy.init_node('wave_position', anonymous=True)
28 | initializing = True
29 | frequency = rospy.get_param("config/rate")
30 |
31 | wp = Wave_position(frequency, time_range, refresh_time)
32 |
33 | def update_wp_queue(msg):
34 | wp.update(msg.linear_acceleration.z)
35 |
36 | rospy.Subscriber('/imu/data', Imu, update_wp_queue)
37 |
38 | def talker():
39 | global initializing
40 | pub = rospy.Publisher('wave_position', Float32, queue_size=10)
41 | rate = rospy.Rate(frequency)
42 | while not rospy.is_shutdown():
43 | wave_position = wp.get_position()
44 | # Start publishing only after initialization ends.
45 | if(initializing):
46 | if (wave_position != 'initializing'):
47 | initializing = False
48 | rospy.loginfo("Initialization of wave_position completed.")
49 | rospy.loginfo("Sart publishing wave_position prediction.")
50 | else:
51 | pub.publish(wave_position)
52 | rate.sleep()
53 |
54 | if __name__ == '__main__':
55 | try:
56 | talker()
57 | except rospy.ROSInterruptException:
58 | pass
59 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/eastleigh-jul-17.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/sailingClub-jul-27.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/sailingClub-aug-23.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/sailingClub-aug-25.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/eastleigh-jul-24.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/force_jibe_tack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import rospy
4 | from std_msgs.msg import Float32, Float64, String
5 |
6 | import time, math
7 |
8 |
9 | class Force_jibe():
10 | def __init__(self):
11 | """
12 | This node detect if a tack or a jibe fails (taking too much time)
13 | and triggers a jibe/tack
14 | """
15 | self.jibe_tack_now_pub = rospy.Publisher('jibe_tack_now', String, queue_size=10)
16 |
17 | rospy.init_node("Force_jibe", anonymous=True)
18 |
19 | rospy.Subscriber('sailing_state', String, self.update_sailing_state)
20 | self.sailing_state = 'normal'
21 | self.previous_sailing_state = 'normal'
22 | self.timer = time.time()
23 |
24 |
25 | self.rate = rospy.Rate(rospy.get_param("config/rate"))
26 |
27 | self.time_tack = rospy.get_param("force_jibe/time_tack")
28 | self.looper()
29 |
30 | def update_sailing_state(self, msg):
31 | self.sailing_state = msg.data
32 |
33 | # reset timer when the sailing state changes
34 | if msg.data == 'normal' or \
35 | self.previous_sailing_state != self.sailing_state:
36 | self.timer = time.time()
37 | self.previous_sailing_state = self.sailing_state
38 |
39 | def looper(self):
40 |
41 | while not rospy.is_shutdown():
42 |
43 | # check tacking issue based on its duration
44 | if self.sailing_state != 'normal' and \
45 | (time.time() - self.timer) > self.time_tack:
46 | rospy.logerr("Issue with tacking/jibing")
47 |
48 | # "auto" is set, hence the boat will tack if it was jibing and jibe if it was tacking
49 | self.jibe_tack_now_pub.publish('auto')
50 |
51 | # wait untill the sailing state comes back to normal
52 | while self.sailing_state != 'normal' and not rospy.is_shutdown():
53 | self.rate.sleep()
54 | self.timer = time.time()
55 |
56 |
57 | self.rate.sleep()
58 |
59 |
60 | if __name__ == '__main__':
61 | try:
62 | Force_jibe()
63 | except rospy.ROSInterruptException:
64 | pass
65 |
66 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/simulator.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # toplevel CMakeLists.txt for a catkin workspace
2 | # catkin/cmake/toplevel.cmake
3 |
4 | cmake_minimum_required(VERSION 2.8.3)
5 |
6 | set(CATKIN_TOPLEVEL TRUE)
7 |
8 | # search for catkin within the workspace
9 | set(_cmd "catkin_find_pkg" "catkin" "${CMAKE_SOURCE_DIR}")
10 | execute_process(COMMAND ${_cmd}
11 | RESULT_VARIABLE _res
12 | OUTPUT_VARIABLE _out
13 | ERROR_VARIABLE _err
14 | OUTPUT_STRIP_TRAILING_WHITESPACE
15 | ERROR_STRIP_TRAILING_WHITESPACE
16 | )
17 | if(NOT _res EQUAL 0 AND NOT _res EQUAL 2)
18 | # searching fot catkin resulted in an error
19 | string(REPLACE ";" " " _cmd_str "${_cmd}")
20 | message(FATAL_ERROR "Search for 'catkin' in workspace failed (${_cmd_str}): ${_err}")
21 | endif()
22 |
23 | # include catkin from workspace or via find_package()
24 | if(_res EQUAL 0)
25 | set(catkin_EXTRAS_DIR "${CMAKE_SOURCE_DIR}/${_out}/cmake")
26 | # include all.cmake without add_subdirectory to let it operate in same scope
27 | include(${catkin_EXTRAS_DIR}/all.cmake NO_POLICY_SCOPE)
28 | add_subdirectory("${_out}")
29 |
30 | else()
31 | # use either CMAKE_PREFIX_PATH explicitly passed to CMake as a command line argument
32 | # or CMAKE_PREFIX_PATH from the environment
33 | if(NOT DEFINED CMAKE_PREFIX_PATH)
34 | if(NOT "$ENV{CMAKE_PREFIX_PATH}" STREQUAL "")
35 | string(REPLACE ":" ";" CMAKE_PREFIX_PATH $ENV{CMAKE_PREFIX_PATH})
36 | endif()
37 | endif()
38 |
39 | # list of catkin workspaces
40 | set(catkin_search_path "")
41 | foreach(path ${CMAKE_PREFIX_PATH})
42 | if(EXISTS "${path}/.catkin")
43 | list(FIND catkin_search_path ${path} _index)
44 | if(_index EQUAL -1)
45 | list(APPEND catkin_search_path ${path})
46 | endif()
47 | endif()
48 | endforeach()
49 |
50 | # search for catkin in all workspaces
51 | set(CATKIN_TOPLEVEL_FIND_PACKAGE TRUE)
52 | find_package(catkin QUIET
53 | NO_POLICY_SCOPE
54 | PATHS ${catkin_search_path}
55 | NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
56 | unset(CATKIN_TOPLEVEL_FIND_PACKAGE)
57 |
58 | if(NOT catkin_FOUND)
59 | message(FATAL_ERROR "find_package(catkin) failed. catkin was neither found in the workspace nor in the CMAKE_PREFIX_PATH. One reason may be that no ROS setup.sh was sourced before.")
60 | endif()
61 | endif()
62 |
63 | catkin_workspace()
64 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/actuator_driver_rudder:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | """Control the rudder servo
3 |
4 | Subscribes: rudder_control (Int16)
5 | """
6 | import time
7 | import pigpio
8 | import rospy
9 | from std_msgs.msg import UInt16, Int16
10 | import numpy as np
11 |
12 | rudderdata = rospy.get_param('rudder')
13 | rudderservo_PWM_offset = rudderdata['PWMoffset']
14 | rudderservo_lower_limits = rudderdata['servolowerlimits']
15 | rudderservo_higher_limits = rudderdata['servohigherlimits']
16 | rudderservo_netural_point = (rudderservo_lower_limits +
17 | rudderservo_higher_limits) / 2
18 | rudderservo_range = (rudderservo_higher_limits - rudderservo_lower_limits)
19 |
20 | PIN = rospy.get_param('rudder/pin')
21 |
22 | def setup():
23 | pi = pigpio.pi()
24 | pi.set_mode(13, pigpio.OUTPUT) # GPIO 13/RPi PIN 33 as rudder servo pin
25 |
26 | def rudderservoPWMcontrol(data):
27 | """This function takes in the /rudder_control (90 to -90) value and directly write PWM signal to the rudder servo. Netural point was determined by the start and end points. rudderservoPWMoffset is used for software level trim. """
28 | degrees = data.data
29 | pwm = rudderservo_range*(-1.0*degrees)/90 + rudderservo_netural_point +\
30 | rudderservo_PWM_offset
31 | pi.set_servo_pulsewidth(PIN, pwm)
32 |
33 | def post():
34 | '''Power-On Self Test'''
35 | if not rospy.get_param('do_post', False):
36 | return
37 |
38 | rospy.logwarn('rudder test: lower limit')
39 | for _ in range(4):
40 | pi.set_servo_pulsewidth(PIN, rudderservo_netural_point)
41 | time.sleep(0.25)
42 | pi.set_servo_pulsewidth(PIN, rudderservo_lower_limits)
43 | time.sleep(0.25)
44 |
45 | rospy.logwarn('rudder test: higher limit')
46 | for _ in range(4):
47 | pi.set_servo_pulsewidth(PIN, rudderservo_netural_point)
48 | time.sleep(0.25)
49 | pi.set_servo_pulsewidth(PIN, rudderservo_higher_limits)
50 | time.sleep(0.25)
51 |
52 | if __name__ == '__main__':
53 | pi = pigpio.pi()
54 | post()
55 | try:
56 | rospy.init_node('actuator_driver_servos', anonymous=True)
57 | rospy.Subscriber('rudder_control', Int16, rudderservoPWMcontrol)
58 | rospy.spin()
59 | except rospy.ROSInterruptException:
60 | pass
61 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/use-shell-launch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # set prefix to be used for all collected information
3 | fileprefix="foobar"
4 |
5 | # paths on the pi
6 | # for simulation on your machine: replace these with your local folders
7 | # define where logs should go
8 | logpath="/home/pi/logs/"
9 | # define where the parameters are loaded from
10 | parampath="/home/pi/sailing-robot/src/sailing-robot/launch/parameters/"
11 | # save the start time for timestamping all files
12 | timestamp="_$(date +%Y-%m-%d_%H:%M:%S)"
13 |
14 | # set some colors
15 | GREEN='\033[0;32m'
16 | NC='\033[0m'
17 |
18 | # set ros log directory to timestamped directory
19 | export ROS_LOG_DIR=$logpath$fileprefix$timestamp
20 |
21 | # start roscore, move to background, and give it a moment to start
22 | roscore &
23 | roscoreID=$!
24 | sleep 2
25 | echo ${GREEN} started roscore ${NC}
26 |
27 | # load parameters
28 | rosparam load ${parampath}default.yaml
29 | rosparam load ${parampath}calibration_blackpython.yaml /calibration
30 | # below this, load any parameters that are specific for this mission
31 |
32 | # record rosbag with the timestamp we got
33 | rosbag record -a -O ${logpath}${fileprefix}${timestamp}.bag &
34 | echo ${GREEN} rosbag recording started ${NC}
35 |
36 | #
37 | # insert further separate ros node runs here
38 | # move every node in the background by adding '&' at the end of line
39 | # save away their process ID and remember to kill in the end, before killing rosnode
40 | # (exception: rosbag - it is hard to kill and needs special treatment)
41 |
42 | # launch the main launch file
43 | # this should contain all nodes that have required="true",
44 | # since it controls the timing of the rest of the script
45 |
46 | #
47 | # after finishing the mission, before killling roscore:
48 | #
49 |
50 | # saving parameters at the end helps catching rogue parameter settings
51 | rosparam dump ${logpath}${fileprefix}${timestamp}_PARAM
52 |
53 | # rosbag is hard to kill, so let's get rid of that one first
54 | # (SIGINT usually doesn't work, and KILL will leave .bag.active instead of .bag)
55 | rosbagnode=$(rosnode list | grep record)
56 | rosnode kill $rosbagnode
57 |
58 | # stop roscore
59 | kill -INT $roscoreID
60 |
61 | # now all is stopped, it may be worth saving away this file, too
62 | scriptname=`basename "$0"`
63 | cp ./$basename ${logpath}${fileprefix}${timestamp}_sh
64 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/Viana/test-viana.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/utilities/waypoint_generator/waypoint_generator_round_buoy.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python2
2 | # READY FOR MIT
3 | # Script to generate waypoints to round buoys
4 | # man:
5 | # waypoint_generator path/to/waypoint.yaml
6 |
7 | from __future__ import print_function
8 |
9 | import sys
10 | import yaml
11 | import numpy as np
12 | from sailing_robot.navigation import Navigation
13 |
14 |
15 | RADIUS = 2 # [m]
16 | CLOCKWISE = False
17 |
18 |
19 | # Load yaml file given in argument
20 | input_file = sys.argv[1]
21 | with open(input_file, 'r') as f:
22 | yaml_data = yaml.safe_load(f)
23 |
24 | output_file = input_file[:-5] + "_gen_round.yaml"
25 |
26 |
27 | # Generate the list of buoys
28 | BUOY_LIST = []
29 | wp_table = {}
30 | id_wp = 1
31 | for idx in yaml_data['wp/list']:
32 | BUOY_LIST.append(yaml_data['wp/table'][idx])
33 |
34 | wp_table['b' + str(id_wp)] = yaml_data['wp/table'][idx] # to keep buoys in the final waypoint table
35 | id_wp += 1
36 |
37 |
38 |
39 | def leg_wp(wpA, wpB):
40 | '''
41 | Generate 2 waypoints on the leg between wpA and wpB
42 | '''
43 | nav = Navigation()
44 | wpA_utm = nav.latlon_to_utm(wpA[0], wpA[1])
45 | wpB_utm = nav.latlon_to_utm(wpB[0], wpB[1])
46 |
47 | vAB = np.array([wpB_utm[0] - wpA_utm[0], wpB_utm[1] - wpA_utm[1]])
48 | AB = np.linalg.norm(vAB)
49 |
50 | if CLOCKWISE:
51 | vAB_orth = np.array([-vAB[1], vAB[0]])/AB
52 | else:
53 | vAB_orth = np.array([vAB[1], -vAB[0]])/AB
54 |
55 | wpA_p_utm = wpA_utm + vAB_orth*RADIUS
56 | wpB_p_utm = wpB_utm + vAB_orth*RADIUS
57 |
58 | wpA_p = nav.utm_to_latlon(wpA_p_utm[0], wpA_p_utm[1])
59 | wpB_p = nav.utm_to_latlon(wpB_p_utm[0], wpB_p_utm[1])
60 |
61 | return wpA_p, wpB_p
62 |
63 |
64 | n = len(BUOY_LIST)
65 | BUOY_LIST.append(BUOY_LIST[0])
66 |
67 | waypoint_list = []
68 | for idx in range(n):
69 | wpA_p, wpB_p = leg_wp(BUOY_LIST[idx], BUOY_LIST[idx+1])
70 | waypoint_list.append(wpA_p)
71 | waypoint_list.append(wpB_p)
72 |
73 |
74 | idx = 1
75 | for wp in waypoint_list:
76 | wp_table[str(idx)] = [float(wp.lat), float(wp.lon)]
77 | idx += 1
78 |
79 | wp_list = [ str(i+1) for i in range(idx-1)]
80 |
81 | yaml_data['wp/list'] = wp_list
82 | yaml_data['wp/table'] = wp_table
83 |
84 | with open(output_file, 'w') as f:
85 | yaml.dump(yaml_data, f)
86 |
87 | print(yaml.dump(yaml_data))
88 | print()
89 | print('Written to:', output_file)
90 |
--------------------------------------------------------------------------------
/src/sailing_robot/msg/BatteryState.msg:
--------------------------------------------------------------------------------
1 | # Constants are chosen to match the enums in the linux kernel
2 | # defined in include/linux/power_supply.h as of version 3.7
3 | # The one difference is for style reasons the constants are
4 | # all uppercase not mixed case.
5 |
6 | # Power supply status constants
7 | uint8 POWER_SUPPLY_STATUS_UNKNOWN = 0
8 | uint8 POWER_SUPPLY_STATUS_CHARGING = 1
9 | uint8 POWER_SUPPLY_STATUS_DISCHARGING = 2
10 | uint8 POWER_SUPPLY_STATUS_NOT_CHARGING = 3
11 | uint8 POWER_SUPPLY_STATUS_FULL = 4
12 |
13 | # Power supply health constants
14 | uint8 POWER_SUPPLY_HEALTH_UNKNOWN = 0
15 | uint8 POWER_SUPPLY_HEALTH_GOOD = 1
16 | uint8 POWER_SUPPLY_HEALTH_OVERHEAT = 2
17 | uint8 POWER_SUPPLY_HEALTH_DEAD = 3
18 | uint8 POWER_SUPPLY_HEALTH_OVERVOLTAGE = 4
19 | uint8 POWER_SUPPLY_HEALTH_UNSPEC_FAILURE = 5
20 | uint8 POWER_SUPPLY_HEALTH_COLD = 6
21 | uint8 POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE = 7
22 | uint8 POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE = 8
23 |
24 | # Power supply technology (chemistry) constants
25 | uint8 POWER_SUPPLY_TECHNOLOGY_UNKNOWN = 0
26 | uint8 POWER_SUPPLY_TECHNOLOGY_NIMH = 1
27 | uint8 POWER_SUPPLY_TECHNOLOGY_LION = 2
28 | uint8 POWER_SUPPLY_TECHNOLOGY_LIPO = 3
29 | uint8 POWER_SUPPLY_TECHNOLOGY_LIFE = 4
30 | uint8 POWER_SUPPLY_TECHNOLOGY_NICD = 5
31 | uint8 POWER_SUPPLY_TECHNOLOGY_LIMN = 6
32 |
33 | Header header
34 | float32 power # Power in mW
35 | float32 voltage # Voltage in Volts (Mandatory)
36 | float32 current # Negative when discharging (mA) (If unmeasured NaN)
37 | float32 charge # Current charge in Ah (If unmeasured NaN)
38 | float32 capacity # Capacity in Ah (last full capacity) (If unmeasured NaN)
39 | float32 design_capacity # Capacity in Ah (design capacity) (If unmeasured NaN)
40 | float32 percentage # Charge percentage on 0 to 1 range (If unmeasured NaN)
41 | uint8 power_supply_status # The charging status as reported. Values defined above
42 | uint8 power_supply_health # The battery health metric. Values defined above
43 | uint8 power_supply_technology # The battery chemistry. Values defined above
44 | bool present # True if the battery is present
45 |
46 | float32[] cell_voltage # An array of individual cell voltages for each cell in the pack
47 | # If individual voltages unknown but number of cells known set each to NaN
48 | string location # The location into which the battery is inserted. (slot number or plug)
49 | string serial_number # The best approximation of the battery serial number
50 |
--------------------------------------------------------------------------------
/src/sailing_robot/doc/sailing_robot.rst:
--------------------------------------------------------------------------------
1 | sailing_robot package
2 | =====================
3 |
4 | Submodules
5 | ----------
6 |
7 | sailing_robot.heading_planning module
8 | -------------------------------------
9 |
10 | .. automodule:: sailing_robot.heading_planning
11 | :members:
12 | :undoc-members:
13 | :show-inheritance:
14 |
15 | sailing_robot.heading_planning_dumb module
16 | ------------------------------------------
17 |
18 | .. automodule:: sailing_robot.heading_planning_dumb
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | sailing_robot.navigation module
24 | -------------------------------
25 |
26 | .. automodule:: sailing_robot.navigation
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
31 | sailing_robot.pid_control module
32 | --------------------------------
33 |
34 | .. automodule:: sailing_robot.pid_control
35 | :members:
36 | :undoc-members:
37 | :show-inheritance:
38 |
39 | sailing_robot.pid_data module
40 | -----------------------------
41 |
42 | .. automodule:: sailing_robot.pid_data
43 | :members:
44 | :undoc-members:
45 | :show-inheritance:
46 |
47 | sailing_robot.sail_data module
48 | ------------------------------
49 |
50 | .. automodule:: sailing_robot.sail_data
51 | :members:
52 | :undoc-members:
53 | :show-inheritance:
54 |
55 | sailing_robot.station_keeping module
56 | ------------------------------------
57 |
58 | .. automodule:: sailing_robot.station_keeping
59 | :members:
60 | :undoc-members:
61 | :show-inheritance:
62 |
63 | sailing_robot.tack_control module
64 | ---------------------------------
65 |
66 | .. automodule:: sailing_robot.tack_control
67 | :members:
68 | :undoc-members:
69 | :show-inheritance:
70 |
71 | sailing_robot.taskbase module
72 | -----------------------------
73 |
74 | .. automodule:: sailing_robot.taskbase
75 | :members:
76 | :undoc-members:
77 | :show-inheritance:
78 |
79 | sailing_robot.tasks module
80 | --------------------------
81 |
82 | .. automodule:: sailing_robot.tasks
83 | :members:
84 | :undoc-members:
85 | :show-inheritance:
86 |
87 | sailing_robot.tasks_ros module
88 | ------------------------------
89 |
90 | .. automodule:: sailing_robot.tasks_ros
91 | :members:
92 | :undoc-members:
93 | :show-inheritance:
94 |
95 |
96 | Module contents
97 | ---------------
98 |
99 | .. automodule:: sailing_robot
100 | :members:
101 | :undoc-members:
102 | :show-inheritance:
103 |
--------------------------------------------------------------------------------
/src/sailing_robot/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | sailing_robot
4 | 0.0.0
5 | The sailing_robot package
6 |
7 |
8 |
9 |
10 | sophia
11 |
12 |
13 |
14 |
15 |
16 | MIT
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | catkin
43 | python-catkin-pkg
44 | rospy
45 | std_msgs
46 | sensor_msgs
47 | message_generation
48 | robot_localization
49 | message_runtime
50 | rospy
51 | std_msgs
52 | sensor_msgs
53 | robot_localization
54 | rostest
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/sailing_robot/scripts/simulation_heading:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # Simulator for the heading
3 |
4 |
5 | import rospy
6 | from std_msgs.msg import Float32, Int16
7 | from sailing_robot.msg import Velocity
8 | import time, math
9 |
10 |
11 | class Heading_simu():
12 | def __init__(self):
13 | """
14 | Compute the heading thanks to the rudder angle and the boat velocity
15 | The boat moves on a circle defined by the keel and the rudder
16 | """
17 |
18 | self.heading_pub = rospy.Publisher('heading', Float32, queue_size=10)
19 |
20 | rospy.init_node("simulation_heading", anonymous=True)
21 |
22 | rospy.Subscriber('rudder_control', Int16, self.update_rudder)
23 | self.rudder = 0
24 | rospy.Subscriber('gps_velocity', Velocity, self.update_velocity)
25 | self.speed = 0
26 |
27 | self.heading = rospy.get_param("simulation/heading_init")
28 |
29 | self.diff_heading_coefficient = rospy.get_param("simulation/heading/coefficient")
30 | self.freq = rospy.get_param("config/rate")
31 | self.rate = rospy.Rate(self.freq)
32 |
33 |
34 | rospy.loginfo("Heading simulated")
35 | self.heading_publisher()
36 |
37 |
38 | def update_rudder(self, msg):
39 | self.rudder = msg.data
40 |
41 | def update_velocity(self, msg):
42 | self.speed = msg.speed
43 |
44 | def diff_heading(self):
45 | if self.rudder == 0:
46 | return 0
47 |
48 |
49 | Ay = -0.25 # -1/4 of the size of the boat [m]
50 | r = 0.05 # radius of the rudder [m]
51 | By = Ay*2
52 | Cx = - r*math.sin(math.radians(self.rudder))
53 | Cy = By - r*math.cos(math.radians(self.rudder))
54 |
55 | d = self.speed/self.freq
56 |
57 | y = d
58 | x = (-(-Cx**2-Cy**2+Ay*Cy+math.sqrt(Cx**4+Cy**4-2*Ay*Cy**3+Ay**2*Cy**2+2*Cx**2*Cy**2-4*Cx**2*d**2-2*Ay*Cx**2*Cy+4*Ay*Cx**2*d))/(2*Cx))
59 | #x=(-(-Cx**2-Cy**2+Ay*Cy-(math.sqrt(Cx**4+Cy**4-2*Ay*Cy**3+Ay**2*Cy**2+2*Cx**2*Cy**2-4*Cx**2*d**2-2*Ay*Cx**2*Cy+4*Ay*Cx**2*d)))/(2*Cx))
60 |
61 | return -math.degrees(math.atan2(y,x)) + 90
62 |
63 |
64 | def heading_publisher(self):
65 |
66 | while not rospy.is_shutdown():
67 |
68 | self.heading = (self.diff_heading_coefficient * self.diff_heading() + self.heading) % 360
69 |
70 | self.heading_pub.publish(self.heading)
71 | self.rate.sleep()
72 |
73 |
74 | if __name__ == '__main__':
75 | try:
76 | Heading_simu()
77 | except rospy.ROSInterruptException:
78 | pass
79 |
80 |
--------------------------------------------------------------------------------
/utilities/send_ublx_gps_msg.py:
--------------------------------------------------------------------------------
1 |
2 | import struct
3 | import smbus
4 | import time
5 | from sailing_robot.gps_utils import UBXMessage, get_port, UbxNmeaParser
6 |
7 | BUS = None
8 | address = 0x42
9 |
10 |
11 | def connectBus():
12 | global BUS
13 | BUS = smbus.SMBus(1)
14 |
15 | connectBus()
16 |
17 | def sendData(msg):
18 | bytelist = []
19 | firstbyte = ''
20 | for char in msg:
21 | if not firstbyte:
22 | firstbyte = int(char.encode('hex'), 16)
23 | else:
24 | bytelist.append(int(char.encode('hex'), 16))
25 | BUS.write_i2c_block_data(address, firstbyte, bytelist)
26 |
27 | #GxGSA
28 | sendData(b'\xB5\x62\x06\x00\x08\x00\xF0\x02\x00\x00\x00\x00\x00\x01\x02\x32\x10\x13')
29 | time.sleep(0.1)
30 |
31 | # GxGSV off
32 | sendData(b'\xB5\x62\x06\x00\x08\x00\xF0\x03\x00\x00\x00\x00\x00\x01\x03\x39\x10\x13')
33 | time.sleep(0.1)
34 |
35 | # GxRMC off
36 | sendData(b'\xB5\x62\x06\x00\x08\x00\xF0\x04\x00\x00\x00\x00\x00\x01\x04\x40\x10\x13')
37 | time.sleep(0.1)
38 |
39 | # GxVTG off
40 | sendData(b'\xB5\x62\x06\x00\x08\x00\xF0\x05\x00\x00\x00\x00\x00\x01\x05\x47\x10\x13')
41 | time.sleep(0.1)
42 |
43 | # GxGLL off
44 | sendData(b'\xB5\x62\x06\x00\x08\x00\xF0\x01\x00\x00\x00\x00\x00\x01\x01\x2B\x10\x13')
45 | time.sleep(0.1)
46 |
47 | # NMEA rate: 5Hz
48 | #sendData(b'\xB5\x62\x06\x00\x06\x00\xC8\x00\x01\x00\x01\x00\xDE\x6A\x10\x13')
49 | sendData(UBXMessage(b'\x06\x08', payload=b'\x00\x48\x00\x01\x00\x01').serialise())
50 | time.sleep(0.1)
51 |
52 |
53 |
54 | # GxZDA on (time measurement)
55 | sendData(UBXMessage(b'\x06\x01', payload=b'\xF0\x08\x08').serialise())
56 |
57 | # GPVTG on speed feedback
58 | sendData(UBXMessage(b'\x06\x01', payload=b'\xF0\x05\x01').serialise())
59 |
60 |
61 |
62 | # GxGSA
63 | sendData(b'\xB5\x62\x06\x01\x08\x00\xF0\x02\x00\x00\x00\x00\x00\x01\x02\x32\x10\x13')
64 | time.sleep(0.1)
65 |
66 | # GxGSV off
67 | sendData(b'\xB5\x62\x06\x01\x08\x00\xF0\x03\x00\x00\x00\x00\x00\x01\x03\x39\x10\x13')
68 | time.sleep(0.1)
69 |
70 | # GxRMC off
71 | sendData(b'\xB5\x62\x06\x01\x08\x00\xF0\x04\x00\x00\x00\x00\x00\x01\x04\x40\x10\x13')
72 | time.sleep(0.1)
73 |
74 | # GxVTG off
75 | sendData(b'\xB5\x62\x06\x01\x08\x00\xF0\x05\x00\x00\x00\x00\x00\x01\x05\x47\x10\x13')
76 | time.sleep(0.1)
77 |
78 | # GxGLL off
79 | sendData(b'\xB5\x62\x06\x01\x08\x00\xF0\x01\x00\x00\x00\x00\x00\x01\x01\x2B\x10\x13')
80 | time.sleep(0.1)
81 |
82 | # NMEA rate: 5Hz
83 | sendData(b'\xB5\x62\x06\x08\x06\x00\xC8\x00\x01\x00\x01\x00\xDE\x6A\x10\x13')
84 | time.sleep(0.1)
85 |
86 | # GxZDA on (time measurement)
87 | time.sleep(0.1)
88 |
89 | # GPVTG on speed feedback
90 | time.sleep(0.1)
91 |
92 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/Viana/viana.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/sailing_robot/src/sailing_robot/obstacle_waypoints.py:
--------------------------------------------------------------------------------
1 | """Code for staying near a target point (2016 station keeping challenge)"""
2 | from LatLon import LatLon
3 | from shapely.geometry import Point
4 | import time
5 |
6 | from .taskbase import TaskBase
7 | from .heading_planning_laylines import HeadingPlan
8 | from .navigation import angleAbsDistance
9 |
10 | class ObstacleWaypoints(TaskBase):
11 | def __init__(self, nav, normal_wp_plan, obstacle_wp_plan):
12 | """Machinery to stay near a given point.
13 |
14 | This is meant to be started when we're already close to the marker; we'll
15 | normally put it immediately after a to_waypoint task to go to the marker.
16 |
17 | nav : a Navigation object for common machinery.
18 | marker_ll : a (lat, lon) point marking where we'll try to stay close to.
19 | linger : time in seconds to stay there
20 | radius : distance in metres which we'll try to bounce around the marker
21 | wind_angle : the absolute wind angle to sail (in degrees) when inside
22 | radius. This will automatically be flipped according to the tack.
23 | """
24 | self.nav = nav
25 | self.normal_wp_plan = normal_wp_plan
26 | self.obstacle_wp_plan = obstacle_wp_plan
27 | self.obstacle_detected = False
28 |
29 | debug_topics = [
30 | ('dbg_heading_to_waypoint', 'Float32'),
31 | ('dbg_distance_to_waypoint', 'Float32'),
32 | ('dbg_goal_wind_angle', 'Float32'),
33 | ]
34 |
35 | def receive_detection(self, msg):
36 | if msg.data == 'detected':
37 | # Check if we're facing in the direction of the waypoint
38 | dwp, hwp = self.nav.distance_and_heading(self.normal_wp_plan.waypoint_xy)
39 | if angleAbsDistance(hwp, self.nav.heading) < 30:
40 | self.obstacle_detected = True
41 |
42 | def init_ros(self):
43 | import rospy
44 | from std_msgs.msg import String
45 | rospy.Subscriber('camera_detection', String, self.receive_detection)
46 |
47 | def start(self):
48 | self.obstacle_detected = False
49 |
50 | @property
51 | def active_plan(self):
52 | return self.obstacle_wp_plan if self.obstacle_detected else self.normal_wp_plan
53 |
54 | def check_end_condition(self):
55 | "Are we done yet?"
56 | return self.active_plan.check_end_condition()
57 |
58 | def calculate_state_and_goal(self):
59 | """Work out what we want the boat to do
60 | """
61 | self.debug_pub('dbg_latest_waypoint_id', self.active_plan.waypoint_id)
62 | return self.active_plan.calculate_state_and_goal()
63 |
--------------------------------------------------------------------------------
/utilities/postprocessing/analyse_area_scanning:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """
4 | Analyse the result of a area scanning attempt
5 | This script is for use independent of ROS!
6 |
7 | READY FOR MIT LICENCE
8 |
9 | man:
10 | analyse_are_scanning path/to/yaml/parameter/file.yaml path/to/gps/log.cvs
11 | """
12 |
13 | import csv
14 | import sys
15 | import yaml
16 | from sailing_robot.navigation import Navigation
17 | import numpy as np
18 |
19 | # CSV values
20 | # hhmmssdd
21 | # Lat *10^^7
22 | # Lon *10^^7
23 |
24 |
25 | # Number of subdivision (8 for MS class)
26 | sub = 6
27 |
28 | # Load yaml file given in argument
29 | yaml_file = sys.argv[1]
30 | yaml_data = yaml.load(file(yaml_file, 'r'), Loader=yaml.Loader)
31 |
32 | # gps log file given as the 2nd argument
33 | cvs_file = sys.argv[2]
34 |
35 |
36 | wpA = yaml_data['wp/table']['A']
37 | wpB = yaml_data['wp/table']['B']
38 |
39 |
40 | nav = Navigation()
41 | wpA_utm = nav.latlon_to_utm(wpA[0], wpA[1])
42 | wpB_utm = nav.latlon_to_utm(wpB[0], wpB[1])
43 |
44 |
45 | position_utm = []
46 | # Read log file
47 | with open(cvs_file) as logfile:
48 | data = csv.reader(logfile, delimiter=',')
49 | for row in data:
50 | latitude = float(row[1])/10**7
51 | longitude = float(row[2])/10**7
52 | position_utm.append(nav.latlon_to_utm(latitude, longitude))
53 |
54 |
55 |
56 | # Unit vector AB
57 | vAB = np.array([wpB_utm[0] - wpA_utm[0], wpB_utm[1] - wpA_utm[1]])
58 | AB = np.linalg.norm(vAB)
59 | vAB = vAB/AB
60 |
61 | # Unit vector orth to AB
62 | vAB_orth = np.array([-vAB[1], vAB[0]])
63 |
64 | output_array = np.zeros((sub, sub))
65 | for point in position_utm:
66 | # projection of point in the (wpA_utm; vAB,vAB_orth) base
67 | point_base = [point[0] - wpA_utm[0], point[1] - wpA_utm[1]]
68 | point_i = np.dot(point_base, vAB)*sub/2/AB
69 | point_j = np.dot(point_base, vAB_orth)*sub/2/AB
70 |
71 | if point_i < 0 or point_j < 0 or point_i > sub or point_j > sub:
72 | continue
73 |
74 | output_array[int(point_i)][int(point_j)] = 1
75 |
76 |
77 | # Output the grid
78 | print u'┌─' + u'┬─'*(sub-1) + u'┐'
79 | score = 0
80 | for i in range(sub):
81 | row = ''
82 | for j in range(sub):
83 | if output_array[i][j] == 1:
84 | if i<=(sub/2 -1) or j>(sub/2 -1):
85 | score += 1
86 | row = row + u"│X"
87 | else:
88 | row = row + u"│o"
89 | else:
90 | row = row + u"│ "
91 | print row + u'│'
92 | if i<(sub-1):
93 | print u'├─' + u'┼─'*(sub-1) + u'┤'
94 | print u'└─' + u'┴─'*(sub-1) + u'┘'
95 |
96 | print 'Score: ' + str(score) + '/' + str(sub*sub*3/4)
97 |
--------------------------------------------------------------------------------
/src/sailing_robot/launch/archive/eastleigh-april-22.launch:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/sailing_robot/src/sailing_robot/sail_table.py:
--------------------------------------------------------------------------------
1 | """Intepolate a sail setting from a table of wind_direction -> sail_setting
2 | """
3 |
4 | from __future__ import division
5 |
6 | class SailTable(object):
7 | def __init__(self, table_dict):
8 | """table_dict should be a mapping from wind direction (in degrees) to
9 | sail setting (0=fully in to 1=fully out).
10 |
11 | Because of the limitations of the YAML parameter files, the keys may be
12 | in strings.
13 | """
14 | self.table = sorted([(int(k), v) for k,v in table_dict.items()])
15 |
16 | def interpolate_sail_setting(self, wind_direction):
17 | """Turn a wind angle in degrees into a sail setting (0 to 1).
18 |
19 | Wind angle should be between 0 and 180, so e.g. 315 should be normalised
20 | to 45 before calling this.
21 | """
22 | last_wind_direction, last_sail_setting = self.table[0]
23 |
24 | # Loop through the table to find which entries the current wind
25 | # direction is between.
26 | for next_wind_direction, next_sail_setting in self.table[1:]:
27 | if next_wind_direction > wind_direction:
28 | # It's in the interval (last_wind_direction, next_wind_direction)
29 | if next_wind_direction == last_wind_direction:
30 | delta = 0 # Avoid division by 0
31 | else:
32 | # Where are we within the interval?
33 | delta = (wind_direction - last_wind_direction) \
34 | / (next_wind_direction - last_wind_direction)
35 |
36 | # Interpolated value
37 | return last_sail_setting + \
38 | delta * (next_sail_setting - last_sail_setting)
39 |
40 | # Haven't found the interval yet; update the lower bound for the
41 | # next loop.
42 | last_wind_direction, last_sail_setting = \
43 | next_wind_direction, next_sail_setting
44 |
45 | # We ran past the end of the table
46 | return last_sail_setting
47 |
48 | class SailData(object):
49 | def __init__(self, sail_table):
50 | self.wind_direction_apparent = 0
51 | self.sailing_state = 'normal'
52 | self.sail_table = sail_table
53 |
54 | def update_wind(self, msg):
55 | self.wind_direction_apparent = msg.data
56 |
57 | def update_sailing_state(self, msg):
58 | self.sailing_state = msg.data
59 |
60 | def calculate_sheet_setting(self):
61 | windDirection = self.wind_direction_apparent
62 | if windDirection > 180:
63 | windDirection = 360 - windDirection
64 |
65 | return self.sail_table.interpolate_sail_setting(windDirection)
66 |
--------------------------------------------------------------------------------