mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-04-14 10:07:39 +08:00
* Added the board configs for encryption, I had to disable smbus and px4 io in the arkv6x * Added the key generator script * Added the decryptor, logs are needed for it though * Added the log download and modified the decryptor * Quick fixes & README * Additional modifications & cleanup * Tested upd connection Adjusted the log downloader to handle multiple entry responses from the FC Edited README * Reverted IP address change * Added pycryptodome to the requirements.txt * fixes for log download and decryption * Removed old log decryptors and updated README * Pointed the ark borads to the dummy key updated the README accordingly * Adjusted the folders in README, removed new lines * Extended command line arguments for all possibilities for description * Added MAV_CMD_REQUEST_AUTOPILOT_CAPABILITIES after heartbeat received to make sure log request is answered in all cases * Update Tools/log_encryption/README.md Co-authored-by: Jacob Dahl <37091262+dakejahl@users.noreply.github.com> * Update Tools/log_encryption/README.md Co-authored-by: Jacob Dahl <37091262+dakejahl@users.noreply.github.com> * Update Tools/log_encryption/README.md Co-authored-by: Jacob Dahl <37091262+dakejahl@users.noreply.github.com> * Update Tools/log_encryption/README.md Co-authored-by: Jacob Dahl <37091262+dakejahl@users.noreply.github.com> * Edited README, changed the serial connection logic and updated logdownload, made decryption a bit easier to understand * Update Tools/log_encryption/README.md Co-authored-by: Hamish Willee <hamishwillee@gmail.com> * Removed new lines * arkv6x: add individual mags to default.px4board --------- Co-authored-by: Jacob Dahl <dahl.jakejacob@gmail.com> Co-authored-by: Alex Klimaj <alex@arkelectron.com> Co-authored-by: Jacob Dahl <37091262+dakejahl@users.noreply.github.com> Co-authored-by: Hamish Willee <hamishwillee@gmail.com>
191 lines
6.9 KiB
Python
191 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
||
|
||
import os
|
||
import sys
|
||
import time
|
||
import shutil
|
||
import threading
|
||
from pymavlink import mavutil
|
||
from argparse import ArgumentParser
|
||
|
||
class MavlinkLogDownloader:
|
||
def __init__(self, connection_url, output_dir, baudrate=57600, source_system=254):
|
||
self.connection_url = connection_url
|
||
self.output_dir = output_dir
|
||
self.encrypted_dir = os.path.join(output_dir, "encrypted")
|
||
self.running = True
|
||
|
||
# Ensure directories exist
|
||
os.makedirs(self.output_dir, exist_ok=True)
|
||
os.makedirs(self.encrypted_dir, exist_ok=True)
|
||
|
||
# # Handle serial or UDP connections
|
||
if os.path.exists(connection_url): # likely a serial device
|
||
self.mav = mavutil.mavlink_connection(connection_url, baud=baudrate, source_system=source_system)
|
||
else:
|
||
self.mav = mavutil.mavlink_connection(connection_url, source_system=source_system)
|
||
|
||
|
||
self.mav.WIRE_PROTOCOL_VERSION = "2.0"
|
||
|
||
# Start heartbeat thread
|
||
self.heartbeat_thread = threading.Thread(target=self.send_heartbeat_thread)
|
||
self.heartbeat_thread.daemon = True
|
||
self.heartbeat_thread.start()
|
||
|
||
self.mav.wait_heartbeat()
|
||
print(f"Heartbeat received from system {self.mav.target_system}, component {self.mav.target_component}")
|
||
|
||
# Waking up the autopilot, it is needed to ensure we get answer for log request
|
||
self.mav.mav.command_long_send(
|
||
self.mav.target_system,
|
||
self.mav.target_component,
|
||
mavutil.mavlink.MAV_CMD_REQUEST_MESSAGE, # Command ID 512
|
||
0, # Confirmation
|
||
mavutil.mavlink.MAVLINK_MSG_ID_AUTOPILOT_VERSION, # param1: Message ID 148
|
||
0, 0, 0, 0, 0, 0 # params 2–7 are not used for this message
|
||
)
|
||
|
||
# Allow heartbeats to establish connection
|
||
time.sleep(3)
|
||
|
||
|
||
def send_heartbeat_thread(self):
|
||
while self.running:
|
||
self.mav.mav.heartbeat_send(
|
||
mavutil.mavlink.MAV_TYPE_GCS,
|
||
mavutil.mavlink.MAV_AUTOPILOT_GENERIC,
|
||
0, 0, 0
|
||
)
|
||
time.sleep(1)
|
||
|
||
|
||
def download_logs(self):
|
||
"""Downloads logs to the output_dir."""
|
||
print("Request logs...")
|
||
self.mav.mav.log_request_list_send(self.mav.target_system, self.mav.target_component, 0, 0xFFFF)
|
||
|
||
log_entries = {}
|
||
total_logs = None
|
||
start_time = time.time()
|
||
last_entry_time = None
|
||
|
||
while True:
|
||
current_time = time.time()
|
||
|
||
# Case 1: If we haven't received any entries yet and we've waited more than 5 seconds
|
||
if not log_entries and current_time > start_time + 5:
|
||
print("Timed out waiting for first log entry (5s)")
|
||
break
|
||
|
||
# Case 2: If we have received at least one entry and it's been more than 3 seconds since the last one
|
||
if last_entry_time and current_time - last_entry_time > 3:
|
||
print(f"No new log entries received for 3 seconds. Moving on.")
|
||
break
|
||
|
||
# Case 3: If we've received all expected logs
|
||
if total_logs is not None and len(log_entries) >= total_logs:
|
||
print(f"Received all {total_logs} log entries.")
|
||
break
|
||
|
||
msg = self.mav.recv_match(type='LOG_ENTRY', blocking=True, timeout=1)
|
||
|
||
if msg and msg.id not in log_entries:
|
||
last_entry_time = time.time()
|
||
log_entries[msg.id] = msg
|
||
|
||
if total_logs is None:
|
||
total_logs = msg.num_logs
|
||
|
||
print(f"Log ID: {msg.id}, Size: {msg.size} bytes, Date: {msg.time_utc} ({len(log_entries)}/{total_logs})")
|
||
|
||
if not log_entries:
|
||
print("No log entries found.")
|
||
return
|
||
|
||
for entry in log_entries.values():
|
||
self.download_log_file(entry)
|
||
|
||
self.classify_logs()
|
||
|
||
|
||
def download_log_file(self, log_entry):
|
||
"""Downloads a log file to the output_dir."""
|
||
log_id = log_entry.id
|
||
log_size = log_entry.size
|
||
log_date = time.strftime("%Y-%m-%d_%H-%M-%S", time.gmtime(log_entry.time_utc))
|
||
output_filename = os.path.join(self.output_dir, f"log-{log_date}_{log_id}.ulg")
|
||
|
||
print(f"Downloading log {log_id} ({log_size} bytes) to {output_filename}...")
|
||
|
||
with open(output_filename, 'wb') as f:
|
||
self.mav.mav.log_request_data_send(self.mav.target_system, self.mav.target_component, log_id, 0, 0xFFFFFFFF)
|
||
|
||
bytes_received = 0
|
||
while bytes_received < log_size:
|
||
msg = self.mav.recv_match(type='LOG_DATA', blocking=True, timeout=5)
|
||
if msg:
|
||
data_bytes = bytes(msg.data[:msg.count])
|
||
f.write(data_bytes)
|
||
bytes_received += msg.count
|
||
else:
|
||
print("Timeout waiting for log data.")
|
||
break
|
||
|
||
print(f"Finished downloading log {log_id}.")
|
||
|
||
def classify_logs(self):
|
||
"""Classifies logs as encrypted (.ulge) based on file content."""
|
||
for log_file in os.listdir(self.output_dir):
|
||
log_path = os.path.join(self.output_dir, log_file)
|
||
|
||
if not os.path.isfile(log_path):
|
||
continue
|
||
|
||
# Read first 10 bytes to check for "ULogEnc"
|
||
with open(log_path, 'rb') as f:
|
||
first_bytes = f.read(10)
|
||
|
||
if b'ULogEnc' in first_bytes:
|
||
new_filename = log_file.replace(".ulg", ".ulge")
|
||
new_path = os.path.join(self.encrypted_dir, new_filename)
|
||
print(f"Found encrypted log: {new_path}")
|
||
shutil.move(log_path, new_path)
|
||
|
||
|
||
def cleanup(self):
|
||
"""Stop the heartbeat thread and clean up resources."""
|
||
self.running = False
|
||
if self.heartbeat_thread.is_alive():
|
||
self.heartbeat_thread.join(timeout=2.0)
|
||
|
||
|
||
def main():
|
||
parser = ArgumentParser(description="Download PX4 log files over MAVLink.")
|
||
parser.add_argument('connection_url', help="MAVLink connection URL (e.g., udp:0.0.0.0:14550, /dev/ttyACM0 --baudrate 57600)")
|
||
parser.add_argument('--output', '-o', default=os.path.join(os.path.dirname(__file__), "../..", "logs"), help="Output directory for log files (default: ../../logs)")
|
||
parser.add_argument('--baudrate', type=int, default=57600, help="Baudrate for serial connection (default: 57600)")
|
||
parser.add_argument('--source-system', type=int, default=254, help="MAVLink source system ID (default: 254)")
|
||
|
||
|
||
args = parser.parse_args()
|
||
|
||
output_dir = os.path.abspath(args.output)
|
||
|
||
print(f"Connecting to {args.connection_url}...")
|
||
log_downloader = MavlinkLogDownloader(
|
||
args.connection_url,
|
||
output_dir,
|
||
baudrate=args.baudrate,
|
||
source_system=args.source_system
|
||
)
|
||
|
||
|
||
try:
|
||
log_downloader.download_logs()
|
||
finally:
|
||
log_downloader.cleanup()
|
||
|
||
if __name__ == '__main__':
|
||
main()
|