diff --git a/ardupilot_methodic_configurator/data_model_parameter_editor.py b/ardupilot_methodic_configurator/data_model_parameter_editor.py index 3df81d14a..ae1cda858 100644 --- a/ardupilot_methodic_configurator/data_model_parameter_editor.py +++ b/ardupilot_methodic_configurator/data_model_parameter_editor.py @@ -1077,7 +1077,7 @@ def _validate_uploaded_parameters(self, selected_params: dict) -> list[str]: self._flight_controller.fc_parameters[param_name], ) param_upload_error.append(param_name) - if param_name not in self._flight_controller.fc_parameters: + if param_name not in self._flight_controller.fc_parameters and param is not None: logging_error( _("Parameter %s upload to the flight controller failed. Expected: %f, Actual: N/A"), param_name, diff --git a/ardupilot_methodic_configurator/data_model_vehicle_components_base.py b/ardupilot_methodic_configurator/data_model_vehicle_components_base.py index 04af2f4ea..f4b739008 100644 --- a/ardupilot_methodic_configurator/data_model_vehicle_components_base.py +++ b/ardupilot_methodic_configurator/data_model_vehicle_components_base.py @@ -262,6 +262,7 @@ def update_json_structure( "Specifications": { "TOW min Kg": 1, "TOW max Kg": 1, + "Frame class": "Undefined", } }, "Flight Controller": { diff --git a/ardupilot_methodic_configurator/data_model_vehicle_components_import.py b/ardupilot_methodic_configurator/data_model_vehicle_components_import.py index a40753ccc..e0e20c32c 100644 --- a/ardupilot_methodic_configurator/data_model_vehicle_components_import.py +++ b/ardupilot_methodic_configurator/data_model_vehicle_components_import.py @@ -17,6 +17,7 @@ from typing import Any, Optional from ardupilot_methodic_configurator import _ +from ardupilot_methodic_configurator.backend_flightcontroller_business_logic import get_frame_info from ardupilot_methodic_configurator.battery_cell_voltages import ( BATTERY_CELL_VOLTAGE_TYPES, BATTERY_DEFAULT_CHEMISTRY, @@ -36,6 +37,7 @@ SERVO_FUNCTION_ESC_CONTROL, ComponentDataModelValidation, get_esc_connection_sub_dict, + get_frame_class_sub_dict, ) @@ -183,6 +185,14 @@ def process_fc_parameters( self._verify_dict_is_uptodate(doc, get_esc_connection_sub_dict(fw_type), "MOT_PWM_TYPE", "values") self._verify_dict_is_uptodate(doc, RC_PROTOCOLS_DICT, "RC_PROTOCOLS", "Bitmask") + # Process frame information if FRAME_CLASS is present in FC parameters + if "FRAME_CLASS" in fc_parameters: + frame_class, _ = get_frame_info(fc_parameters) + self.set_component_value( + ("Frame", "Specifications", "Frame class"), + get_frame_class_sub_dict(fw_type).get(frame_class, "Undefined"), + ) + # Process parameters in sequence self._set_gnss_type_from_fc_parameters(fc_parameters) esc_is_serial = self._set_serial_type_from_fc_parameters(fc_parameters) diff --git a/ardupilot_methodic_configurator/data_model_vehicle_components_validation.py b/ardupilot_methodic_configurator/data_model_vehicle_components_validation.py index 6dc761810..2da8a591f 100644 --- a/ardupilot_methodic_configurator/data_model_vehicle_components_validation.py +++ b/ardupilot_methodic_configurator/data_model_vehicle_components_validation.py @@ -316,6 +316,46 @@ def get_esc_connection_sub_dict( "65536": {"type": RC_PORTS + SERIAL_PORTS, "protocol": "MAVRadio"}, # Bit 16 } +FRAME_CLASS_DICT: dict[str, dict[int, str]] = { + "ArduCopter": { + 0: "Undefined", + 1: "Quad", + 2: "Hexa", + 3: "Octa", + 4: "OctaQuad", + 5: "Y6", + 6: "Heli", + 7: "Tri", + 8: "SingleCopter", + 9: "CoaxCopter", + 10: "BiCopter", + 11: "Heli_Dual", + 12: "DodecaHexa", + 13: "HeliQuad", + 14: "Deca", + 15: "Scripting Matrix", + 16: "6DoF Scripting", + 17: "Dynamic Scripting Matrix", + }, + "Heli": { + 6: "Heli", + 11: "Heli_Dual", + 13: "HeliQuad", + }, + "Rover": { + 0: "Undefined", + 1: "Rover", + 2: "Boat", + 3: "BalanceBot", + }, + "ArduPlane": {}, +} + + +def get_frame_class_sub_dict(vehicle_type: str) -> dict[int, str]: + """Return the vehicle-type-specific frame class mapping from FRAME_CLASS_DICT.""" + return FRAME_CLASS_DICT.get(vehicle_type, FRAME_CLASS_DICT["ArduCopter"]) + class ComponentDataModelValidation(ComponentDataModelBase): """ diff --git a/ardupilot_methodic_configurator/vehicle_components.py b/ardupilot_methodic_configurator/vehicle_components.py index 5eeed3398..7869c8a20 100644 --- a/ardupilot_methodic_configurator/vehicle_components.py +++ b/ardupilot_methodic_configurator/vehicle_components.py @@ -32,6 +32,7 @@ def translatable_strings() -> None: _vehicle_components_strings = _("Flight Controller") _vehicle_components_strings = _("Format version") _vehicle_components_strings = _("Frame") + _vehicle_components_strings = _("Frame class") _vehicle_components_strings = _("GNSS Receiver") _vehicle_components_strings = _("MCU Series") _vehicle_components_strings = _("Manufacturer") @@ -90,6 +91,7 @@ def translatable_descriptions() -> None: # noqa: PLR0915 # pylint: disable=too- _vehicle_components_descriptions = _("Flight controller component that runs the ArduPilot firmware") _vehicle_components_descriptions = _("Flight controller firmware information") _vehicle_components_descriptions = _("Flight controller that runs ArduPilot firmware") + _vehicle_components_descriptions = _("Frame class/category (e.g., Quad, Hexa, Octa, etc.)") _vehicle_components_descriptions = _("Global Navigation Satellite System receiver for positioning") _vehicle_components_descriptions = _("How this component connects to the flight controller") _vehicle_components_descriptions = _("Information about the firmware running on a component") diff --git a/ardupilot_methodic_configurator/vehicle_components_schema.json b/ardupilot_methodic_configurator/vehicle_components_schema.json index 30530b023..97a18bd25 100644 --- a/ardupilot_methodic_configurator/vehicle_components_schema.json +++ b/ardupilot_methodic_configurator/vehicle_components_schema.json @@ -199,6 +199,10 @@ "TOW max Kg": { "type": "number", "description": "Maximum take-off weight in kilograms" + }, + "Frame class": { + "type": "string", + "description": "Frame class/category (e.g., Quad, Hexa, Octa, etc.)" } }, "description": "Technical specifications of the vehicle frame" diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/AirCar_v1/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/AirCar_v1/vehicle_components.json index 2a160542f..2cb3cf918 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/AirCar_v1/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/AirCar_v1/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 320, - "TOW max Kg": 500 + "TOW max Kg": 500, + "Frame class": "OctaQuad" }, "Notes": "Frame is custom, X type frame. Arm length is 2650mm." }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Big_Owl/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Big_Owl/vehicle_components.json index f5af7a112..3f6ed41da 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Big_Owl/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Big_Owl/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 7.0, - "TOW max Kg": 12.0 + "TOW max Kg": 12.0, + "Frame class": "Quad" }, "Notes": "" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Chimera7/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Chimera7/vehicle_components.json index 7a6fd7ea4..b73ecf472 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Chimera7/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Chimera7/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.9, - "TOW max Kg": 0.9 + "TOW max Kg": 0.9, + "Frame class": "Quad" }, "Notes": "" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Demo32Motor_PeterHall/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Demo32Motor_PeterHall/vehicle_components.json index 42a744ce5..72a64044f 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Demo32Motor_PeterHall/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Demo32Motor_PeterHall/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.1, - "TOW max Kg": 0.1 + "TOW max Kg": 0.1, + "Frame class": "Scripting Matrix" }, "Notes": "" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/FETtec-5/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/FETtec-5/vehicle_components.json index b514c0afb..807e99372 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/FETtec-5/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/FETtec-5/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 1.3, - "TOW max Kg": 2.0 + "TOW max Kg": 2.0, + "Frame class": "Quad" }, "Notes": "5\" Props" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/GazeboIrisWithTargetFollow/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/GazeboIrisWithTargetFollow/vehicle_components.json index dcfeb835f..a028c8126 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/GazeboIrisWithTargetFollow/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/GazeboIrisWithTargetFollow/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.6, - "TOW max Kg": 0.6 + "TOW max Kg": 0.6, + "Frame class": "Quad" }, "Notes": "TOW in this case a taken ideal, and not calculated including gimbal." }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500/vehicle_components.json index 2580b9e1c..4ae789c69 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 1.09, - "TOW max Kg": 2.11 + "TOW max Kg": 2.11, + "Frame class": "Quad" }, "Notes": "Frame is X500 type. Look on holybro site for more specifications" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500_V2/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500_V2/vehicle_components.json index 4f336b0a1..beca2abc4 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500_V2/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X500_V2/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 1.09, - "TOW max Kg": 2.11 + "TOW max Kg": 2.11, + "Frame class": "Quad" }, "Notes": "Frame is X500 V2 type. Look on holybro site for more specifications" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X650_LTE/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X650_LTE/vehicle_components.json index de0c92163..6000bf1e1 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X650_LTE/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Holybro_X650_LTE/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 5.6, - "TOW max Kg": 7.6 + "TOW max Kg": 7.6, + "Frame class": "Quad" }, "Notes": "Frame is X650 V2 type. Look on holybro site for more specifications" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X11+/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X11+/vehicle_components.json index 0622f4661..79795c55c 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X11+/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X11+/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 35, - "TOW max Kg": 50 + "TOW max Kg": 50, + "Frame class": "Quad" }, "Notes": "customized drone" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X13/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X13/vehicle_components.json index a6574c249..68ec81f15 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X13/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Hoverit_X13/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 56, - "TOW max Kg": 90 + "TOW max Kg": 90, + "Frame class": "Quad" }, "Notes": "z50 eft frame" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Marmotte5v2/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Marmotte5v2/vehicle_components.json index 1b469a159..8be09c7d9 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Marmotte5v2/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Marmotte5v2/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.7, - "TOW max Kg": 0.7 + "TOW max Kg": 0.7, + "Frame class": "Quad" }, "Notes": "" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/ReadyToSkyZD550/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/ReadyToSkyZD550/vehicle_components.json index 704dfd8fb..e1b776135 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/ReadyToSkyZD550/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/ReadyToSkyZD550/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 2.884, - "TOW max Kg": 2.884 + "TOW max Kg": 2.884, + "Frame class": "Quad" }, "Notes": "10-14\" Props" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/vehicle_components.json index e3dd3c79c..eb012573b 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/TarotFY680Hexacopter/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 1.6, - "TOW max Kg": 5.39 + "TOW max Kg": 5.39, + "Frame class": "Hexa" }, "Notes": "With or without 1 Gallon of water (for spraying my small garden/vineyard)" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Tarot_X4/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Tarot_X4/vehicle_components.json index 5d5b2e37b..115c149f7 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Tarot_X4/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/Tarot_X4/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 5, - "TOW max Kg": 7 + "TOW max Kg": 7, + "Frame class": "Quad" }, "Notes": "A frame with 960mm diameter (motor to notor) using T-Motor MN7005(7206) propulsion sets for 25mm tube arms" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/X11_plus/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/X11_plus/vehicle_components.json index 0d95b7c32..c32fb431b 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/X11_plus/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/X11_plus/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 20, - "TOW max Kg": 50 + "TOW max Kg": 50, + "Frame class": "Quad" }, "Notes": "Frame is custom, X type frame, the middle is welded. Arm length is 1950mm." }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.3.8-params/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.3.8-params/vehicle_components.json index 2d80f534e..569f31d6b 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.3.8-params/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.3.8-params/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.6, - "TOW max Kg": 0.6 + "TOW max Kg": 0.6, + "Frame class": "Quad" }, "Notes": "A small 3'' ducted frame" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.4.4-params/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.4.4-params/vehicle_components.json index 8d49a22f9..4485a3728 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.4.4-params/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.4.4-params/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.6, - "TOW max Kg": 0.6 + "TOW max Kg": 0.6, + "Frame class": "Quad" }, "Notes": "A small 3'' ducted frame" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.5.x-params/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.5.x-params/vehicle_components.json index b2e6c99db..f247d1689 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.5.x-params/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.5.x-params/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.6, - "TOW max Kg": 0.6 + "TOW max Kg": 0.6, + "Frame class": "Quad" }, "Notes": "A small 3'' ducted frame" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.6.x-params/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.6.x-params/vehicle_components.json index 461047877..e3e72ba79 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.6.x-params/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/diatone_taycan_mxc/4.6.x-params/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.6, - "TOW max Kg": 0.6 + "TOW max Kg": 0.6, + "Frame class": "Quad" }, "Notes": "A small 3'' ducted frame" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.5.x/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.5.x/vehicle_components.json index 09ddc92ef..2e8310d61 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.5.x/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.5.x/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.1, - "TOW max Kg": 0.1 + "TOW max Kg": 0.1, + "Frame class": "Undefined" }, "Notes": "" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.6.x/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.6.x/vehicle_components.json index 5371f8626..5053626a5 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.6.x/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduCopter/empty_4.6.x/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.1, - "TOW max Kg": 0.1 + "TOW max Kg": 0.1, + "Frame class": "Undefined" }, "Notes": "" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/ArduPlane/normal_plane/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/ArduPlane/normal_plane/vehicle_components.json index cf29a4c50..ff6960162 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/ArduPlane/normal_plane/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/ArduPlane/normal_plane/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 5, - "TOW max Kg": 7 + "TOW max Kg": 7, + "Frame class": "Undefined" }, "Notes": "" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/Heli/OMP_M4/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/Heli/OMP_M4/vehicle_components.json index f4167ce96..aeef3c672 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/Heli/OMP_M4/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/Heli/OMP_M4/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 1, - "TOW max Kg": 1 + "TOW max Kg": 1, + "Frame class": "Heli" }, "Notes": "This was done with the Basic M4 Combo kit, not the max" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/Rover/AION_R1/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/Rover/AION_R1/vehicle_components.json index c97382b1b..028142d4c 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/Rover/AION_R1/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/Rover/AION_R1/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.6, - "TOW max Kg": 0.6 + "TOW max Kg": 0.6, + "Frame class": "Rover" }, "Notes": "" }, diff --git a/ardupilot_methodic_configurator/vehicle_templates/Rover/Carisma_SCA-1E/vehicle_components.json b/ardupilot_methodic_configurator/vehicle_templates/Rover/Carisma_SCA-1E/vehicle_components.json index 770cc21f5..949a35cd7 100644 --- a/ardupilot_methodic_configurator/vehicle_templates/Rover/Carisma_SCA-1E/vehicle_components.json +++ b/ardupilot_methodic_configurator/vehicle_templates/Rover/Carisma_SCA-1E/vehicle_components.json @@ -26,7 +26,8 @@ }, "Specifications": { "TOW min Kg": 0.5, - "TOW max Kg": 0.5 + "TOW max Kg": 0.5, + "Frame class": "Rover" }, "Notes": "" }, diff --git a/tests/test_data_model_vehicle_components_import.py b/tests/test_data_model_vehicle_components_import.py index 17c300df2..4c7208fb2 100755 --- a/tests/test_data_model_vehicle_components_import.py +++ b/tests/test_data_model_vehicle_components_import.py @@ -309,6 +309,17 @@ def test_system_imports_esc_aio_configuration(self, realistic_model) -> None: assert esc_type == "AIO" assert esc_protocol == "DShot600" + def test_user_can_import_frame_class_from_fc(self, realistic_model) -> None: + """Given FRAME_CLASS from FC, set Frame.Specifications.Class.""" + fc_parameters = {"FRAME_CLASS": 3, "MOT_PWM_TYPE": 6, "SERVO1_FUNCTION": 0} + doc = {"MOT_PWM_TYPE": {"values": {"6": "DShot600"}}} + + with patch.object(realistic_model, "_verify_dict_is_uptodate", return_value=True): + realistic_model.process_fc_parameters(fc_parameters, doc) + + frame_class = realistic_model.get_component_value(("Frame", "Specifications", "Frame class")) + assert frame_class == "Octa" + def test_user_can_import_esc_connection_and_telemetry_from_serial_fc(self, realistic_model) -> None: """ Import ESC serial config into FC->ESC Connection and ESC->FC Telemetry. diff --git a/tests/unit_data_model_vehicle_components_validation_constants.py b/tests/unit_data_model_vehicle_components_validation_constants.py index 321ce1909..4bd640640 100755 --- a/tests/unit_data_model_vehicle_components_validation_constants.py +++ b/tests/unit_data_model_vehicle_components_validation_constants.py @@ -21,6 +21,7 @@ ESC_CONNECTION_DICT, ESC_SERIAL_SAME_PORT_PROTOCOLS, FC_CONNECTION_TYPE_PATHS, + FRAME_CLASS_DICT, GNSS_RECEIVER_CONNECTION, I2C_PORTS, OTHER_PORTS, @@ -33,6 +34,7 @@ SERIAL_PROTOCOLS_DICT, SPI_PORTS, get_connection_type_tuples_with_labels, + get_frame_class_sub_dict, ) @@ -556,3 +558,83 @@ def test_esc_serial_same_port_protocols_contents(self) -> None: assert protocol in vehicle_protocols, ( f"Protocol '{protocol}' from ESC_SERIAL_SAME_PORT_PROTOCOLS not found in ESC_CONNECTION_DICT['{vtype}']" ) + + +class TestFrameClassDict: + """Tests for FRAME_CLASS_DICT and get_frame_class_sub_dict.""" + + def test_frame_class_dict_structure(self) -> None: + """FRAME_CLASS_DICT is keyed by vehicle type with int->str sub-dicts.""" + assert isinstance(FRAME_CLASS_DICT, dict) + assert len(FRAME_CLASS_DICT) > 0 + + for vtype, sub_dict in FRAME_CLASS_DICT.items(): + assert isinstance(vtype, str), f"Vehicle type key '{vtype}' is not a string" + assert isinstance(sub_dict, dict), f"Sub-dict for '{vtype}' is not a dict" + for key, value in sub_dict.items(): + assert isinstance(key, int), f"Frame class key '{key}' in '{vtype}' is not an int" + assert isinstance(value, str), f"Frame class value '{value}' in '{vtype}' is not a string" + assert value.strip(), f"Frame class name for key {key} in '{vtype}' is empty" + + def test_frame_class_dict_contains_required_vehicle_types(self) -> None: + """FRAME_CLASS_DICT contains entries for all expected vehicle types.""" + for required in ("ArduCopter", "Heli", "Rover", "ArduPlane"): + assert required in FRAME_CLASS_DICT, f"Missing vehicle type '{required}' in FRAME_CLASS_DICT" + + def test_arducopter_frame_class_values(self) -> None: + """ArduCopter sub-dict contains the standard multirotor frame classes.""" + sub = FRAME_CLASS_DICT["ArduCopter"] + assert sub[1] == "Quad" + assert sub[2] == "Hexa" + assert sub[3] == "Octa" + assert sub[6] == "Heli" + assert sub[11] == "Heli_Dual" + assert sub[13] == "HeliQuad" + + def test_heli_frame_class_values(self) -> None: + """Heli sub-dict contains only helicopter-relevant frame classes.""" + sub = FRAME_CLASS_DICT["Heli"] + assert sub[6] == "Heli" + assert sub[11] == "Heli_Dual" + assert sub[13] == "HeliQuad" + # Non-heli classes must not appear + assert 1 not in sub, "Quad should not be in Heli FRAME_CLASS_DICT" + assert 2 not in sub, "Hexa should not be in Heli FRAME_CLASS_DICT" + + def test_rover_frame_class_values(self) -> None: + """Rover sub-dict uses Rover-specific frame class values.""" + sub = FRAME_CLASS_DICT["Rover"] + assert sub[1] == "Rover" + assert sub[2] == "Boat" + assert sub[3] == "BalanceBot" + # Multirotor classes must not appear in Rover + assert 4 not in sub, "OctaQuad should not be in Rover FRAME_CLASS_DICT" + + def test_arduplane_frame_class_is_empty(self) -> None: + """ArduPlane has no FRAME_CLASS parameter, so its sub-dict must be empty.""" + assert not FRAME_CLASS_DICT["ArduPlane"] + + def test_get_frame_class_sub_dict_known_vehicle_types(self) -> None: + """get_frame_class_sub_dict returns the correct sub-dict for known vehicle types.""" + assert get_frame_class_sub_dict("ArduCopter") is FRAME_CLASS_DICT["ArduCopter"] + assert get_frame_class_sub_dict("Heli") is FRAME_CLASS_DICT["Heli"] + assert get_frame_class_sub_dict("Rover") is FRAME_CLASS_DICT["Rover"] + assert get_frame_class_sub_dict("ArduPlane") is FRAME_CLASS_DICT["ArduPlane"] + + def test_get_frame_class_sub_dict_unknown_type_falls_back_to_arducopter(self) -> None: + """get_frame_class_sub_dict falls back to ArduCopter for unknown vehicle types.""" + result = get_frame_class_sub_dict("UnknownVehicle") + assert result is FRAME_CLASS_DICT["ArduCopter"] + + def test_get_frame_class_sub_dict_empty_string_falls_back_to_arducopter(self) -> None: + """get_frame_class_sub_dict falls back to ArduCopter when fw_type is empty.""" + result = get_frame_class_sub_dict("") + assert result is FRAME_CLASS_DICT["ArduCopter"] + + def test_frame_class_values_are_unique_per_vehicle_type(self) -> None: + """Within each vehicle type, frame class names must be unique.""" + for vtype, sub_dict in FRAME_CLASS_DICT.items(): + if not sub_dict: + continue + names = list(sub_dict.values()) + assert len(names) == len(set(names)), f"Duplicate frame class names found in '{vtype}'"