Skip to content

Commit 1b4af7d

Browse files
authored
Resolve errors in goto command (#91)
* Implemented potential fixes for the goto command * Implement pre-planned mission configuration file support (#93) * Added support for loading goto locations from yaml file * Cleaned up example and added support for coordinate frame specification * Fix docstring * Resolved errors in example and goto command * Updated readme * Resolved tox and pre-commit errors * Removed debugging statements in example * Make the groundspeed setting non-dependent on the goto command success
1 parent 52a618f commit 1b4af7d

File tree

11 files changed

+231
-100
lines changed

11 files changed

+231
-100
lines changed

.devcontainer/Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ RUN pip3 install \
1313
pandas \
1414
monotonic \
1515
pymavlink \
16-
pyserial
16+
pyserial \
17+
PyYAML
1718

1819

1920
###################################################################

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ repos:
4747
language_version: python3.10
4848
- id: check-toml
4949
- id: check-yaml
50+
args: ["--unsafe"]

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ monotonic = ">=1.6"
88
pymavlink = ">=2.3.3"
99
pyserial = ">=3.0"
1010
pandas = ">=1.4.3"
11+
PyYAML = ">=6.0"
1112

1213
[dev-packages]
1314
black = "*"

Pipfile.lock

Lines changed: 41 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Here are some of the main features of `pymavswarm`:
1818
- Develop applications for on-board companion computers
1919
- Implement custom swarm ground control stations
2020
- Log incoming MAVLink messages for future evaluation and debugging
21+
- Construct pre-planned missions
2122

2223
## Dependencies
2324

examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ computer
1212
- `custom_observer.py`: demonstrates how to implement a callback for an agent
1313
state change
1414
- `goto.py`: demonstrates how to command an agent to fly to a specified location
15+
using a configuration file; an example configuration file is provided in
16+
`resources/goto.yaml`
1517
- `set_flight_mode.py`: demonstrates how to set the agents' flight mode
1618
- `set_home_position.py`: demonstrates how to set the swarm agents' home
1719
position

examples/goto.py

Lines changed: 77 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

1919
import time
2020
from argparse import ArgumentParser
21+
from concurrent.futures import Future
2122
from typing import Any
2223

2324
from pymavswarm import MavSwarm
24-
from pymavswarm.types import AgentID
2525

2626

2727
def parse_args() -> Any:
@@ -36,48 +36,96 @@ def parse_args() -> Any:
3636
"port", type=str, help="port to establish a MAVLink connection over"
3737
)
3838
parser.add_argument("baud", type=int, help="baudrate to establish a connection at")
39+
parser.add_argument("config", type=str, help="Pre-planned positions to load")
3940
parser.add_argument(
4041
"--takeoff_alt", type=float, default=3.0, help="altitude to takeoff to [m]"
4142
)
43+
parser.add_argument(
44+
"--ground_speed",
45+
type=float,
46+
default=3.0,
47+
help="ground speed that the agents should fly at when flying to the target "
48+
"location",
49+
)
4250
return parser.parse_args()
4351

4452

53+
def print_message_response_cb(future: Future) -> None:
54+
"""
55+
Print the result of the future.
56+
57+
:param future: message execution future
58+
:type future: Future
59+
"""
60+
responses = future.result()
61+
62+
if isinstance(responses, list):
63+
for response in responses:
64+
print(
65+
f"Result of {response.message_type} message sent to "
66+
f"({response.target_agent_id}): {response.code}"
67+
)
68+
else:
69+
print(
70+
f"Result of {responses.message_type} message sent to "
71+
f"({responses.target_agent_id}): {responses.code}"
72+
)
73+
74+
return
75+
76+
4577
def main() -> None:
4678
"""Demonstrate how to command agents to go to a location."""
4779
# Parse the script arguments
4880
args = parse_args()
4981

5082
# Create a new MavSwarm instance
51-
mavswarm = MavSwarm()
83+
mavswarm = MavSwarm(log_to_file=True)
5284

5385
# Attempt to create a new MAVLink connection
5486
if not mavswarm.connect(args.port, args.baud):
5587
return
5688

57-
# Wait for the swarm to auto-register new agents
58-
while not list(filter(lambda agent_id: agent_id[1] == 1, mavswarm.agent_ids)):
59-
print("Waiting for the system to recognize agents in the network...")
89+
# Get the target agents specified in the config file
90+
target_agents = list(mavswarm.parse_yaml_mission(args.config)[0].keys())
91+
92+
# Wait for the swarm to register all target agents
93+
while not all(agent_id in mavswarm.agent_ids for agent_id in target_agents):
94+
print("Waiting for the system to recognize all target agents...")
6095
time.sleep(0.5)
6196

6297
# Set each agent to guided mode before attempting a takeoff sequence
63-
future = mavswarm.set_mode("GUIDED", retry=True)
98+
future = mavswarm.set_mode(
99+
"GUIDED", agent_ids=target_agents, retry=True, verify_state=True
100+
)
101+
future.add_done_callback(print_message_response_cb)
64102

65103
while not future.done():
66104
pass
67105

68106
responses = future.result()
69107

70-
for response in responses:
71-
if not response.result:
108+
# Exit if all agents didn't successfully switch into GUIDED mode
109+
if isinstance(responses, list):
110+
for response in responses:
111+
if not response.result:
112+
print(
113+
"Failed to set the flight mode of agent "
114+
f"{response.target_agent_id} to GUIDED prior to the takeoff "
115+
"sequence. Exiting."
116+
)
117+
return
118+
else:
119+
if not responses.result:
72120
print(
73-
"Failed to set the flight mode of all agents to GUIDED prior to the "
74-
"takeoff sequence. Exiting."
121+
"Failed to set the flight mode of agent {responses.target_agent_id} to "
122+
"GUIDED prior to the takeoff sequence. Exiting."
75123
)
76124
return
77125

78126
# Perform takeoff with all agents in the swarm; retry on message failure
79127
responses = mavswarm.takeoff_sequence(
80-
args.takeoff_alt, verify_state=True, retry=True
128+
args.takeoff_alt, agent_ids=target_agents, verify_state=True, retry=True
81129
)
82130

83131
if isinstance(responses, list):
@@ -92,64 +140,40 @@ def main() -> None:
92140
f"({response.target_agent_id}): {responses.code}"
93141
)
94142

95-
target_locations: dict[AgentID, tuple[float, float, float]] = {}
96-
97-
# Get the target positions
98-
for agent_id in mavswarm.agent_ids:
99-
lat = float(input(f"Enter the target latitude for agent {agent_id}: "))
100-
lon = float(input(f"Enter the target longitude for agent {agent_id}: "))
101-
alt = float(input(f"Enter the target altitude for agent {agent_id}: "))
102-
103-
target_locations[agent_id] = (lat, lon, alt)
104-
105-
print(f"Target locations specified: {target_locations}")
106-
107143
# Wait for the user to indicate that the agents should fly to their waypoints
108144
input("Press any key to command the agents to fly to their waypoints")
109145

110-
for agent_id in target_locations.keys():
111-
future = mavswarm.goto(
112-
target_locations[agent_id][0],
113-
target_locations[agent_id][1],
114-
target_locations[agent_id][2],
115-
agent_ids=agent_id,
116-
retry=True,
117-
)
118-
119-
while not future.done():
120-
pass
146+
# Command the agent to the target location
147+
# We don't need to specify the target agents because these are captured from the
148+
# config file
149+
future = mavswarm.goto(config_file=args.config, retry=True)
150+
future.add_done_callback(print_message_response_cb)
121151

122-
response = future.result()
152+
while not future.done():
153+
pass
123154

124-
print(
125-
f"Result of {response.message_type} message sent to "
126-
f"({response.target_agent_id}): {response.code}"
127-
)
155+
# Set the groundspeed
156+
future = mavswarm.set_groundspeed(
157+
args.ground_speed, agent_ids=target_agents, retry=True
158+
)
159+
future.add_done_callback(print_message_response_cb)
128160

129-
if not response.result:
130-
print(
131-
f"Failed to command agent {agent_id} to location "
132-
f"{target_locations[agent_id]}"
133-
)
161+
while not future.done():
162+
pass
134163

135164
# Wait for user input
136165
input("Press any key to command the agents to land")
137166

138167
# Attempt to land the agents
139-
future = mavswarm.set_mode("LAND", retry=True, verify_state=True)
168+
future = mavswarm.set_mode(
169+
"LAND", agent_ids=target_agents, retry=True, verify_state=True
170+
)
171+
future.add_done_callback(print_message_response_cb)
140172

141173
# Wait for the land command to complete
142174
while not future.done():
143175
pass
144176

145-
landing_responses = future.result()
146-
147-
for response in landing_responses:
148-
print(
149-
f"Result of {response.message_type} message sent to "
150-
f"({response.target_agent_id}): {response.code}"
151-
)
152-
153177
# Disconnect from the swarm
154178
mavswarm.disconnect()
155179

examples/resources/goto.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
mission:
2+
[
3+
{
4+
!!python/tuple [5, 1]: { x: 40.851849, y: -96.608464, z: 4.0, hold: 0.0 },
5+
!!python/tuple [6, 1]: { x: 40.851838, y: -96.608184, z: 9.0, hold: 0.0 },
6+
!!python/tuple [7, 1]: { x: 40.851864, y: -96.608645, z: 7.0, hold: 0.0 },
7+
},
8+
]

0 commit comments

Comments
 (0)