Chronaxis用户手册English
本页目录

chronaxis.execution.frame_capture

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""为显式报文导出保存同次记录和新编码所需DBC上下文。23默认关闭时不创建这些对象。TextFrameStore只保存本次文本读取已交付的原帧、4物理值和定位,可为多个窗口有限回放;不重新读原文件。FrameDBCContext负责5新编码的DBC身份/字段声明,可借给新文本解码器共用一次加载。旧BLF链自身的6DBC加载和解码不改变;为新增编码另加载同一哈希的定义属于新输出的显式成本。7"""89from hashlib import file_digest10from pathlib import Path1112from ..dbc_loading import load_dbc_file1314from ..legacy.records import LegacyCANField15from ..legacy.binding import dbc_candidates, select_message16from ..results.budget import _reserve_owned_ram,_release_owned_ram,_cleanup_owner17from ..results.errors import ResultClosedError18from ..results.frame_exports import as_export_frame19from ..results.frame_patch import DBCFramePatch20from ..results.frame_rebuild import FrameSample,_sample_packet,_restore_sample21from ..results.frame_sort import FrameSort22from .history import _metadata_bytes232425class FrameDBCContext:26    """拥有供新输出使用的DBC定义、摘要和额外请求字段,借用者不负责释放。"""2728    def __init__(self,path,rules,budget,*,cycle_periods=False,integrity=True):29        """加载并核身份,生成连续量/选择器请求。参数: path、规则tuple、budget。返回: owner。异常: DBC/预算/字段/I/O错。30        """31        if type(integrity) is not bool: raise TypeError("integrity需要bool")32        self.path=Path(path); self.rules=rules; self._budget=budget33        self._held_cost=self._fixed_cost=0; self.closed=self._closed=False34        self.database=self._handle=self._patcher=None; self.sha256=None; self.extra_fields=();self.periods=None35        self._candidates=(); self._selected=[]; self._identities=[]; self._cycle_periods=cycle_periods36        self._by_frame_id={}; self._cycle_values={}37        try:38            size=self.path.stat().st_size39            _reserve_owned_ram(self,1048576+size*64+_metadata_bytes(rules)*32,'_fixed_cost')40            before_stat=self.path.stat()41            before=self._hash() if integrity else None42            from ..can_dependencies import import_can_dependency43            cantools = import_can_dependency('cantools')44            self.database=load_dbc_file(str(self.path), cantools)45            # 导出的未知报文策略需要判断DBC是否认识该帧;一次建索引,不逐帧扫描DBC。46            for message in self.database.messages:47                self._by_frame_id.setdefault(message.frame_id,[]).append(message)48            after_stat=self.path.stat()49            changed=(before_stat.st_size,before_stat.st_mtime_ns,before_stat.st_ino)!=(after_stat.st_size,after_stat.st_mtime_ns,after_stat.st_ino)50            if changed or integrity and self._hash()!=before:51                raise ValueError('新增编码加载DBC期间内容发生变化')52            self.sha256=before; self.version=cantools.__version__53            fields=[]; candidates=[]54            for rule in rules:55                choices=dbc_candidates(self.database,rule.frame_id,(*rule.continuous,*rule.protected),rule.is_extended,rule.is_fd)56                if not choices:57                    raise ValueError('DBC缺少重建报文或指定信号: '+str(rule.frame_id))58                candidates.append(choices)59                # 候选定义均须可重建;最终选择等实际帧到达,不按DBC顺序取第一个。60                for message in choices:61                    if cycle_periods and id(message) not in self._cycle_values:62                        # 声明DBC网格时,在打开日志前拒绝缺失或非法周期;只读取一次。63                        from ..legacy.dbc_cycles import cycle_time_ns64                        self._cycle_values[id(message)]=cycle_time_ns(message.cycle_time)65                    try:66                        self._patcher=DBCFramePatch(rule,message,budget)67                    except BaseException as error:68                        self._patcher=getattr(error,'frame_patch_cleanup_owner',None)69                        raise70                    self._patcher.close(); self._patcher=None71                    names=tuple(dict.fromkeys((*rule.continuous,*(s.name for s in message.signals if s.is_multiplexer))))72                    fields.extend(LegacyCANField(rule.channel,name,rule.frame_id,message.is_extended_frame,rule.is_fd) for name in names)73            self.extra_fields=tuple(dict.fromkeys(fields))74            self._candidates=tuple(candidates)75            self._selected=[None]*len(rules); self._identities=[None]*len(rules)76        except BaseException as error:77            _cleanup_owner(self,error,'frame_dbc_cleanup_owner','导出DBC上下文')78            raise7980    def _hash(self):81        """核同一文件的完整内容身份。参数: 无。返回: SHA256文本。异常: 打开/读取/真实关闭错。82        """83        self._handle=self.path.open('rb')84        digest=file_digest(self._handle,'sha256').hexdigest()85        self._handle.close(); self._handle=None86        return digest8788    def require_open(self):89        """禁止使用已释放DBC。参数: 无。返回: None。异常: ResultClosedError。90        """91        if self.closed:92            raise ResultClosedError('导出DBC上下文已关闭')9394    def pairs(self):95        """交付规则与同次定义的有限借用。参数: 无。返回: tuple。异常: 已关闭或身份缺失。96        """97        self.require_open()98        pairs=[]99        for index,rule in enumerate(self.rules):100            message=self._selected[index]101            if message is None:102                choices=self._candidates[index]103                # 从未收到该报文时无需构造一个猜测的多候选输出规则。104                if len(choices)!=1:105                    continue106                message=choices[0]107            pairs.append((rule,message))108        if self._cycle_periods:109            self.periods=tuple(self._cycle_values[id(message)] for _,message in pairs)110        return tuple(pairs)111112    def observe(self,frame):113        """用正在读取的实际帧确定输出定义,禁止一条自动规则混用两种身份。114115        参数: frame为原始帧。返回: 匹配的DBC Message或None。异常: DBC或实际身份歧义。116        副作用: 每条规则只保存一个已识别定义和身份,不保留额外帧或扫描文件。117        """118        self.require_open()119        if frame.is_remote_frame or frame.is_error_frame:120            return None121        selected=[]122        for index,rule in enumerate(self.rules):123            if not rule.matches(frame):124                continue125            message=select_message(self._candidates[index],frame)126            if message is None:127                continue128            identity=(frame.is_extended_id,frame.is_fd)129            prior=self._identities[index]130            if prior is not None and prior!=identity:131                differing='is_extended' if prior[0]!=identity[0] else 'is_fd'132                raise ValueError('报文重建规则匹配到多种实际帧,请补充'+differing)133            self._identities[index]=identity; self._selected[index]=message134            selected.append(message)135        if len(selected)>1:136            raise ValueError('同一实际报文匹配多个重建规则,请收紧帧类型约束')137        return selected[0] if selected else None138139    def is_known_frame(self,frame):140        """只判断未知报文输出策略,不要求未选报文的信号或定义唯一。141142        参数: frame为原始帧。返回: bool。异常: 已关闭时ResultClosedError。143        副作用: 无,不解码或记录帧内容。144        """145        self.require_open()146        return any(message.is_extended_frame==frame.is_extended_id147            and (not frame.is_fd or message.is_fd)148            for message in self._by_frame_id.get(frame.arbitration_id,()))149150    def close(self):151        """在借用者关闭后释放定义和实际句柄。参数: 无。返回: None。异常: 原关闭/释放错可恢复。152        """153        if self.closed:154            return155        if self._handle is not None:156            self._handle.close(); self._handle=None157        if self._patcher is not None:158            self._patcher.close(); self._patcher=None159        self.database=None; self.extra_fields=();self.periods=None160        self._candidates=(); self._selected=[]; self._identities=[]; self._by_frame_id={}; self._cycle_values={}161        _release_owned_ram(self,self._fixed_cost,'_fixed_cost'); self.closed=self._closed=True162163164class TextFrameStore:165    """保存连续原序的同次文本CAN事实;封口后允许多个窗口逐个回放。"""166167    def __init__(self,path,budget,source,session_id,read_identity,*,database_context=None,channel=None,integrity=True,trace=True):168        """建立有限排序暂存及真实读取身份。参数: 新路径、budget、源/会话/read身份,可借用重建DBC及声明通道。返回: owner。异常: I/O/预算错。169        """170        self.path=Path(path); self._budget=budget; self._held_cost=self._fixed_cost=self._row_cost=0171        self.source_name=source; self.session_id=session_id; self.read_identity=read_identity172        self.database_context=database_context; self.channel=channel; self.trace=trace173        self.sort=None; self.count=0; self.last_ns=None; self.state='building'; self.closed=self._closed=False174        try:175            _reserve_owned_ram(self,131072,'_fixed_cost')176            try:177                self.sort=FrameSort(self.path,budget,integrity=integrity)178            except BaseException as error:179                self.sort=getattr(error,'frame_sort_cleanup_owner',None)180                raise181        except BaseException as error:182            _cleanup_owner(self,error,'text_frame_store_cleanup_owner','文本原帧暂存构造')183            raise184185    def append(self,record):186        """保原序原时间,不让排序掩盖回退。参数: DecodedCANRecord同次记录。返回: None。异常: 时序/状态/预算/I/O错。187        """188        if self.closed or self.state!='building' or record.sequence!=self.count:189            raise ValueError('文本原帧仓状态或连续序号不符')190        if self.last_ns is not None and record.raw.time_ns<self.last_ns:191            raise ValueError('文本原帧时间回退')192        _reserve_owned_ram(self,1048576+_metadata_bytes((record.values,record.frame_values,record.frame_layout))*32,'_row_cost')193        try:194            known=record.status!='unknown'195            if self.database_context is not None:196                self.database_context.require_open()197                frame=record.raw.frame198                # 实际类型只在同次读取中匹配,输出不使用按ID覆盖的DBC字典。199                message=self.database_context.observe(frame)200                known=frame.channel==self.channel and self.database_context.is_known_frame(frame)201            sample=FrameSample(as_export_frame(record,compact=not self.trace),record.frame_values,record.frame_layout,known)202            self.sort.append((record.sequence,),_sample_packet(sample))203            self.count+=1; self.last_ns=record.raw.time_ns204        finally:205            _release_owned_ram(self,self._row_cost,'_row_cost')206207    def seal(self):208        """完整真实EOF后封口。参数: 无。返回: None。异常: 状态/配额/I/O错。209        """210        if self.closed or self.state!='building':211            raise ResultClosedError('文本原帧仓不能重复封口')212        self.sort.seal(); self.state='sealed'213214    def iter_samples(self):215        """打开一个有界完整回放,提前关闭不声称完整核验。参数: 无。返回: FrameSample生成器。异常: 仓状态/篡改/I/O错。216        """217        if self.closed or self.state!='sealed':218            raise ResultClosedError('文本原帧仓不可读')219        cursor=self.sort._read(self.sort.path/f'{self.sort.level}-0.jsonl')220        try:221            for _,sequence,packet in cursor:222                sample=_restore_sample(packet)223                if sample.record.sequence!=sequence:224                    raise ValueError('文本原帧暂存序号不一致')225                yield sample226        finally:227            cursor.close()228229    def iter_frames(self):230        """只交付原帧及定位,不重新解码。参数: 无。返回: ExportCANFrame生成器。异常: 原回放错。231        """232        cursor=self.iter_samples()233        try:234            for sample in cursor:235                yield sample.record236        finally:237            cursor.close()238239    def close(self):240        """关闭全部真实回放FD和排序仓,保诊断文件。参数: 无。返回: None。异常: 清理错可重试。241        """242        if self.closed:243            return244        if self.sort is not None:245            self.sort.close()246        _release_owned_ram(self,self._row_cost,'_row_cost')247        _release_owned_ram(self,self._fixed_cost,'_fixed_cost'); self.closed=self._closed=True248
Chronaxis 用户手册 · 离线阅读