Chronaxis用户手册English
本页目录

chronaxis.sources.text_can_decode

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""按需解码新ASC/CSV原帧,并接到既有数值归一化核心。23CANTextDecodeRead消费显式CANTextSpec与本次需要的SignalDefinition,逐个交付4DecodedCANRecord;未知ID、未选中和非数据帧仍保原记录,仅所需帧调用cantools。5DBC沿用decode_choices=False/allow_truncated=True参数,但新入口先严格核已选帧长度;6已定义报文解码失败直接定位,不冒充未知ID。pack_text_records输出既有SignalSeries。7完整读取核对全部原帧时间;按需ASC仅核所需通道/ID的连续顺序,并同次统计来源范围。8文件不重读;DBC加载和临时对象先付预算。9解析文件逐个关闭,失败/取消保原错误及必要恢复owner;纯BLF串行复用显式解码政策。10"""1112from dataclasses import dataclass13from decimal import Decimal14from hashlib import file_digest15from pathlib import Path1617from ..dbc_loading import load_dbc_file1819from ..core.binding import SignalDefinition20from ..core.validation import require_identifier21from ..execution.history import _metadata_bytes22from ..legacy.records import field_to_dict23from ..legacy.decode_policy import check_decode_length, decode_serial_frame24from ..legacy.worker import _field_metadata25from ..results.budget import ResourceBudget, _reserve_owned_ram, _release_owned_ram, _cleanup_owner26from ..results.errors import ResultClosedError27from .asc import ASCSpec, ASCRead28from .blf import BLFSpec, BLFRead, _UntimedBLFRecord29from .raw_csv import CANCSVRead30from .raw_can import RawCANRecord, CANTextError31from .can_spec import can_field_from_key32from .models import freeze_metadata33from .packing import pack_decoded34from .text_can_ref import CANTextSourceRef35from .text_can_spec import CANTextSpec363738@dataclass(frozen=True)39class DecodedCANRecord:40    """保留一条原帧及本次所需原信号值;非decoded记录的values必须为空。"""4142    source: str  # 当前来源名称。43    input_index: int  # 原输入列表位置。44    sequence: int  # 全部文件连续的原帧序号,不仅是参与计算的序号。45    raw: RawCANRecord | _UntimedBLFRecord  # 保留原帧;无时间需求的BLF记录明确不提供时间。46    values: tuple[tuple[str, int | float | bool | None], ...]  # 规范CAN字段键到DBC物理值。47    status: str  # decoded/unknown/unselected/non_data;不把错误记成unknown。48    frame_values: tuple[tuple[str,int | float | bool | None],...] = ()  # 显式重建所需原物理字段。49    frame_layout: tuple[tuple[str,int],...] = ()  # 当次复用选择器的原始码值。5051    def __post_init__(self):52        """核记录冻结结构。参数: 无。返回: None。异常: 来源、位置、状态或值结构非法。53        """54        self._validate(None)5556    @classmethod57    def _from_bound_values(cls, source, input_index, sequence, raw, values, status,58                           frame_values, frame_layout, known_fields):59        """用本次绑定已经解析的字段名建立内部记录,其余检查与公开构造相同。6061        参数: 前八项与记录字段相同;known_fields为当前读取器从已验证绑定取得的字段键集合。62        返回: 新的冻结DecodedCANRecord,不持有额外的绑定副本。63        异常: 字段不在当前绑定中、值类型或记录结构不合法时拒绝。64        外部构造仍调用完整字段解析;此入口只供同次解码结果复用绑定检查。65        """66        record = object.__new__(cls)67        # 仅安装当前固定模型的八项字段,不按输入提供的属性名恢复对象。68        for name, value in zip(('source', 'input_index', 'sequence', 'raw', 'values',69                                'status', 'frame_values', 'frame_layout'),70                               (source, input_index, sequence, raw, values, status,71                                frame_values, frame_layout)):72            object.__setattr__(record, name, value)73        record._validate(known_fields)74        return record7576    def _validate(self, known_fields):77        """检查固定记录,内部已绑定键只核成员关系。7879        参数: known_fields为本次已解析字段集合,None表示公开入口需要完整解析。80        返回: None。异常: 非法结构、未知字段、重复键或无值状态携带数据。81        """82        # 该模型不开放任意对象值,也不在外部构造时接受重复字段覆盖。83        require_identifier(self.source,'decoded_can.source')84        if (type(self.raw) not in (RawCANRecord,_UntimedBLFRecord) or type(self.input_index) is not int or self.input_index < 085                or type(self.sequence) is not int or self.sequence < 0 or type(self.values) is not tuple86                or self.status not in ('decoded','unknown','unselected','non_data')):87            raise ValueError('解码CAN记录结构或状态非法')88        keys = set()89        for pair in self.values:90            if type(pair) is not tuple or len(pair) != 2 or type(pair[0]) is not str or type(pair[1]) not in (int,float,bool,type(None)):91                raise TypeError('解码值需要规范字段键及原生数值/None')92            if known_fields is None:93                can_field_from_key(pair[0])94            elif pair[0] not in known_fields:95                raise ValueError('解码CAN字段不属于当前绑定')96            if pair[0] in keys:97                raise ValueError('解码CAN字段重复')98            keys.add(pair[0])99        if self.status != 'decoded' and self.values:100            raise ValueError('未解码原帧不能携带信号值')101        for pairs,kinds in ((self.frame_values,(int,float,bool,type(None))),(self.frame_layout,(int,))):102            if type(pairs) is not tuple or len(pairs)>4096 or any(type(p) is not tuple or len(p)!=2 or type(p[0]) is not str103                    or not p[0] or type(p[1]) not in kinds for p in pairs) or len({p[0] for p in pairs})!=len(pairs):104                raise ValueError('重建物理值或布局结构非法')105        if self.status!='decoded' and (self.frame_values or self.frame_layout):106            raise ValueError('未解码原帧不能伪造重建值')107108109class CANTextDecodeRead:110    """拥有单次DBC和逐文件解析器;EOF后保有限元数据到显式close。"""111112    def __init__(self, spec, budget, signals, *, check_cancel=None, database_context=None, classify_unselected=True, cycle_fields=(), metadata_fields=(), integrity=True, trace=True, retain_source_time=True, selected_ids=None, time_frame_ids=None, emit_frame_ids=None, blf_time_scope='source',legacy_blf_decode=False,serial_blf_policy=None,post_decode_frame_ids=None):113        """只核声明并预约固定费用。参数: spec来源、budget账本、signals所需定义tuple、check_cancel取消回调;classify_unselected保旧逐帧分类,消息消费者无需区分无关ID是否在DBC中;cycle_fields只请求初始化周期,metadata_fields只请求候选,两者不加入解码投影。114115        time_frame_ids仅在无需完整顺序/原帧导出时传入:选中通道/ID之间必须有序,116        其他帧仍用于同次来源最早/最晚时间统计,不交付给计算或作为单调性前点。117        selected_ids仅供不需要全来源观测或原帧导出的纯消息计算使用:原始CSV先按ID跳过118        无关行;None保留全部行。count保原始行计数,retained_count只数实际交付的记录。119        legacy_blf_decode默认False;纯can_blf转接保留legacy长度政策和DecodeError缺值行为。120        serial_blf_policy仅纯BLF串行设置requested/all:唯一ID沿原数据库入口的普通异常121        缺值处理,多定义ID按实际帧型直接解码;all还核完整值。并行和文本政策保持独立。122        post_decode_frame_ids仅串行all且无原帧导出时传入有限ID集合:每帧完成解码和123        完整值检查后再跳过无消费者的帧。None交付全部帧;计数只数实际交付。124        纯BLF串行沿用按ID检查时间和交付,通道由后续绑定筛选;其他来源按通道及ID。125126        返回: 未开始读取的owner。异常: 声明/预算错误;持续清理失败附text_can_cleanup_owner。127        """128        # 构造不访问DBC/输入;只接受显式同来源、同通道且唯一的别名定义。129        if (type(spec) is not CANTextSpec or not isinstance(budget,ResourceBudget)130                or type(signals) is not tuple or len(signals) > 4096131                or any(type(item) is not SignalDefinition or item.source != spec.name for item in signals)132                or check_cancel is not None and not callable(check_cancel)):133            raise TypeError('文本CAN读取需要静态声明、预算及所需信号定义')134        if type(classify_unselected) is not bool:135            raise TypeError('classify_unselected需要bool')136        if type(legacy_blf_decode) is not bool or (legacy_blf_decode and137                (spec.decode_policy!='legacy' or any(type(item) is not BLFSpec for item in spec.inputs))):138            raise ValueError('legacy_blf_decode仅用于明确的纯BLF legacy转接')139        self._legacy_blf_decode=legacy_blf_decode140        if serial_blf_policy not in (None,'requested','all') or (serial_blf_policy is not None and any(type(item) is not BLFSpec for item in spec.inputs)):141            raise ValueError('纯BLF串行政策需要requested/all及全部BLF输入')142        self._serial_blf_policy=serial_blf_policy143        if post_decode_frame_ids is not None and serial_blf_policy!='all':144            raise ValueError('解码后投影仅用于纯BLF串行all政策')145        self._post_decode_frame_ids=None if post_decode_frame_ids is None else frozenset(post_decode_frame_ids)146        self._duplicate_ids=frozenset()147        if len({item.name for item in signals}) != len(signals):148            raise ValueError('文本CAN别名不能重复')149        from ..legacy.dbc_cycles import require_cycle_fields150        require_cycle_fields(cycle_fields,spec.channel)151        require_cycle_fields(metadata_fields,spec.channel)152        self._metadata_fields = metadata_fields153        self._selected_ids = selected_ids154        # 并行传输只发送实际消费者需要的帧;时间检查和来源范围仍在筛选前完成。155        self._emit_frame_ids = None if emit_frame_ids is None else frozenset(emit_frame_ids)156        self._blf_time_scope=blf_time_scope157        self.first_order_time = None158        if time_frame_ids is not None and (type(time_frame_ids) is not tuple or len(time_frame_ids)>4096159                or any(type(value) is not int or not 0 <= value <= 0x1fffffff for value in time_frame_ids)):160            raise ValueError('参与时间检查的CAN ID需要有限合法整数tuple')161        if time_frame_ids is not None and spec.input_check == 'full':162            raise ValueError('完整输入检查不能限制时间顺序检查范围')163        self._time_frame_ids = None if time_frame_ids is None else frozenset(time_frame_ids)164        self._last_selected_time = None165        parsed = tuple((item,can_field_from_key(item.field)) for item in signals)166        if any(field.channel != spec.channel for _,field in parsed):167            raise ValueError('文本CAN字段通道与来源声明不一致')168        # 所有资源槽先安装,任何后续预算/读取错误均可由同一个owner关闭。169        self.spec,self._budget,self.signals,self._fields = spec,budget,signals,parsed170        self._cycle_fields = cycle_fields171        if type(integrity) is not bool: raise TypeError("integrity需要bool")172        self._integrity=integrity173        self._trace=trace174        self._retain_source_time=retain_source_time175        self._check_cancel = check_cancel176        self._classify_unselected = classify_unselected177        self._binding_resolver = None178        self._database_context=database_context179        self._extra_fields=() if database_context is None else database_context.extra_fields180        self._frame_values=self._frame_layout=()181        self._known_field_keys = None182        self._held_cost = self._fixed_cost = self._dbc_cost = self._metadata_cost = 0183        self._db = self._raw = self._metadata = None184        self._input_index = 0185        self._completed_count = 0186        self.count = self.decode_calls = self.retained_count = 0187        self.full_check_count = 0188        self.first_ns = self.last_ns = None189        self._closed = self._exhausted = self._failed = self._closing = False190        try:191            # 固定费用包含所需字段、路径和DBC元数据;不保存随原帧数增长的列表。192            cost = 131072 + _metadata_bytes((signals,self._extra_fields,selected_ids,time_frame_ids))*16193            if cycle_fields or metadata_fields:194                cost += 16384 + _metadata_bytes((cycle_fields,metadata_fields))*32195            if database_context is not None:196                database_context.require_open()197                if database_context.path!=spec.dbc_path or database_context._budget is not budget:198                    raise ValueError('共用DBC上下文必须匹配当前文件和共享预算')199            cost += sum(1024+len(str(item.path).encode('utf-8'))*16 for item in spec.inputs)200            _reserve_owned_ram(self,cost,'_fixed_cost')201        except BaseException as error:202            _cleanup_owner(self,error,'text_can_cleanup_owner','文本CAN构造')203            raise204205    @property206    def closed(self):207        """返回真实资源关闭事实。参数: 无。返回: bool。异常: 无。208        """209        return self._closed210211    @property212    def exhausted(self):213        """区别真实全文件EOF与提前close。参数: 无。返回: bool。异常: 无。214        """215        return self._exhausted216217    @property218    def metadata(self):219        """取得本次实际DBC事实。参数: 无。返回: 冻结Mapping。异常: 未启动或已关闭时ResultClosedError。220        """221        if self._metadata is None:222            raise ResultClosedError('文本CAN尚无可用的同次DBC元数据')223        return self._metadata224225    def prepare_metadata(self):226        """只初始化一次DBC,供计算在首帧前取得周期。227228        参数: 无。返回: 同次冻结元数据。异常: 失败/关闭拒绝,启动错误交实际owner清理。229        不打开原始文件,不改变EOF状态。230        """231        if self._closed or self._closing or self._failed:232            raise ResultClosedError('文本CAN读取已关闭或失败')233        try:234            if self._metadata is None:235                self._start()236            return self.metadata237        except BaseException as error:238            self._failed = True239            _cleanup_owner(self, error, 'text_can_cleanup_owner', '文本CAN初始化')240            raise241242    def _start(self):243        """一次加载并核所需定位/单位。参数: 无。返回: None。异常: 原DBC/I/O/预算或单位不符。244        """245        # 只有首次next进入此处;为库的DBC对象图和哈希缓冲预约保守费用,不声称RSS硬上限。246        from ..can_dependencies import import_can_dependency247        cantools = import_can_dependency('cantools')248        if self._database_context is None:249            size = self.spec.dbc_path.stat().st_size250            _reserve_owned_ram(self,1048576+size*64,'_dbc_cost')251            before_stat=self.spec.dbc_path.stat()252            before=None253            if self._integrity:254                with self.spec.dbc_path.open('rb') as stream:255                    before = file_digest(stream,'sha256').hexdigest()256            self._db = load_dbc_file(str(self.spec.dbc_path), cantools)257        else:258            # 新文本读取和新输出共用同一已付费DBC;借用结束不能替父owner释放费用。259            self._database_context.require_open()260            self._db=self._database_context.database261            before=self._database_context.sha256262        fields = tuple(dict.fromkeys((*[field for _,field in self._fields],*self._extra_fields)))263        # 仅缓存已冻结声明的规范键;范围固定,避免每条消息重建相同JSON字段键。264        self._field_keys={field:field.key for field in fields}265        # 与字段字典共用已预付的声明空间;只保存引用,不复制每帧字段或扩大声明范围。266        self._known_field_keys = frozenset(self._field_keys.values())267        from ..legacy.binding import FrameBindingResolver268        self._binding_resolver = FrameBindingResolver(self._db, fields)269        if self._serial_blf_policy is not None:270            from collections import Counter271            self._duplicate_ids=frozenset(key for key,count in Counter(message.frame_id for message in self._db.messages).items() if count>1)272        if self._cycle_fields or self._metadata_fields:273            fields=tuple(dict.fromkeys((*fields,*self._cycle_fields,*self._metadata_fields)))274            facts=_field_metadata(self._db,fields,cycle_fields=self._cycle_fields)275        else:276            facts = _field_metadata(self._db,fields)277        # 已知非空DBC单位与input_unit逐字核对;缺单位不伪装成已认证单位。278        for definition,field in self._fields:279            unit = next(item['unit'] for item in facts if item['locator']==field_to_dict(field))280            if unit not in (None,'') and unit != definition.input_unit:281                raise ValueError('DBC非空单位与input_unit不同: '+definition.name)282        if self._database_context is None:283            after=None284            if self._integrity:285                with self.spec.dbc_path.open('rb') as stream:286                    after = file_digest(stream,'sha256').hexdigest()287            after_stat=self.spec.dbc_path.stat()288            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)289            if changed or before != after:290                raise ValueError('加载期间DBC内容发生变化')291        # EOF后仍保留单位/定位等事实;与DBC对象图分开付费,不能随DBC释放而漏账。292        _reserve_owned_ram(self,4096+_metadata_bytes(freeze_metadata(facts))*16293            +len(str(self.spec.dbc_path).encode('utf-8'))*16,'_metadata_cost')294        self._metadata = freeze_metadata({'profile':'can-text-v1','dbc_path':str(self.spec.dbc_path),295            'dbc_sha256':before,'fields':facts,'decode_choices':False,'allow_truncated':True,296            'selected_length_check':'legacy' if self._legacy_blf_decode else 'partial' if self.spec.decode_policy=='partial' else 'exact',297            'decode_scope':self._serial_blf_policy or 'requested','cantools_version':cantools.__version__,298            'record_check_scope':'selected_ids' if ((self._selected_ids is not None and self.spec.source_type=='raw_can_csv') or (self._serial_blf_policy is not None and self._emit_frame_ids is not None and self.spec.input_check=='auto')) else 'source',299            'time_order_check_scope':'none' if self._blf_time_scope=='none' else 'selected_ids' if self._time_frame_ids is not None else 'source'})300301    def _decode(self, raw):302        """给本条原帧分类并按需解码。参数: raw为本次实际RawCANRecord。返回: (values,status)。303304        异常: 所选政策的长度检查或非DBC解码错误。文本legacy遇DecodeError停止;305        strict/partial及明确的纯BLF legacy转接将其记为本条无值,缺失信号不沿用前值。306        """307        frame = raw.frame308        self._frame_values=self._frame_layout=()309        # 普通文本政策不从远程/错误帧生成信号;纯BLF legacy沿旧政策尝试解码。310        if (frame.is_remote_frame or frame.is_error_frame) and not self._legacy_blf_decode:311            return (),'non_data'312        selected = self._binding_resolver.fields_for(frame)313        decode_all = self._serial_blf_policy == 'all'314        if not selected and not decode_all:315            if not self._classify_unselected:316                return (),'unselected'317            return (), 'unselected' if any(message.frame_id == frame.arbitration_id for message in self._db.messages) else 'unknown'318        message = self._binding_resolver.message_for(frame, selected) if selected else None319        if message is not None:320            try:321                check_decode_length(frame,message.length,322                    'strict' if self.spec.decode_policy == 'legacy' and not self._legacy_blf_decode else self.spec.decode_policy)323            except ValueError as error:324                raise CANTextError(raw.path,raw.line_number,str(error)) from error325        from cantools.database.errors import DecodeError326        self.decode_calls += 1327        if self._serial_blf_policy is not None:328            try:329                values=decode_serial_frame(self._db,frame,message,self._duplicate_ids)330            except (ValueError,KeyError) as error:331                raise CANTextError(raw.path,raw.line_number,'已定义DBC报文解码失败: '+str(error)) from error332        else:333            try:334                values = message.decode(frame.data, decode_choices=False, allow_truncated=True)335            except DecodeError as error:336                if self.spec.decode_policy == 'legacy' and not self._legacy_blf_decode:337                    raise CANTextError(raw.path,raw.line_number,'已定义DBC报文解码失败: '+str(error)) from error338                values = {}339            except (ValueError,KeyError) as error:340                raise CANTextError(raw.path,raw.line_number,'已定义DBC报文解码失败: '+str(error)) from error341        if decode_all:342            # 显式完整诊断保留旧全部值与64KiB限制;正常requested不构造这份副本。343            from ..legacy.records import LegacyDecodedFrame344            LegacyDecodedFrame(self._completed_count+raw.sequence,frame,values)345        if message is not None and self._database_context is not None and any(rule.matches(frame) for rule in self._database_context.rules):346            from ..legacy.binding import field_matches347            names={field.signal for field in self._extra_fields if field_matches(field, frame)}348            self._frame_values=tuple((name,value) for name,value in values.items() if name in names)349            self._frame_layout=tuple((signal.name,int(signal.conversion.numeric_scaled_to_raw(values[signal.name])))350                for signal in message.signals if signal.is_multiplexer and signal.name in values)351        # 同一原字段可有多个分析别名;只交付一次物理值,额外转换由pack_decoded按别名执行。352        return tuple((self._field_keys[field],values.get(field.signal)) for field in dict.fromkeys(selected)),'decoded'353354    def __iter__(self):355        """迭代同次读取。参数: 无。返回: self。异常: 无。356        """357        return self358359    def _time_selected(self, frame):360        """判断此帧是否属于所需通道与ID;不解码、不改时间。361362        参数: frame为已解析原帧。返回: bool;None选择全部帧。异常: 无。363        纯BLF串行按ID沿用旧时间范围,其他来源同时匹配通道;同ID帧型交绑定解析器核对。364        """365        return self._time_frame_ids is None or ((self._serial_blf_policy is not None366            or frame.channel == self.spec.channel) and frame.arbitration_id in self._time_frame_ids)367368    def __next__(self):369        """逐条交付原帧和所需解码值。参数: 无。返回: DecodedCANRecord。异常: EOF/定位数据错/原I/O、预算、取消。370        """371        if self._closed or self._exhausted:372            raise StopIteration373        if self._closing or self._failed:374            raise ResultClosedError('文本CAN读取已失败或正在关闭')375        try:376            if self._check_cancel is not None:377                self._check_cancel()378            if self._database_context is not None:379                self._database_context.require_open()380            if self._db is None:381                self._start()382            while self._input_index < len(self.spec.inputs):383                if self._raw is None:384                    # 实际parser owner在创建成功后安装;失败的未返回owner也必须接管清理。385                    item = self.spec.inputs[self._input_index]386                    factory = ASCRead if type(item) is ASCSpec else BLFRead if type(item) is BLFSpec else CANCSVRead387                    try:388                        self._raw = factory(item,self._budget,check_cancel=self._check_cancel,389                            retain_original=self._trace,retain_source_time=self._retain_source_time or self._trace,390                            **({'flag_resolver':self._binding_resolver.missing_flags,391                                'selected_ids':self._selected_ids} if factory is CANCSVRead else392                               {'time_selector':self._time_selected if self._time_frame_ids is not None else None,393                                'previous_exact_time':self._last_selected_time} if factory is ASCRead else394                               {'emit_frame_ids':self._emit_frame_ids,'channel':None if self._serial_blf_policy is not None else self.spec.channel,395                                'binary_time':self._serial_blf_policy is not None,396                                'previous_order_ns':int(self._last_selected_time.scaleb(9)) if self._serial_blf_policy is not None and self._last_selected_time is not None else None,397                                'projection_only':self.spec.input_check=='auto',398                                'time_scope':self._blf_time_scope,399                                'time_check_all':self._time_frame_ids is None} if self._emit_frame_ids is not None else {'binary_time':True} if factory is BLFRead and self._serial_blf_policy is not None else {}))400                    except BaseException as error:401                        self._raw = (getattr(error,'asc_read_cleanup_owner',None)402                            or getattr(error,'raw_csv_cleanup_owner',None) or getattr(error,'blf_read_cleanup_owner',None))403                        raise404                try:405                    raw = next(self._raw)406                except StopIteration:407                    # 单文件真实EOF才换下一文件;不重排输入、不从头回退另一个parser。408                    self.count = self._completed_count + self._raw.count409                    self._completed_count = self.count410                    # full及all是主动全检;这里只累计auto实际发生的非原生回退。411                    if isinstance(self._raw, BLFRead) and self.spec.input_check == 'auto' and self._serial_blf_policy != 'all':412                        self.full_check_count += self._raw.full_check_count413                    self._blf_observation()414                    if isinstance(self._raw, ASCRead):415                        # full模式的最后元数据时刻也延续;按需模式只保所需帧,空文件不清零。416                        self._last_selected_time = self._raw.last_exact_time417                        first=self._raw.first_exact_time418                        if first is not None and (self.first_order_time is None or first<self.first_order_time):419                            self.first_order_time=first420                    self._raw.close()421                    self._raw = None422                    self._input_index += 1423                    continue424                # 来源范围与时序输入不是一回事:范围取同次读到的极值,不要求无关帧有序。425                if raw.time_ns is not None:426                    self.first_ns = raw.time_ns if self.first_ns is None else min(self.first_ns,raw.time_ns)427                    self.last_ns = raw.time_ns if self.last_ns is None else max(self.last_ns,raw.time_ns)428                self.count = self._completed_count + self._raw.count429                self._blf_observation()430                if not self._time_selected(raw.frame):431                    continue432                # ASC只借当前比较点,避免跨文件舍入为同一ns后漏掉精确回退。433                # 其他格式沿用其原有整数ns精度;不在每条记录中保存双份时间。434                current_time = (self._raw.last_exact_time if isinstance(self._raw, ASCRead)435                                else None if raw.time_ns is None else Decimal(f'{raw.time_ns}e-9'))436                if current_time is not None and self._last_selected_time is not None and current_time < self._last_selected_time:437                    raise CANTextError(raw.path,raw.line_number,'连续文件时间回退,需显式对齐起点')438                self._last_selected_time = current_time439                if self.first_order_time is None:440                    self.first_order_time = current_time441                if self._emit_frame_ids is not None and ((self._serial_blf_policy is None442                        and raw.frame.channel != self.spec.channel) or raw.frame.arbitration_id not in self._emit_frame_ids):443                    continue444                values,status = self._decode(raw)445                if self._post_decode_frame_ids is not None and raw.frame.arbitration_id not in self._post_decode_frame_ids:446                    continue447                result = DecodedCANRecord._from_bound_values(448                    self.spec.name, self._input_index, self._completed_count + raw.sequence,449                    raw, values, status, self._frame_values, self._frame_layout,450                    self._known_field_keys)451                # 来源范围已在筛选前统计;原序号和count包含跳过行,retained_count仅数交付。452                self.retained_count += 1453                return result454            # EOF后释放大DBC图,有限事实保留到显式close,供父层先接收并付费。455            self._db = self._binding_resolver = None456            _release_owned_ram(self,self._dbc_cost,'_dbc_cost')457            self._exhausted = True458            raise StopIteration459        except StopIteration:460            raise461        except BaseException as error:462            self._failed = True463            _cleanup_owner(self,error,'text_can_cleanup_owner','文本CAN读取')464            raise465466    def _blf_observation(self):467        """接收BLF投影前已核的范围和顺序事实。参数: 无。返回: None。异常: 无。468469        文件尾部只有无关帧时也更新来源结束;这些事实不能当成已消费水位。470        """471        if isinstance(self._raw,BLFRead) and self._raw.first_ns is not None:472            self.first_ns=self._raw.first_ns if self.first_ns is None else min(self.first_ns,self._raw.first_ns)473            self.last_ns=self._raw.last_ns if self.last_ns is None else max(self.last_ns,self._raw.last_ns)474            if self._time_frame_ids is None:475                if self.first_order_time is None and self._raw.first_order_ns is not None:476                    self.first_order_time=Decimal(f'{self._raw.first_order_ns}e-9')477                if self._raw.last_order_ns is not None:478                    current=Decimal(f'{self._raw.last_order_ns}e-9')479                    # EOF或当前记录之后的全来源时间,不在当前记录检查之前前移比较点。480                    if self._raw.closed:481                        self._last_selected_time=current482    def close(self):483        """关闭自有解析器及DBC状态,可重试。参数: 无。返回: None。异常: 原关闭/预算错误。484        """485        if self._closed:486            return487        self._closing = True488        # 子文件真实关闭后才撤责任;持续失败仍由本owner保留引用。489        if self._raw is not None:490            self._raw.close()491            self._raw = None492        self._db = self._metadata = self._binding_resolver = None493        self._field_keys = self._known_field_keys = None494        self._time_frame_ids = None495        self._last_selected_time = None496        _release_owned_ram(self,self._dbc_cost,'_dbc_cost')497        _release_owned_ram(self,self._metadata_cost,'_metadata_cost')498        _release_owned_ram(self,self._fixed_cost,'_fixed_cost')499        self._closed = True500501    def __enter__(self):502        """建立显式资源上下文。参数: 无。返回: self。异常: 无。503        """504        return self505506    def __exit__(self, exception_type, error, traceback):507        """保原业务错误并收尾。参数: exception_type/error/traceback异常上下文。返回: False。异常: 正常退出时的关闭错误。508        """509        if error is None:510            self.close()511        else:512            _cleanup_owner(self,error,'text_can_cleanup_owner','文本CAN上下文')513        return False514515516def pack_text_records(definition, records, session_id, backend):517    """把有限已解码原帧交给现有数值核心,不再次解码或重复DBC缩放。518519    参数: definition为SignalDefinition;records为有限DecodedCANRecord tuple;session_id会话;backend为reference/numpy。520    返回: SignalSeries,保精确纳秒、同刻原序号和CANTextSourceRef;调用方负责批次数组的长期预算。521    异常: 来源/声明/记录类型不符,或既有pack_decoded类型、数值转换错误。522    """523    if type(definition) is not SignalDefinition or type(records) is not tuple or any(type(item) is not DecodedCANRecord for item in records):524        raise TypeError('文本CAN归一化需要定义及有限解码记录tuple')525    require_identifier(session_id,'text_can.session_id')526    samples,refs = [],[]527    for record in records:528        if record.source != definition.source:529            raise ValueError('文本CAN记录与信号来源不一致')530        # 未选中帧无该字段,不制造更新;匹配报文实际缺值则None更新,覆盖旧值。531        values = dict(record.values)532        if definition.field not in values:533            continue534        raw = record.raw535        samples.append((raw.time_ns,record.sequence,values[definition.field]))536        refs.append(CANTextSourceRef(session_id,record.source,definition.field,str(raw.path),537            raw.parser_version,raw.line_number,record.input_index,raw.sequence,record.sequence,538            raw.raw_time,raw.time_quantized))539    return pack_decoded(definition,tuple(samples),tuple(refs),backend)540
Chronaxis 用户手册 · 离线阅读