Skip to content

Commit 5782bab

Browse files
committed
feat(project-opener): create vehicle project from ArduPilot .bin log file
Add a "Create a vehicle project from a .bin log file" button to the New Vehicle panel in the project-opener window. Orchestration in data_model_vehicle_project.py (create_new_vehicle_from_bin_log): - Scaffolds the project from empty_4.6.x with current FC params used for template substitution - Replaces the template's 00_default.param with the .bin-extracted defaults snapshot (key deviation from normal template-based creation) - Exports remaining current parameters into a numbered *_imported_bin_log_parameters.param file GUI additions: - BinLogSelectionWidgets reusable widget added to frontend_tkinter_directory_selection.py - Widget wired into VehicleProjectOpenerWindow.create_option1_widgets() Address Copilot PR review comments for bin log import feature - Move open_vehicle_directory() to end of create_new_vehicle_from_bin_log() to ensure UI/session operates on authoritative filesystem state after all file modifications (fixes stale state issue) - Add exception handler for VehicleProjectOpenError in BinLogSelectionWidgets UI to display friendly error dialog instead of crashing - Extract template directory name to BIN_IMPORT_TEMPLATE_DIR class constant to reduce duplication and ease future maintenance when template version changes - Add clarifying multi-line comment explaining why infer_comp_specs_and_conn_from_fc_params and use_fc_params flags are reused for log-derived parameters All 100 tests pass, all code quality checks pass.
1 parent a8b0cd7 commit 5782bab

9 files changed

Lines changed: 406 additions & 7 deletions

ARCHITECTURE_3_directory_selection.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,38 @@ The data flow follows the layered architecture pattern with clear separation of
254254
- Project creation delegated to specialized creator services
255255
- Success/failure feedback provided through manager interface
256256

257-
4. **Existing Project Opening Flow**
257+
4. **.bin Log Import Flow**
258+
- User clicks *Create a vehicle project from a .bin log file* in VehicleProjectOpenerWindow (option-1 panel, via `BinLogSelectionWidgets`)
259+
- Frontend opens a file-picker; user selects a `.bin` ArduPilot log file
260+
- Frontend delegates to `project_manager.create_new_vehicle_from_bin_log(bin_file)`
261+
- `VehicleProjectManager` orchestrates the following steps via `VehicleProjectCreator`:
262+
1. `extract_firmware_version_from_bin_log()` — reads the `VER` (or `MSG`) record from the
263+
log to determine vehicle type (e.g. `ArduCopter`) and firmware version (e.g. `4.6.3`);
264+
`pymavlink` is imported lazily at call time so it does not slow application startup
265+
2. `template_dir_for_bin_import()` — resolves and validates the matching template directory
266+
(e.g. `ArduCopter/empty_4.6.x`); raises a user-friendly error if none is installed
267+
3. `create_new_vehicle_from_template()` — copies the template with `fc_connected=False`
268+
so no live FC values are injected
269+
4. `extract_param_files_from_bin_log()` — extracts default and current parameter values
270+
from the log's `PARM` messages; also imported lazily
271+
5. `LocalFilesystem.fw_version` is set to `"major.minor.patch"` before `re_init()` is
272+
called, preventing the template's placeholder version from being used
273+
6. `re_init()` — points the filesystem at the new vehicle directory
274+
7. `set_fc_fw_version_and_type_in_components_json()` — persists the detected firmware
275+
version and type into `vehicle_components.json`
276+
8. `write_param_default_values_to_file()` — overwrites `00_default.param` with the
277+
log-extracted defaults
278+
9. Parameters present in the log but absent/different in the template files are exported
279+
to `xx_imported_bin_log_parameters.param`; `re_init()` is called again to pick up
280+
the new file
281+
10. Manager state (`_settings`, `configuration_template`, recent-dir history) is updated
282+
only after `open_vehicle_directory()` succeeds, ensuring manager metadata
283+
is committed only on success
284+
- Success/failure feedback provided through manager interface; on failure the new directory
285+
is not registered in session history and manager in-memory state is not updated,
286+
though filesystem changes to the new project directory are not rolled back
287+
288+
5. **Existing Project Opening Flow**
258289
- User interacts with VehicleProjectOpenerWindow
259290
- Frontend presents project opening and re-opening options
260291
- User selects existing directory through DirectorySelectionWidgets with callbacks
@@ -263,14 +294,14 @@ The data flow follows the layered architecture pattern with clear separation of
263294
- Project state is reconstructed and validated
264295
- Error handling managed through consistent interface
265296

266-
5. **Architecture Benefits**
297+
6. **Architecture Benefits**
267298
- Frontend never directly accesses backend services
268299
- All business logic centralized in VehicleProjectManager
269300
- Easy to test with mock VehicleProjectManager
270301
- Changes to backend services don't affect frontend code
271302
- Clean separation between project opening and project creation concerns
272303

273-
6. **Recent Directories History Flow**
304+
7. **Recent Directories History Flow**
274305
- On application startup, `ProgramSettings.get_recent_vehicle_dirs()` loads history from settings.json; the history is passed through the manager
275306
- History is passed to VehicleProjectOpenerWindow to populate the combobox widget
276307
- User selects a directory from the combobox dropdown

USECASES.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Hence the two main use cases are:
1212

1313
But there are other use cases as well:
1414

15+
- [Create a vehicle project from a .bin log file](#create-a-vehicle-project-from-a-bin-log-file)
1516
- [Create a vehicle configuration based on a correctly configured vehicle](#create-a-vehicle-configuration-based-on-a-correctly-configured-vehicle)
1617
- [Review and or edit configuration files without having the vehicle FC](#review-and-or-edit-configuration-files-without-having-the-vehicle-fc)
1718
- [Use the correct default values](#use-the-correct-default-values)
@@ -78,6 +79,42 @@ Please scroll down and make sure you do not miss a property.
7879

7980
If something is not clear, read the [ArduPilot Methodic Configurator user manual](USERMANUAL.md)
8081

82+
## Create a vehicle project from a .bin log file
83+
84+
Use this workflow when you have an ArduPilot `.bin` flight-log file recorded by a vehicle that was already
85+
running a valid configuration and you want to reconstruct a methodic-configurator project from it —
86+
no physical flight controller required.
87+
88+
The software reads the `.bin` file to automatically determine:
89+
90+
- **Vehicle type** (e.g. ArduCopter, ArduPlane, Rover) from the log's `VER` or `MSG` record
91+
- **Firmware version** (major.minor.patch) from the same record — used to select the right
92+
parameter-documentation metadata and to populate `vehicle_components.json`
93+
- **Default parameter values** — the per-build defaults stored in the log (`PARM` messages with `Default` attribute)
94+
- **Current parameter values** — the values that were actually active when the log was recorded
95+
96+
1. Open the *ArduPilot Methodic Configurator* software.
97+
1. Select `Skip FC connection, just edit .param files on disk` button.
98+
![AMC no connection](images/App_screenshot_FC_connection_no_connection.png)
99+
1. Click the **Create a vehicle project from a .bin log file** button.
100+
1. In the file-picker that opens, select your `.bin` log file.
101+
- The software automatically detects the vehicle type and firmware version.
102+
- A matching template directory (e.g. `ArduCopter/empty_4.6.x`) is selected automatically.
103+
- The project is named after the log file (without the `.bin` extension) and created in the
104+
default vehicles directory.
105+
- `00_default.param` is populated with the default values extracted from the log.
106+
- A `xx_imported_bin_log_parameters.param` file is created for any current values that differ
107+
from the template's parameter files — giving you a clear delta to review and tune.
108+
- `vehicle_components.json` is updated with the detected firmware type and version.
109+
1. Review and edit the vehicle components in the *Vehicle Component Editor* window.
110+
The firmware type and version fields will already be pre-filled from the log.
111+
1. Press *Save data and start configuration*.
112+
1. You should now see the *Parameter file editor and uploader* window.
113+
![AMC parameter file editor and uploader](images/App_screenshot2.png)
114+
1. Follow the procedure to [configure the vehicle parameters](USERMANUAL.md#step-4-parameter-file-editor-and-uploader-interface).
115+
116+
If something is not clear, read the [ArduPilot Methodic Configurator user manual](USERMANUAL.md)
117+
81118
## Create a vehicle configuration based on a correctly configured vehicle
82119

83120
1. Connect the flight controller to the computer using a USB cable.

USERMANUAL.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ It provides three main options for selecting a vehicle directory:
167167

168168
#### New
169169

170-
Create a new vehicle configuration directory
170+
Create a new vehicle configuration directory, either from a template or from a `.bin` log file.
171171

172172
#### Open
173173

@@ -201,6 +201,23 @@ It's useful for setting up a new vehicle configuration quickly.
201201
- Enter the name for the new vehicle directory in the "Destination new vehicle name" field.
202202
- Click the "Create vehicle directory from template" button to create the new vehicle directory on the base directory and copy the template files to it.
203203

204+
### Create a New Vehicle Configuration Directory from a .bin Log File
205+
206+
If you have an ArduPilot `.bin` log file recorded by a correctly running vehicle, the software can
207+
build a complete project from it — no physical flight controller connection required.
208+
209+
- Click the **Create a vehicle project from a .bin log file** button in the **New** panel.
210+
- In the file-picker, select your `.bin` log file.
211+
- The software automatically:
212+
- Reads the vehicle type and firmware version from the log's `VER` (or `MSG`) record.
213+
- Selects the matching template directory (e.g. `ArduCopter/empty_4.6.x`).
214+
- Names the project after the log file (without the `.bin` extension).
215+
- Writes the default parameter values extracted from the log into `00_default.param`.
216+
- Creates `xx_imported_bin_log_parameters.param` for any current values that differ from the
217+
template's parameter files, giving you a clear delta to review.
218+
- Sets the firmware type and version in `vehicle_components.json`.
219+
- You are taken directly to the Vehicle Component Editor with the firmware fields pre-filled.
220+
204221
### Step 3: Vehicle Component Editor Interface
205222

206223
Here you specify the components of your vehicle, their properties and how they are connected to the flight controller.

ardupilot_methodic_configurator/backend_filesystem_vehicle_components.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
from json import dump as json_dump
1414
from json import load as json_load
1515

16-
# from logging import warning as logging_warning
1716
# from sys import exit as sys_exit
1817
from logging import debug as logging_debug
1918
from logging import error as logging_error
2019
from logging import info as logging_info
20+
from logging import warning as logging_warning
2121
from os import makedirs as os_makedirs
2222
from os import path as os_path
2323
from os import walk as os_walk
@@ -338,6 +338,28 @@ def get_fc_fw_version_from_vehicle_components_json(self) -> str:
338338
logging_error(error_msg.format(version_str=version_str, filename=self.vehicle_components_fs.json_filename))
339339
return ""
340340

341+
def set_fc_fw_version_and_type_in_components_json(self, fw_version: str, vehicle_type: str, vehicle_dir: str) -> None:
342+
"""
343+
Update the firmware version and type in the loaded vehicle_components.json and save it to disk.
344+
345+
This ensures the project's component metadata reflects the actual firmware that was
346+
running when the .bin log was recorded, rather than the template's placeholder values.
347+
348+
Args:
349+
fw_version: Full firmware version string in "major.minor.patch" format (e.g. "4.6.3").
350+
vehicle_type: Firmware type string (e.g. "ArduCopter").
351+
vehicle_dir: Path to the vehicle directory containing vehicle_components.json.
352+
353+
"""
354+
data = self.vehicle_components_fs.data
355+
if data and "Components" in data:
356+
fc_firmware = data["Components"].setdefault("Flight Controller", {}).setdefault("Firmware", {})
357+
fc_firmware["Version"] = fw_version
358+
fc_firmware["Type"] = vehicle_type
359+
error_occurred, msg = self.save_vehicle_components_json_data(data, vehicle_dir)
360+
if error_occurred:
361+
logging_warning("%s", msg)
362+
341363
@staticmethod
342364
def supported_vehicles() -> tuple[str, ...]:
343365
return ("AP_Periph", "AntennaTracker", "ArduCopter", "ArduPlane", "ArduSub", "Blimp", "Heli", "Rover", "SITL")

ardupilot_methodic_configurator/data_model_vehicle_project.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from ardupilot_methodic_configurator import _
1818
from ardupilot_methodic_configurator.backend_filesystem import LocalFilesystem
19+
from ardupilot_methodic_configurator.data_model_par_dict import is_within_tolerance
1920
from ardupilot_methodic_configurator.data_model_vehicle_project_creator import NewVehicleProjectSettings, VehicleProjectCreator
2021
from ardupilot_methodic_configurator.data_model_vehicle_project_opener import VehicleProjectOpener
2122

@@ -156,6 +157,92 @@ def create_new_vehicle_from_template(
156157
self.open_vehicle_directory(new_path)
157158
return new_path
158159

160+
def create_new_vehicle_from_bin_log(self, bin_file: str) -> str:
161+
"""
162+
Create a new vehicle configuration directory from an ArduPilot .bin log file.
163+
164+
The vehicle type and firmware version are extracted from the .bin file itself.
165+
The project is based on the matching empty_{major}.{minor}.x template, uses the
166+
extracted current parameter values for template substitution, replaces the
167+
template's 00_default.param with the extracted defaults snapshot, and exports any
168+
remaining parameters missing from the AMC files into a final import file.
169+
170+
Args:
171+
bin_file: Path to the ArduPilot .bin log file
172+
173+
Returns:
174+
The created vehicle directory path
175+
176+
Raises:
177+
VehicleProjectCreationError: If creation, extraction, or template lookup fails
178+
179+
"""
180+
firmware_info = self._creator.extract_firmware_version_from_bin_log(bin_file)
181+
vehicle_type = firmware_info[0]
182+
fw_version = f"{firmware_info[1]}.{firmware_info[2]}.{firmware_info[3]}"
183+
template_dir = self._creator.template_dir_for_bin_import(vehicle_type, firmware_info[1], firmware_info[2])
184+
default_params, current_params = self._creator.extract_param_files_from_bin_log(bin_file)
185+
fc_parameters = {name: param.value for name, param in current_params.items()}
186+
settings = NewVehicleProjectSettings(
187+
blank_change_reason=True,
188+
infer_comp_specs_and_conn_from_fc_params=True,
189+
use_fc_params=True,
190+
)
191+
192+
new_path = self._creator.create_new_vehicle_from_template(
193+
template_dir,
194+
str(LocalFilesystem.get_vehicles_default_dir()),
195+
self._creator.vehicle_name_from_bin_log(bin_file),
196+
settings,
197+
fc_connected=False,
198+
fc_parameters=fc_parameters,
199+
)
200+
201+
# Point the filesystem at the new vehicle directory before any reads or writes.
202+
# write_param_default_values_to_file() and compound_params() rely on vehicle_dir,
203+
# so re_init() must be called here to avoid accidentally operating on the previously-open project.
204+
# Set fw_version first so re_init() does not override it with the template's placeholder version.
205+
self._local_filesystem.fw_version = fw_version
206+
self._local_filesystem.re_init(new_path, vehicle_type)
207+
# Persist the correct firmware version and type into vehicle_components.json so subsequent
208+
# re_init calls (and the user when they inspect the project) see the actual recorded firmware.
209+
self._local_filesystem.set_fc_fw_version_and_type_in_components_json(fw_version, vehicle_type, new_path)
210+
211+
self._local_filesystem.write_param_default_values_to_file(default_params)
212+
213+
# Build the baseline from log-extracted defaults plus compounded AMC step files.
214+
# This avoids exporting params that merely match 00_default.param but are absent
215+
# from the numbered step files.
216+
compounded_step_params, _first_config_step = self._local_filesystem.compound_params(skip_default=True)
217+
baseline_params = default_params.deep_copy()
218+
baseline_params.update(compounded_step_params)
219+
220+
if imported_params := current_params.get_missing_or_different(baseline_params, is_within_tolerance):
221+
self._local_filesystem.export_to_param(
222+
imported_params,
223+
self._creator.next_import_filename(new_path),
224+
annotate_doc=False,
225+
)
226+
self._local_filesystem.re_init(new_path, vehicle_type)
227+
228+
if self._flight_controller is not None:
229+
self._flight_controller.fc_parameters = fc_parameters
230+
231+
# Open the vehicle directory only after all file modifications are complete and filesystem state is synced.
232+
# This ensures the UI/session operates on the authoritative filesystem state, not stale in-memory cache.
233+
# Also note: infer_comp_specs_and_conn_from_fc_params and use_fc_params are reused for log-derived params
234+
# because the semantics align: we're supplying external parameter values for template substitution.
235+
self.open_vehicle_directory(new_path)
236+
237+
# Update manager state only after the whole import succeeds so that a failure in any preceding step
238+
# (including open_vehicle_directory) does not leave the manager in a partially-updated state and
239+
# does not pollute the recently-used template history with an incomplete project.
240+
self._settings = settings
241+
self.configuration_template = self.get_directory_name_from_path(template_dir)
242+
self.store_recently_used_template_dirs(template_dir, str(LocalFilesystem.get_vehicles_default_dir()))
243+
244+
return new_path
245+
159246
# Vehicle project opening operations
160247
def open_vehicle_directory(self, vehicle_dir: str) -> str:
161248
"""

0 commit comments

Comments
 (0)