Skip to content

Commit 43be9b1

Browse files
authored
Merge pull request #134 from CWRUbotix/smooth_thrusters
Smooth thrusters
2 parents 99f1636 + 73dc0bd commit 43be9b1

File tree

4 files changed

+202
-50
lines changed

4 files changed

+202
-50
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ line-length = 100
2424
flake8-quotes.inline-quotes='single'
2525
extend-select = ["ALL"]
2626
fixable = ["ALL"]
27-
ignore = ["D100", "D101", "D102", "D103", "D104", "D107", "T201", "FIX002", "TD003", "TD002", "TRY003", "EM101", "EM102", "RET504", "D211", "COM812", "ISC001", "ERA001", "S602", "S603", "D205"]
27+
ignore = ["D100", "D101", "D102", "D103", "D104", "D107", "T201", "FIX002", "TD003", "TD002", "TRY003", "EM101", "EM102", "RET504", "D211", "COM812", "ISC001", "ERA001", "S602", "S603", "D205", "UP040"]
2828

2929
[tool.ruff.lint.pydocstyle]
3030
convention = "numpy"

src/surface/flight_control/flight_control/multiplexer.py

Lines changed: 121 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from collections.abc import Callable
21
from typing import Final
32

43
import rclpy
@@ -7,14 +6,19 @@
76
from rclpy.node import Node
87
from rclpy.qos import QoSPresetProfiles
98

9+
from flight_control.pixhawk_instruction_utils import (
10+
apply_function,
11+
pixhawk_instruction_to_tuple,
12+
tuple_to_pixhawk_instruction,
13+
)
1014
from rov_msgs.msg import PixhawkInstruction
1115
from rov_msgs.srv import AutonomousFlight
1216

1317
# Brown out protection
14-
SPEED_THROTTLE = 0.85
18+
SPEED_THROTTLE: Final = 0.65
1519

1620
# Joystick curve
17-
JOYSTICK_EXPONENT = 3
21+
JOYSTICK_EXPONENT: Final = 3
1822

1923
# Range of values Pixhawk takes
2024
# In microseconds
@@ -27,25 +31,108 @@
2731

2832
EXTENSIONS_CODE: Final = 0b00000011
2933

30-
# Channels for RC command
31-
MAX_CHANNEL = 8
32-
MIN_CHANNEL = 1
33-
34-
FORWARD_CHANNEL = 4 # X
35-
THROTTLE_CHANNEL = 2 # Z (vertical)
36-
LATERAL_CHANNEL = 5 # Y (left & right)
37-
PITCH_CHANNEL = 0 # Pitch
38-
YAW_CHANNEL = 3 # Yaw
39-
ROLL_CHANNEL = 1 # Roll
34+
NEXT_INSTR_FRAC: Final = 0.05
35+
PREV_INSTR_FRAC: Final = 1 - NEXT_INSTR_FRAC
36+
INSTR_EPSILON: Final = 0.05
4037

4138

4239
def joystick_map(raw: float) -> float:
40+
"""
41+
Convert the provided joystick position to a
42+
float in [-1.0, 1.0] for use in a PixhawkInstruction.
43+
44+
Parameters
45+
----------
46+
raw : float
47+
The joystick position to convert
48+
49+
Returns
50+
-------
51+
float
52+
A float in [-1.0, 1.0] to act as a PixhawkInstruction dimension
53+
"""
4354
mapped = abs(raw) ** JOYSTICK_EXPONENT
4455
if raw < 0:
4556
mapped *= -1
4657
return mapped
4758

4859

60+
def manual_control_map(value: float) -> float:
61+
"""
62+
Convert the provided float in [-1.0, 1.0] to a ManualControl dimension.
63+
64+
Parameters
65+
----------
66+
value : float
67+
The float in [-1.0, 1.0] to convert to a ManualControl dimension
68+
69+
Returns
70+
-------
71+
float
72+
The resulting ManualControl dimension
73+
"""
74+
return RANGE_SPEED * value + ZERO_SPEED
75+
76+
77+
def smooth_value(prev_value: float, next_value: float) -> float:
78+
"""
79+
Get a value that interpolates prev_value & next_value.
80+
81+
Parameters
82+
----------
83+
prev_value : float
84+
The previous value, affects the result based on PREV_INSTR_FRAC
85+
next_value : float
86+
The next value, affects the result based on NEXT_INSTR_FRAC
87+
88+
Returns
89+
-------
90+
float
91+
The resulting value between prev_value & next_value
92+
"""
93+
smoothed_value = PREV_INSTR_FRAC * prev_value + NEXT_INSTR_FRAC * next_value
94+
95+
# If close to target value, snap to it
96+
# (we want to get there eventually, not approach in the limit)
97+
if next_value - INSTR_EPSILON <= smoothed_value <= next_value + INSTR_EPSILON:
98+
return next_value
99+
100+
return smoothed_value
101+
102+
103+
def to_manual_control(msg: PixhawkInstruction) -> ManualControl:
104+
"""
105+
Convert the provided PixhawkInstruction to a ManualControl message.
106+
107+
Parameters
108+
----------
109+
msg : PixhawkInstruction
110+
The PixhawkInstruction to convert
111+
112+
Returns
113+
-------
114+
ManualControl
115+
The resulting ManualControl message
116+
"""
117+
mc_msg = ManualControl()
118+
119+
# Maps to PWM
120+
mapped_msg = apply_function(msg, manual_control_map)
121+
122+
# To account for different z limits
123+
mapped_msg.vertical = Z_RANGE_SPEED * msg.vertical + Z_ZERO_SPEED
124+
125+
mc_msg.x = mapped_msg.forward
126+
mc_msg.z = mapped_msg.vertical
127+
mc_msg.y = mapped_msg.lateral
128+
mc_msg.r = mapped_msg.yaw
129+
mc_msg.enabled_extensions = EXTENSIONS_CODE
130+
mc_msg.s = mapped_msg.pitch
131+
mc_msg.t = mapped_msg.roll
132+
133+
return mc_msg
134+
135+
49136
class MultiplexerNode(Node):
50137
def __init__(self) -> None:
51138
super().__init__('multiplexer', parameter_overrides=[])
@@ -67,35 +154,24 @@ def __init__(self) -> None:
67154
ManualControl, 'mavros/manual_control/send', QoSPresetProfiles.DEFAULT.value
68155
)
69156

70-
@staticmethod
71-
def apply(msg: PixhawkInstruction, function_to_apply: Callable[[float], float]) -> None:
72-
"""Apply a function to each dimension of this PixhawkInstruction."""
73-
msg.forward = function_to_apply(msg.forward)
74-
msg.vertical = msg.vertical
75-
msg.lateral = function_to_apply(msg.lateral)
76-
msg.pitch = function_to_apply(msg.pitch)
77-
msg.yaw = function_to_apply(msg.yaw)
78-
msg.roll = function_to_apply(msg.roll)
79-
80-
@staticmethod
81-
def to_manual_control(msg: PixhawkInstruction) -> ManualControl:
82-
"""Convert this PixhawkInstruction to an rc_msg with the appropriate channels array."""
83-
mc_msg = ManualControl()
84-
85-
# Maps to PWM
86-
MultiplexerNode.apply(msg, lambda value: (RANGE_SPEED * value) + ZERO_SPEED)
87-
88-
mc_msg.x = msg.forward
89-
mc_msg.z = (
90-
Z_RANGE_SPEED * msg.vertical
91-
) + Z_ZERO_SPEED # To account for different z limits
92-
mc_msg.y = msg.lateral
93-
mc_msg.r = msg.yaw
94-
mc_msg.enabled_extensions = EXTENSIONS_CODE
95-
mc_msg.s = msg.pitch
96-
mc_msg.t = msg.roll
97-
98-
return mc_msg
157+
self.previous_instruction_tuple: tuple[float, ...] = pixhawk_instruction_to_tuple(
158+
PixhawkInstruction()
159+
)
160+
161+
def smooth_pixhawk_instruction(self, msg: PixhawkInstruction) -> PixhawkInstruction:
162+
instruction_tuple = pixhawk_instruction_to_tuple(msg)
163+
164+
smoothed_tuple = tuple(
165+
smooth_value(previous_value, value)
166+
for (previous_value, value) in zip(
167+
self.previous_instruction_tuple, instruction_tuple, strict=True
168+
)
169+
)
170+
smoothed_instruction = tuple_to_pixhawk_instruction(smoothed_tuple, msg.author)
171+
172+
self.previous_instruction_tuple = smoothed_tuple
173+
174+
return smoothed_instruction
99175

100176
def state_control(
101177
self, req: AutonomousFlight.Request, res: AutonomousFlight.Response
@@ -111,7 +187,7 @@ def control_callback(self, msg: PixhawkInstruction) -> None:
111187
):
112188
# Smooth out adjustments
113189
# TODO: look into maybe doing inheritance on a PixhawkInstruction
114-
MultiplexerNode.apply(msg, joystick_map)
190+
msg = apply_function(msg, joystick_map)
115191
elif (
116192
msg.author == PixhawkInstruction.KEYBOARD_CONTROL
117193
and self.state == AutonomousFlight.Request.STOP
@@ -123,7 +199,8 @@ def control_callback(self, msg: PixhawkInstruction) -> None:
123199
else:
124200
return
125201

126-
self.mc_pub.publish(self.to_manual_control(msg))
202+
smoothed_instruction = self.smooth_pixhawk_instruction(msg)
203+
self.mc_pub.publish(to_manual_control(smoothed_instruction))
127204

128205

129206
def main() -> None:
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from collections.abc import Callable
2+
3+
from rov_msgs.msg import PixhawkInstruction
4+
5+
6+
def pixhawk_instruction_to_tuple(
7+
msg: PixhawkInstruction,
8+
) -> tuple[float, float, float, float, float, float]:
9+
"""
10+
Convert PixhawkInstruction to tuple of dimensions.
11+
12+
Parameters
13+
----------
14+
msg : PixhawkInstruction
15+
PixhawkInstruction to convert
16+
17+
Returns
18+
-------
19+
tuple[float, float, float, float, float, float]
20+
Tuple of dimensions from the instruction
21+
"""
22+
return (msg.forward, msg.vertical, msg.lateral, msg.pitch, msg.yaw, msg.roll)
23+
24+
25+
def tuple_to_pixhawk_instruction(
26+
instruction_tuple: tuple[float, ...], author: int = PixhawkInstruction.MANUAL_CONTROL
27+
) -> PixhawkInstruction:
28+
"""
29+
Convert tuple of dimensions and author to a PixhawkInstruction.
30+
31+
Parameters
32+
----------
33+
instruction_tuple : tuple[float, ...]
34+
Tuple of dimensions
35+
author : int, optional
36+
Author of the PixhawkInstruction, by default PixhawkInstruction.MANUAL_CONTROL
37+
38+
Returns
39+
-------
40+
PixhawkInstruction
41+
A new PixhawkInstruction with the provided dimensions and author
42+
"""
43+
return PixhawkInstruction(
44+
forward=instruction_tuple[0],
45+
vertical=instruction_tuple[1],
46+
lateral=instruction_tuple[2],
47+
pitch=instruction_tuple[3],
48+
yaw=instruction_tuple[4],
49+
roll=instruction_tuple[5],
50+
author=author,
51+
)
52+
53+
54+
def apply_function(
55+
msg: PixhawkInstruction, function_to_apply: Callable[[float], float]
56+
) -> PixhawkInstruction:
57+
"""
58+
Run the provided function on each dimension of a PixhawkInstruction.
59+
Does not modify the original PixhawkInstruction.
60+
61+
Parameters
62+
----------
63+
msg : PixhawkInstruction
64+
The instruction to run the function on
65+
function_to_apply : Callable[[float], float]
66+
The function to apply to each dimension
67+
68+
Returns
69+
-------
70+
PixhawkInstruction
71+
The new PixhawkInstruction made by applying the function to each dimension of msg
72+
"""
73+
instruction_tuple = pixhawk_instruction_to_tuple(msg)
74+
modified_tuple = tuple(function_to_apply(value) for value in instruction_tuple)
75+
return tuple_to_pixhawk_instruction(modified_tuple, msg.author)

src/surface/flight_control/test/test_manual_control.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Z_RANGE_SPEED,
66
Z_ZERO_SPEED,
77
ZERO_SPEED,
8-
MultiplexerNode,
8+
to_manual_control,
99
)
1010

1111
from rov_msgs.msg import PixhawkInstruction
@@ -31,14 +31,14 @@ def test_joystick_profiles() -> None:
3131
roll=0.92,
3232
)
3333

34-
msg = MultiplexerNode.to_manual_control(instruction)
34+
msg = to_manual_control(instruction)
3535

3636
assert msg.x == ZERO_SPEED
3737
assert msg.z == (Z_ZERO_SPEED + Z_RANGE_SPEED)
3838
assert msg.y == (ZERO_SPEED - RANGE_SPEED)
3939

4040
# 1539 1378
4141

42-
assert msg.s == ZERO_SPEED + int(RANGE_SPEED * 0.34)
43-
assert msg.r == ZERO_SPEED + int(RANGE_SPEED * -0.6)
44-
assert msg.t == ZERO_SPEED + int(RANGE_SPEED * 0.92)
42+
assert msg.s == ZERO_SPEED + RANGE_SPEED * 0.34
43+
assert msg.r == ZERO_SPEED + RANGE_SPEED * -0.6
44+
assert msg.t == ZERO_SPEED + RANGE_SPEED * 0.92

0 commit comments

Comments
 (0)