Chronaxis用户手册English
本页目录

chronaxis.execution.raw_signal_groups

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""把三格式共用的已解码更新交给实时完整同刻组消费者,不写历史文件。23只归一化当前声明需要的字段;同刻组跨读取批/连续文件保持,看到下一个相关4时间后才交付上一组,EOF显式finish交付末组。每条待交付样本预付有限预算,5大同刻组超预算明确失败。跨来源调用方必须先按实际时间合并;本组件不重排6原文件、不解码DBC、不查询完整性证明,不把单个来源EOF当整个会话EOF。7"""89from ..core.models import SourceRef10from ..legacy.updates import LegacyDecodedUpdate11from ..results.budget import ResourceBudget, _reserve_owned_ram, _release_owned_ram, _cleanup_owner12from ..results.errors import ResultClosedError13from ..sources.can_spec import can_field_from_key14from ..sources.packing import pack_message_values15from .groups import DecodedTimeGroup16from .history import _metadata_bytes17from .record_sample import _record_from_validated_fields18from .calculation_values import _scalar_from_validated_column19from ..core.numeric import ScalarValue20from ..core.types import QualityFlag as Q,SignalDType as D212223class RawSignalGroups:24    """保存当前同刻的必要更新;组交付后立即释放,消费方另行承担最新值状态。"""2526    def __init__(self, budget, definitions, session_id, consumer, *, backend='numpy',clock_only=()):27        """建立所需帧/信号索引和固定来源引用,不打开原始文件。2829        参数: budget共享账本;definitions仅本次所需信号tuple;session_id会话;30            consumer同步借用完整DecodedTimeGroup;backend选择原归一化内核。31            clock_only只使用对应报文的更新时间,共用无效占位而不逐帧归一化无关值。32        返回: owner。异常: 配置、预算或取消;构造恢复句柄raw_groups_cleanup_owner。33        """34        if not isinstance(budget, ResourceBudget) or type(definitions) is not tuple or not callable(consumer):35            raise TypeError('实时原始信号组需要预算、定义tuple及消费者')36        if backend not in ('reference', 'numpy'):37            raise ValueError('实时原始信号组后端错误')38        self._budget, self._backend, self._session = budget, backend, session_id39        self._held_cost = self._fixed_cost = self._group_cost = self._work_cost = 040        self._closed = self._closing = self._failed = self._finished = False41        self._consumer = self._fields = self._samples = None42        self._time = None43        self._clock_values=None44        self._clock_candidates={}45        self._automatic_names=set()46        try:47            _reserve_owned_ram(self, 65536 + 32 * _metadata_bytes((definitions, session_id)), '_fixed_cost')48            if len({item.name for item in definitions}) != len(definitions):49                raise ValueError('实时原始信号组不能重复声明别名')50            self._fields = {}51            if not set(clock_only)<={item.name for item in definitions}:52                raise ValueError('纯时钟字段必须属于当前组定义')53            self._clock_values={item.name:ScalarValue(None,item.dtype,False,Q.MISSING,54                tuple(dict.fromkeys(label for _,label in item.choices)) if item.dtype is D.ENUM else ())55                for item in definitions if item.name in clock_only}56            for definition in definitions:57                field = can_field_from_key(definition.field)58                # 自动类型需辨别“不是本帧字段”;显式类型的低层缺键仍表示缺值。59                if field.is_extended is None or field.is_fd is None:60                    self._automatic_names.add(definition.name)61                key = (definition.source, field.channel, field.frame_id, field.is_extended, field.is_fd)62                ref = SourceRef(session_id, definition.source, definition.field)63                self._fields.setdefault(key, []).append((definition, field.signal, ref))64            self._fields = {key: tuple(items) for key, items in self._fields.items()}65            self._samples, self._consumer = [], consumer66        except BaseException as exc:67            _cleanup_owner(self, exc, 'raw_groups_cleanup_owner', '实时原始信号组')68            raise69        finally:70            definitions = definition = field = ref = consumer = None7172    def _require_open(self):73        """拒绝失败/关闭后的输入。参数: 无。返回: None。异常: ResultClosedError。"""74        if self._closed or self._closing or self._failed:75            raise ResultClosedError('实时原始信号组已失败或关闭')7677    def configure_metadata(self, facts):78        """借用同次DBC候选识别纯时钟字段,不解码或读取额外记录。7980        参数: facts为已加载字段事实。返回: None。异常: 定位结构错误。81        副作用: 仅保存当前纯时钟字段的有限候选,费用包含在定义固定预算中。82        """83        self._require_open()84        from ..legacy.records import field_from_dict85        # 同物理字段可能有多个时钟别名,按别名共享候选,不把后一别名覆盖掉。86        by_field={field_from_dict(dict(fact['locator'])).key: fact['dbc_frames']87                  for fact in facts if 'dbc_frames' in fact}88        self._clock_candidates={definition.name: by_field[definition.field]89            for items in self._fields.values() for definition, _, _ in items90            if definition.name in self._clock_values and definition.field in by_field}9192    def consume(self, update):93        """接收一条已投影更新,仅所需帧参与时间检查与同刻归组。9495        参数: update为LegacyDecodedUpdate;缺失的匹配字段仍生成无效更新覆盖旧值。96        返回: None。异常: 身份/时间回退、预算、归一化或消费者错误;失败后禁止finish。97        """98        self._require_open()99        if self._finished:100            raise ResultClosedError('实时原始信号组已经结束')101        column = sample = value = fields = None102        primary = None103        try:104            if not isinstance(update, LegacyDecodedUpdate) or update.source_ref.session_id != self._session:105                raise ValueError('更新不属于当前会话')106            # 每种选择器只有一个键;最多查询四种明确/省略类型组合,不扫描全部信号。107            prefix = (update.source, update.channel, update.frame_id)108            fields = tuple(item for extended in (update.is_extended, None) for fd in (update.is_fd, None)109                           for item in self._fields.get((*prefix, extended, fd), ()))110            # 投影会为属于本帧但解码失败的字段保留None;未出现的键才是另一帧的字段。111            # 纯时钟不要求解码值,使用初始化时已有的DBC候选判定所属帧。112            fields = tuple(item for item in fields if113                (item[0].name not in self._clock_values and114                    (item[0].name not in self._automatic_names or item[1] in update.values))115                or (item[0].name in self._clock_values and116                    (item[0].name not in self._clock_candidates or any(117                        extended == update.is_extended and (fd == update.is_fd or fd and not update.is_fd)118                        for extended, fd in self._clock_candidates[item[0].name]))))119            if not fields or update.is_remote or update.is_error:120                return121            if update.time_ns is None:122                raise ValueError('驱动表达式需要相关报文的实际时间')123            if self._time is not None and update.time_ns < self._time:124                raise ValueError('相关报文回退;多来源须先按实际时间有界归并')125            if self._time is not None and update.time_ns > self._time:126                self._flush()127            self._time = update.time_ns128            _reserve_owned_ram(self, 16384 + 4096 * len(fields), '_work_cost')129            for definition, name, ref in fields:130                # 本条缺字段同样归一化为无效;不保原值副本或逐条原帧定位。131                _reserve_owned_ram(self, 8192, '_group_cost')132                if definition.name in self._clock_values:133                    value=self._clock_values[definition.name]134                    array_value=False if definition.dtype is D.BOOL else 0. if definition.dtype is D.FLOAT64 else 0135                else:136                    column = pack_message_values(definition, (update.values.get(name),), self._backend)137                    value = _scalar_from_validated_column(column, 0)138                    array_value=column.values[0].item() if definition.dtype is D.ENUM else None139                # pack_message_values刚完成数值检查;时间/序号来自已校验更新。140                sample = _record_from_validated_fields(definition.name, update.time_ns, update.sequence,141                    value, definition.unit, ref, array_value if definition.dtype is D.ENUM else None)142                self._samples.append(sample)143        except BaseException as exc:144            primary = exc145            self._failed = True146            raise147        finally:148            column = sample = value = update = fields = None149            try:150                _release_owned_ram(self, self._work_cost, '_work_cost')151            except BaseException as cleanup:152                self._failed = True153                if primary is None:154                    raise155                primary.add_note('实时原始信号组临时费用清理失败:' + type(cleanup).__name__)156157    def _flush(self):158        """同步借出完整组后释放本组,不保输出副本。参数: 无。返回: None。异常: 消费/清理错误。"""159        if not self._samples:160            return161        group = None162        try:163            group = DecodedTimeGroup(self._time, tuple(self._samples))164            self._consumer(group)165        finally:166            group = None167        self._samples.clear()168        _release_owned_ram(self, self._group_cost, '_group_cost')169170    def finish(self):171        """所有输入EOF并成功关闭后交付末组,不创建末尾周期。参数: 无。返回: None。异常: 消费/清理错误。"""172        self._require_open()173        if self._finished:174            return175        try:176            self._flush()177            self._finished = True178        except BaseException:179            self._failed = True180            raise181182    def close(self):183        """仅退自有费用,不替调用方完成剩余组。参数: 无。返回: None。异常: 释放失败可重试。"""184        if self._closed:185            return186        self._closing = True187        self._samples = self._fields = self._consumer = self._clock_values = None188        self._clock_candidates={}189        self._automatic_names.clear()190        _release_owned_ram(self, self._work_cost, '_work_cost')191        _release_owned_ram(self, self._group_cost, '_group_cost')192        _release_owned_ram(self, self._fixed_cost, '_fixed_cost')193        self._closed = True194
Chronaxis 用户手册 · 离线阅读