""" Custom timing decoder for proprietary Chapter 10 timing frames Handles the 0x72xx-0x78xx timing sequence found in ACTTS-like systems """ import struct from typing import Dict, Any, Optional, List from .base import DataTypeDecoder, DecodedPayload class CustomTimingDecoder(DataTypeDecoder): """Decoder for custom timing frames (0x7200-0x7899)""" def __init__(self): super().__init__() self.data_type_base = 0x72 self.data_type_name = "Custom Timing" self.supported_formats = [] # Support all 0x72xx - 0x78xx variants for base in range(0x72, 0x79): for variant in range(0x00, 0x100): self.supported_formats.append((base << 8) | variant) def can_decode(self, data_type: int) -> bool: # Check if data type is in the 0x72xx-0x78xx range return 0x7200 <= data_type <= 0x78FF def get_data_type_name(self, data_type: int) -> str: base = (data_type >> 8) & 0xFF variant = data_type & 0xFF timing_types = { 0x72: "Custom ACTTS Timing", 0x73: "Extended Timing Format", 0x74: "Sync Timing Format", 0x75: "Clock Reference Format", 0x76: "Time Correlation Format", 0x77: "Timing Validation Format", 0x78: "Multi-Source Timing" } base_name = timing_types.get(base, f"Timing Format 0x{base:02x}") return f"{base_name} (Variant 0x{variant:02x})" def decode(self, payload: bytes, ch10_header: Dict[str, Any]) -> Optional[DecodedPayload]: """Decode custom timing payload""" data_type = ch10_header.get('data_type', 0) if not self.can_decode(data_type): return None decoded_data = {} errors = [] # Parse IPH if present iph = self._parse_intra_packet_header(payload) if iph: decoded_data.update(iph) data_start = iph['data_start'] else: data_start = 0 # For custom timing, missing IPH might be normal # Analyze timing data structure timing_analysis = self._analyze_timing_structure(payload, data_start, data_type) decoded_data.update(timing_analysis) # Extract CH-10 timing information from header ch10_time = ch10_header.get('relative_time_counter', 0) decoded_data['ch10_time_counter'] = ch10_time decoded_data['ch10_sequence'] = ch10_header.get('sequence_number', 0) decoded_data['ch10_channel'] = ch10_header.get('channel_id', 0) # Calculate timing metrics if 'timing_samples' in decoded_data and decoded_data['timing_samples']: timing_metrics = self._calculate_timing_metrics(decoded_data['timing_samples']) decoded_data['timing_metrics'] = timing_metrics return DecodedPayload( data_type=data_type, data_type_name=self.get_data_type_name(data_type), format_version=(data_type >> 8) & 0x0F, decoded_data=decoded_data, raw_payload=payload, errors=errors, metadata={'decoder': 'CustomTimingDecoder', 'timing_type': 'proprietary'} ) def _analyze_timing_structure(self, payload: bytes, data_start: int, data_type: int) -> Dict[str, Any]: """Analyze the structure of timing data""" analysis = {} if data_start >= len(payload): return {'error': 'No timing data available'} timing_data = payload[data_start:] analysis['timing_data_length'] = len(timing_data) # Look for timing patterns timing_samples = [] timestamps = [] # Try different word sizes for timing data for word_size in [4, 8]: if len(timing_data) >= word_size: samples = self._extract_timing_words(timing_data, word_size) if samples: timing_samples.extend(samples[:50]) # Limit to first 50 samples analysis['timing_samples'] = timing_samples analysis['sample_count'] = len(timing_samples) # Look for embedded timing markers timing_markers = self._find_timing_markers(timing_data) if timing_markers: analysis['timing_markers'] = timing_markers # Detect timing format based on data type base_type = (data_type >> 8) & 0xFF if base_type == 0x72: # ACTTS-style timing actts_analysis = self._analyze_actts_timing(timing_data) analysis.update(actts_analysis) elif base_type in [0x73, 0x74, 0x75, 0x76, 0x77]: # Extended timing formats extended_analysis = self._analyze_extended_timing(timing_data, base_type) analysis.update(extended_analysis) elif base_type == 0x78: # Multi-source timing multi_analysis = self._analyze_multi_source_timing(timing_data) analysis.update(multi_analysis) return analysis def _extract_timing_words(self, data: bytes, word_size: int) -> List[int]: """Extract timing words from binary data""" words = [] format_str = '= 100: # Limit extraction break return words def _find_timing_markers(self, data: bytes) -> List[Dict[str, Any]]: """Find timing synchronization markers in data""" markers = [] # Common timing sync patterns sync_patterns = [ b'\x81\x81\x81\x81', # Potential sync pattern b'\x82\x82\x82\x82', # Another potential pattern b'\xa9\xa9\xa9\xa9', # Observed in your data ] for pattern in sync_patterns: offset = 0 while True: pos = data.find(pattern, offset) if pos == -1: break markers.append({ 'pattern': pattern.hex(), 'offset': pos, 'description': f'Sync pattern at offset {pos}' }) offset = pos + 1 if len(markers) >= 20: # Limit markers break return markers def _analyze_actts_timing(self, data: bytes) -> Dict[str, Any]: """Analyze ACTTS-style timing data""" analysis = {'timing_format': 'ACTTS-style'} if len(data) >= 16: # Look for ACTTS-like header try: header = struct.unpack(' Dict[str, Any]: """Analyze extended timing formats (0x73-0x77)""" analysis = {'timing_format': f'Extended Format 0x{base_type:02x}'} # Look for timing sequences if len(data) >= 8: try: seq_data = struct.unpack(' Dict[str, Any]: """Analyze multi-source timing data (0x78)""" analysis = {'timing_format': 'Multi-source timing'} # Look for multiple timing sources sources = [] offset = 0 while offset + 8 <= len(data): try: source_data = struct.unpack('= 10: # Limit sources break analysis['timing_sources'] = sources analysis['source_count'] = len(sources) return analysis def _calculate_timing_metrics(self, samples: List[int]) -> Dict[str, Any]: """Calculate timing statistics from samples""" if not samples or len(samples) < 2: return {} # Calculate deltas deltas = [samples[i+1] - samples[i] for i in range(len(samples)-1)] # Basic statistics metrics = { 'sample_count': len(samples), 'min_value': min(samples), 'max_value': max(samples), 'range': max(samples) - min(samples), 'first_sample': samples[0], 'last_sample': samples[-1] } if deltas: metrics.update({ 'min_delta': min(deltas), 'max_delta': max(deltas), 'avg_delta': sum(deltas) / len(deltas), 'zero_deltas': deltas.count(0), 'constant_rate': len(set(deltas)) == 1 }) return metrics