├── .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 | --------------------------------------------------------------------------------