chronaxis.sources.blf
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""为普通分析与高性能积分器共用既有python-can BLFReader;不重新实现BLF格式解析。23只保留当前报文,DBC由混合来源共用一次。账本计入本层对象;第三方解压器内部4分配与原BLF路径一样不代表硬RSS上限。不扫描文件、不补日期、不改原帧字段。5"""6from dataclasses import dataclass7from pathlib import Path89from ..legacy.worker import _frame10from ..results.budget import _reserve_owned_ram, _release_owned_ram, _cleanup_owner11from ..timebase.conversion import convert_time, checked_add_ns, require_int64_ns12from .raw_can import RawCANRecord, CANTextError131415@dataclass(frozen=True)16class BLFSpec:17 """单文件BLF声明;offset_ns仅平移分析坐标,原timestamp不改。"""18 path: Path19 offset_ns: int = 02021 def __post_init__(self):22 """核对静态路径与偏移,不打开输入。失败抛类型或范围错误。"""23 if not isinstance(self.path, Path) or self.path.suffix.lower() != '.blf':24 raise ValueError('BLFSpec需要.blf文件Path')25 require_int64_ns(self.offset_ns)262728class BLFRead:29 """持有一个真实BLFReader与当前迭代器,EOF、失败、取消均关闭本次文件。"""3031 def __init__(self, spec, budget, *, check_cancel=None, retain_original=False,32 retain_source_time=False, selected_ids=None, emit_frame_ids=None,33 channel=None, projection_only=False, time_check_all=False, time_scope='source', frame_only=False, binary_time=False, previous_order_ns=None):34 """接管真实文件并打开BLF库读取器,构造失败附恢复owner。3536 参数: spec为BLFSpec,budget为共享账本;选择/时间选项来自已有消费者。37 frame_only只供fast借用同次原消息,其时间和长度仍由fast检查;不构造样本。38 binary_time使纯BLF串行保原二进制float秒换算,previous_order_ns延续前文件顺序。39 返回: 未读首帧的owner。异常: 配置、文件头、预算或取消原错;失败不泄漏自有文件。40 """41 if type(spec) is not BLFSpec:42 raise TypeError('BLFRead需要BLFSpec')43 self.spec, self._budget, self._check_cancel = spec, budget, check_cancel44 self._retain_source_time = retain_source_time45 if type(frame_only) is not bool or type(binary_time) is not bool:46 raise TypeError('BLF内部帧投影与时间转换选项需要bool')47 # fast借同次原消息,保留自己的选择、时间及长度检查顺序。48 self._frame_only = frame_only49 self._binary_time = binary_time50 self._selected_ids = selected_ids51 self._emit_frame_ids = None if emit_frame_ids is None else frozenset(emit_frame_ids)52 self._channel=channel53 self._projection_only=projection_only and self._emit_frame_ids is not None54 self._time_check_all=time_check_all55 self._time_scope=time_scope56 if time_scope not in ('source','selected','none') or time_scope!='source' and not self._projection_only:57 raise ValueError('按需BLF时间范围必须配合明确的目标帧投影')58 self.first_ns=self.last_ns=self.first_order_ns=None59 self.last_order_ns=previous_order_ns60 if self._projection_only or binary_time:61 # 复用已有批读取的精确二进制秒转换,避免对无关帧建立完整模型。62 from ..execution.can_batch_reader import _float_seconds_ns63 self._convert_float=_float_seconds_ns64 self._file = self._reader = self._iterator = None65 self._fixed_cost = self._held_cost = 066 self._closed = False67 self.count = 068 self.full_check_count = 069 try:70 _reserve_owned_ram(self,65536,'_fixed_cost')71 if check_cancel is not None: check_cancel()72 from ..can_dependencies import import_can_dependency73 can = import_can_dependency('can')74 self._native_message_type=can.Message75 # 文件先安装到owner;BLF头解析失败时也能关闭,不依赖析构回收。76 self._file = spec.path.open('rb')77 self._reader = can.BLFReader(self._file)78 self._iterator = iter(self._reader)79 except BaseException as error:80 _cleanup_owner(self,error,'blf_read_cleanup_owner','混合来源BLF构造')81 raise8283 @property84 def closed(self):85 """返回本层所有文件和费用是否已释放。"""86 return self._closed8788 def __iter__(self):89 """返回同次迭代器,不重复打开文件。"""90 return self9192 def __next__(self):93 """读取下一实际帧,先按ID选择再转换;失败保原异常并清理。"""94 if self._closed: raise StopIteration95 try:96 while True:97 if self._check_cancel is not None: self._check_cancel()98 message = next(self._iterator)99 sequence = self.count100 self.count += 1101 if self._frame_only:102 return message103 serial_id=message.arbitration_id if self._binary_time else None104 fallback_frame=None105 if self._binary_time and type(message) is not self._native_message_type:106 # 非原生替身保持旧完整字段检查,不能用投影绕过属性或结构错误。107 fallback_frame=_frame(message)108 from ..legacy.batch_validation import _item_base_size109 _item_base_size(fallback_frame,sequence)110 self.full_check_count += 1111 if self._projection_only:112 selected=(self._channel is None or message.channel==self._channel) and (serial_id if self._binary_time else message.arbitration_id) in self._emit_frame_ids113 # 纯消息公式不读取时间;保留原消息供同次DBC解码,不制造零秒或原帧导出能力。114 if self._time_scope=='none':115 if not selected:116 continue117 return _UntimedBLFRecord(message,self.spec.path,sequence)118 if self._time_scope=='selected' and not selected:119 continue120 raw_ns,quantized=self._convert_float(message.timestamp)121 mapped=checked_add_ns(raw_ns,self.spec.offset_ns)122 self.first_ns=mapped if self.first_ns is None else min(self.first_ns,mapped)123 self.last_ns=mapped if self.last_ns is None else max(self.last_ns,mapped)124 if self._time_check_all or selected:125 if self.last_order_ns is not None and mapped<self.last_order_ns:126 raise CANTextError(self.spec.path,None,f'原记录{sequence}时间回退')127 if self.first_order_ns is None:128 self.first_order_ns=mapped129 self.last_order_ns=mapped130 if not selected:131 continue132 if self._selected_ids is not None and message.arbitration_id not in self._selected_ids:133 continue134 frame = fallback_frame if fallback_frame is not None else _frame(message)135 if not self._projection_only:136 if self._binary_time:137 raw_ns,quantized=self._convert_float(frame.timestamp)138 else:139 timestamp = convert_time(frame.timestamp,'s')140 raw_ns,quantized=timestamp.time_ns,timestamp.quantized141 mapped=checked_add_ns(raw_ns,self.spec.offset_ns)142 # BLF远程帧已由读取库给出请求长度;保留明确的0,不能当作ASC省略DLC。143 remote_dlc = frame.dlc if frame.is_remote_frame else None144 return RawCANRecord(frame,mapped,self.spec.path,None,sequence,None,'blf-v1',remote_dlc,145 quantized,raw_ns if self._retain_source_time else None)146 except StopIteration:147 self.close()148 raise149 except BaseException as error:150 _cleanup_owner(self,error,'blf_read_cleanup_owner','混合来源BLF读取')151 raise152153 def close(self):154 """关闭本次库迭代器和文件再释放费用;失败保留owner以便重试。"""155 if self._closed: return156 if self._iterator is not None:157 self._iterator.close()158 self._iterator = None159 if self._reader is not None:160 self._reader.stop()161 self._reader = None162 if self._file is not None:163 self._file.close()164 self._file = None165 if self._fixed_cost: _release_owned_ram(self,self._fixed_cost,'_fixed_cost')166 self._closed = True167168169@dataclass(frozen=True)170class _UntimedBLFRecord:171 """内部按需消息:保存真实输入身份与载荷,明确没有采集分析时间,禁止原帧导出。172173 参数: 本次python-can消息、路径、原序号。返回: 有限内部记录。异常: 工厂以真实解析输入构造。174 生命周期仅覆盖同次解码和有界传输;不作为公开RawCANRecord或时间线样本。175 """176 frame: object177 path: Path178 sequence: int179 time_ns: None = None180 raw_time: None = None181 line_number: None = None182