Chronaxis用户手册English
本页目录

chronaxis.legacy.required_records

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""保存按需分析使用的CAN身份和值,允许明确不采集时间。23输入是已匹配原消息的必要身份和所需解码值;输出为冻结模型与有界JSON记录。4时间为None表示没有采集,不能代替零秒。记录不保存载荷、DLC和写出标志,不能5用于原始BLF导出。复用既有严格数值、非有限标签和64KiB限制;无I/O或跨请求状态。6"""78from collections.abc import Mapping9from dataclasses import dataclass10import json11from math import isfinite12from types import MappingProxyType1314from .records import (MAX_WIRE_BYTES, _integer, _shape, _wire_tree, _decoded_values_size,15    _decoded_entry_size, _value_to_wire, _value_from_wire, _unique_object, _reject_constant)161718_FRAME_FIELDS = ('timestamp', 'arbitration_id', 'channel', 'is_extended_id', 'is_fd',19                 'is_remote_frame', 'is_error_frame')202122@dataclass(frozen=True)23class RequiredCANFrame:24    """仅持有所需匹配身份,时间None明确表示未采集;模型不具备原始写出能力。"""2526    timestamp: float | None  # 有限原float秒,或本任务未采集。27    arbitration_id: int  # 原29位以内ID,不含通道。28    channel: int | str | None  # 原通道身份,保持原类型,不猜测。29    is_extended_id: bool  # 原标准/扩展标志。30    is_fd: bool  # 原经典CAN/FD标志。31    is_remote_frame: bool  # 匹配时排除远程帧所需。32    is_error_frame: bool  # 匹配时排除错误帧所需。3334    def __post_init__(self):35        """检查身份及可选时间。参数: 无。返回: None。异常: 原类型或边界错误。"""36        if self.timestamp is not None and (type(self.timestamp) is not float or not isfinite(self.timestamp)):37            raise ValueError('所需报文timestamp需要有限float秒或None')38        _integer(self.arbitration_id, 'arbitration_id', maximum=0x1fffffff)39        if self.channel is not None and type(self.channel) not in (int, str):40            raise TypeError('所需报文channel需要int/str/None')41        if type(self.channel) is str and len(self.channel) > MAX_WIRE_BYTES:42            raise ValueError('所需报文channel文本过长')43        for name in ('is_extended_id', 'is_fd', 'is_remote_frame', 'is_error_frame'):44            if type(getattr(self, name)) is not bool:45                raise TypeError(name + '需要bool')464748def required_frame_to_dict(frame):49    """输出固定身份字段。参数: frame为RequiredCANFrame。返回: dict。异常: TypeError。"""50    if type(frame) is not RequiredCANFrame:51        raise TypeError('需要RequiredCANFrame')52    return {name: getattr(frame, name) for name in _FRAME_FIELDS}535455@dataclass(frozen=True)56class RequiredDecodedFrame:57    """持有一次目标原序号和冻结所需值;不根据未采集的时间建立时序坐标。"""5859    sequence: int  # 原yield序号,可有无关帧形成的间隙。60    frame: RequiredCANFrame  # 必要身份与可选时间。61    values: Mapping[str, int | float | bool]  # 缺键保持缺失,诊断非有限值保留。6263    def __post_init__(self):64        """先检查上界再复制外部Mapping。参数: 无。返回: None。异常: 类型、编码或超限错误。"""65        _integer(self.sequence, 'sequence', maximum=(1 << 64)-1)66        if type(self.frame) is not RequiredCANFrame or not isinstance(self.values, Mapping):67            raise TypeError('按需记录需要RequiredCANFrame与数值Mapping')68        # 外壳与值共用64KiB上限;复制时继续以这次精确余量防止可变Mapping扩大前缀。69        base = _wire_tree({'op': 'item', 'request_id': self.sequence,70            'record': {'sequence': self.sequence, 'frame': required_frame_to_dict(self.frame), 'values': {}}})71        available = MAX_WIRE_BYTES-base+272        _decoded_values_size(self.values, available)73        copied, used = {}, 274        for name, value in self.values.items():75            # 外部Mapping两次遍历可变化,每个插入仍须验证;常见worker原生字典复用上界。76            separator = 1 if copied else 077            used += separator + _decoded_entry_size(name, value, available-used-separator)78            copied[name] = value79        object.__setattr__(self, 'values', MappingProxyType(copied))808182def required_to_dict(record):83    """编码固定按需记录。参数: record为RequiredDecodedFrame。返回: dict。异常: 类型或值错误。"""84    if type(record) is not RequiredDecodedFrame:85        raise TypeError('需要RequiredDecodedFrame')86    return {'sequence': record.sequence, 'frame': required_frame_to_dict(record.frame),87            'values': {name: _value_to_wire(value) for name, value in record.values.items()}}888990def required_from_dict(value):91    """从独立JSON树恢复按需记录。参数: value为固定dict。返回: 冻结模型。异常: 形状、类型或限额错误。"""92    _shape(value, ('sequence', 'frame', 'values'), 'required decoded frame')93    _wire_tree(value)94    _shape(value['frame'], _FRAME_FIELDS, 'required CAN frame')95    if type(value['values']) is not dict:96        raise TypeError('required decoded values需要dict')97    return RequiredDecodedFrame(value['sequence'], RequiredCANFrame(**value['frame']),98        {name: _value_from_wire(item) for name, item in value['values'].items()})99100101def required_from_packet(payload):102    """严格解析单条按需JSON,固定模型检查代替同一树的重复通用遍历。103104    参数: payload为64KiB内bytes。返回: 独立冻结模型。105    异常: UTF-8、重复键、深度、裸非有限常量或模型错误;不解任意对象标签。106    """107    parsed = values = frame = None108    try:109        if type(payload) is not bytes or not 0 < len(payload) <= MAX_WIRE_BYTES:110            raise ValueError('按需记录需要65536字节内bytes')111        parsed = json.loads(payload.decode('utf-8'), object_pairs_hook=_unique_object,112                            parse_constant=_reject_constant)113        _shape(parsed, ('sequence', 'frame', 'values'), 'required decoded frame')114        _shape(parsed['frame'], _FRAME_FIELDS, 'required CAN frame')115        if type(parsed['values']) is not dict:116            raise TypeError('required decoded values需要dict')117        frame = RequiredCANFrame(**parsed['frame'])118        values = {name: _value_from_wire(item) for name, item in parsed['values'].items()}119        # 所有可接受容器都是固定浅层结构;未知嵌套先拒绝,不需要再次遍历JSON树。120        return RequiredDecodedFrame(parsed['sequence'], frame, values)121    finally:122        payload = parsed = values = frame = None123
Chronaxis 用户手册 · 离线阅读