-
Notifications
You must be signed in to change notification settings - Fork 387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Restructured BetaFlight controller into BetaAviary and CTBRControl. P… #170
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
01f5c27
Restructured BetaFlight controller into BetaAviary and CTBRControl. P…
spencerteetaert 645896c
Use eeprom.bin instead of BF configurator
JacopoPan d27a768
Merge branch 'main' into dev-sitl
JacopoPan e462b80
Added variable number of drones in beta.py
JacopoPan 38c78c3
Removed typo, clarified README
JacopoPan 645b7d5
Renaming last_action attribute and re-ordering betaaviary step method
JacopoPan 65fbd1c
Multidrone in BetaAviary using different UDP ports
JacopoPan 0542623
Reinstated bf configurator file
JacopoPan c7ee6b6
Added wip script to clone, compile, configure, run betaflight SITL
JacopoPan 92a9c3c
Added wip script to clone, compile, configure, run betaflight SITL
JacopoPan 66210ab
Update clone_bfs.sh
JacopoPan a8f3c2c
Updated automation script
JacopoPan 0da365f
Fixed betaflight sitl state port bug
JacopoPan adaebb1
Removed unnecessary uart port change
JacopoPan fc4a87e
Automated multi-bf SITL creation
JacopoPan 2291bac
Fully automated SITL betaflight
JacopoPan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
#!/bin/bash | ||
|
||
# Clone, edit, build, configure, multiple Betaflight SITL executables | ||
|
||
# Check for the correct number of command-line arguments | ||
if [ "$#" -ne 1 ]; then | ||
echo "Usage: $0 <desired_max_num_drones>" | ||
exit 1 | ||
fi | ||
|
||
# Extract command-line arguments | ||
desired_max_num_drones="$1" | ||
|
||
# Create gitignored directory in gym-pybullet-donres | ||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) | ||
cd $SCRIPT_DIR | ||
cd ../../ | ||
mkdir betaflight_sitl/ | ||
cd betaflight_sitl/ | ||
|
||
# Step 1: Clone and open betaflight's source: | ||
git clone https://github.com/betaflight/betaflight temp/ | ||
|
||
|
||
# Step 2: Comment out line `delayMicroseconds_real(50); // max rate 20kHz` | ||
# (https://github.com/betaflight/betaflight/blob/master/src/main/main.c#L52) | ||
# from Betaflight's `SIMULATOR_BUILD` | ||
cd temp/ | ||
sed -i "s/delayMicroseconds_real(50);/\/\/delayMicroseconds_real(50);/g" ./src/main/main.c | ||
sed -i "s/ret = udpInit(\&stateLink, NULL, 9003, true);/\/\/ret = udpInit(\&stateLink, NULL, PORT_STATE, true);/g" ./src/main/target/SITL/sitl.c | ||
sed -i "s/printf(\"start UDP server.../\/\/printf(\"start UDP server.../g" ./src/main/target/SITL/sitl.c | ||
|
||
# Prepare | ||
make arm_sdk_install | ||
|
||
cd .. | ||
|
||
pattern1="PORT_PWM_RAW 9001" | ||
pattern2="PORT_PWM 9002" | ||
pattern3="PORT_STATE 9003" | ||
pattern4="PORT_RC 9004" | ||
# pattern5="BASE_PORT 5760" | ||
|
||
for ((i = 0; i < desired_max_num_drones; i++)); do | ||
|
||
# Copy | ||
cp -r temp/ "bf${i}/" | ||
cd "bf${i}/" | ||
|
||
# Step 3: Change the UDP ports used by each Betaflight SITL instancet | ||
replacement1="PORT_PWM_RAW 90${i}1" | ||
sed -i "s/$pattern1/$replacement1/g" ./src/main/target/SITL/sitl.c | ||
replacement2="PORT_PWM 90${i}2" | ||
sed -i "s/$pattern2/$replacement2/g" ./src/main/target/SITL/sitl.c | ||
replacement3="PORT_STATE 90${i}3" | ||
sed -i "s/$pattern3/$replacement3/g" ./src/main/target/SITL/sitl.c | ||
replacement4="PORT_RC 90${i}4" | ||
sed -i "s/$pattern4/$replacement4/g" ./src/main/target/SITL/sitl.c | ||
# replacement5="BASE_PORT 57${i}0" | ||
# sed -i "s/$pattern5/$replacement5/g" ./src/main/drivers/serial_tcp.c | ||
|
||
# Build | ||
make TARGET=SITL | ||
|
||
cd .. | ||
|
||
done | ||
|
||
for ((i = 0; i < desired_max_num_drones; i++)); do | ||
|
||
# Step 4: Copy over the configured `eeprom.bin` file from folder | ||
cp "${SCRIPT_DIR}/eeprom.bin" "bf${i}/" | ||
|
||
done |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
import os | ||
import numpy as np | ||
import xml.etree.ElementTree as etxml | ||
import pkg_resources | ||
import socket | ||
import struct | ||
|
||
from transforms3d.quaternions import rotate_vector, qconjugate, mat2quat, qmult | ||
from transforms3d.utils import normalized_vector | ||
|
||
from gym_pybullet_drones.utils.enums import DroneModel | ||
|
||
class CTBRControl(object): | ||
"""Base class for control. | ||
|
||
Implements `__init__()`, `reset(), and interface `computeControlFromState()`, | ||
the main method `computeControl()` should be implemented by its subclasses. | ||
|
||
""" | ||
|
||
################################################################################ | ||
|
||
def __init__(self, | ||
drone_model: DroneModel, | ||
g: float=9.8 | ||
): | ||
"""Common control classes __init__ method. | ||
|
||
Parameters | ||
---------- | ||
drone_model : DroneModel | ||
The type of drone to control (detailed in an .urdf file in folder `assets`). | ||
g : float, optional | ||
The gravitational acceleration in m/s^2. | ||
|
||
""" | ||
#### Set general use constants ############################# | ||
self.DRONE_MODEL = drone_model | ||
"""DroneModel: The type of drone to control.""" | ||
self.GRAVITY = g*self._getURDFParameter('m') | ||
"""float: The gravitational force (M*g) acting on each drone.""" | ||
self.KF = self._getURDFParameter('kf') | ||
"""float: The coefficient converting RPMs into thrust.""" | ||
self.KM = self._getURDFParameter('km') | ||
"""float: The coefficient converting RPMs into torque.""" | ||
|
||
self.reset() | ||
|
||
################################################################################ | ||
|
||
def reset(self): | ||
"""Reset the control classes. | ||
|
||
A general use counter is set to zero. | ||
|
||
""" | ||
self.control_counter = 0 | ||
|
||
################################################################################ | ||
|
||
def computeControlFromState(self, | ||
control_timestep, | ||
state, | ||
target_pos, | ||
target_rpy=np.zeros(3), | ||
target_vel=np.zeros(3), | ||
target_rpy_rates=np.zeros(3) | ||
): | ||
"""Interface method using `computeControl`. | ||
|
||
It can be used to compute a control action directly from the value of key "state" | ||
in the `obs` returned by a call to BaseAviary.step(). | ||
|
||
Parameters | ||
---------- | ||
control_timestep : float | ||
The time step at which control is computed. | ||
state : ndarray | ||
(20,)-shaped array of floats containing the current state of the drone. | ||
target_pos : ndarray | ||
(3,1)-shaped array of floats containing the desired position. | ||
target_rpy : ndarray, optional | ||
(3,1)-shaped array of floats containing the desired orientation as roll, pitch, yaw. | ||
target_vel : ndarray, optional | ||
(3,1)-shaped array of floats containing the desired velocity. | ||
target_rpy_rates : ndarray, optional | ||
(3,1)-shaped array of floats containing the desired roll, pitch, and yaw rates. | ||
""" | ||
|
||
return self.computeControl(control_timestep=control_timestep, | ||
cur_pos=state[0:3], | ||
cur_quat=np.array([state[6], state[3], state[4], state[5]]), | ||
cur_vel=state[10:13], | ||
cur_ang_vel=state[13:16], | ||
target_pos=target_pos, | ||
target_rpy=target_rpy, | ||
target_vel=target_vel, | ||
target_rpy_rates=target_rpy_rates | ||
) | ||
|
||
################################################################################ | ||
|
||
def computeControl(self, | ||
control_timestep, | ||
cur_pos, | ||
cur_quat, | ||
cur_vel, | ||
cur_ang_vel, | ||
target_pos, | ||
target_rpy=np.zeros(3), | ||
target_vel=np.zeros(3), | ||
target_rpy_rates=np.zeros(3) | ||
): | ||
"""Abstract method to compute the control action for a single drone. | ||
|
||
It must be implemented by each subclass of `BaseControl`. | ||
|
||
Parameters | ||
---------- | ||
control_timestep : float | ||
The time step at which control is computed. | ||
cur_pos : ndarray | ||
(3,1)-shaped array of floats containing the current position. | ||
cur_quat : ndarray | ||
(4,1)-shaped array of floats containing the current orientation as a quaternion. | ||
cur_vel : ndarray | ||
(3,1)-shaped array of floats containing the current velocity. | ||
cur_ang_vel : ndarray | ||
(3,1)-shaped array of floats containing the current angular velocity. | ||
target_pos : ndarray | ||
(3,1)-shaped array of floats containing the desired position. | ||
target_rpy : ndarray, optional | ||
(3,1)-shaped array of floats containing the desired orientation as roll, pitch, yaw. | ||
target_vel : ndarray, optional | ||
(3,1)-shaped array of floats containing the desired velocity. | ||
target_rpy_rates : ndarray, optional | ||
(3,1)-shaped array of floats containing the desired roll, pitch, and yaw rates. | ||
|
||
""" | ||
assert(cur_pos.shape == (3,)), f"cur_pos {cur_pos.shape}" | ||
assert(cur_quat.shape == (4,)), f"cur_quat {cur_quat.shape}" | ||
assert(cur_vel.shape == (3,)), f"cur_vel {cur_vel.shape}" | ||
assert(cur_ang_vel.shape == (3,)), f"cur_ang_vel {cur_ang_vel.shape}" | ||
assert(target_pos.shape == (3,)), f"target_pos {target_pos.shape}" | ||
assert(target_rpy.shape == (3,)), f"target_rpy {target_rpy.shape}" | ||
assert(target_vel.shape == (3,)), f"target_vel {target_vel.shape}" | ||
assert(target_rpy_rates.shape == (3,)), f"target_rpy_rates {target_rpy_rates.shape}" | ||
|
||
G = np.array([.0, .0, -9.8]) | ||
K_P = np.array([3., 3., 8.]) | ||
K_D = np.array([2.5, 2.5, 5.]) | ||
K_RATES = np.array([5., 5., 1.]) | ||
P = target_pos - cur_pos | ||
D = target_vel - cur_vel | ||
tar_acc = K_P * P + K_D * D - G | ||
norm_thrust = np.dot(tar_acc, rotate_vector([.0, .0, 1.], cur_quat)) | ||
# Calculate target attitude | ||
z_body = normalized_vector(tar_acc) | ||
x_body = normalized_vector(np.cross(np.array([.0, 1., .0]), z_body)) | ||
y_body = normalized_vector(np.cross(z_body, x_body)) | ||
tar_att = mat2quat(np.vstack([x_body, y_body, z_body]).T) | ||
# Calculate body rates | ||
q_error = qmult(qconjugate(cur_quat), tar_att) | ||
body_rates = 2 * K_RATES * q_error[1:] | ||
if q_error[0] < 0: | ||
body_rates = -body_rates | ||
|
||
return norm_thrust, *body_rates | ||
|
||
################################################################################ | ||
|
||
def setPIDCoefficients(self, | ||
p_coeff_pos=None, | ||
i_coeff_pos=None, | ||
d_coeff_pos=None, | ||
p_coeff_att=None, | ||
i_coeff_att=None, | ||
d_coeff_att=None | ||
): | ||
"""Sets the coefficients of a PID controller. | ||
|
||
This method throws an error message and exist is the coefficients | ||
were not initialized (e.g. when the controller is not a PID one). | ||
|
||
Parameters | ||
---------- | ||
p_coeff_pos : ndarray, optional | ||
(3,1)-shaped array of floats containing the position control proportional coefficients. | ||
i_coeff_pos : ndarray, optional | ||
(3,1)-shaped array of floats containing the position control integral coefficients. | ||
d_coeff_pos : ndarray, optional | ||
(3,1)-shaped array of floats containing the position control derivative coefficients. | ||
p_coeff_att : ndarray, optional | ||
(3,1)-shaped array of floats containing the attitude control proportional coefficients. | ||
i_coeff_att : ndarray, optional | ||
(3,1)-shaped array of floats containing the attitude control integral coefficients. | ||
d_coeff_att : ndarray, optional | ||
(3,1)-shaped array of floats containing the attitude control derivative coefficients. | ||
|
||
""" | ||
ATTR_LIST = ['P_COEFF_FOR', 'I_COEFF_FOR', 'D_COEFF_FOR', 'P_COEFF_TOR', 'I_COEFF_TOR', 'D_COEFF_TOR'] | ||
if not all(hasattr(self, attr) for attr in ATTR_LIST): | ||
print("[ERROR] in BaseControl.setPIDCoefficients(), not all PID coefficients exist as attributes in the instantiated control class.") | ||
exit() | ||
else: | ||
self.P_COEFF_FOR = self.P_COEFF_FOR if p_coeff_pos is None else p_coeff_pos | ||
self.I_COEFF_FOR = self.I_COEFF_FOR if i_coeff_pos is None else i_coeff_pos | ||
self.D_COEFF_FOR = self.D_COEFF_FOR if d_coeff_pos is None else d_coeff_pos | ||
self.P_COEFF_TOR = self.P_COEFF_TOR if p_coeff_att is None else p_coeff_att | ||
self.I_COEFF_TOR = self.I_COEFF_TOR if i_coeff_att is None else i_coeff_att | ||
self.D_COEFF_TOR = self.D_COEFF_TOR if d_coeff_att is None else d_coeff_att | ||
|
||
################################################################################ | ||
|
||
def _getURDFParameter(self, | ||
parameter_name: str | ||
): | ||
"""Reads a parameter from a drone's URDF file. | ||
|
||
This method is nothing more than a custom XML parser for the .urdf | ||
files in folder `assets/`. | ||
|
||
Parameters | ||
---------- | ||
parameter_name : str | ||
The name of the parameter to read. | ||
|
||
Returns | ||
------- | ||
float | ||
The value of the parameter. | ||
|
||
""" | ||
#### Get the XML tree of the drone model to control ######## | ||
URDF = self.DRONE_MODEL.value + ".urdf" | ||
path = pkg_resources.resource_filename('gym_pybullet_drones', 'assets/'+URDF) | ||
URDF_TREE = etxml.parse(path).getroot() | ||
#### Find and return the desired parameter ################# | ||
if parameter_name == 'm': | ||
return float(URDF_TREE[1][0][1].attrib['value']) | ||
elif parameter_name in ['ixx', 'iyy', 'izz']: | ||
return float(URDF_TREE[1][0][2].attrib[parameter_name]) | ||
elif parameter_name in ['arm', 'thrust2weight', 'kf', 'km', 'max_speed_kmh', 'gnd_eff_coeff' 'prop_radius', \ | ||
'drag_coeff_xy', 'drag_coeff_z', 'dw_coeff_1', 'dw_coeff_2', 'dw_coeff_3']: | ||
return float(URDF_TREE[0].attrib[parameter_name]) | ||
elif parameter_name in ['length', 'radius']: | ||
return float(URDF_TREE[1][2][1][0].attrib[parameter_name]) | ||
elif parameter_name == 'collision_z_offset': | ||
COLLISION_SHAPE_OFFSETS = [float(s) for s in URDF_TREE[1][2][0].attrib['xyz'].split(' ')] | ||
return COLLISION_SHAPE_OFFSETS[2] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cp cp ~/gym-pybullet-drones/gym_pybullet_drones/assets/eeprom.bin ~/betaflight/
added by error? Assumes specific file structure in user's computer which is not generally true given the previous install instructions. Also has floating command in line 49, this is meant to be the object copied?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instructions clearer in beta.py. Approved, maybe remove the duplicate instructions and reference one from another