add another 1553 data extract
This commit is contained in:
parent
39c950ded4
commit
2cfb0c1342
@ -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_out_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": [
|
||||
{
|
||||
"name": "prova1",
|
||||
@ -152,6 +152,51 @@
|
||||
"column_name": "target_detections",
|
||||
"data_path": "cdp_sts_results.payload.data.detections_chunk.data.target_detections",
|
||||
"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",
|
||||
"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",
|
||||
"data_path": "main_header.ge_header.mode.master_mode",
|
||||
"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",
|
||||
"data_path": "main_header.ge_header.mode.range_scale",
|
||||
@ -257,6 +327,21 @@
|
||||
"column_name": "true_heading_deg",
|
||||
"data_path": "d1553_data.true_heading_deg",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -6,10 +6,15 @@ binary layout of the radar data file's C/C++ structs.
|
||||
"""
|
||||
|
||||
import ctypes
|
||||
import math
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from dataclasses import dataclass, field
|
||||
import numpy as np
|
||||
|
||||
from ..utils import logger
|
||||
|
||||
log = logger.get_logger(__name__)
|
||||
|
||||
|
||||
# --- Base Block (Python-side Representation, not ctypes) ---
|
||||
@dataclass
|
||||
@ -695,25 +700,95 @@ class D1553Block(BaseBlock):
|
||||
def true_heading_deg(self) -> Optional[float]:
|
||||
if not (self.is_valid and self.payload):
|
||||
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
|
||||
def north_velocity_ms(self) -> Optional[float]:
|
||||
if not (self.is_valid and self.payload):
|
||||
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
|
||||
def east_velocity_ms(self) -> Optional[float]:
|
||||
if not (self.is_valid and self.payload):
|
||||
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
|
||||
def down_velocity_ms(self) -> Optional[float]:
|
||||
if not (self.is_valid and self.payload):
|
||||
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)
|
||||
|
||||
@ -103,6 +103,45 @@ def _parse_timer_block(
|
||||
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 ---
|
||||
def _parse_aesa_block(
|
||||
@ -215,38 +254,8 @@ def _parse_aesa_block(
|
||||
def _parse_d1553_block(
|
||||
block_data_bytes: bytes, block_name: str, block_size_words: int
|
||||
) -> ds.D1553Block:
|
||||
|
||||
# IPOTESI: Applichiamo lo stesso offset visto per i blocchi SOFT
|
||||
# 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
|
||||
)
|
||||
"""Handles native D1553 blocks by parsing their avionics payload."""
|
||||
return _parse_avionics_payload(block_data_bytes, block_name, block_size_words)
|
||||
|
||||
|
||||
# --- 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)
|
||||
elif block_name == "SOFT":
|
||||
# Per C++: if ((p[17]==0x35353144) && (p[18]==0x00000033))
|
||||
# d1553_extract(p, gps_save_mode);
|
||||
# Questo blocco SOFT contiene dati 1553
|
||||
# This SOFT block contains 1553-like data.
|
||||
if block_size_words > 18:
|
||||
marker1 = block_data_numpy[17]
|
||||
marker2 = block_data_numpy[18]
|
||||
if marker1 == 0x35353144 and marker2 == 0x00000033:
|
||||
log.debug(
|
||||
f"SOFT block at offset contains 1553 data. Parsing as D1553."
|
||||
log.debug("SOFT block contains avionics data. Parsing.")
|
||||
# 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
|
||||
return ds.GenericBlock(block_name=block_name, block_size_words=block_size_words)
|
||||
# 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
|
||||
)
|
||||
elif block_name == "EXP":
|
||||
return _parse_exp_block(block_data_bytes, block_name, block_size_words)
|
||||
elif block_name == "PC":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user