Skip to content

Commit 88dd79b

Browse files
authored
Integrate real-time safety verification for collision avoidance (#95)
* Fixed check state example * Saving point * Finished initial version of single face lift * Started implementation of face lifting iterative improvement method * Finished initial implementation of collision checker and started implementing test cases * Integrated bandit into dev environment for security checks * Started implementation of hyperrectangle unit tests * started implementing unit tests for safety checker and converted safety checker class to static class * Added tests for collision detection * Started integrating collision responses * Implemented initial version of collision response * Finished implementation of collision checking * added acceleration measurement support and cleaned up state handlers * Utilize force disarm for disarm methods * started implementing functionality to expose collision detection * Completed initial integration of collision avoidance * Added warning to collision detection method * Resolved bugs in collision avoidance and added examples * Fix stale docstring * Add check to make sure that the timestamps intersect before marking a collision * Added check for stale states * Added location specific config files * Fix logging error in heartbeat handler * Updated mavswarm collision avoidance * Did some quick maths * Remove unused example argument * Document safety feature as a main feature of pymavswarm * DO NOT TOUCH ANYTHING lol * Cleaned up collision avoidance * Resolved unfinished changes for PR
1 parent 69757aa commit 88dd79b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2463
-328
lines changed

.devcontainer/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ RUN pip3 install --upgrade pip
1111
# Install all required Python packages
1212
RUN pip3 install \
1313
pandas \
14+
numpy \
1415
monotonic \
1516
pymavlink \
1617
pyserial \

.devcontainer/devcontainer.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
"--volume=/dev:/dev",
1717
"--privileged"
1818
],
19+
"containerEnv": {
20+
"DISPLAY": "${localEnv:DISPLAY}"
21+
},
1922
"settings": {
2023
"terminal.integrated.profiles.linux": {
2124
"bash": {
@@ -35,6 +38,7 @@
3538
"python.linting.pylintEnabled": false,
3639
"python.linting.pydocstyleEnabled": true,
3740
"python.linting.mypyEnabled": true,
41+
"python.linting.banditEnabled": true,
3842
"python.formatting.provider": "black",
3943
"python.formatting.blackPath": "/usr/local/bin/black",
4044
"python.testing.unittestArgs": [
@@ -91,7 +95,8 @@
9195
"DavidAnson.vscode-markdownlint",
9296
"esbenp.prettier-vscode",
9397
"LittleFoxTeam.vscode-python-test-adapter",
94-
"tamasfe.even-better-toml"
98+
"tamasfe.even-better-toml",
99+
"alefragnani.Bookmarks"
95100
],
96101
"postStartCommand": "pip3 install -e ."
97102
}

.github/CONTRIBUTING.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ Contributions include but are not restricted to:
1414

1515
## Workflow
1616

17-
- Send all pull requests to the `main` branch (unless otherwise requested)
18-
- Limit each pull request to resolving a single [issue](https://github.com/unl-nimbus-lab/pymavswarm/issues)
17+
- Send all pull requests to the `main` branch (unless otherwise requested).
18+
- Limit each pull request to resolving a single
19+
[issue](https://github.com/unl-nimbus-lab/pymavswarm/issues).
1920
- It is your responsibility to ensure that your development branch is up-to-date
20-
with the `main` branch. You may either rebase on `main` or merge `main` into
21-
your development branch.
21+
with the `main` branch. You may either rebase on `main` or merge `main` into
22+
your development branch.
2223
- Always test and document your code. We also encourage performing field tests
23-
for significant changes.
24+
for significant changes.
2425
- Ensure that your changes pass our CI. We will not review your PR until the CI
25-
passes.
26+
passes.
2627

2728
## Setting up a local development environment
2829

@@ -34,7 +35,7 @@ passes.
3435
A VSCode development container has been provided to offer a fully sandboxed
3536
development environment. This environment includes all development Python
3637
packages (e.g., `pre-commit`) used by the `pymavswarm` development team and
37-
utilizes a variety of VSCode packages that make it easy to run tests and to
38+
utilizes a variety of VSCode extensions that make it easy to run tests and to
3839
ensure that all style conventions are followed. Follow the instructions
3940
[here](https://code.visualstudio.com/docs/remote/containers) to learn how to
4041
install and launch development containers using VSCode. Once these steps have

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ include a high-level discussion regarding the implementation of the PR.*
2020

2121
Fixes # (issue)
2222

23-
## Files Changes
23+
## Files Changed
2424

2525
*A list of all files changed and a summary of the changes made to the respective
2626
files.*

.pre-commit-config.yaml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,26 @@ repos:
3838
hooks:
3939
- id: pydocstyle
4040

41+
- repo: https://github.com/PyCQA/bandit
42+
rev: 1.7.4
43+
hooks:
44+
- id: bandit
45+
4146
- repo: https://github.com/pre-commit/pre-commit-hooks
4247
rev: v4.3.0
4348
hooks:
44-
- id: trailing-whitespace
45-
- id: end-of-file-fixer
46-
- id: debug-statements
47-
language_version: python3.10
49+
- id: check-added-large-files
50+
- id: check-case-conflict
4851
- id: check-toml
4952
- id: check-yaml
5053
args: ["--unsafe"]
54+
- id: check-merge-conflict
55+
- id: debug-statements
56+
- id: detect-private-key
57+
- id: trailing-whitespace
58+
- id: check-docstring-first
59+
- id: debug-statements
60+
- id: end-of-file-fixer
61+
- id: mixed-line-ending
62+
- id: name-tests-test
63+
args: ["--pytest-test-first"]

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+
numpy = ">=1.23.1"
1112
PyYAML = ">=6.0"
1213

1314
[dev-packages]

Pipfile.lock

Lines changed: 6 additions & 6 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
@@ -19,6 +19,7 @@ Here are some of the main features of `pymavswarm`:
1919
- Implement custom swarm ground control stations
2020
- Log incoming MAVLink messages for future evaluation and debugging
2121
- Construct pre-planned missions
22+
- Multi-agent collision avoidance using reachability analysis
2223

2324
## Dependencies
2425

examples/arming.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def main() -> None:
9696
time.sleep(5)
9797

9898
# Disarm each of the agents; retry on message failure
99-
future = mavswarm.disarm(retry=True, verify_state=True)
99+
future = mavswarm.disarm(retry=True, verify_state=True, force=True)
100100
future.add_done_callback(print_message_response_cb)
101101

102102
# Wait for the disarm command to complete

examples/collision_prevention.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# pymavswarm is an interface for swarm control and interaction
2+
# Copyright (C) 2022 Evan Palmer
3+
4+
# This program is free software: you can redistribute it and/or modify
5+
# it under the terms of the GNU General Public License as published by
6+
# the Free Software Foundation, either version 3 of the License, or
7+
# (at your option) any later version.
8+
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
from __future__ import annotations
18+
19+
import time
20+
from argparse import ArgumentParser
21+
from concurrent.futures import Future
22+
from typing import Any
23+
24+
from pymavswarm import MavSwarm
25+
26+
27+
def parse_args() -> Any:
28+
"""
29+
Parse the script arguments.
30+
31+
:return: argument namespace
32+
:rtype: Any
33+
"""
34+
parser = ArgumentParser()
35+
parser.add_argument(
36+
"port", type=str, help="port to establish a MAVLink connection over"
37+
)
38+
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")
40+
parser.add_argument(
41+
"--takeoff_alt", type=float, default=3.0, help="altitude to takeoff to [m]"
42+
)
43+
parser.add_argument(
44+
"--ground_speed",
45+
type=float,
46+
default=0.5,
47+
help="ground speed that the agents should fly at when flying toward each other",
48+
)
49+
return parser.parse_args()
50+
51+
52+
def print_message_response_cb(future: Future) -> None:
53+
"""
54+
Print the result of the future.
55+
56+
:param future: message execution future
57+
:type future: Future
58+
"""
59+
responses = future.result()
60+
61+
if isinstance(responses, list):
62+
for response in responses:
63+
print(
64+
f"Result of {response.message_type} message sent to "
65+
f"({response.target_agent_id}): {response.code}"
66+
)
67+
else:
68+
print(
69+
f"Result of {responses.message_type} message sent to "
70+
f"({responses.target_agent_id}): {responses.code}"
71+
)
72+
73+
return
74+
75+
76+
def main() -> None:
77+
"""Demonstrate how to use collision avoidance."""
78+
# Parse the script arguments
79+
args = parse_args()
80+
81+
# Create a new MavSwarm instance
82+
mavswarm = MavSwarm()
83+
84+
# Attempt to create a new MAVLink connection
85+
if not mavswarm.connect(args.port, args.baud):
86+
return
87+
88+
# Get the target agents specified in the config file
89+
target_agents = list(mavswarm.parse_yaml_mission(args.config)[0].keys())
90+
91+
# Wait for the swarm to register all target agents
92+
while not all(agent_id in mavswarm.agent_ids for agent_id in target_agents):
93+
print("Waiting for the system to recognize all target agents...")
94+
time.sleep(0.5)
95+
96+
# Enable collision avoidance
97+
mavswarm.enable_collision_avoidance(
98+
3.0, 2.5, 0.1, MavSwarm.COLLISION_RESPONSE_LOITER
99+
)
100+
101+
# Set each agent to guided mode before attempting a takeoff sequence
102+
future = mavswarm.set_mode(
103+
"GUIDED", agent_ids=target_agents, retry=True, verify_state=True
104+
)
105+
future.add_done_callback(print_message_response_cb)
106+
107+
while not future.done():
108+
pass
109+
110+
responses = future.result()
111+
112+
# Exit if all agents didn't successfully switch into GUIDED mode
113+
if isinstance(responses, list):
114+
for response in responses:
115+
if not response.result:
116+
print(
117+
"Failed to set the flight mode of agent "
118+
f"{response.target_agent_id} to GUIDED prior to the takeoff "
119+
"sequence. Exiting."
120+
)
121+
return
122+
else:
123+
if not responses.result:
124+
print(
125+
"Failed to set the flight mode of agent {responses.target_agent_id} to "
126+
"GUIDED prior to the takeoff sequence. Exiting."
127+
)
128+
return
129+
130+
# Perform takeoff with all agents in the swarm; retry on message failure
131+
responses = mavswarm.takeoff_sequence(
132+
args.takeoff_alt, agent_ids=target_agents, verify_state=True, retry=True
133+
)
134+
135+
if isinstance(responses, list):
136+
for response in responses:
137+
print(
138+
f"Result of {response.message_type} message sent to "
139+
f"({response.target_agent_id}): {response.code}"
140+
)
141+
else:
142+
print(
143+
f"Result of {responses.message_type} message sent to "
144+
f"({response.target_agent_id}): {responses.code}"
145+
)
146+
147+
# Wait for the user to indicate that the agents should fly to their waypoints
148+
input("Press the 'enter' key to command the agents to fly to their waypoints")
149+
150+
# Command the agent to the target location
151+
# We don't need to specify the target agents because these are captured from the
152+
# config file
153+
future = mavswarm.goto(config_file=args.config, retry=True)
154+
future.add_done_callback(print_message_response_cb)
155+
156+
while not future.done():
157+
pass
158+
159+
# Set the groundspeed; make sure that this is set low when running the examples on
160+
# real hardware
161+
future = mavswarm.set_groundspeed(
162+
args.ground_speed, agent_ids=target_agents, retry=True
163+
)
164+
future.add_done_callback(print_message_response_cb)
165+
166+
while not future.done():
167+
pass
168+
169+
# Wait for user input
170+
input("Press the 'enter' key to command the agents to land\n")
171+
172+
# Attempt to land the agents
173+
future = mavswarm.set_mode(
174+
"LAND", agent_ids=target_agents, retry=True, verify_state=True
175+
)
176+
future.add_done_callback(print_message_response_cb)
177+
178+
# Wait for the land command to complete
179+
while not future.done():
180+
pass
181+
182+
# Disable collision avoidance
183+
mavswarm.disable_collision_avoidance()
184+
185+
# Disconnect from the swarm
186+
mavswarm.disconnect()
187+
188+
return
189+
190+
191+
if __name__ == "__main__":
192+
main()

0 commit comments

Comments
 (0)