add another 1553 data extract

This commit is contained in:
VALLONGOL 2025-06-25 14:00:15 +02:00
parent 39c950ded4
commit 2cfb0c1342
3 changed files with 217 additions and 57 deletions

View File

@ -3,7 +3,7 @@
"last_opened_rec_file": "C:/src/____GitProjects/radar_data_reader/_rec/_25-05-15-12-22-52_sata_345.rec", "last_opened_rec_file": "C:/src/____GitProjects/radar_data_reader/_rec/_25-05-15-12-22-52_sata_345.rec",
"last_out_output_dir": "C:/src/____GitProjects/radar_data_reader/_rec", "last_out_output_dir": "C:/src/____GitProjects/radar_data_reader/_rec",
"last_rec_output_dir": "C:\\src\\____GitProjects\\radar_data_reader\\_rec", "last_rec_output_dir": "C:\\src\\____GitProjects\\radar_data_reader\\_rec",
"active_out_export_profile_name": "prova1", "active_out_export_profile_name": "gsp_data",
"export_profiles": [ "export_profiles": [
{ {
"name": "prova1", "name": "prova1",
@ -152,6 +152,51 @@
"column_name": "target_detections", "column_name": "target_detections",
"data_path": "cdp_sts_results.payload.data.detections_chunk.data.target_detections", "data_path": "cdp_sts_results.payload.data.detections_chunk.data.target_detections",
"translate_with_enum": false "translate_with_enum": false
},
{
"column_name": "magnetic_heading_deg",
"data_path": "d1553_data.magnetic_heading_deg",
"translate_with_enum": false
},
{
"column_name": "mission_timestamp_str",
"data_path": "d1553_data.mission_timestamp_str",
"translate_with_enum": false
},
{
"column_name": "platform_azimuth_deg",
"data_path": "d1553_data.platform_azimuth_deg",
"translate_with_enum": false
},
{
"column_name": "master_mode",
"data_path": "main_header.ge_header.mode.master_mode",
"translate_with_enum": true
},
{
"column_name": "operation_mode",
"data_path": "main_header.ge_header.mode.operation_mode",
"translate_with_enum": true
},
{
"column_name": "radiate",
"data_path": "main_header.ge_header.mode.radiate",
"translate_with_enum": true
},
{
"column_name": "standby",
"data_path": "main_header.ge_header.mode.standby",
"translate_with_enum": true
},
{
"column_name": "range_scale",
"data_path": "main_header.ge_header.mode.range_scale",
"translate_with_enum": true
},
{
"column_name": "scan_rate",
"data_path": "main_header.ge_header.mode.scan_rate",
"translate_with_enum": true
} }
] ]
}, },
@ -163,11 +208,36 @@
"data_path": "batch_id", "data_path": "batch_id",
"translate_with_enum": false "translate_with_enum": false
}, },
{
"column_name": "batch_counter",
"data_path": "main_header.ge_header.signal_descr.batch_counter",
"translate_with_enum": false
},
{
"column_name": "timetag",
"data_path": "main_header.ge_header.general_settings.mission_time.timetag",
"translate_with_enum": false
},
{
"column_name": "ttag",
"data_path": "main_header.ge_header.signal_descr.ttag",
"translate_with_enum": false
},
{
"column_name": "mission_timestamp_str",
"data_path": "d1553_data.mission_timestamp_str",
"translate_with_enum": false
},
{ {
"column_name": "master_mode", "column_name": "master_mode",
"data_path": "main_header.ge_header.mode.master_mode", "data_path": "main_header.ge_header.mode.master_mode",
"translate_with_enum": false "translate_with_enum": false
}, },
{
"column_name": "operation_mode",
"data_path": "main_header.ge_header.mode.operation_mode",
"translate_with_enum": false
},
{ {
"column_name": "range_scale", "column_name": "range_scale",
"data_path": "main_header.ge_header.mode.range_scale", "data_path": "main_header.ge_header.mode.range_scale",
@ -257,6 +327,21 @@
"column_name": "true_heading_deg", "column_name": "true_heading_deg",
"data_path": "d1553_data.true_heading_deg", "data_path": "d1553_data.true_heading_deg",
"translate_with_enum": false "translate_with_enum": false
},
{
"column_name": "platform_azimuth_deg",
"data_path": "d1553_data.platform_azimuth_deg",
"translate_with_enum": false
},
{
"column_name": "magnetic_heading_deg",
"data_path": "d1553_data.magnetic_heading_deg",
"translate_with_enum": false
},
{
"column_name": "ground_track_angle_deg",
"data_path": "d1553_data.ground_track_angle_deg",
"translate_with_enum": false
} }
] ]
} }

View File

@ -6,10 +6,15 @@ binary layout of the radar data file's C/C++ structs.
""" """
import ctypes import ctypes
import math
from typing import List, Optional, Tuple, Union from typing import List, Optional, Tuple, Union
from dataclasses import dataclass, field from dataclasses import dataclass, field
import numpy as np import numpy as np
from ..utils import logger
log = logger.get_logger(__name__)
# --- Base Block (Python-side Representation, not ctypes) --- # --- Base Block (Python-side Representation, not ctypes) ---
@dataclass @dataclass
@ -695,25 +700,95 @@ class D1553Block(BaseBlock):
def true_heading_deg(self) -> Optional[float]: def true_heading_deg(self) -> Optional[float]:
if not (self.is_valid and self.payload): if not (self.is_valid and self.payload):
return None return None
return ctypes.c_int16(self.payload.d.a4[2]).value * ICD1553_SEMICIRCLE_DEG_LSB raw_val = self.payload.d.a4[2]
return ctypes.c_int16(raw_val).value * ICD1553_SEMICIRCLE_DEG_LSB
@property
def magnetic_heading_deg(self) -> Optional[float]:
if not (self.is_valid and self.payload):
return None
# From C++: ((signed short)x->a4[3])*ICD1553_SEMICIRCLE_DEG_LSB
raw_val = self.payload.d.a4[3]
return ctypes.c_int16(raw_val).value * ICD1553_SEMICIRCLE_DEG_LSB
@property
def platform_azimuth_deg(self) -> Optional[float]:
if not (self.is_valid and self.payload):
return None
# From C++: ((signed short)x->a5[8])*ICD1553_SEMICIRCLE_DEG_LSB
raw_val = self.payload.d.a5[8]
return ctypes.c_int16(raw_val).value * ICD1553_SEMICIRCLE_DEG_LSB
@property @property
def north_velocity_ms(self) -> Optional[float]: def north_velocity_ms(self) -> Optional[float]:
if not (self.is_valid and self.payload): if not (self.is_valid and self.payload):
return None return None
return ctypes.c_int16(self.payload.d.a4[10]).value * ICD1553_VELOCITY_METERS_LSB # The C++ uses w2f, which combines two words. Replicating that logic.
raw_val = (self.payload.d.a5[2] << 16) | self.payload.d.a5[3]
return ctypes.c_int32(raw_val).value * ICD1553_VELOCITY_METERS_LSB
@property @property
def east_velocity_ms(self) -> Optional[float]: def east_velocity_ms(self) -> Optional[float]:
if not (self.is_valid and self.payload): if not (self.is_valid and self.payload):
return None return None
return ctypes.c_int16(self.payload.d.a4[11]).value * ICD1553_VELOCITY_METERS_LSB raw_val = (self.payload.d.a5[4] << 16) | self.payload.d.a5[5]
return ctypes.c_int32(raw_val).value * ICD1553_VELOCITY_METERS_LSB
@property @property
def down_velocity_ms(self) -> Optional[float]: def down_velocity_ms(self) -> Optional[float]:
if not (self.is_valid and self.payload): if not (self.is_valid and self.payload):
return None return None
return ctypes.c_int16(self.payload.d.a4[12]).value * ICD1553_VELOCITY_METERS_LSB raw_val = (self.payload.d.a5[6] << 16) | self.payload.d.a5[7]
return ctypes.c_int32(raw_val).value * ICD1553_VELOCITY_METERS_LSB
@property
def ground_track_angle_deg(self) -> Optional[float]:
"""Calculated from north and east velocities."""
if not (self.is_valid and self.payload):
return None
# Requires north_velocity_ms and east_velocity_ms to be valid
vx = self.east_velocity_ms
vy = self.north_velocity_ms
if vx is None or vy is None:
return None
# From C++: atan2(vy, vx) gives angle from East, atan2(vx, vy) from North
# The C++ code `atan2(vx,vy)` calculates angle from North.
track_rad = math.atan2(vx, vy)
return math.degrees(track_rad)
@property
def mission_timestamp_str(self) -> Optional[str]:
if not (self.is_valid and self.payload):
return None
try:
# From C++: date=x->a1[5] and time=x->a1[6]
date_word = self.payload.d.a1[5]
time_word = self.payload.d.a1[6]
# Decoding date (emulating the C++ logic, including the fixed year)
# This seems specific and might need review if data from other years is used
year = 2022
month = (date_word >> 6) & 0xF
day = (date_word >> 10) & 0x3F
# Decoding time
total_seconds = time_word * 2
h = total_seconds // 3600
m = (total_seconds % 3600) // 60
s = total_seconds % 60
# Basic validation of parsed values
if not (1 <= month <= 12 and 1 <= day <= 31 and 0 <= h <= 23 and 0 <= m <= 59 and 0 <= s <= 59):
log.warning(f"Invalid mission date/time decoded: Y={year}, M={month}, D={day}, H={h}, m={m}, s={s}")
return "Invalid DateTime"
return f"{year:04d}-{month:02d}-{day:02d}T{h:02d}:{m:02d}:{s:02d}"
except (IndexError, TypeError):
return None
# Generic block dataclasses (defined AFTER ALL ctypes structs) # Generic block dataclasses (defined AFTER ALL ctypes structs)

View File

@ -103,6 +103,45 @@ def _parse_timer_block(
block_name=block_name, block_size_words=block_size_words, is_valid=False block_name=block_name, block_size_words=block_size_words, is_valid=False
) )
def _parse_avionics_payload(
block_data_bytes: bytes, block_name: str, block_size_words: int
) -> ds.D1553Block:
"""
Parses a buffer known to contain an avionics payload (like MIL-STD-1553 data).
This payload has a 144-byte header that must be skipped.
"""
# From C++ analysis, the actual payload data starts after a 36-word (144-byte) header.
AVIONICS_PAYLOAD_OFFSET_BYTES = 36 * 4
required_size = AVIONICS_PAYLOAD_OFFSET_BYTES + ctypes.sizeof(ds.D1553Payload)
actual_size = len(block_data_bytes)
if actual_size < required_size:
log.warning(
f"{block_name} block is too small for the avionics payload with offset. "
f"Size: {actual_size}, Required: {required_size}."
)
return ds.D1553Block(
block_name=block_name, block_size_words=block_size_words, is_valid=False
)
try:
# Apply the offset to skip the header before mapping the structure
payload = ds.D1553Payload.from_buffer_copy(
block_data_bytes, AVIONICS_PAYLOAD_OFFSET_BYTES
)
log.debug(f"Parsed {block_name} with offset {AVIONICS_PAYLOAD_OFFSET_BYTES}.")
return ds.D1553Block(
block_name=block_name,
block_size_words=block_size_words,
is_valid=True,
payload=payload,
)
except Exception as e:
log.error(f"Failed to map data to D1553Payload from {block_name}: {e}", exc_info=True)
return ds.D1553Block(
block_name=block_name, block_size_words=block_size_words, is_valid=False
)
# --- Funzione di parsing AESA --- # --- Funzione di parsing AESA ---
def _parse_aesa_block( def _parse_aesa_block(
@ -215,38 +254,8 @@ def _parse_aesa_block(
def _parse_d1553_block( def _parse_d1553_block(
block_data_bytes: bytes, block_name: str, block_size_words: int block_data_bytes: bytes, block_name: str, block_size_words: int
) -> ds.D1553Block: ) -> ds.D1553Block:
"""Handles native D1553 blocks by parsing their avionics payload."""
# IPOTESI: Applichiamo lo stesso offset visto per i blocchi SOFT return _parse_avionics_payload(block_data_bytes, block_name, block_size_words)
# anche ai blocchi D1553 nativi.
d1553_data_offset_bytes = 36 * 4
# Controlliamo se il blocco è abbastanza grande per contenere l'offset E il payload
required_size = d1553_data_offset_bytes + ctypes.sizeof(ds.D1553Payload)
actual_size = len(block_data_bytes)
if actual_size < required_size:
log.warning(
f"{block_name} block is too small for D1553Payload with offset. Size: {actual_size}, Required: {required_size}."
)
return ds.D1553Block(
block_name=block_name, block_size_words=block_size_words, is_valid=False
)
try:
# Applichiamo l'offset prima di mappare la struttura
payload = ds.D1553Payload.from_buffer_copy(block_data_bytes, d1553_data_offset_bytes)
log.debug(f"Parsed {block_name} with offset {d1553_data_offset_bytes}.")
return ds.D1553Block(
block_name=block_name,
block_size_words=block_size_words,
is_valid=True,
payload=payload,
)
except Exception as e:
log.error(f"Failed to map data to D1553Payload from {block_name}: {e}", exc_info=True)
return ds.D1553Block(
block_name=block_name, block_size_words=block_size_words, is_valid=False
)
# --- Funzioni di parsing EXP, PC, DET (con gestione raw_data_bytes) --- # --- Funzioni di parsing EXP, PC, DET (con gestione raw_data_bytes) ---
@ -446,30 +455,21 @@ def parse_block(
return _parse_d1553_block(block_data_bytes, block_name, block_size_words) return _parse_d1553_block(block_data_bytes, block_name, block_size_words)
elif block_name == "SOFT": elif block_name == "SOFT":
# Per C++: if ((p[17]==0x35353144) && (p[18]==0x00000033)) # Per C++: if ((p[17]==0x35353144) && (p[18]==0x00000033))
# d1553_extract(p, gps_save_mode); # This SOFT block contains 1553-like data.
# Questo blocco SOFT contiene dati 1553
if block_size_words > 18: if block_size_words > 18:
marker1 = block_data_numpy[17] marker1 = block_data_numpy[17]
marker2 = block_data_numpy[18] marker2 = block_data_numpy[18]
if marker1 == 0x35353144 and marker2 == 0x00000033: if marker1 == 0x35353144 and marker2 == 0x00000033:
log.debug( log.debug("SOFT block contains avionics data. Parsing.")
f"SOFT block at offset contains 1553 data. Parsing as D1553." # Let the dedicated avionics payload parser handle it.
return _parse_avionics_payload(
block_data_bytes, "D1553_from_SOFT", block_size_words
) )
# Il codice C++ passa l'intero puntatore, ma la funzione
# d1553_extract applica un offset di 36 parole.
# Applichiamo lo stesso offset qui.
d1553_data_offset_bytes = 36 * 4
if len(block_data_bytes) > d1553_data_offset_bytes:
return _parse_d1553_block(
block_data_bytes[d1553_data_offset_bytes:],
"D1553_from_SOFT", # Usiamo un nome per tracciarlo
(len(block_data_bytes) - d1553_data_offset_bytes) // 4,
)
else:
log.warning("SOFT block with 1553 markers is too small for payload.")
# Se non è un blocco SOFT con dati 1553, trattalo come generico # If it's not the specific SOFT block with avionics, treat as generic.
return ds.GenericBlock(block_name=block_name, block_size_words=block_size_words) return ds.GenericBlock(
block_name=block_name, block_size_words=block_size_words
)
elif block_name == "EXP": elif block_name == "EXP":
return _parse_exp_block(block_data_bytes, block_name, block_size_words) return _parse_exp_block(block_data_bytes, block_name, block_size_words)
elif block_name == "PC": elif block_name == "PC":