Skip to content

Commit 3ea3b8e

Browse files
committed
feat(rover support): Improve support for Rover ESCs
Improve Heli support as well
1 parent 153941a commit 3ea3b8e

7 files changed

Lines changed: 238 additions & 101 deletions

ardupilot_methodic_configurator/annotate_params.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ def get_fallback_xml_url(vehicle_type: str, firmware_version: str) -> str:
580580
"ArduPlane": "Plane-",
581581
"Rover": "Rover-",
582582
"ArduSub": "Sub-",
583+
"Heli": "Copter-",
583584
}
584585
try:
585586
vehicle_subdir = vehicle_parm_subdir[vehicle_type] + firmware_version[0:3]

ardupilot_methodic_configurator/configuration_steps_Rover.json

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,14 @@
129129
"BATT_FS_LOW_ACT": { "New Value": 2, "Change Reason": "Return and land at home or rally point" }
130130
},
131131
"derived_parameters": {
132-
"BATT_ARM_VOLT": { "New Value": "(vehicle_components['Battery']['Specifications']['Number of cells']-1)*0.1+(vehicle_components['Battery']['Specifications']['Volt per cell crit']+0.3)*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "Do not allow arming below this voltage" },
133-
"BATT_CAPACITY": { "New Value": "(vehicle_components['Battery']['Specifications']['Capacity mAh'])", "Change Reason": "Total battery capacity specified in the component editor" },
134-
"BATT_CRT_VOLT": { "New Value": "(vehicle_components['Battery']['Specifications']['Volt per cell crit'])*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "(Critical voltage + 0.0) x no. of cells" },
135-
"BATT_LOW_VOLT": { "New Value": "(vehicle_components['Battery']['Specifications']['Volt per cell low'])*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "(Low voltage + 0.0) x no. of cells" },
132+
"BATT_ARM_VOLT": { "New Value": "vehicle_components['Battery']['Specifications']['Volt per cell arm']*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "Only arm above this voltage, to avoid taking off with insufficient battery capacity" },
133+
"BATT_CAPACITY": { "New Value": "vehicle_components['Battery']['Specifications']['Capacity mAh']", "Change Reason": "Total battery capacity specified in the component editor" },
134+
"BATT_CRT_VOLT": { "New Value": "vehicle_components['Battery']['Specifications']['Volt per cell crit']*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "Critical failsafe voltage x nr. of cells" },
135+
"BATT_LOW_VOLT": { "New Value": "vehicle_components['Battery']['Specifications']['Volt per cell low']*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "Low failsafe voltage x nr. of cells" },
136136
"BATT_MONITOR": { "New Value": "vehicle_components['Battery Monitor']['FC Connection']['Protocol']", "Change Reason": "Selected in component editor window" },
137137
"BATT_I2C_BUS": { "New Value": "1 if vehicle_components['Battery Monitor']['FC Connection']['Type'] == 'I2C2' else 2 if vehicle_components['Battery Monitor']['FC Connection']['Type'] == 'I2C3' else 3 if vehicle_components['Battery Monitor']['FC Connection']['Type'] == 'I2C4' else 0", "Change Reason": "Selected in component editor window" },
138-
"MOT_BAT_VOLT_MAX": { "New Value": "(vehicle_components['Battery']['Specifications']['Volt per cell max']+0.0)*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "(Max voltage + 0.0) x no. of cells" },
139-
"MOT_BAT_VOLT_MIN": { "New Value": "(vehicle_components['Battery']['Specifications']['Volt per cell crit']+0.0)*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "(Critical voltage + 0.0) x no. of cells" }
138+
"MOT_BAT_VOLT_MAX": { "New Value": "vehicle_components['Battery']['Specifications']['Volt per cell max']*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "Scale the PIDs up when battery voltage is below this threshold" },
139+
"MOT_BAT_VOLT_MIN": { "New Value": "vehicle_components['Battery']['Specifications']['Volt per cell min']*vehicle_components['Battery']['Specifications']['Number of cells']", "Change Reason": "Scale the PIDs up when battery voltage is above this threshold" }
140140
},
141141
"rename_connection": "vehicle_components['Battery Monitor']['FC Connection']['Type']",
142142
"old_filenames": [],
@@ -170,7 +170,8 @@
170170
"mandatory_text": "100% mandatory (0% optional)",
171171
"auto_changed_by": "",
172172
"derived_parameters": {
173-
"GPS_TYPE": { "New Value": "vehicle_components['GNSS Receiver']['FC Connection']['Protocol']", "Change Reason": "Defined in component editor" }
173+
"GPS_TYPE": { "New Value": "vehicle_components['GNSS Receiver']['FC Connection']['Protocol']", "Change Reason": "Defined in component editor" },
174+
"GPS1_TYPE": { "New Value": "vehicle_components['GNSS Receiver']['FC Connection']['Protocol']", "Change Reason": "Defined in component editor" }
174175
},
175176
"rename_connection": "vehicle_components['GNSS Receiver']['FC Connection']['Type']",
176177
"old_filenames": []
@@ -269,7 +270,11 @@
269270
"external_tool_url": "",
270271
"mandatory_text": "40% mandatory (60% optional)",
271272
"auto_changed_by": "",
272-
"old_filenames": ["14_motor.param"]
273+
"old_filenames": ["14_motor.param"],
274+
"plugin": {
275+
"name": "motor_test",
276+
"placement": "left"
277+
}
273278
},
274279
"16_pid_adjustment.param": {
275280
"why": "With very large or very small vehicles the default PID values are not suitable for the first flight",

ardupilot_methodic_configurator/data_model_vehicle_components_import.py

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
CAN_PORTS,
3131
GNSS_RECEIVER_CONNECTION,
3232
I2C_PORTS,
33-
MOT_PWM_TYPE_DICT,
3433
RC_PROTOCOLS_DICT,
3534
SERIAL_PORTS,
3635
SERIAL_PROTOCOLS_DICT,
3736
ComponentDataModelValidation,
37+
get_mot_pwm_type_sub_dict,
3838
)
3939

4040

@@ -139,6 +139,21 @@ def _verify_dict_is_uptodate(
139139
if check_key in dict_to_check:
140140
code_protocol = dict_to_check[check_key].get("protocol", None)
141141
if code_protocol != doc_protocol:
142+
if code_protocol == "Septentrio(SBF)" and doc_protocol == "SBF":
143+
# ArduPilot 4.6 renamed "SBF" to "Septentrio(SBF)"
144+
continue
145+
if code_protocol == "Trimble(GSOF)" and doc_protocol == "GSOF":
146+
# ArduPilot 4.6 renamed "GSOF" to "Trimble(GSOF)"
147+
continue
148+
if code_protocol == "MAVLink" and doc_protocol == "MAV":
149+
# ArduPilot 4.6 renamed "MAV" to "MAVLink"
150+
continue
151+
if code_protocol == "Septentrio-DualAntenna(SBF)" and doc_protocol == "SBF-DualAntenna":
152+
# ArduPilot 4.6 renamed "SBF-DualAntenna" to "Septentrio-DualAntenna(SBF)"
153+
continue
154+
if code_protocol == "Gimbal" and doc_protocol == "SToRM32 Gimbal Serial":
155+
# ArduPilot 4.6 renamed "SToRM32 Gimbal Serial" to "Gimbal"
156+
continue
142157
logging_warning(_("Protocol %s does not match %s in %s metadata"), code_protocol, doc_protocol, doc_key)
143158
is_valid = False
144159
else:
@@ -166,7 +181,8 @@ def process_fc_parameters(
166181
self._verify_dict_is_uptodate(doc, GNSS_RECEIVER_CONNECTION, "GPS1_TYPE", "values")
167182
elif "GPS_TYPE" in doc:
168183
self._verify_dict_is_uptodate(doc, GNSS_RECEIVER_CONNECTION, "GPS_TYPE", "values")
169-
self._verify_dict_is_uptodate(doc, MOT_PWM_TYPE_DICT, "MOT_PWM_TYPE", "values")
184+
fw_type = str(self.get_component_value(("Flight Controller", "Firmware", "Type")) or "")
185+
self._verify_dict_is_uptodate(doc, get_mot_pwm_type_sub_dict(fw_type), "MOT_PWM_TYPE", "values")
170186
self._verify_dict_is_uptodate(doc, RC_PROTOCOLS_DICT, "RC_PROTOCOLS", "Bitmask")
171187

172188
# Process frame information first (if available)
@@ -264,7 +280,11 @@ def _set_serial_type_from_fc_parameters( # pylint: disable=too-many-branches,to
264280
rc = 1
265281
telem = 1
266282
gnss = 1
283+
# esc counts FC->ESC *control* serial protocols only (FETtecOneWire, Torqeedo, CoDevESC).
284+
# Telemetry-only protocols (ESC Telemetry=16, Scripting=28) do NOT count here because
285+
# FC->ESC is still PWM/DShot/BDShot in those cases and _set_esc_type_from_fc_parameters must run.
267286
esc = 1
287+
esc_telemetry_set = False # True once ESC->FC Telemetry is populated from a serial port
268288
for serial in SERIAL_PORTS:
269289
if serial + "_PROTOCOL" not in fc_parameters:
270290
continue
@@ -306,14 +326,23 @@ def _set_serial_type_from_fc_parameters( # pylint: disable=too-many-branches,to
306326
self.set_component_value(("GNSS Receiver", "FC Connection", "Type"), serial)
307327
gnss += 1
308328
elif component == "ESC":
309-
if esc == 1:
310-
# Only set component values for the first ESC
311-
self.set_component_value(("ESC", "FC->ESC Connection", "Type"), serial)
312-
self.set_component_value(("ESC", "FC->ESC Connection", "Protocol"), protocol)
313-
self.set_component_value(("ESC", "ESC->FC Telemetry", "Type"), serial)
314-
self.set_component_value(("ESC", "ESC->FC Telemetry", "Protocol"), protocol)
315-
# Count all ESC components
316-
esc += 1
329+
if protocol in {"ESC Telemetry", "Scripting"}:
330+
# Serial ESC->FC telemetry only (DShot with UART feedback, or Hobbywing Datalink v2).
331+
# FC->ESC connection is still PWM/DShot; _set_esc_type_from_fc_parameters handles it.
332+
# Do NOT increment esc so that function is still called.
333+
if not esc_telemetry_set:
334+
self.set_component_value(("ESC", "ESC->FC Telemetry", "Type"), serial)
335+
self.set_component_value(("ESC", "ESC->FC Telemetry", "Protocol"), protocol)
336+
esc_telemetry_set = True
337+
else:
338+
# FC->ESC serial control protocol (FETtecOneWire, Torqeedo, CoDevESC):
339+
# the same serial port carries both directions.
340+
if esc == 1:
341+
self.set_component_value(("ESC", "FC->ESC Connection", "Type"), serial)
342+
self.set_component_value(("ESC", "FC->ESC Connection", "Protocol"), protocol)
343+
self.set_component_value(("ESC", "ESC->FC Telemetry", "Type"), serial)
344+
self.set_component_value(("ESC", "ESC->FC Telemetry", "Protocol"), protocol)
345+
esc += 1
317346

318347
return esc >= 2
319348

@@ -326,29 +355,38 @@ def _set_esc_type_from_fc_parameters(self, fc_parameters: dict[str, float], doc:
326355
logging_error(_("Invalid non-integer value for MOT_PWM_TYPE %s"), mot_pwm_type)
327356
mot_pwm_type = 0
328357

329-
main_out_functions = [fc_parameters.get("SERVO" + str(i) + "_FUNCTION", 0) for i in range(1, 9)]
358+
aio_functions = [fc_parameters.get("SERVO" + str(i) + "_FUNCTION", 0) for i in range(9, 15)]
330359

331-
# if any element of main_out_functions is in [33, 34, 35, 36] then ESC is connected to main_out
332-
if any(servo_function in {33, 34, 35, 36} for servo_function in main_out_functions):
333-
self.set_component_value(("ESC", "FC->ESC Connection", "Type"), "Main Out")
334-
else:
360+
# if any element of aio_functions is in [33, 34, 35, 36, 73, 74] then ESC is connected to AIO
361+
if any(servo_function in {33, 34, 35, 36, 73, 74} for servo_function in aio_functions):
335362
self.set_component_value(("ESC", "FC->ESC Connection", "Type"), "AIO")
363+
else:
364+
self.set_component_value(("ESC", "FC->ESC Connection", "Type"), "Main Out")
336365

337366
protocol = ""
338367
if "MOT_PWM_TYPE" in doc and "values" in doc["MOT_PWM_TYPE"]:
339368
protocol = doc["MOT_PWM_TYPE"]["values"].get(str(mot_pwm_type), "")
340369
if protocol:
341370
self.set_component_value(("ESC", "FC->ESC Connection", "Protocol"), protocol)
342371
# Fallback to MOT_PWM_TYPE_DICT if doc is not available
343-
elif str(mot_pwm_type) in MOT_PWM_TYPE_DICT:
344-
protocol = str(MOT_PWM_TYPE_DICT[str(mot_pwm_type)]["protocol"])
345-
self.set_component_value(("ESC", "FC->ESC Connection", "Protocol"), protocol)
372+
else:
373+
mot_pwm_sub = get_mot_pwm_type_sub_dict(
374+
str(self.get_component_value(("Flight Controller", "Firmware", "Type")) or "")
375+
)
376+
if str(mot_pwm_type) in mot_pwm_sub:
377+
protocol = str(mot_pwm_sub[str(mot_pwm_type)]["protocol"])
378+
self.set_component_value(("ESC", "FC->ESC Connection", "Protocol"), protocol)
346379

347-
# Set ESC->FC Telemetry: DShot protocols support BDShot telemetry on the same PWM wire
380+
# Set ESC->FC Telemetry: DShot supports BDShot telemetry on the same PWM wire.
381+
# However, if _set_serial_type_from_fc_parameters already found a dedicated serial telemetry
382+
# port (SERIAL*_PROTOCOL=16 or 28), that takes priority and must not be overwritten.
348383
esc_conn_type = self.get_component_value(("ESC", "FC->ESC Connection", "Type"))
349384
if protocol and protocol.startswith("DShot"):
350-
self.set_component_value(("ESC", "ESC->FC Telemetry", "Type"), esc_conn_type)
351-
self.set_component_value(("ESC", "ESC->FC Telemetry", "Protocol"), "BDShot")
385+
current_telemetry_type = self.get_component_value(("ESC", "ESC->FC Telemetry", "Type"))
386+
if current_telemetry_type not in SERIAL_PORTS:
387+
# No dedicated serial telemetry port detected; fall back to BDShot on the PWM wire
388+
self.set_component_value(("ESC", "ESC->FC Telemetry", "Type"), esc_conn_type)
389+
self.set_component_value(("ESC", "ESC->FC Telemetry", "Protocol"), "BDShot")
352390
else:
353391
self.set_component_value(("ESC", "ESC->FC Telemetry", "Type"), "None")
354392
self.set_component_value(("ESC", "ESC->FC Telemetry", "Protocol"), "None")
@@ -447,6 +485,12 @@ def _import_bat_values_from_fc(self, specs: BatteryVoltageSpecs) -> None:
447485
self.import_bat_voltage(specs, "BATT_LOW_VOLT", "Volt per cell low")
448486
self.import_bat_voltage(specs, "BATT_CRT_VOLT", "Volt per cell crit")
449487
self.import_bat_voltage(specs, "MOT_BAT_VOLT_MIN", "Volt per cell min")
488+
else:
489+
self.set_component_value(("Battery", "Specifications", "Volt per cell max"), "0")
490+
self.set_component_value(("Battery", "Specifications", "Volt per cell arm"), "0")
491+
self.set_component_value(("Battery", "Specifications", "Volt per cell low"), "0")
492+
self.set_component_value(("Battery", "Specifications", "Volt per cell crit"), "0")
493+
self.set_component_value(("Battery", "Specifications", "Volt per cell min"), "0")
450494

451495
def import_bat_voltage(self, specs: BatteryVoltageSpecs, param_name: str, voltage_type: str) -> None:
452496
if param_name in specs.fc_parameters:
@@ -636,7 +680,10 @@ def _set_motor_poles_from_fc_parameters(self, fc_parameters: dict[str, float]) -
636680
poles = 0.0
637681
if "MOT_PWM_TYPE" in fc_parameters:
638682
mot_pwm_type_str = str(int(fc_parameters["MOT_PWM_TYPE"]))
639-
if mot_pwm_type_str in MOT_PWM_TYPE_DICT and MOT_PWM_TYPE_DICT[mot_pwm_type_str].get("is_dshot", False):
683+
mot_pwm_sub = get_mot_pwm_type_sub_dict(
684+
str(self.get_component_value(("Flight Controller", "Firmware", "Type")) or "")
685+
)
686+
if mot_pwm_type_str in mot_pwm_sub and mot_pwm_sub[mot_pwm_type_str].get("is_dshot", False):
640687
if fc_parameters.get("SERVO_BLH_POLES"): # DShot ESCs
641688
poles = fc_parameters["SERVO_BLH_POLES"]
642689
elif fc_parameters.get("SERVO_FTW_MASK") and fc_parameters.get("SERVO_FTW_POLES"):

0 commit comments

Comments
 (0)