Skip to content
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

Smooth thrusters #110

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
58a00f8
Basic smoothing function
benjaminwp18 Jun 21, 2024
dabe736
Slew MORE
benjaminwp18 Jun 21, 2024
618c071
Throttle at .65
Jun 21, 2024
b6a01b1
Merge branch 'main' into smooth_thrusters
benjaminwp18 Jun 21, 2024
1b8bcd0
Changed override_rc_in to manual_control
Bunando Oct 29, 2024
2d8e789
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 2, 2024
8cd9afb
Merge branch 'main' into smooth_thrusters
benjaminwp18 Nov 2, 2024
84a4ca3
Use default param init for manual control
benjaminwp18 Nov 9, 2024
6512592
Make constants final
benjaminwp18 Nov 9, 2024
18ac802
Cleaner pixhawk instruction smoothing
benjaminwp18 Nov 9, 2024
7d39feb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 9, 2024
6a0191c
Fixed node mappings
Nov 9, 2024
0080554
Correct constant typing
benjaminwp18 Nov 12, 2024
b9504c6
Fix import & ignore error on Ruff
benjaminwp18 Nov 12, 2024
6b99aa2
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 12, 2024
51adf33
Consistant tuple typing & convert correct message
benjaminwp18 Nov 13, 2024
40484e9
Merge branch 'smooth_thrusters' of github.com:CWRUbotix/rov-25 into s…
benjaminwp18 Nov 13, 2024
f82ff10
Undo typing change do to typing errors in utils file
benjaminwp18 Nov 13, 2024
dc4ed3a
Fixed instruction ranges
Nov 13, 2024
8340124
Cleaned up code
Nov 16, 2024
873df40
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 16, 2024
ee81f13
Clone submodules in ROS CI
benjaminwp18 Nov 19, 2024
c7582ae
Merge pull request #121 from CWRUbotix/ci-with-submodules
benjaminwp18 Nov 20, 2024
09cfa19
Code Cleanup
Bunando Nov 23, 2024
695e412
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 23, 2024
00f136d
Merge branch 'manual-control' into smooth_thrusters
benjaminwp18 Nov 26, 2024
52690ad
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/industrial_ci_action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
CCACHE_DIR: "${{ github.workspace }}/.ccache" # directory for ccache (and how we enable ccache in industrial_ci)
steps:
- uses: actions/checkout@v4 # clone target repository
with:
submodules: recursive
- uses: actions/cache@v4 # fetch/store the directory used by ccache before/after the ci run
with:
path: ${{ env.CCACHE_DIR }}
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "src/surface/ros2_video_streamer"]
path = src/surface/ros2_video_streamer
url = git@github.com:CWRUbotix/ros2_video_streamer.git
[submodule "src/pi/mavros"]
path = src/pi/mavros
url = https://github.com/mavlink/mavros.git
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ line-length = 100
flake8-quotes.inline-quotes='single'
extend-select = ["ALL"]
fixable = ["ALL"]
ignore = ["D100", "D101", "D102", "D103", "D104", "D107", "T201", "FIX002", "TD003", "TD002", "TRY003", "EM101", "EM102", "RET504", "D211", "COM812", "ISC001", "ERA001", "S602", "S603", "D205"]
ignore = ["D100", "D101", "D102", "D103", "D104", "D107", "T201", "FIX002", "TD003", "TD002", "TRY003", "EM101", "EM102", "RET504", "D211", "COM812", "ISC001", "ERA001", "S602", "S603", "D205", "UP040"]

[tool.ruff.lint.pydocstyle]
convention = "numpy"
Expand Down
1 change: 1 addition & 0 deletions src/pi/mavros
Submodule mavros added at b49095
4 changes: 2 additions & 2 deletions src/pi/pixhawk_communication/launch/mavros_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ def generate_launch_description() -> LaunchDescription:
# receive a signal from a GCS.
{'system_id': 255},
# plugin_allowlist allows which mavros nodes get launched. The default is all of them.
{'plugin_allowlist': ['sys_status', 'rc_io', 'command']},
{'plugin_allowlist': ['sys_status', 'rc_io', 'command', 'manual_control']},
{'fcu_url': '/dev/ttyPixhawk'},
],
remappings=[
('/pi/mavros/state', '/tether/mavros/state'),
('/pi/mavros/rc/override', '/tether/mavros/rc/override'),
('/pi/mavros/manual_control/send', '/tether/mavros/manual_control/send'),
('/pi/mavros/cmd/arming', '/tether/mavros/cmd/arming'),
('/pi/mavros/cmd/command', '/tether/mavros/cmd/command'),
],
Expand Down
110 changes: 82 additions & 28 deletions src/surface/flight_control/flight_control/multiplexer.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
from collections.abc import Callable
from typing import Final

import rclpy
from mavros_msgs.msg import OverrideRCIn
from mavros_msgs.msg import ManualControl
from rclpy.executors import MultiThreadedExecutor
from rclpy.node import Node
from rclpy.qos import QoSPresetProfiles

from flight_control.pixhawk_instruction_utils import (
apply_function,
pixhawk_instruction_to_tuple,
tuple_to_pixhawk_instruction,
)
from rov_msgs.msg import PixhawkInstruction
from rov_msgs.srv import AutonomousFlight

# Brown out protection
SPEED_THROTTLE = 0.85
SPEED_THROTTLE = 0.65

# Joystick curve
JOYSTICK_EXPONENT = 3

# Range of values Pixhawk takes
# In microseconds
ZERO_SPEED = 1500
MAX_RANGE_SPEED = 400
RANGE_SPEED = MAX_RANGE_SPEED * SPEED_THROTTLE
ZERO_SPEED: Final = 0
Z_ZERO_SPEED: Final = 500
MAX_RANGE_SPEED: Final = 2000
Z_MAX_RANGE_SPEED: Final = 1000
RANGE_SPEED: Final = MAX_RANGE_SPEED * SPEED_THROTTLE
Z_RANGE_SPEED: Final = Z_MAX_RANGE_SPEED * SPEED_THROTTLE

EXTENSIONS_CODE: Final = 0b00000011

# Channels for RC command
MAX_CHANNEL = 8
Expand All @@ -32,6 +42,9 @@
YAW_CHANNEL = 3 # Yaw
ROLL_CHANNEL = 1 # Roll

NEXT_INSTR_FRAC: Final[float] = 0.05
PREV_INSTR_FRAC: Final[float] = 1 - NEXT_INSTR_FRAC


def joystick_map(raw: float) -> float:
mapped = abs(raw) ** JOYSTICK_EXPONENT
Expand All @@ -40,6 +53,10 @@ def joystick_map(raw: float) -> float:
return mapped


def manual_control_map(value: float) -> float:
return RANGE_SPEED * value + ZERO_SPEED


class MultiplexerNode(Node):
def __init__(self) -> None:
super().__init__('multiplexer', parameter_overrides=[])
Expand All @@ -57,36 +74,69 @@ def __init__(self) -> None:
QoSPresetProfiles.DEFAULT.value,
)

self.rc_pub = self.create_publisher(
OverrideRCIn, 'mavros/rc/override', QoSPresetProfiles.DEFAULT.value
self.mc_pub = self.create_publisher(
ManualControl, 'mavros/manual_control/send', QoSPresetProfiles.DEFAULT.value
)

self.previous_instruction = PixhawkInstruction(author=PixhawkInstruction.MANUAL_CONTROL)

@staticmethod
def apply(msg: PixhawkInstruction, function_to_apply: Callable[[float], float]) -> None:
"""Apply a function to each dimension of this PixhawkInstruction."""
msg.forward = function_to_apply(msg.forward)
msg.vertical = function_to_apply(msg.vertical)
msg.lateral = function_to_apply(msg.lateral)
msg.pitch = function_to_apply(msg.pitch)
msg.yaw = function_to_apply(msg.yaw)
msg.roll = function_to_apply(msg.roll)
def smooth_value(prev_value: float, next_value: float) -> float:
return PREV_INSTR_FRAC * prev_value + NEXT_INSTR_FRAC * next_value

def smooth_pixhawk_instruction(self, msg: PixhawkInstruction) -> PixhawkInstruction:
instruction_tuple = pixhawk_instruction_to_tuple(msg)
previous_instruction_tuple = pixhawk_instruction_to_tuple(self.previous_instruction)

instruction_tuple = tuple(
MultiplexerNode.smooth_value(previous_value, value)
for (previous_value, value) in zip(
previous_instruction_tuple, instruction_tuple, strict=True
)
)
smoothed_instruction = tuple_to_pixhawk_instruction(instruction_tuple, msg.author)

self.previous_instruction = smoothed_instruction

return smoothed_instruction

# def apply(msg: PixhawkInstruction, function_to_apply: Callable[[float], float]) -> None:
# """Apply a function to each dimension of this PixhawkInstruction."""
# msg.forward = function_to_apply(msg.forward)
# msg.vertical = msg.vertical
# msg.lateral = function_to_apply(msg.lateral)
# msg.pitch = function_to_apply(msg.pitch)
# msg.yaw = function_to_apply(msg.yaw)
# msg.roll = function_to_apply(msg.roll)

@staticmethod
def to_override_rc_in(msg: PixhawkInstruction) -> OverrideRCIn:
def to_manual_control(msg: PixhawkInstruction) -> ManualControl:
"""Convert this PixhawkInstruction to an rc_msg with the appropriate channels array."""
rc_msg = OverrideRCIn()
mc_msg = ManualControl()

# Maps to PWM
MultiplexerNode.apply(msg, lambda value: int(RANGE_SPEED * value) + ZERO_SPEED)
# instruction_tuple = pixhawk_instruction_to_tuple(msg)
# instruction_tuple = tuple(manual_control_map(value) for value in instruction_tuple)
# mapped_msg = tuple_to_pixhawk_instruction(instruction_tuple)
mapped_msg = apply_function(msg, manual_control_map)

# To account for different z limits
mapped_msg.vertical = Z_RANGE_SPEED * msg.vertical + Z_ZERO_SPEED

# MultiplexerNode.apply(msg, lambda value: (RANGE_SPEED * value) + ZERO_SPEED)

rc_msg.channels[FORWARD_CHANNEL] = msg.forward
rc_msg.channels[THROTTLE_CHANNEL] = msg.vertical
rc_msg.channels[LATERAL_CHANNEL] = msg.lateral
rc_msg.channels[PITCH_CHANNEL] = msg.pitch
rc_msg.channels[YAW_CHANNEL] = msg.yaw
rc_msg.channels[ROLL_CHANNEL] = msg.roll
mc_msg.x = mapped_msg.forward
mc_msg.z = mapped_msg.vertical
# (
# Z_RANGE_SPEED * mapped_msg.vertical
# ) + Z_ZERO_SPEED # To account for different z limits
mc_msg.y = mapped_msg.lateral
mc_msg.r = mapped_msg.yaw
mc_msg.enabled_extensions = EXTENSIONS_CODE
mc_msg.s = mapped_msg.pitch
mc_msg.t = mapped_msg.roll

return rc_msg
return mc_msg

def state_control(
self, req: AutonomousFlight.Request, res: AutonomousFlight.Response
Expand All @@ -102,7 +152,10 @@ def control_callback(self, msg: PixhawkInstruction) -> None:
):
# Smooth out adjustments
# TODO: look into maybe doing inheritance on a PixhawkInstruction
MultiplexerNode.apply(msg, joystick_map)
# instruction_tuple = pixhawk_instruction_to_tuple(msg)
# instruction_tuple = tuple(joystick_map(value) for value in instruction_tuple)
# msg = tuple_to_pixhawk_instruction(instruction_tuple)
msg = apply_function(msg, joystick_map)
elif (
msg.author == PixhawkInstruction.KEYBOARD_CONTROL
and self.state == AutonomousFlight.Request.STOP
Expand All @@ -113,7 +166,8 @@ def control_callback(self, msg: PixhawkInstruction) -> None:
else:
return

self.rc_pub.publish(msg=self.to_override_rc_in(msg))
smoothed_instruction = self.smooth_pixhawk_instruction(msg)
self.mc_pub.publish(self.to_manual_control(smoothed_instruction))


def main() -> None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from collections.abc import Callable

from rov_msgs.msg import PixhawkInstruction


def pixhawk_instruction_to_tuple(msg: PixhawkInstruction) -> tuple[float, ...]:
return (msg.forward, msg.vertical, msg.lateral, msg.pitch, msg.yaw, msg.roll)


def tuple_to_pixhawk_instruction(
instruction_tuple: tuple[float, ...], author: int = PixhawkInstruction.MANUAL_CONTROL
) -> PixhawkInstruction:
return PixhawkInstruction(
forward=instruction_tuple[0],
lateral=instruction_tuple[1],
vertical=instruction_tuple[2],
roll=instruction_tuple[3],
pitch=instruction_tuple[4],
yaw=instruction_tuple[5],
author=author,
)


def apply_function(
msg: PixhawkInstruction, function_to_apply: Callable[[float], float]
) -> PixhawkInstruction:
instruction_tuple = pixhawk_instruction_to_tuple(msg)
instruction_tuple = tuple(function_to_apply(value) for value in instruction_tuple)
return tuple_to_pixhawk_instruction(instruction_tuple, msg.author)
2 changes: 1 addition & 1 deletion src/surface/flight_control/launch/flight_control_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def generate_launch_description() -> LaunchDescription:
multiplexer_node = Node(
package='flight_control',
executable='multiplexer_node',
remappings=[('/surface/mavros/rc/override', '/tether/mavros/rc/override')],
remappings=[('/surface/mavros/manual_control/send', '/tether/mavros/manual_control/send')],
emulate_tty=True,
output='screen',
)
Expand Down
22 changes: 9 additions & 13 deletions src/surface/flight_control/test/test_manual_control.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import rclpy
from flight_control.manual_control_node import ManualControlNode
from flight_control.multiplexer import (
FORWARD_CHANNEL,
LATERAL_CHANNEL,
PITCH_CHANNEL,
RANGE_SPEED,
ROLL_CHANNEL,
THROTTLE_CHANNEL,
YAW_CHANNEL,
Z_RANGE_SPEED,
Z_ZERO_SPEED,
ZERO_SPEED,
MultiplexerNode,
)
Expand Down Expand Up @@ -35,14 +31,14 @@ def test_joystick_profiles() -> None:
roll=0.92,
)

msg = MultiplexerNode.to_override_rc_in(instruction)
msg = MultiplexerNode.to_manual_control(instruction)

assert msg.channels[FORWARD_CHANNEL] == ZERO_SPEED
assert msg.channels[THROTTLE_CHANNEL] == (ZERO_SPEED + RANGE_SPEED)
assert msg.channels[LATERAL_CHANNEL] == (ZERO_SPEED - RANGE_SPEED)
assert msg.x == ZERO_SPEED
assert msg.z == (Z_ZERO_SPEED + Z_RANGE_SPEED)
assert msg.y == (ZERO_SPEED - RANGE_SPEED)

# 1539 1378

assert msg.channels[PITCH_CHANNEL] == ZERO_SPEED + int(RANGE_SPEED * 0.34)
assert msg.channels[YAW_CHANNEL] == ZERO_SPEED + int(RANGE_SPEED * -0.6)
assert msg.channels[ROLL_CHANNEL] == ZERO_SPEED + int(RANGE_SPEED * 0.92)
assert msg.s == ZERO_SPEED + int(RANGE_SPEED * 0.34)
assert msg.r == ZERO_SPEED + int(RANGE_SPEED * -0.6)
assert msg.t == ZERO_SPEED + int(RANGE_SPEED * 0.92)