mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-04-14 10:07:39 +08:00
feat(tools): ship metadata, IO and BL files on SD card
This pushes the metadata files as well as the IO firmware and the bootloader binary to the SD card. That way, the files are don't have to be added to the firmware binary or served via s3 for FLASH_CONSTRAINED targets. The files are pushed after upload and verified in commander as part of the preflight checks. If missing, a warning is displayed and arming is prevented. This means that an SD card can't be swapped out without reflashing (or copying over the contents).
This commit is contained in:
parent
5dba9990b4
commit
81e4e33811
@ -136,6 +136,14 @@ add_custom_command(
|
||||
)
|
||||
|
||||
|
||||
# Compute IO firmware path for filepaths generator
|
||||
set(iofw_path_arg)
|
||||
if(CONFIG_BOARD_IO)
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
set(iofw_path_arg --iofw-path ${CONFIG_BOARD_ROOT_PATH}/px4/extras/${CONFIG_BOARD_IO}.bin)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(romfs_copy_stamp ${CMAKE_CURRENT_BINARY_DIR}/romfs_copy.stamp)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
@ -157,6 +165,7 @@ add_custom_command(
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_SOURCE_DIR}/Tools/filepaths/generate_config.py
|
||||
--rc-dir ${romfs_gen_root_dir}/init.d
|
||||
--params-file ${CONFIG_BOARD_PARAM_FILE}
|
||||
${iofw_path_arg}
|
||||
COMMAND ${CMAKE_COMMAND} -E touch ${romfs_copy_stamp}
|
||||
WORKING_DIRECTORY ${romfs_gen_root_dir}
|
||||
DEPENDS ${romfs_tar_file}
|
||||
@ -255,7 +264,8 @@ set(OPTIONAL_BOARD_EXTRAS)
|
||||
file(GLOB OPTIONAL_BOARD_EXTRAS ${PX4_BOARD_DIR}/extras/*)
|
||||
|
||||
# bootloader (optional)
|
||||
# - if systemcmds/bl_update included and board bootloader available then generate rc.board_bootloader_upgrade and copy bootloader binary
|
||||
# - if systemcmds/bl_update included and board bootloader available then generate rc.board_bootloader_upgrade
|
||||
# - when CONFIG_BOARD_ROOT_PATH is set, bootloader binary is on SD card (not in ROMFS)
|
||||
# - otherwise remove bootloader binary from extras in final ROMFS
|
||||
foreach(board_extra_file ${OPTIONAL_BOARD_EXTRAS})
|
||||
file(RELATIVE_PATH extra_file_base_name ${PX4_BOARD_DIR}/extras/ ${board_extra_file})
|
||||
@ -263,6 +273,13 @@ foreach(board_extra_file ${OPTIONAL_BOARD_EXTRAS})
|
||||
if(CONFIG_SYSTEMCMDS_BL_UPDATE)
|
||||
# generate rc.board_bootloader_upgrade
|
||||
set(BOARD_FIRMWARE_BIN "${PX4_BOARD_VENDOR}_${PX4_BOARD_MODEL}_bootloader.bin")
|
||||
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
set(BOARD_FIRMWARE_PATH "${CONFIG_BOARD_ROOT_PATH}/px4/extras/${BOARD_FIRMWARE_BIN}")
|
||||
else()
|
||||
set(BOARD_FIRMWARE_PATH "/etc/extras/${BOARD_FIRMWARE_BIN}")
|
||||
endif()
|
||||
|
||||
message(STATUS "ROMFS: Adding platforms/nuttx/init/rc.board_bootloader_upgrade -> /etc/init.d/rc.board_bootloader_upgrade")
|
||||
|
||||
# Generate the file using configure_file at configure time to a temporary location
|
||||
@ -286,13 +303,19 @@ foreach(board_extra_file ${OPTIONAL_BOARD_EXTRAS})
|
||||
list(APPEND extras_dependencies
|
||||
rc.board_bootloader_upgrade.stamp
|
||||
)
|
||||
else()
|
||||
# remove bootloader from extras
|
||||
list(REMOVE_ITEM OPTIONAL_BOARD_EXTRAS ${board_extra_file})
|
||||
endif()
|
||||
|
||||
# remove bootloader binary from ROMFS extras (either on SD card or bl_update not enabled)
|
||||
list(REMOVE_ITEM OPTIONAL_BOARD_EXTRAS ${board_extra_file})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# When CONFIG_BOARD_ROOT_PATH is set, IO firmware is on SD card - remove from ROMFS extras
|
||||
if(CONFIG_BOARD_ROOT_PATH AND CONFIG_BOARD_IO)
|
||||
set(_io_bin_path "${PX4_BOARD_DIR}/extras/${CONFIG_BOARD_IO}.bin")
|
||||
list(REMOVE_ITEM OPTIONAL_BOARD_EXTRAS ${_io_bin_path})
|
||||
endif()
|
||||
|
||||
foreach(board_extra_file ${OPTIONAL_BOARD_EXTRAS})
|
||||
|
||||
if(EXISTS "${board_extra_file}")
|
||||
|
||||
@ -35,6 +35,10 @@ parser.add_argument('--rc-dir', type=str, action='store',
|
||||
help='ROMFS output directory', default=None)
|
||||
parser.add_argument('--params-file', type=str, action='store',
|
||||
help='Parameter output file', default=None)
|
||||
parser.add_argument('--iofw-path', type=str, action='store',
|
||||
help='IO firmware binary path', default=None)
|
||||
parser.add_argument('--bootloader-path', type=str, action='store',
|
||||
help='Bootloader binary path', default=None)
|
||||
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true',
|
||||
help='Verbose Output')
|
||||
|
||||
@ -56,6 +60,6 @@ if rc_filepaths_output_dir is not None:
|
||||
if verbose: print("Generating {:}".format(rc_filepath_output_file))
|
||||
template = jinja_env.get_template(rc_filepaths_template)
|
||||
with open(rc_filepath_output_file, 'w') as fid:
|
||||
fid.write(template.render(constrained_flash=constrained_flash, params_file=args.params_file))
|
||||
fid.write(template.render(constrained_flash=constrained_flash, params_file=args.params_file, iofw_path=args.iofw_path, bootloader_path=args.bootloader_path))
|
||||
else:
|
||||
raise Exception("--rc-dir needs to be specified")
|
||||
|
||||
@ -4,3 +4,6 @@
|
||||
|
||||
|
||||
set PARAM_FILE {{ params_file }}
|
||||
{% if iofw_path %}
|
||||
set IOFW "{{ iofw_path }}"
|
||||
{% endif %}
|
||||
|
||||
223
Tools/px4_sdcard_upload.py
Executable file
223
Tools/px4_sdcard_upload.py
Executable file
@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Upload files to PX4 SD card via MAVLink FTP.
|
||||
|
||||
Pushes a local directory tree to the device's SD card using MAVSDK's
|
||||
FTP plugin. Intended to be used after firmware upload to push metadata
|
||||
files (parameters, events, actuators) for constrained flash boards.
|
||||
|
||||
Uses are_files_identical to skip files that already match on the device.
|
||||
|
||||
Usage:
|
||||
python3 px4_sdcard_upload.py --port serial:///dev/ttyACM0:57600 --sdcard-dir build/board/sdcard
|
||||
python3 px4_sdcard_upload.py --sdcard-dir build/board/sdcard # auto-detect port
|
||||
|
||||
Requires: mavsdk
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import glob
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import time
|
||||
|
||||
from mavsdk import System
|
||||
from mavsdk.ftp import FtpError
|
||||
|
||||
|
||||
def detect_ports():
|
||||
"""Try to auto-detect a PX4 serial port."""
|
||||
candidates = []
|
||||
system = platform.system()
|
||||
|
||||
if system == "Linux":
|
||||
candidates.extend(sorted(glob.glob("/dev/serial/by-id/usb-*PX4*")))
|
||||
candidates.extend(sorted(glob.glob("/dev/serial/by-id/usb-*px4*")))
|
||||
candidates.extend(sorted(glob.glob("/dev/ttyACM*")))
|
||||
elif system == "Darwin":
|
||||
candidates.extend(sorted(glob.glob("/dev/cu.usbmodem*")))
|
||||
elif system == "Windows":
|
||||
for i in range(10):
|
||||
candidates.append(f"COM{i}")
|
||||
|
||||
return candidates
|
||||
|
||||
|
||||
def make_system_address(port, baudrate):
|
||||
"""Convert a device path to a MAVSDK system address string."""
|
||||
if "://" in port:
|
||||
return port
|
||||
return f"serial://{port}:{baudrate}"
|
||||
|
||||
|
||||
async def list_remote_files(drone, remote_dir):
|
||||
"""Return set of filenames in remote_dir, or None if dir doesn't exist."""
|
||||
try:
|
||||
result = await drone.ftp.list_directory(remote_dir)
|
||||
return set(result.files)
|
||||
except FtpError:
|
||||
return None
|
||||
|
||||
|
||||
async def ensure_remote_dirs(drone, remote_dir, device_root, created_dirs):
|
||||
"""Create remote directory hierarchy as needed."""
|
||||
if remote_dir in created_dirs:
|
||||
return
|
||||
suffix = remote_dir[len(device_root.rstrip("/")):]
|
||||
parts = suffix.strip("/").split("/")
|
||||
path = device_root.rstrip("/")
|
||||
for part in parts:
|
||||
if not part:
|
||||
continue
|
||||
path = path + "/" + part
|
||||
if path not in created_dirs:
|
||||
print(f" Creating directory: {path}")
|
||||
try:
|
||||
await drone.ftp.create_directory(path)
|
||||
except FtpError:
|
||||
pass # Already exists
|
||||
created_dirs.add(path)
|
||||
|
||||
|
||||
async def upload_directory(drone, local_dir, device_root):
|
||||
"""Walk local_dir and upload changed files to device_root on the device."""
|
||||
local_dir = os.path.abspath(local_dir)
|
||||
upload_count = 0
|
||||
skip_count = 0
|
||||
fail_count = 0
|
||||
created_dirs = set()
|
||||
remote_file_cache = {} # remote_dir -> set of filenames or None
|
||||
|
||||
for dirpath, _dirnames, filenames in os.walk(local_dir):
|
||||
for filename in filenames:
|
||||
local_path = os.path.join(dirpath, filename)
|
||||
rel_path = os.path.relpath(local_path, local_dir)
|
||||
remote_path = device_root.rstrip("/") + "/" + rel_path.replace(os.sep, "/")
|
||||
remote_dir = os.path.dirname(remote_path)
|
||||
file_size = os.path.getsize(local_path)
|
||||
|
||||
# List remote directory once to know which files exist
|
||||
if remote_dir not in remote_file_cache:
|
||||
remote_file_cache[remote_dir] = await list_remote_files(
|
||||
drone, remote_dir)
|
||||
if remote_file_cache[remote_dir] is not None:
|
||||
created_dirs.add(remote_dir)
|
||||
|
||||
remote_files = remote_file_cache[remote_dir]
|
||||
|
||||
if remote_files is not None and filename in remote_files:
|
||||
# File exists remotely, check CRC
|
||||
print(f" {rel_path}: checking...", end="", flush=True)
|
||||
try:
|
||||
if await drone.ftp.are_files_identical(
|
||||
local_path, remote_path):
|
||||
print(" identical, skipped")
|
||||
skip_count += 1
|
||||
continue
|
||||
else:
|
||||
print(f" changed, uploading ({file_size} bytes)...",
|
||||
end="", flush=True)
|
||||
except FtpError as e:
|
||||
print(f" check failed ({e}), uploading ({file_size}"
|
||||
f" bytes)...", end="", flush=True)
|
||||
else:
|
||||
# File doesn't exist remotely
|
||||
await ensure_remote_dirs(
|
||||
drone, remote_dir, device_root, created_dirs)
|
||||
print(f" {rel_path}: new, uploading ({file_size} bytes)...",
|
||||
end="", flush=True)
|
||||
|
||||
try:
|
||||
async for _progress in drone.ftp.upload(local_path, remote_dir):
|
||||
pass
|
||||
print(" done")
|
||||
upload_count += 1
|
||||
except FtpError as e:
|
||||
print(f" FAILED ({e})")
|
||||
fail_count += 1
|
||||
|
||||
return upload_count, skip_count, fail_count
|
||||
|
||||
|
||||
async def run(args):
|
||||
print("Waiting for serial port...")
|
||||
deadline = time.monotonic() + args.port_timeout
|
||||
while time.monotonic() < deadline:
|
||||
if args.port is not None:
|
||||
ports = [args.port]
|
||||
else:
|
||||
ports = detect_ports()
|
||||
if len(ports) == 0:
|
||||
await asyncio.sleep(0.5)
|
||||
continue
|
||||
|
||||
for port in ports:
|
||||
system_address = make_system_address(port, args.baudrate)
|
||||
print(f"Connecting to {system_address} ...")
|
||||
|
||||
try:
|
||||
async with asyncio.timeout(1):
|
||||
if args.mavsdk_server:
|
||||
host, _, port_str = args.mavsdk_server.partition(":")
|
||||
grpc_port = int(port_str) if port_str else 50051
|
||||
drone = System(mavsdk_server_address=host, port=grpc_port)
|
||||
await drone.connect()
|
||||
else:
|
||||
drone = System()
|
||||
await drone.connect(system_address=system_address)
|
||||
|
||||
print("Waiting for connection...")
|
||||
async for state in drone.core.connection_state():
|
||||
if state.is_connected:
|
||||
print("Connected.")
|
||||
break
|
||||
except TimeoutError:
|
||||
continue
|
||||
|
||||
print(f"Checking {args.total_files} file(s) from {args.sdcard_dir} "
|
||||
f"against {args.device_root} ...")
|
||||
|
||||
uploaded, skipped, failed = await upload_directory(
|
||||
drone, args.sdcard_dir, args.device_root)
|
||||
|
||||
print(f"\nDone: {uploaded} uploaded, {skipped} skipped, {failed} failed "
|
||||
f"(out of {args.total_files} files)")
|
||||
if failed > 0:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Upload files to PX4 SD card via MAVLink FTP")
|
||||
parser.add_argument("--port", default=None,
|
||||
help="MAVLink connection string (e.g. serial:///dev/ttyACM0:57600, "
|
||||
"udp://:14540). Auto-detected if not specified.")
|
||||
parser.add_argument("--baudrate", type=int, default=57600,
|
||||
help="Serial baud rate (default: 57600)")
|
||||
parser.add_argument("--sdcard-dir", required=True,
|
||||
help="Local directory tree to upload (mirrors SD card layout)")
|
||||
parser.add_argument("--device-root", default="/fs/microsd",
|
||||
help="SD card root path on device (default: /fs/microsd)")
|
||||
parser.add_argument("--port-timeout", type=float, default=5,
|
||||
help="Seconds to wait for serial port to appear (default: 5)")
|
||||
parser.add_argument("--mavsdk-server", default=None,
|
||||
help="Connect to external mavsdk_server at host:port "
|
||||
"(e.g. localhost:50051) instead of starting one")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.isdir(args.sdcard_dir):
|
||||
print(f"Error: sdcard directory not found: {args.sdcard_dir}")
|
||||
sys.exit(1)
|
||||
|
||||
total_files = sum(len(files) for _, _, files in os.walk(args.sdcard_dir))
|
||||
if total_files == 0:
|
||||
print("No files to upload.")
|
||||
sys.exit(0)
|
||||
|
||||
args.total_files = total_files
|
||||
asyncio.run(run(args))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -81,6 +81,7 @@ CONFIG_MODULES_TEMPERATURE_COMPENSATION=y
|
||||
CONFIG_MODULES_UXRCE_DDS_CLIENT=y
|
||||
CONFIG_MODULES_VTOL_ATT_CONTROL=y
|
||||
CONFIG_SYSTEMCMDS_ACTUATOR_TEST=y
|
||||
CONFIG_SYSTEMCMDS_BL_UPDATE=y
|
||||
CONFIG_SYSTEMCMDS_BSONDUMP=y
|
||||
CONFIG_SYSTEMCMDS_DMESG=y
|
||||
CONFIG_SYSTEMCMDS_GPIO=y
|
||||
|
||||
@ -264,7 +264,7 @@ if(EXISTS ${BOARD_DEFCONFIG})
|
||||
set(romfs_extra_files)
|
||||
set(config_romfs_extra_dependencies)
|
||||
# additional embedded metadata
|
||||
if(NOT CONSTRAINED_FLASH AND NOT EXTERNAL_METADATA AND NOT ${PX4_BOARD_LABEL} STREQUAL "test")
|
||||
if(NOT CONSTRAINED_FLASH AND NOT EXTERNAL_METADATA AND NOT ROOT_PATH AND NOT ${PX4_BOARD_LABEL} STREQUAL "test")
|
||||
list(APPEND romfs_extra_files
|
||||
${PX4_BINARY_DIR}/parameters.json.xz
|
||||
${PX4_BINARY_DIR}/events/all_events.json.xz
|
||||
|
||||
@ -33,24 +33,68 @@
|
||||
|
||||
# Uploader script auto-detects PX4 devices by USB VID/PID
|
||||
set(PX4_UPLOADER_SCRIPT "${PX4_SOURCE_DIR}/Tools/px4_uploader.py")
|
||||
set(PX4_SDCARD_UPLOAD_SCRIPT "${PX4_SOURCE_DIR}/Tools/px4_sdcard_upload.py")
|
||||
|
||||
add_custom_target(upload
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_UPLOADER_SCRIPT} ${fw_package}
|
||||
DEPENDS ${fw_package}
|
||||
COMMENT "uploading px4"
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
WORKING_DIRECTORY ${PX4_BINARY_DIR}
|
||||
)
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
|
||||
add_custom_target(force-upload
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_UPLOADER_SCRIPT} --force ${fw_package}
|
||||
DEPENDS ${fw_package}
|
||||
COMMENT "uploading px4 with --force"
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
WORKING_DIRECTORY ${PX4_BINARY_DIR}
|
||||
)
|
||||
# Flash firmware, then push SD card files (metadata, bootloader, IO binary)
|
||||
add_custom_target(upload
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_UPLOADER_SCRIPT} ${fw_package}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_SDCARD_UPLOAD_SCRIPT}
|
||||
--sdcard-dir ${PX4_BINARY_DIR}/sdcard
|
||||
--device-root ${CONFIG_BOARD_ROOT_PATH}
|
||||
DEPENDS ${fw_package} sdcard_files
|
||||
COMMENT "uploading px4 and SD card files"
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
WORKING_DIRECTORY ${PX4_BINARY_DIR}
|
||||
)
|
||||
|
||||
add_custom_target(force-upload
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_UPLOADER_SCRIPT} --force ${fw_package}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_SDCARD_UPLOAD_SCRIPT}
|
||||
--sdcard-dir ${PX4_BINARY_DIR}/sdcard
|
||||
--device-root ${CONFIG_BOARD_ROOT_PATH}
|
||||
DEPENDS ${fw_package} sdcard_files
|
||||
COMMENT "uploading px4 with --force and SD card files"
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
WORKING_DIRECTORY ${PX4_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Standalone target: upload only SD card files (e.g. with --port)
|
||||
add_custom_target(upload_sdcard
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_SDCARD_UPLOAD_SCRIPT}
|
||||
--sdcard-dir ${PX4_BINARY_DIR}/sdcard
|
||||
--device-root ${CONFIG_BOARD_ROOT_PATH}
|
||||
DEPENDS sdcard_files
|
||||
COMMENT "uploading SD card files via MAVLink FTP"
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
WORKING_DIRECTORY ${PX4_BINARY_DIR}
|
||||
)
|
||||
|
||||
else()
|
||||
|
||||
add_custom_target(upload
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_UPLOADER_SCRIPT} ${fw_package}
|
||||
DEPENDS ${fw_package}
|
||||
COMMENT "uploading px4"
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
WORKING_DIRECTORY ${PX4_BINARY_DIR}
|
||||
)
|
||||
|
||||
add_custom_target(force-upload
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_UPLOADER_SCRIPT} --force ${fw_package}
|
||||
DEPENDS ${fw_package}
|
||||
COMMENT "uploading px4 with --force"
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
WORKING_DIRECTORY ${PX4_BINARY_DIR}
|
||||
)
|
||||
|
||||
endif()
|
||||
|
||||
add_custom_target(upload-verbose
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${PX4_UPLOADER_SCRIPT} --verbose ${fw_package}
|
||||
|
||||
@ -5,12 +5,12 @@
|
||||
#
|
||||
if param compare -s SYS_BL_UPDATE 1
|
||||
then
|
||||
if [ -f "/etc/extras/@BOARD_FIRMWARE_BIN@" ]
|
||||
if [ -f "@BOARD_FIRMWARE_PATH@" ]
|
||||
then
|
||||
param set SYS_BL_UPDATE 0
|
||||
param save
|
||||
echo "bootloader update..."
|
||||
bl_update "/etc/extras/@BOARD_FIRMWARE_BIN@"
|
||||
bl_update "@BOARD_FIRMWARE_PATH@"
|
||||
echo "bootloader update done, rebooting"
|
||||
reboot
|
||||
fi
|
||||
|
||||
@ -71,6 +71,7 @@ add_subdirectory(ringbuffer EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(rl_tools EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(rover_control EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(rtl EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(sdcard_check EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(sensor_calibration EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(slew_rate EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(sticks EXCLUDE_FROM_ALL)
|
||||
|
||||
@ -41,13 +41,23 @@
|
||||
set(comp_metadata_types)
|
||||
set(comp_metadata_board "${PX4_BOARD_VENDOR}_${PX4_BOARD_MODEL}_${PX4_BOARD_LABEL}")
|
||||
set(s3_url "https://px4-travis.s3.amazonaws.com")
|
||||
set(sdcard_metadata_subdir "px4/metadata")
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
string(REGEX REPLACE "^/" "" sdcard_root "${CONFIG_BOARD_ROOT_PATH}")
|
||||
endif()
|
||||
|
||||
set(comp_metadata_param_uri_board "${s3_url}/Firmware/{version}/${comp_metadata_board}/parameters.json.xz")
|
||||
list(FIND config_romfs_extra_dependencies "parameters_xml" index)
|
||||
if (${index} EQUAL -1)
|
||||
set(comp_metadata_param_uri ${comp_metadata_param_uri_board})
|
||||
# use generic URL as fallback
|
||||
set(comp_metadata_param_uri_fallback "${s3_url}/Firmware/{version}/_general/parameters.json.xz")
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
# SD card path as primary, S3 as fallback
|
||||
set(comp_metadata_param_uri "mftp://${sdcard_root}/${sdcard_metadata_subdir}/parameters.json.xz")
|
||||
set(comp_metadata_param_uri_fallback ${comp_metadata_param_uri_board})
|
||||
else()
|
||||
set(comp_metadata_param_uri ${comp_metadata_param_uri_board})
|
||||
# use generic URL as fallback
|
||||
set(comp_metadata_param_uri_fallback "${s3_url}/Firmware/{version}/_general/parameters.json.xz")
|
||||
endif()
|
||||
else()
|
||||
set(comp_metadata_param_uri "mftp://etc/extras/parameters.json.xz")
|
||||
set(comp_metadata_param_uri_fallback ${comp_metadata_param_uri_board})
|
||||
@ -59,9 +69,14 @@ list(APPEND comp_metadata_types "--type" "1,${PX4_BINARY_DIR}/parameters.json.xz
|
||||
set(comp_metadata_events_uri_board "${s3_url}/Firmware/{version}/${comp_metadata_board}/all_events.json.xz")
|
||||
list(FIND config_romfs_extra_dependencies "events_json" index)
|
||||
if (${index} EQUAL -1)
|
||||
set(comp_metadata_events_uri ${comp_metadata_events_uri_board})
|
||||
# use generic URL as fallback
|
||||
set(comp_metadata_events_uri_fallback "${s3_url}/Firmware/{version}/_general/all_events.json.xz")
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
set(comp_metadata_events_uri "mftp://${sdcard_root}/${sdcard_metadata_subdir}/all_events.json.xz")
|
||||
set(comp_metadata_events_uri_fallback ${comp_metadata_events_uri_board})
|
||||
else()
|
||||
set(comp_metadata_events_uri ${comp_metadata_events_uri_board})
|
||||
# use generic URL as fallback
|
||||
set(comp_metadata_events_uri_fallback "${s3_url}/Firmware/{version}/_general/all_events.json.xz")
|
||||
endif()
|
||||
else()
|
||||
set(comp_metadata_events_uri "mftp://etc/extras/all_events.json.xz")
|
||||
set(comp_metadata_events_uri_fallback ${comp_metadata_events_uri_board})
|
||||
@ -71,8 +86,13 @@ list(APPEND comp_metadata_types "--type" "4,${PX4_BINARY_DIR}/events/all_events.
|
||||
set(comp_metadata_actuators_uri_board "${s3_url}/Firmware/{version}/${comp_metadata_board}/actuators.json.xz")
|
||||
list(FIND config_romfs_extra_dependencies "actuators_json" index)
|
||||
if (${index} EQUAL -1)
|
||||
set(comp_metadata_actuators_uri ${comp_metadata_actuators_uri_board})
|
||||
set(comp_metadata_actuators_uri_fallback "")
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
set(comp_metadata_actuators_uri "mftp://${sdcard_root}/${sdcard_metadata_subdir}/actuators.json.xz")
|
||||
set(comp_metadata_actuators_uri_fallback ${comp_metadata_actuators_uri_board})
|
||||
else()
|
||||
set(comp_metadata_actuators_uri ${comp_metadata_actuators_uri_board})
|
||||
set(comp_metadata_actuators_uri_fallback "")
|
||||
endif()
|
||||
else()
|
||||
set(comp_metadata_actuators_uri "mftp://etc/extras/actuators.json.xz")
|
||||
set(comp_metadata_actuators_uri_fallback ${comp_metadata_actuators_uri_board})
|
||||
@ -105,3 +125,69 @@ add_custom_command(OUTPUT ${component_general_json} ${component_general_json}.xz
|
||||
COMMENT "Generating component_general.json and checksums.h"
|
||||
)
|
||||
add_custom_target(component_general_json DEPENDS ${component_general_json})
|
||||
|
||||
# Copy metadata files (and optionally bootloader/IO binaries) to sdcard output
|
||||
# directory so they can be uploaded to the SD card after firmware flash
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
set(sdcard_metadata_dir ${PX4_BINARY_DIR}/sdcard/${sdcard_metadata_subdir})
|
||||
set(sdcard_extras_dir ${PX4_BINARY_DIR}/sdcard/px4/extras)
|
||||
set(sdcard_files_outputs)
|
||||
set(sdcard_files_deps)
|
||||
|
||||
# Metadata files
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${sdcard_metadata_dir}/parameters.json.xz
|
||||
${sdcard_metadata_dir}/all_events.json.xz
|
||||
${sdcard_metadata_dir}/actuators.json.xz
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${sdcard_metadata_dir}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${PX4_BINARY_DIR}/parameters.json.xz ${sdcard_metadata_dir}/
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${PX4_BINARY_DIR}/events/all_events.json.xz ${sdcard_metadata_dir}/
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${PX4_BINARY_DIR}/actuators.json.xz ${sdcard_metadata_dir}/
|
||||
DEPENDS
|
||||
${PX4_BINARY_DIR}/parameters.json.xz
|
||||
${PX4_BINARY_DIR}/events/all_events.json.xz
|
||||
${PX4_BINARY_DIR}/actuators.json.xz
|
||||
parameters_xml
|
||||
events_json
|
||||
actuators_json
|
||||
COMMENT "Copying metadata files to sdcard output directory"
|
||||
)
|
||||
list(APPEND sdcard_files_outputs
|
||||
${sdcard_metadata_dir}/parameters.json.xz
|
||||
${sdcard_metadata_dir}/all_events.json.xz
|
||||
${sdcard_metadata_dir}/actuators.json.xz
|
||||
)
|
||||
|
||||
# Bootloader binary (optional)
|
||||
set(bootloader_bin "${PX4_BOARD_VENDOR}_${PX4_BOARD_MODEL}_bootloader.bin")
|
||||
if(EXISTS "${PX4_BOARD_DIR}/extras/${bootloader_bin}")
|
||||
add_custom_command(
|
||||
OUTPUT ${sdcard_extras_dir}/${bootloader_bin}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${sdcard_extras_dir}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${PX4_BOARD_DIR}/extras/${bootloader_bin} ${sdcard_extras_dir}/
|
||||
DEPENDS ${PX4_BOARD_DIR}/extras/${bootloader_bin}
|
||||
COMMENT "Copying bootloader binary to sdcard output directory"
|
||||
)
|
||||
list(APPEND sdcard_files_outputs ${sdcard_extras_dir}/${bootloader_bin})
|
||||
endif()
|
||||
|
||||
# IO firmware binary (optional)
|
||||
if(CONFIG_BOARD_IO)
|
||||
set(io_bin "${CONFIG_BOARD_IO}.bin")
|
||||
if(EXISTS "${PX4_BOARD_DIR}/extras/${io_bin}")
|
||||
add_custom_command(
|
||||
OUTPUT ${sdcard_extras_dir}/${io_bin}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${sdcard_extras_dir}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${PX4_BOARD_DIR}/extras/${io_bin} ${sdcard_extras_dir}/
|
||||
DEPENDS ${PX4_BOARD_DIR}/extras/${io_bin}
|
||||
COMMENT "Copying IO firmware binary to sdcard output directory"
|
||||
)
|
||||
list(APPEND sdcard_files_outputs ${sdcard_extras_dir}/${io_bin})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_custom_target(sdcard_files ALL
|
||||
DEPENDS ${sdcard_files_outputs}
|
||||
)
|
||||
endif()
|
||||
|
||||
26
src/lib/sdcard_check/CMakeLists.txt
Normal file
26
src/lib/sdcard_check/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
||||
# Generate checksums header from sdcard build directory
|
||||
set(sdcard_checksums_header ${CMAKE_CURRENT_BINARY_DIR}/sdcard_checksums.h)
|
||||
|
||||
if(CONFIG_BOARD_ROOT_PATH)
|
||||
set(sdcard_dir ${PX4_BINARY_DIR}/sdcard)
|
||||
set(sdcard_dir_arg --sdcard-dir ${sdcard_dir})
|
||||
set(sdcard_deps sdcard_files)
|
||||
else()
|
||||
set(sdcard_dir_arg)
|
||||
set(sdcard_deps)
|
||||
endif()
|
||||
|
||||
add_custom_command(OUTPUT ${sdcard_checksums_header}
|
||||
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generate_sdcard_checksums.py
|
||||
${sdcard_dir_arg}
|
||||
--output ${sdcard_checksums_header}
|
||||
DEPENDS
|
||||
generate_sdcard_checksums.py
|
||||
${sdcard_deps}
|
||||
COMMENT "Generating SD card file checksums"
|
||||
)
|
||||
add_custom_target(sdcard_checksums_gen DEPENDS ${sdcard_checksums_header})
|
||||
|
||||
px4_add_library(sdcard_check sdcard_check.cpp)
|
||||
target_include_directories(sdcard_check PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
add_dependencies(sdcard_check sdcard_checksums_gen)
|
||||
83
src/lib/sdcard_check/generate_sdcard_checksums.py
Normal file
83
src/lib/sdcard_check/generate_sdcard_checksums.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Generate SD card file checksums header for firmware integrity verification."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
|
||||
def create_table():
|
||||
a = []
|
||||
for i in range(256):
|
||||
k = i
|
||||
for j in range(8):
|
||||
if k & 1:
|
||||
k ^= 0x1db710640
|
||||
k >>= 1
|
||||
a.append(k)
|
||||
return a
|
||||
|
||||
|
||||
def crc_update(buf, crc_table, crc):
|
||||
for k in buf:
|
||||
crc = (crc >> 8) ^ crc_table[(crc & 0xff) ^ k]
|
||||
return crc
|
||||
|
||||
|
||||
def compute_file_crc(filepath, crc_table):
|
||||
crc = 0
|
||||
with open(filepath, 'rb') as f:
|
||||
while True:
|
||||
chunk = f.read(4096)
|
||||
if not chunk:
|
||||
break
|
||||
crc = crc_update(chunk, crc_table, crc)
|
||||
return crc
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate SD card file checksums header')
|
||||
parser.add_argument('--sdcard-dir', metavar='DIR',
|
||||
help='SD card build directory to scan')
|
||||
parser.add_argument('--output', metavar='FILE', required=True,
|
||||
help='Output header file path')
|
||||
args = parser.parse_args()
|
||||
|
||||
entries = []
|
||||
|
||||
if args.sdcard_dir and os.path.isdir(args.sdcard_dir):
|
||||
crc_table = create_table()
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(args.sdcard_dir):
|
||||
dirnames.sort()
|
||||
for filename in sorted(filenames):
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
rel_path = os.path.relpath(filepath, args.sdcard_dir)
|
||||
crc = compute_file_crc(filepath, crc_table)
|
||||
entries.append((rel_path, crc))
|
||||
|
||||
with open(args.output, 'w') as f:
|
||||
f.write('#pragma once\n')
|
||||
f.write('#include <stdint.h>\n')
|
||||
f.write('namespace sdcard_check {\n')
|
||||
f.write('struct FileEntry {\n')
|
||||
f.write(' const char *relative_path;\n')
|
||||
f.write(' uint32_t expected_crc;\n')
|
||||
f.write('};\n')
|
||||
f.write('static constexpr unsigned num_files = {};\n'.format(
|
||||
len(entries)))
|
||||
|
||||
if entries:
|
||||
f.write('static constexpr FileEntry files[] = {\n')
|
||||
for rel_path, crc in entries:
|
||||
f.write(' {{"{}", {}u}},\n'.format(rel_path, crc))
|
||||
f.write('};\n')
|
||||
else:
|
||||
f.write('static constexpr FileEntry *files = nullptr;\n')
|
||||
|
||||
f.write('} // namespace sdcard_check\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
52
src/lib/sdcard_check/sdcard_check.cpp
Normal file
52
src/lib/sdcard_check/sdcard_check.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
#include "sdcard_check.h"
|
||||
#include "sdcard_checksums.h"
|
||||
|
||||
#include <crc32.h>
|
||||
#include <cstdio>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <px4_platform_common/defines.h>
|
||||
|
||||
namespace sdcard_check
|
||||
{
|
||||
|
||||
Result verify_all()
|
||||
{
|
||||
Result result{};
|
||||
|
||||
#ifdef PX4_STORAGEDIR
|
||||
|
||||
for (unsigned i = 0; i < num_files; i++) {
|
||||
result.num_checked++;
|
||||
|
||||
char path[256];
|
||||
snprintf(path, sizeof(path), "%s/%s", PX4_STORAGEDIR, files[i].relative_path);
|
||||
|
||||
int fd = open(path, O_RDONLY);
|
||||
|
||||
if (fd < 0) {
|
||||
result.num_missing++;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t crc = 0;
|
||||
uint8_t buf[256];
|
||||
ssize_t n;
|
||||
|
||||
while ((n = read(fd, buf, sizeof(buf))) > 0) {
|
||||
crc = crc32part(buf, n, crc);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
if (crc != files[i].expected_crc) {
|
||||
result.num_mismatch++;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PX4_STORAGEDIR */
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace sdcard_check
|
||||
14
src/lib/sdcard_check/sdcard_check.h
Normal file
14
src/lib/sdcard_check/sdcard_check.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace sdcard_check
|
||||
{
|
||||
|
||||
struct Result {
|
||||
int num_checked;
|
||||
int num_missing;
|
||||
int num_mismatch;
|
||||
};
|
||||
|
||||
Result verify_all();
|
||||
|
||||
} // namespace sdcard_check
|
||||
@ -74,6 +74,7 @@ px4_add_library(health_and_arming_checks
|
||||
)
|
||||
set_property(GLOBAL APPEND PROPERTY PX4_MODULE_CONFIG_FILES ${CMAKE_CURRENT_SOURCE_DIR}/esc_check_params.yaml)
|
||||
add_dependencies(health_and_arming_checks mode_util)
|
||||
target_link_libraries(health_and_arming_checks PRIVATE sdcard_check)
|
||||
|
||||
px4_add_functional_gtest(SRC HealthAndArmingChecksTest.cpp
|
||||
LINKLIBS health_and_arming_checks mode_util
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
****************************************************************************/
|
||||
|
||||
#include "sdcardCheck.hpp"
|
||||
#include <sdcard_check/sdcard_check.h>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
|
||||
@ -52,7 +53,10 @@ void SdCardChecks::checkAndReport(const Context &context, Report &reporter)
|
||||
|
||||
if (!_sdcard_detected && statfs(PX4_STORAGEDIR, &statfs_buf) == 0) {
|
||||
// on NuttX we get a data block count f_blocks and byte count per block f_bsize if an SD card is inserted
|
||||
_sdcard_detected = (statfs_buf.f_blocks > 0) && (statfs_buf.f_bsize > 0);
|
||||
if ((statfs_buf.f_blocks > 0) && (statfs_buf.f_bsize > 0)) {
|
||||
_sdcard_detected = true;
|
||||
_sdcard_detected_time = hrt_absolute_time();
|
||||
}
|
||||
}
|
||||
|
||||
if (!_sdcard_detected) {
|
||||
@ -178,5 +182,56 @@ void SdCardChecks::checkAndReport(const Context &context, Report &reporter)
|
||||
#endif /* CONFIG_MODULES_TASK_WATCHDOG */
|
||||
|
||||
#endif /* __PX4_NUTTX */
|
||||
|
||||
// Check SD card metadata file integrity (delay to allow upload after flash)
|
||||
if (!_metadata_checked && _param_com_arm_metadata_check.get() && _sdcard_detected
|
||||
&& hrt_elapsed_time(&_sdcard_detected_time) > 10_s) {
|
||||
_metadata_checked = true;
|
||||
auto result = sdcard_check::verify_all();
|
||||
|
||||
_metadata_missing = result.num_missing > 0;
|
||||
_metadata_mismatch = result.num_mismatch > 0;
|
||||
|
||||
if (!_metadata_missing && !_metadata_mismatch && result.num_checked > 0) {
|
||||
PX4_INFO("SD card files verified: %d files OK", result.num_checked);
|
||||
}
|
||||
}
|
||||
|
||||
if (_metadata_missing && _param_com_arm_metadata_check.get()) {
|
||||
/* EVENT
|
||||
* @description
|
||||
* SD card metadata files are missing. Re-upload the SD card files matching this firmware version.
|
||||
*
|
||||
* <profile name="dev">
|
||||
* This check can be configured via <param>COM_ARM_META_CHK</param> parameter.
|
||||
* </profile>
|
||||
*/
|
||||
reporter.armingCheckFailure(NavModes::All, health_component_t::system,
|
||||
events::ID("check_sdcard_metadata_missing"),
|
||||
events::Log::Error, "SD card metadata files missing");
|
||||
|
||||
if (reporter.mavlink_log_pub()) {
|
||||
mavlink_log_critical(reporter.mavlink_log_pub(), "Preflight Fail: SD card metadata files missing");
|
||||
}
|
||||
}
|
||||
|
||||
if (_metadata_mismatch && _param_com_arm_metadata_check.get()) {
|
||||
/* EVENT
|
||||
* @description
|
||||
* SD card metadata files do not match the firmware. Re-upload the SD card files matching this firmware version.
|
||||
*
|
||||
* <profile name="dev">
|
||||
* This check can be configured via <param>COM_ARM_META_CHK</param> parameter.
|
||||
* </profile>
|
||||
*/
|
||||
reporter.armingCheckFailure(NavModes::All, health_component_t::system,
|
||||
events::ID("check_sdcard_metadata_mismatch"),
|
||||
events::Log::Error, "SD card metadata files mismatch");
|
||||
|
||||
if (reporter.mavlink_log_pub()) {
|
||||
mavlink_log_critical(reporter.mavlink_log_pub(), "Preflight Fail: SD card metadata mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* PX4_STORAGEDIR */
|
||||
}
|
||||
|
||||
@ -53,10 +53,17 @@ private:
|
||||
bool _watchdog_checked_once {false};
|
||||
bool _watchdog_file_present {false};
|
||||
#endif
|
||||
|
||||
hrt_abstime _sdcard_detected_time {0};
|
||||
|
||||
bool _metadata_checked {false};
|
||||
bool _metadata_mismatch {false};
|
||||
bool _metadata_missing {false};
|
||||
#endif
|
||||
|
||||
DEFINE_PARAMETERS_CUSTOM_PARENT(HealthAndArmingCheckBase,
|
||||
(ParamInt<px4::params::COM_ARM_SDCARD>) _param_com_arm_sdcard,
|
||||
(ParamBool<px4::params::COM_ARM_HFLT_CHK>) _param_com_arm_hardfault_check
|
||||
(ParamBool<px4::params::COM_ARM_HFLT_CHK>) _param_com_arm_hardfault_check,
|
||||
(ParamBool<px4::params::COM_ARM_META_CHK>) _param_com_arm_metadata_check
|
||||
)
|
||||
};
|
||||
|
||||
@ -291,6 +291,14 @@ parameters:
|
||||
1: Deny arming
|
||||
2: Warning only
|
||||
default: 2
|
||||
COM_ARM_META_CHK:
|
||||
description:
|
||||
short: Enable SD card metadata integrity check
|
||||
long: |-
|
||||
Verifies SD card files match the firmware version at startup.
|
||||
If disabled, no check is performed.
|
||||
type: boolean
|
||||
default: 1
|
||||
COM_RC_OVERRIDE:
|
||||
description:
|
||||
short: Enable manual control stick override
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user