Skip to content

Commit 1799545

Browse files
committed
WIP
1 parent a07f016 commit 1799545

12 files changed

Lines changed: 3233 additions & 1303 deletions

ardupilot_methodic_configurator/backend_flightcontroller.py

Lines changed: 251 additions & 1262 deletions
Large diffs are not rendered by default.
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
"""
2+
Pure business logic functions for flight controller operations.
3+
4+
This module contains stateless, side-effect-free functions that implement business rules
5+
and calculations. These functions are easily testable without needing hardware or mocks.
6+
7+
This file is part of ArduPilot Methodic Configurator. https://github.com/ArduPilot/MethodicConfigurator
8+
9+
SPDX-FileCopyrightText: 2024-2025 Amilcar do Carmo Lucas <amilcar.lucas@iav.de>
10+
11+
SPDX-License-Identifier: GPL-3.0-or-later
12+
"""
13+
14+
from typing import Optional
15+
16+
17+
def calculate_voltage_thresholds(fc_parameters: dict[str, float]) -> tuple[float, float]:
18+
"""
19+
Calculate battery voltage thresholds for motor testing safety.
20+
21+
This is a pure function that extracts the minimum and maximum voltage thresholds
22+
from flight controller parameters.
23+
24+
Args:
25+
fc_parameters: Dictionary of flight controller parameters
26+
27+
Returns:
28+
tuple[float, float]: (min_voltage, max_voltage) for safe motor testing
29+
30+
Examples:
31+
>>> params = {"BATT_ARM_VOLT": 10.5, "MOT_BAT_VOLT_MAX": 25.2}
32+
>>> calculate_voltage_thresholds(params)
33+
(10.5, 25.2)
34+
35+
>>> calculate_voltage_thresholds({})
36+
(0.0, 0.0)
37+
38+
"""
39+
min_voltage = fc_parameters.get("BATT_ARM_VOLT", 0.0)
40+
max_voltage = fc_parameters.get("MOT_BAT_VOLT_MAX", 0.0)
41+
return (min_voltage, max_voltage)
42+
43+
44+
def is_battery_monitoring_enabled(fc_parameters: dict[str, float]) -> bool:
45+
"""
46+
Check if battery monitoring is enabled in flight controller parameters.
47+
48+
Args:
49+
fc_parameters: Dictionary of flight controller parameters
50+
51+
Returns:
52+
bool: True if BATT_MONITOR != 0, False otherwise
53+
54+
Examples:
55+
>>> is_battery_monitoring_enabled({"BATT_MONITOR": 4.0})
56+
True
57+
58+
>>> is_battery_monitoring_enabled({"BATT_MONITOR": 0.0})
59+
False
60+
61+
>>> is_battery_monitoring_enabled({})
62+
False
63+
64+
"""
65+
return fc_parameters.get("BATT_MONITOR", 0) != 0
66+
67+
68+
def get_frame_info(fc_parameters: dict[str, float]) -> tuple[int, int]:
69+
"""
70+
Extract frame class and frame type from flight controller parameters.
71+
72+
Args:
73+
fc_parameters: Dictionary of flight controller parameters
74+
75+
Returns:
76+
tuple[int, int]: (frame_class, frame_type)
77+
frame_class: Frame class (default: 1 = QUAD)
78+
frame_type: Frame type (default: 1 = X)
79+
80+
Examples:
81+
>>> get_frame_info({"FRAME_CLASS": 1.0, "FRAME_TYPE": 3.0})
82+
(1, 3)
83+
84+
>>> get_frame_info({})
85+
(1, 1)
86+
87+
"""
88+
frame_class = int(fc_parameters.get("FRAME_CLASS", 1)) # Default to QUAD
89+
frame_type = int(fc_parameters.get("FRAME_TYPE", 1)) # Default to X
90+
return (frame_class, frame_type)
91+
92+
93+
def validate_battery_voltage(
94+
voltage: float,
95+
min_voltage: float,
96+
max_voltage: float,
97+
) -> tuple[bool, Optional[str]]:
98+
"""
99+
Validate if battery voltage is within safe operating range for motor testing.
100+
101+
Args:
102+
voltage: Current battery voltage in volts
103+
min_voltage: Minimum safe voltage (BATT_ARM_VOLT)
104+
max_voltage: Maximum safe voltage (MOT_BAT_VOLT_MAX)
105+
106+
Returns:
107+
tuple[bool, Optional[str]]: (is_valid, error_message)
108+
is_valid: True if voltage is within safe range
109+
error_message: None if valid, descriptive error message if invalid
110+
111+
Examples:
112+
>>> validate_battery_voltage(12.6, 10.5, 25.2)
113+
(True, None)
114+
115+
>>> validate_battery_voltage(9.0, 10.5, 25.2)
116+
(False, 'Battery voltage 9.00V is below minimum safe voltage 10.50V')
117+
118+
>>> validate_battery_voltage(26.0, 10.5, 25.2)
119+
(False, 'Battery voltage 26.00V is above maximum safe voltage 25.20V')
120+
121+
"""
122+
if voltage < min_voltage:
123+
return False, f"Battery voltage {voltage:.2f}V is below minimum safe voltage {min_voltage:.2f}V"
124+
if voltage > max_voltage:
125+
return False, f"Battery voltage {voltage:.2f}V is above maximum safe voltage {max_voltage:.2f}V"
126+
return True, None
127+
128+
129+
def convert_battery_telemetry_units(
130+
voltage_millivolts: int,
131+
current_centiamps: int,
132+
) -> tuple[float, float]:
133+
"""
134+
Convert battery telemetry from MAVLink units to standard units.
135+
136+
Args:
137+
voltage_millivolts: Battery voltage in millivolts (MAVLink BATTERY_STATUS.voltages)
138+
current_centiamps: Battery current in centiamps (MAVLink BATTERY_STATUS.current_battery)
139+
140+
Returns:
141+
tuple[float, float]: (voltage_volts, current_amps)
142+
voltage_volts: Voltage in volts
143+
current_amps: Current in amperes
144+
145+
Examples:
146+
>>> convert_battery_telemetry_units(12600, 1050)
147+
(12.6, 10.5)
148+
149+
>>> convert_battery_telemetry_units(-1, -1) # Invalid/unavailable readings
150+
(0.0, 0.0)
151+
152+
"""
153+
voltage = voltage_millivolts / 1000.0 if voltage_millivolts != -1 else 0.0
154+
current = current_centiamps / 100.0 if current_centiamps != -1 else 0.0
155+
return (voltage, current)
156+
157+
158+
def validate_throttle_percentage(throttle_percent: int) -> tuple[bool, Optional[str]]:
159+
"""
160+
Validate throttle percentage is within safe range for motor testing.
161+
162+
Args:
163+
throttle_percent: Throttle percentage (0-100)
164+
165+
Returns:
166+
tuple[bool, Optional[str]]: (is_valid, error_message)
167+
is_valid: True if throttle is valid
168+
error_message: None if valid, descriptive error message if invalid
169+
170+
Examples:
171+
>>> validate_throttle_percentage(50)
172+
(True, None)
173+
174+
>>> validate_throttle_percentage(0)
175+
(True, None)
176+
177+
>>> validate_throttle_percentage(-10)
178+
(False, 'Throttle percentage -10 is below minimum (0)')
179+
180+
>>> validate_throttle_percentage(150)
181+
(False, 'Throttle percentage 150 is above maximum (100)')
182+
183+
"""
184+
if throttle_percent < 0:
185+
return False, f"Throttle percentage {throttle_percent} is below minimum (0)"
186+
if throttle_percent > 100:
187+
return False, f"Throttle percentage {throttle_percent} is above maximum (100)"
188+
return True, None
189+
190+
191+
def validate_motor_test_duration(timeout_seconds: int) -> tuple[bool, Optional[str]]:
192+
"""
193+
Validate motor test duration is within safe limits.
194+
195+
Args:
196+
timeout_seconds: Test duration in seconds
197+
198+
Returns:
199+
tuple[bool, Optional[str]]: (is_valid, error_message)
200+
is_valid: True if duration is valid
201+
error_message: None if valid, descriptive error message if invalid
202+
203+
Examples:
204+
>>> validate_motor_test_duration(5)
205+
(True, None)
206+
207+
>>> validate_motor_test_duration(0)
208+
(False, 'Motor test duration 0 seconds is too short (minimum: 1 second)')
209+
210+
>>> validate_motor_test_duration(35)
211+
(False, 'Motor test duration 35 seconds is too long (maximum: 30 seconds)')
212+
213+
"""
214+
MIN_DURATION = 1 # noqa: N806
215+
MAX_DURATION = 30 # noqa: N806
216+
217+
if timeout_seconds < MIN_DURATION:
218+
return False, f"Motor test duration {timeout_seconds} seconds is too short (minimum: {MIN_DURATION} second)"
219+
if timeout_seconds > MAX_DURATION:
220+
return False, f"Motor test duration {timeout_seconds} seconds is too long (maximum: {MAX_DURATION} seconds)"
221+
return True, None
222+
223+
224+
def calculate_motor_sequence_number(motor_index: int, zero_based: bool = True) -> int:
225+
"""
226+
Calculate MAVLink motor sequence number from motor index.
227+
228+
ArduPilot motor test command uses 1-based sequence numbers,
229+
but motor indices are often 0-based in user interfaces.
230+
231+
Args:
232+
motor_index: Motor index (0-based or 1-based depending on zero_based parameter)
233+
zero_based: If True, motor_index is 0-based; if False, motor_index is already 1-based
234+
235+
Returns:
236+
int: MAVLink motor sequence number (1-based)
237+
238+
Examples:
239+
>>> calculate_motor_sequence_number(0, zero_based=True)
240+
1
241+
242+
>>> calculate_motor_sequence_number(3, zero_based=True)
243+
4
244+
245+
>>> calculate_motor_sequence_number(1, zero_based=False)
246+
1
247+
248+
"""
249+
return motor_index + 1 if zero_based else motor_index

0 commit comments

Comments
 (0)