Skip to content

Commit 03760f6

Browse files
committed
Merge branch 'main' into user/adambrett40/601-can-manual-mode
2 parents 35b70b3 + b5027d4 commit 03760f6

File tree

14 files changed

+292
-34
lines changed

14 files changed

+292
-34
lines changed

.github/CODEOWNERS

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,17 @@
1919
/src/controller @eomielan
2020

2121
# diagnostics
22-
/src/diagnostics @samdai01
22+
/src/diagnostics @Jng468
2323

2424
# docs
25-
/docs/ @jamenkaye
25+
/docs/ @FireBoyAJ24
2626

2727
# local_pathfinding
28-
/notebooks/local_pathfinding @jamenkaye @FireBoyAJ24 @SPDonaghy
29-
/src/local_pathfinding @jamenkaye @FireBoyAJ24 @SPDonaghy
28+
/notebooks/local_pathfinding @FireBoyAJ24 @SPDonaghy
29+
/src/local_pathfinding @FireBoyAJ24 @SPDonaghy
3030

3131
# network_systems
3232
/src/network_systems @Jng468 @vaibhavambastha @lross03
3333

3434
# website
35-
/src/website @JordanChen123
35+
/src/website @JordanChen123 @fyang151

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ venv/
3232
.cache/
3333
# Build
3434
site/
35+
36+
# Website
37+
**/package-lock.json

scripts/deployment/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,71 @@
22

33
Deploying our software to our autonomous sailboat's main computer.
44

5+
6+
# Deployment on the RaspberryPi and the Remote Server (Digital Ocean Droplet)
7+
8+
Comment out the website's docker compose file in the `devcontainer.json` to prevent the database from running.
9+
```
10+
"dockerComposeFile": [
11+
"docker-compose.yml",
12+
// "website/docker-compose.website.yml", // website <- COMMENT OUT
13+
14+
// Uncomment the files containing the programs you need
15+
// "docs/docker-compose.docs.yml", // docs
16+
],
17+
```
18+
19+
Comment out the following extensions to prevent the raspberryPi from throttling in `devcontainer.json` as shown below.
20+
21+
```
22+
"extensions": [
23+
// Comment out the extensions when on the RPI
24+
// // template repo
25+
// "althack.ament-task-provider",
26+
// "DotJoshJohnson.xml",
27+
// "ms-azuretools.vscode-docker",
28+
// "ms-python.python",
29+
// "ms-vscode.cpptools",
30+
// "redhat.vscode-yaml",
31+
// "smilerobotics.urdf",
32+
// "streetsidesoftware.code-spell-checker",
33+
// "twxs.cmake",
34+
// "ms-python.flake8",
35+
// "yzhang.markdown-all-in-one",
36+
// // "zachflower.uncrustify",
37+
38+
// // UBCSailbot
39+
// "awesomektvn.toggle-semicolon",
40+
// "bierner.github-markdown-preview",
41+
// "christian-kohler.path-intellisense",
42+
// "cschlosser.doxdocgen",
43+
// "DavidAnson.vscode-markdownlint",
44+
// "eamodio.gitlens",
45+
// // "github.copilot",
46+
// "github.vscode-github-actions",
47+
// "Gruntfuggly.todo-tree",
48+
// "jebbs.plantuml",
49+
// "jeff-hykin.better-cpp-syntax",
50+
// "KevinRose.vsc-python-indent",
51+
// "llvm-vs-code-extensions.vscode-clangd",
52+
// "matepek.vscode-catch2-test-adapter",
53+
// "mechatroner.rainbow-csv",
54+
// "mongodb.mongodb-vscode",
55+
// "ms-iot.vscode-ros",
56+
// "ms-python.black-formatter",
57+
// "ms-python.isort",
58+
// "ms-python.vscode-pylance",
59+
// "ms-toolsai.jupyter",
60+
// "ms-vsliveshare.vsliveshare",
61+
// "ms-python.mypy-type-checker",
62+
// "njpwerner.autodocstring",
63+
// "randomfractalsinc.geo-data-viewer",
64+
// "stevejpurves.cucumber",
65+
// "streetsidesoftware.code-spell-checker",
66+
// "vscode-icons-team.vscode-icons",
67+
// "zxh404.vscode-proto3"
68+
```
69+
570
## Scripts
671

772
### `setup_boot.sh`

src/local_pathfinding/local_pathfinding/local_path.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class LocalPathState:
2222
ais_ships (List[HelperAISShip]): Information about nearby ships.
2323
global_path (List[Tuple[float, float]]): Path to the destination that Sailbot is
2424
navigating along.
25+
target_global_waypoint (ci.HelperLatLon): The global waypoint that we are heading towards.
26+
The global waypoint is the same as the reference latlon.
2527
wind_speed (float): Wind speed.
2628
wind_direction (int): Wind direction.
2729
planner (str): Planner to use for the OMPL query.
@@ -34,6 +36,7 @@ def __init__(
3436
gps: ci.GPS,
3537
ais_ships: ci.AISShips,
3638
global_path: ci.Path,
39+
target_global_waypoint: ci.HelperLatLon,
3740
filtered_wind_sensor: ci.WindSensor,
3841
planner: str,
3942
):
@@ -55,7 +58,7 @@ def __init__(
5558
if not (global_path and global_path.waypoints):
5659
raise ValueError("Cannot create a LocalPathState with an empty global_path")
5760
self.global_path = global_path
58-
self.reference_latlon = self.global_path.waypoints[-1]
61+
self.reference_latlon = target_global_waypoint
5962

6063
if not planner:
6164
raise ValueError("planner must not be None")
@@ -87,6 +90,9 @@ def update_if_needed(
8790
gps: ci.GPS,
8891
ais_ships: ci.AISShips,
8992
global_path: ci.Path,
93+
received_new_global_waypoint: bool,
94+
# ^ Placeholder; will be used for conditions to update local path
95+
target_global_waypoint: ci.HelperLatLon,
9096
filtered_wind_sensor: ci.WindSensor,
9197
planner: str,
9298
land_multi_polygon: MultiPolygon = None,
@@ -101,7 +107,9 @@ def update_if_needed(
101107
filtered_wind_sensor (ci.WindSensor): Wind data.
102108
"""
103109
# this raises ValueError if any of the parameters are not properly initialized
104-
state = LocalPathState(gps, ais_ships, global_path, filtered_wind_sensor, planner)
110+
state = LocalPathState(
111+
gps, ais_ships, global_path, target_global_waypoint, filtered_wind_sensor, planner
112+
)
105113
self.state = state
106114
ompl_path = OMPLPath(
107115
parent_logger=self._logger,

src/local_pathfinding/local_pathfinding/node_navigate.py

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
import local_pathfinding.obstacles as ob
1515
from local_pathfinding.local_path import LocalPath
1616

17-
WAYPOINT_REACHED_THRESH_KM = 0.5
17+
LOCAL_WAYPOINT_REACHED_THRESH_KM = 0.5
18+
GLOBAL_WAYPOINT_REACHED_THRESH_KM = 3
19+
PATHFINDING_RANGE_KM = 30
20+
REALLY_FAR_M = 100000000
1821
GEODESIC = Geod(ellps="WGS84")
1922

2023

@@ -119,7 +122,9 @@ def __init__(self):
119122

120123
# attributes
121124
self.local_path = LocalPath(parent_logger=self.get_logger())
122-
self.current_waypoint_index = 0
125+
self.local_waypoint_index = 0
126+
self.global_waypoint_index = -1
127+
self.saved_target_global_waypoint = None
123128
self.use_mock_land = self.get_parameter("use_mock_land").get_parameter_value().bool_value
124129
self.mode = self.get_parameter("mode").get_parameter_value().string_value
125130
self.planner = self.get_parameter("path_planner").get_parameter_value().string_value
@@ -154,6 +159,8 @@ def global_path_callback(self, msg: ci.Path):
154159
f"Received data from {self.global_path_sub.topic}: {gp.path_to_dict(msg)}"
155160
)
156161
self.global_path = msg
162+
if self.saved_target_global_waypoint is None:
163+
self.saved_target_global_waypoint = self.global_path.waypoints[-1]
157164

158165
def filtered_wind_sensor_callback(self, msg: ci.WindSensor):
159166
self.get_logger().debug(f"Received data from {self.filtered_wind_sensor_sub.topic}: {msg}")
@@ -251,26 +258,89 @@ def get_desired_heading(self) -> float:
251258
float: The desired heading
252259
"""
253260

254-
received_new_path = self.local_path.update_if_needed(
261+
# Extra logic for when the global waypoint changes due to receiving a new global path
262+
received_new_global_waypoint = False
263+
if (
264+
self.global_path.waypoints[self.global_waypoint_index]
265+
!= self.saved_target_global_waypoint
266+
):
267+
received_new_global_waypoint = True
268+
self.global_waypoint_index = self.determine_start_point_in_new_global_path(
269+
self.global_path, self.gps.lat_lon
270+
)
271+
self.saved_target_global_waypoint = self.global_path.waypoints[
272+
self.global_waypoint_index
273+
]
274+
275+
# Check if we're close enough to the global waypoint to head to the next one
276+
_, _, distance_to_waypoint_m = GEODESIC.inv(
277+
self.gps.lat_lon.longitude,
278+
self.gps.lat_lon.latitude,
279+
self.saved_target_global_waypoint.longitude,
280+
self.saved_target_global_waypoint.latitude,
281+
)
282+
if distance_to_waypoint_m < GLOBAL_WAYPOINT_REACHED_THRESH_KM:
283+
received_new_global_waypoint = True
284+
self.global_waypoint_index -= 1 # Since global waypoints are in reverse order
285+
286+
# At the end of the global path, alternate between the last two waypoints
287+
if self.global_waypoint_index < 0:
288+
self.global_waypoint_index = 1
289+
290+
self.saved_target_global_waypoint = self.global_path.waypoints[
291+
self.global_waypoint_index
292+
]
293+
294+
received_new_local_path = self.local_path.update_if_needed(
255295
self.gps,
256296
self.ais_ships,
257297
self.global_path,
298+
received_new_global_waypoint,
299+
self.saved_target_global_waypoint,
258300
self.filtered_wind_sensor,
259301
self.planner,
260302
self.land_multi_polygon,
261303
)
262304

263-
if received_new_path:
264-
self.current_waypoint_index = 0
305+
if received_new_local_path:
306+
self.local_waypoint_index = 0
265307

266-
desired_heading, self.current_waypoint_index = (
308+
desired_heading, self.local_waypoint_index = (
267309
self.calculate_desired_heading_and_waypoint_index(
268-
self.local_path.path, self.current_waypoint_index, self.gps.lat_lon
310+
self.local_path.path, self.local_waypoint_index, self.gps.lat_lon
269311
)
270312
)
271313

272314
return desired_heading
273315

316+
@staticmethod
317+
def determine_start_point_in_new_global_path(
318+
global_path: ci.Path, boat_lat_lon: ci.HelperLatLon
319+
):
320+
"""Used when we receive a new global path.
321+
Finds the index of the first waypoint within 'pathfinding range' of gps location.
322+
If none are found, it returns the index of the nearest waypoint."""
323+
324+
closest_m, index_of_closest = REALLY_FAR_M, -1
325+
for waypoint_index in range(len(global_path.waypoints) - 1, -1, -1):
326+
# Note: the global waypoints are in reverse order (index 0 is final waypoint)
327+
waypoint = global_path.waypoints[waypoint_index]
328+
329+
_, _, distance_to_waypoint_m = GEODESIC.inv(
330+
boat_lat_lon.longitude,
331+
boat_lat_lon.latitude,
332+
waypoint.longitude,
333+
waypoint.latitude,
334+
)
335+
336+
if distance_to_waypoint_m < closest_m:
337+
closest_m, index_of_closest = distance_to_waypoint_m, waypoint_index
338+
339+
if distance_to_waypoint_m < cs.km_to_meters(PATHFINDING_RANGE_KM):
340+
return waypoint_index
341+
342+
return index_of_closest
343+
274344
@staticmethod
275345
def calculate_desired_heading_and_waypoint_index(
276346
path: ci.Path, waypoint_index: int, boat_lat_lon: ci.HelperLatLon
@@ -280,7 +350,7 @@ def calculate_desired_heading_and_waypoint_index(
280350
boat_lat_lon.longitude, boat_lat_lon.latitude, waypoint.longitude, waypoint.latitude
281351
)
282352

283-
if cs.meters_to_km(distance_to_waypoint_m) < WAYPOINT_REACHED_THRESH_KM:
353+
if cs.meters_to_km(distance_to_waypoint_m) < LOCAL_WAYPOINT_REACHED_THRESH_KM:
284354
# If we reached the current local waypoint, aim for the next one
285355
waypoint_index += 1
286356
waypoint = path.waypoints[waypoint_index]
@@ -339,3 +409,4 @@ def _log_inactive_subs_warning(self):
339409

340410
if __name__ == "__main__":
341411
main()
412+
main()

src/local_pathfinding/local_pathfinding/ompl_path.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,7 @@ def _init_simple_setup(self, local_path_state, land_multi_polygon) -> og.SimpleS
225225
start_x = start_position_in_xy.x
226226
start_y = start_position_in_xy.y
227227

228-
goal_position = self.state.global_path.waypoints[-1]
229-
goal_position_in_xy = cs.latlon_to_xy(self.state.reference_latlon, goal_position)
228+
goal_position_in_xy = cs.XY(0, 0) # Global waypoint is used as the reference point
230229
goal_polygon = self.create_buffer_around_position(goal_position_in_xy)
231230
goal_x, goal_y = goal_position_in_xy
232231

src/local_pathfinding/local_pathfinding/visualizer.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ def __init__(self, msgs: List[ci.LPathData]):
8383
*[self._split_coordinates(waypoints) for waypoints in self.all_wp_xy]
8484
)
8585

86+
# AIS ships
87+
self.ais_ships = self.curr_msg.ais_ships.ships
88+
ais_ship_latlons = [ship.lat_lon for ship in self.ais_ships]
89+
self.ais_ship_ids = [ship.id for ship in self.ais_ships]
90+
ais_ship_xy = cs.latlon_list_to_xy_list(self.reference_latlon, ais_ship_latlons)
91+
self.ais_pos_x, self.ais_pos_y = self._split_coordinates(ais_ship_xy)
92+
self.ais_headings = [ship.cog.heading for ship in self.ais_ships]
93+
8694
# TODO: Include other LPathData attributes for plotting their data
8795

8896
# Process land obstacles
@@ -508,6 +516,35 @@ def live_update_plot(state: VisualizerState) -> go.Figure:
508516
y_min = min(state.final_local_wp_y) - 10
509517
y_max = max(state.final_local_wp_y) + 10
510518

519+
# Display AIS Ships
520+
for x_val, y_val, heading, ais_id in zip(
521+
state.ais_pos_x, state.ais_pos_y, state.ais_headings, state.ais_ship_ids
522+
):
523+
fig.add_trace(
524+
go.Scatter(
525+
x=[x_val],
526+
y=[y_val],
527+
mode="markers",
528+
name=f"AIS {str(ais_id)}",
529+
hovertemplate=(
530+
f"<b>🚢 AIS Ship {str(ais_id)}</b><br>"
531+
f"X: {x_val:.2f}<br>"
532+
f"Y: {y_val:.2f}<br>"
533+
f"Heading: {heading:.1f}°<extra></extra>"
534+
),
535+
marker=dict(
536+
symbol="arrow-wide",
537+
line_color="orange",
538+
color="orange",
539+
line_width=2,
540+
size=15,
541+
angleref="up",
542+
angle=cs.true_bearing_to_plotly_cartesian(heading),
543+
),
544+
showlegend=False,
545+
)
546+
)
547+
511548
# Update Layout
512549
fig.update_layout(
513550
title="Path Planning",
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
id,latitude,longitude,heading,speed,rot,width,height
2-
1,45.0,0.0,90.0,1.0,1,1.0,1.0
3-
2,45.0,2.0,90.0,15.0,2,2.0,2.0
4-
3,45.0,3.0,3.0,3.0,3,3.0,3.0
5-
4,45.0,4.0,4.0,4.0,4,4.0,4.0
6-
5,45.0,5.0,5.0,5.0,5,5.0,5.0
2+
1,49.01261318191024,-135.65593922412154,90.0,40.0,30,1.0,1.0
3+
2,48.67919212345747,-132.94724986187234,90.0,40.0,50,2.0,2.0
4+
3,48.11352031871804,-135.67990496461405,30,80.0,-50,3,3.0
5+
4,48.1970229368345,-128.93710143805356,4.0,20.0,-30,4.0,4.0
6+
5,48.51027819947852,-126.15925374786544,5.0,10.0,20,5.0,5.0

0 commit comments

Comments
 (0)