1- from collections .abc import Callable
21from typing import Final
32
43import rclpy
76from rclpy .node import Node
87from 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+ )
1014from rov_msgs .msg import PixhawkInstruction
1115from 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
2731
2832EXTENSIONS_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
4239def 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+
49136class 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
129206def main () -> None :
0 commit comments