Chronaxis用户手册English
本页目录

chronaxis.execution.can_block_codec

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""普通 CAN 有界数据块;只恢复固定记录,绝不加载任意 Python 对象。23CND1 头为 4 字节版本和网络序 uint32 主体长度。主体先存字符串表,再按4固定字段依次存块、来源和原帧。字符串只在本块共享;原数值保留位型和整数精度。5"""6from decimal import Decimal7from pathlib import Path8import struct910LIMIT = 52428811ROWS = 6412_HEADER = struct.Struct('!4sI')13_U32 = struct.Struct('!I')14_I64 = struct.Struct('!q')15_F64 = struct.Struct('!d')16_STATUSES = ('decoded', 'unknown', 'unselected', 'non_data')17_FLAGS = ('is_extended_id', 'is_remote_frame', 'is_error_frame',18          'is_fd', 'bitrate_switch', 'error_state_indicator')192021class CANBlockError(ValueError):22    """固定 CAN 编码结构不完整或不合法。"""232425class _Writer:26    """单块临时编码空间;所有增长先核原有包长上限。"""2728    def __init__(self):29        """建立单块临时空间。3031        参数: 无。32        返回: 新编码器。33        异常: 内存分配错误。34        """35        self.body = bytearray()36        self.strings = []37        self.indices = {}38        self.string_bytes = 43940    def add(self, data):41        """追加已编码字节并先核包长。4243        参数: data为bytes类片段。44        返回: None。45        异常: 超过512KiB时CANBlockError。46        """47        if len(self.body) + self.string_bytes + len(data) + 8 > LIMIT:48            raise CANBlockError('CAN数据块超过512KiB')49        self.body.extend(data)5051    def byte(self, value):52        """写一个uint8。5354        参数: value为0到255整数。55        返回: None。56        异常: 范围或包长错误。57        """58        self.add(bytes((value,)))5960    def uint(self, value):61        """写网络序uint32。6263        参数: value为0到2**32-1整数。64        返回: None。65        异常: 范围或包长错误。66        """67        self.add(_U32.pack(value))6869    def text(self, value):70        """写本块字符串表索引。7172        参数: value为原生str。73        返回: None,首次出现时登记UTF8/surrogatepass内容。74        异常: 类型或包长错误。75        """76        if type(value) is not str:77            raise CANBlockError('CAN文本字段必须是str')78        index = self.indices.get(value)79        if index is None:80            encoded = value.encode('utf-8', 'surrogatepass')81            if len(self.body) + self.string_bytes + len(encoded) + 16 > LIMIT:82                raise CANBlockError('CAN字符串表超过512KiB')83            index = len(self.strings)84            self.indices[value] = index85            self.strings.append(encoded)86            self.string_bytes += 4 + len(encoded)87        self.uint(index)8889    def scalar(self, value):90        # bool 单独编码;大整数不经过 float,也不把非有限原值替换成 None。91        """无损写有限种类原值。9293        参数: value为None/bool/int/float,原float允许NaN/Inf。94        返回: None。95        异常: 未支持类型或包长错误。96        """97        kind = type(value)98        if value is None:99            self.byte(0)100        elif kind is bool:101            self.byte(2 if value else 1)102        elif kind is int:103            self.byte(3 if value >= 0 else 4)104            width = max(1, (abs(value).bit_length() + 7) // 8)105            if width > LIMIT:106                raise CANBlockError('CAN整数超过块长')107            self.uint(width)108            self.add(abs(value).to_bytes(width, 'big'))109        elif kind is float:110            self.byte(5)111            self.add(_F64.pack(value))112        else:113            raise CANBlockError('CAN原值只接受int/float/bool/None')114115    def optional_text(self, value):116        """写可空文本。117118        参数: value为str或None。119        返回: None。120        异常: 类型或包长错误。121        """122        self.byte(value is not None)123        if value is not None:124            self.text(value)125126    def pairs(self, values):127        """写字段值对。128129        参数: values为已经校验的名称和值对序列。130        返回: None。131        异常: 字段类型或包长错误。132        """133        self.uint(len(values))134        for name, value in values:135            self.text(name)136            self.scalar(value)137138    def decimal(self, value):139        """写精确比较时间事实。140141        参数: value为有限Decimal或None。142        返回: None,不按指数展开文本。143        异常: 类型、非有限值或包长错误。144        """145        self.byte(value is not None)146        if value is not None:147            if type(value) is not Decimal or not value.is_finite():148                raise CANBlockError('CAN精确时间事实需要有限Decimal')149            sign, digits, exponent = value.as_tuple()150            self.byte(sign)151            self.scalar(exponent)152            self.uint(len(digits))153            self.add(bytes(digits))154155    def finish(self, magic=b'CND1'):156        """拼接单一头、字符串表和主体。157158        参数: magic为内部写定的CND1或CMT1版本。159        返回: 对应固定格式的完整bytes。160        异常: 超过512KiB时CANBlockError。161        """162        table = bytearray(_U32.pack(len(self.strings)))163        for value in self.strings:164            table.extend(_U32.pack(len(value)))165            table.extend(value)166        size = len(table) + len(self.body)167        if size + 8 > LIMIT:168            raise CANBlockError('CAN数据块超过512KiB')169        table.extend(self.body)170        if magic not in (b'CND1',b'CMT1'):171            raise CANBlockError('内部CAN格式未知')172        return _HEADER.pack(magic, size) + table173174175class _Reader:176    """只按固定顺序取字段;长度检查在切片或容器分配之前执行。"""177178    def __init__(self, packet, expected=b'CND1', *, on_expand=None):179        """核固定头并读取本块字符串表。180181        参数: packet为完整bytes,expected为本固定入口写定的版本;on_expand在模型新增分配前核预约。182        返回: 新读取器,拥有游标及局部展开计数。183        异常: 版本、长度、编码或展开上限错误。184        """185        if type(packet) is not bytes or not 12 <= len(packet) <= LIMIT:186            raise CANBlockError('CAN数据块长度非法')187        magic, size = _HEADER.unpack_from(packet)188        if expected not in (b'CND1',b'CMT1') or magic != expected or size != len(packet) - 8:189            raise CANBlockError('CAN数据块版本或声明长度不符')190        self.data = memoryview(packet)191        # 数据块仍用原6MiB模型部分。READY固定图最多4096字段、8195文本、192        # 每字段3个整数;4*包长 + 128*8195 + (1024+3*64)*4096 < 8MiB。193        # READY的原生图和冻结图并存另由父on_expand在分配前预约,8MiB不是其总峰值。194        self.expansion_limit = (8 if expected==b'CMT1' else 6)*1048576195        self.on_expand = on_expand196        self.expansion = 0197        self.position = 8198        self.strings = []199        value = None200        try:201            count = self.uint()202            if count > self.remaining // 4 or expected==b'CMT1' and count>8195:203                raise CANBlockError('CAN字符串表计数超出剩余包')204            self.charge(count * 128)205            for _ in range(count):206                size = self.uint()207                self.charge(size * 4)208                try:209                    value = bytes(self.take(size)).decode('utf-8', 'surrogatepass')210                except UnicodeDecodeError as error:211                    raise CANBlockError('CAN字符串不是有效内部UTF8') from error212                self.strings.append(value)213        except BaseException:214            self.close()215            value = packet = None216            raise217218    def close(self):219        """清除仅属解码器的视图与字符串表引用。220221        参数: 无。返回: None;返回模型继续持有其实际使用的不可变文本。222        异常: 无,重复调用无副作用。223        """224        if self.data is not None:225            self.data.release()226            self.data = None227        self.strings.clear()228        self.on_expand = None229230    def charge(self, amount):231        """累计本块模型展开上界。232233        参数: amount为本次新增保守字节数。234        返回: None,不改变ResourceBudget账本。235        异常: 超过本固定图边界时CANBlockError;父预约不足或取消原样传播。236        """237        candidate = self.expansion + amount238        if candidate > self.expansion_limit:239            raise CANBlockError('CAN解码展开超过固定图上界')240        if self.on_expand is not None:241            self.on_expand(candidate)242        self.expansion = candidate243244    @property245    def remaining(self):246        """取得未消费字节数。247248        参数: 无。249        返回: 非负int。250        异常: 无。251        """252        return len(self.data) - self.position253254    def take(self, size):255        """取得有界字节视图并移动游标。256257        参数: size为非负字节数。258        返回: memoryview,不复制包主体。259        异常: 长度越界或截断时CANBlockError。260        """261        if size < 0 or size > self.remaining:262            raise CANBlockError('CAN数据块截断')263        offset = self.position264        self.position += size265        return self.data[offset:self.position]266267    def byte(self):268        """读一个uint8。269270        参数: 无。271        返回: int。272        异常: 截断时CANBlockError。273        """274        position = self.position275        if position >= len(self.data):276            raise CANBlockError('CAN数据块截断')277        value = self.data[position]278        self.position = position + 1279        return value280281    def uint(self):282        """读网络序uint32。283284        参数: 无。285        返回: int。286        异常: 截断时CANBlockError。287        """288        position = self.position289        if position + 4 > len(self.data):290            raise CANBlockError('CAN数据块截断')291        value = _U32.unpack_from(self.data, position)[0]292        self.position = position + 4293        return value294295    def boolean(self):296        """读严格布尔位。297298        参数: 无。299        返回: bool。300        异常: 非0/1或截断时CANBlockError。301        """302        value = self.byte()303        if value not in (0, 1):304            raise CANBlockError('CAN布尔字段非法')305        return bool(value)306307    def text(self):308        """取本块已有字符串。309310        参数: 无。311        返回: str,不复制共享文本。312        异常: 索引越界或截断时CANBlockError。313        """314        index = self.uint()315        if index >= len(self.strings):316            raise CANBlockError('CAN字符串索引越界')317        return self.strings[index]318319    def scalar(self):320        """读固定种类数值。321322        参数: 无。323        返回: None/bool/int/float,保留原位型。324        异常: 未知标记、非最短整数、展开或截断错误。325        """326        tag = self.byte()327        if tag == 0:328            return None329        if tag in (1, 2):330            return tag == 2331        if tag in (3, 4):332            width = self.uint()333            if not 1 <= width <= self.remaining:334                raise CANBlockError('CAN整数长度非法')335            self.charge(width * 2 + 64)336            raw = self.take(width)337            value = None338            try:339                if width > 1 and raw[0] == 0:340                    raise CANBlockError('CAN整数不能使用非最短表示')341                value = int.from_bytes(raw, 'big')342                if tag == 4 and value == 0:343                    raise CANBlockError('CAN整数不能使用负零表示')344                return value if tag == 3 else -value345            finally:346                # 内层格式错误的异常栈不能通过切片视图继续持有整包。347                raw.release()348                raw = value = None349        if tag == 5:350            return _F64.unpack(self.take(8))[0]351        raise CANBlockError('CAN原值标记未知')352353    def nonnegative(self):354        """读原生非负序号。355356        参数: 无。357        返回: int。358        异常: 负数、bool、其他类型或截断错误。359        """360        value = self.scalar()361        if type(value) is not int or value < 0:362            raise CANBlockError('CAN序号需要非负整数')363        return value364365    def optional_text(self):366        """读可空文本。367368        参数: 无。369        返回: str或None。370        异常: 存在位、索引或截断错误。371        """372        return self.text() if self.boolean() else None373374    def pairs(self, *, maximum=4096):375        """读有限字段对。376377        参数: maximum为条数上限,默认4096。378        返回: tuple名称值对。379        异常: 计数、展开、标记或截断错误。380        """381        count = self.uint()382        if count > maximum or count > self.remaining // 5:383            raise CANBlockError('CAN字段对计数超限')384        self.charge(count * 256)385        return tuple((self.text(), self.scalar()) for _ in range(count))386387    def decimal(self):388        """读精确比较时间事实。389390        参数: 无。391        返回: 有限Decimal或None。392        异常: 符号、数字、指数、展开或截断错误。393        """394        if not self.boolean():395            return None396        sign = self.byte()397        exponent = self.scalar()398        count = self.uint()399        if sign not in (0, 1) or type(exponent) is not int or not count:400            raise CANBlockError('CAN Decimal结构非法')401        self.charge(count * 16 + 256)402        digits = self.take(count)403        value = None404        try:405            if any(value > 9 for value in digits):406                raise CANBlockError('CAN Decimal数字非法')407            try:408                value = Decimal((sign, tuple(digits), exponent))409            except (ValueError, OverflowError) as error:410                raise CANBlockError('CAN Decimal指数非法') from error411            if not value.is_finite():412                raise CANBlockError('CAN Decimal必须有限')413            return value414        finally:415            digits.release()416            digits = value = exponent = None417418419def _write_frame(writer, frame):420    """写原帧固定公开字段。421422    参数: writer块编码器,frame为已知原帧。423    返回: None。424    异常: 字段或包长错误。425    """426    writer.add(_F64.pack(frame.timestamp))427    writer.uint(frame.arbitration_id)428    writer.byte(sum(int(getattr(frame, name)) << index for index, name in enumerate(_FLAGS)))429    writer.byte(2 if frame.is_rx is None else int(frame.is_rx))430    writer.byte(type(frame.channel) is str)431    if type(frame.channel) is str:432        writer.text(frame.channel)433    else:434        writer.scalar(frame.channel)435    writer.scalar(frame.dlc)436    writer.uint(len(frame.data))437    writer.add(frame.data)438439440def _read_frame(reader, timed):441    """恢复已知原帧类型。442443    参数: reader块读取器,timed指是否为有时间RawCANRecord。444    返回: LegacyRawFrame或已知can.Message。445    异常: 固定字段或原模型校验错误,无时间模式不增加时间有限性检查。446    """447    timestamp = _F64.unpack(reader.take(8))[0]448    frame_id, flags, direction = reader.uint(), reader.byte(), reader.byte()449    if flags & ~63 or direction not in (0, 1, 2):450        raise CANBlockError('CAN原帧标志非法')451    channel = reader.text() if reader.boolean() else reader.scalar()452    if channel is not None and type(channel) not in (int, str):453        raise CANBlockError('CAN通道字段非法')454    dlc = reader.scalar()455    size = reader.uint()456    if size > 64:457        raise CANBlockError('CAN载荷超过64字节')458    arguments = dict(timestamp=timestamp, arbitration_id=frame_id, channel=channel,459                     dlc=dlc, data=bytes(reader.take(size)),460                     is_rx=None if direction == 2 else bool(direction),461                     **{name: bool(flags & (1 << index)) for index, name in enumerate(_FLAGS)})462    if timed:463        from ..legacy.records import LegacyRawFrame464        return LegacyRawFrame(**arguments)465    # 没有时间消费者的 BLF 不额外执行有限时间检查;只能恢复这个已知帧类型。466    from ..can_dependencies import import_can_dependency467    return import_can_dependency('can').Message(**arguments, check=False)468469470def encode_block(number, records, eof, facts):471    """编码一个已校验CAN块。472473    参数: number非负块号,records最多64条,eof真实结束,facts六项文件事实。474    返回: CND1 bytes。475    异常: 数量、字段或原512KiB包长错误。476    """477    from ..sources.raw_can import RawCANRecord478    if type(number) is not int or number < 0 or type(eof) is not bool or len(records) > ROWS:479        raise CANBlockError('CAN块号、EOF或条数非法')480    writer = _Writer()481    writer.scalar(number)482    writer.byte(eof)483    writer.uint(len(records))484    for record in records:485        writer.text(record.source)486        writer.scalar(record.input_index)487        writer.scalar(record.sequence)488        writer.byte(_STATUSES.index(record.status))489        raw = record.raw490        timed = type(raw) is RawCANRecord491        writer.byte(timed)492        _write_frame(writer, raw.frame)493        writer.text(str(raw.path))494        writer.scalar(raw.sequence)495        if timed:496            writer.add(_I64.pack(raw.time_ns))497            writer.scalar(raw.line_number)498            writer.optional_text(raw.raw_time)499            writer.text(raw.parser_version)500            writer.scalar(raw.dlc_code)501            writer.byte(raw.time_quantized)502            writer.scalar(raw.source_time_ns)503        writer.pairs(record.values)504        writer.pairs(record.frame_values)505        writer.pairs(record.frame_layout)506    expected = {'count', 'first_ns', 'last_ns', 'decode_calls', 'first_order_time', 'last_order_time'}507    if set(facts) != expected:508        raise CANBlockError('CAN文件事实字段不符')509    for name in ('count', 'first_ns', 'last_ns', 'decode_calls'):510        writer.scalar(facts[name])511    for name in ('first_order_time', 'last_order_time'):512        writer.decimal(facts[name])513    return writer.finish()514515516def decode_block(packet):517    """恢复一个固定CAN块;失败只保留原诊断,不保留已经解码的整批记录。518519    参数: packet为完整CND1 bytes。520    返回: 块号、records列表、EOF和facts字典;成功时记录所有权交给调用方。521    异常: 结构、模型、展开、截断或尾随字节错误;保留原异常与原因链。522    """523    from ..sources.raw_can import RawCANRecord524    from ..sources.blf import _UntimedBLFRecord525    from ..sources.text_can_decode import DecodedCANRecord526    from ..sources.can_spec import can_field_from_key527    reader = None528    records = []529    frame = raw = facts = source = path = None530    known_fields = values = frame_values = frame_layout = pair = key = None531    try:532        reader = _Reader(packet)533        number, eof, count = reader.nonnegative(), reader.boolean(), reader.uint()534        if count > ROWS:535            raise CANBlockError('CAN记录数超过64')536        reader.charge(count * 2048)537        if count:538            # 集合只引用本块已有字符串;基础空间及扩容峰值在实际分配前计入6MiB。539            reader.charge(512)540            known_fields = set()541        for _ in range(count):542            source, input_index, sequence = reader.text(), reader.nonnegative(), reader.nonnegative()543            status = reader.byte()544            if status >= len(_STATUSES):545                raise CANBlockError('CAN状态标记未知')546            timed = reader.boolean()547            frame = _read_frame(reader, timed)548            path, raw_sequence = Path(reader.text()), reader.nonnegative()549            if timed:550                raw = RawCANRecord(frame, _I64.unpack(reader.take(8))[0], path,551                    reader.scalar(), raw_sequence, reader.optional_text(), reader.text(),552                    reader.scalar(), reader.boolean(), reader.scalar())553            else:554                raw = _UntimedBLFRecord(frame, path, raw_sequence)555            values, frame_values, frame_layout = reader.pairs(), reader.pairs(), reader.pairs()556            # 只有values中的规范CAN键需要完整解析;原物理字段名称不套用这条规则。557            # 私有坏包先核实际出现的键,不承诺与原pickle恢复有相同的首个错误。558            for pair in values:559                key = pair[0]560                if key not in known_fields:561                    can_field_from_key(key)562                    reader.charge(192)563                    known_fields.add(key)564            records.append(DecodedCANRecord._from_bound_values(source, input_index, sequence, raw,565                values, _STATUSES[status], frame_values, frame_layout, known_fields))566        facts = {name: reader.scalar() for name in ('count', 'first_ns', 'last_ns', 'decode_calls')}567        if any(type(facts[name]) is not int or facts[name] < 0 for name in ('count', 'decode_calls')):568            raise CANBlockError('CAN文件计数非法')569        if any(value is not None and (type(value) is not int or not -(2**63) <= value < 2**63)570               for value in (facts['first_ns'], facts['last_ns'])):571            raise CANBlockError('CAN文件纳秒事实非法')572        for name in ('first_order_time', 'last_order_time'):573            facts[name] = reader.decimal()574        if reader.remaining:575            raise CANBlockError('CAN数据块含未消费字节')576        return number, records, eof, facts577    except BaseException:578        # 当前模型的构造诊断可保留该条记录;已经完成但尚未交付的其他记录不属于诊断。579        records.clear()580        raise581    finally:582        if known_fields is not None:583            known_fields.clear()584        if reader is not None:585            reader.close()586        # 成功的返回值已经持有所需模型;这里只撤销当前调用的引用,不修改交付列表。587        packet = records = frame = raw = facts = source = path = None588        known_fields = values = frame_values = frame_layout = pair = key = None589590591_METADATA_KEYS = {'profile','dbc_path','dbc_sha256','fields','decode_choices','allow_truncated',592    'selected_length_check','decode_scope','cantools_version','record_check_scope','time_order_check_scope'}593594595def _metadata(value):596    """核当前CAN读取器字段事实。597598    参数: value为固定元数据映射。599    返回: 原生dict。600    异常: 缺项、未知字段或类型不符。601    """602    item=hint=None603    try:604        from ..legacy.records import field_from_dict605        # 固定字段已经单独检查;只读校验不再复制整份元数据容器。606        if type(value) is not dict or set(value) != _METADATA_KEYS:607            raise CANBlockError('CAN元数据根字段不符')608        if (value['profile'] != 'can-text-v1' or value['decode_choices'] is not False609                or value['allow_truncated'] is not True or value['decode_scope'] != 'requested'610                or value['selected_length_check'] not in ('legacy','partial','exact')611                or value['record_check_scope'] not in ('source','selected_ids')612                or value['time_order_check_scope'] not in ('source','selected_ids','none')613                or type(value['fields']) not in (list,tuple) or len(value['fields']) > 4096):614            raise CANBlockError('CAN元数据政策或字段数量非法')615        for name in ('dbc_path','cantools_version'):616            if type(value[name]) is not str:617                raise CANBlockError('CAN元数据文本非法')618        digest = value['dbc_sha256']619        if digest is not None and (type(digest) is not str or len(digest)!=64620                                  or any(c not in '0123456789abcdef' for c in digest)):621            raise CANBlockError('CAN元数据摘要非法')622        for item in value['fields']:623            allowed = {'locator','unit','dbc_frames','frame_format_compatibility','cycle_ns'}624            if type(item) is not dict or not {'locator','unit'} <= set(item) or not set(item)<=allowed:625                raise CANBlockError('CAN字段事实结构非法')626            field_from_dict(item['locator'])627            if item['unit'] is not None and type(item['unit']) is not str:628                raise CANBlockError('CAN单位事实非法')629            if 'cycle_ns' in item and (type(item['cycle_ns']) is not int or not 0<item['cycle_ns']<2**63):630                raise CANBlockError('CAN周期事实非法')631            if 'dbc_frames' in item:632                if type(item['dbc_frames']) not in (list,tuple) or any(type(pair) not in (list,tuple) or len(pair)!=2633                        or any(type(flag) is not bool for flag in pair) for pair in item['dbc_frames']):634                    raise CANBlockError('CAN DBC帧型事实非法')635            if 'frame_format_compatibility' in item:636                hint=item['frame_format_compatibility']637                if (type(hint) is not dict or set(hint)!={'code','dbc_is_fd','requested_is_fd'}638                        or hint['code']!='dbc_fd_classic_selector' or hint['dbc_is_fd'] is not True639                        or hint['requested_is_fd'] is not False):640                    raise CANBlockError('CAN帧型兼容事实非法')641        return value642    finally:643        value=item=hint=None644645646def encode_metadata(value):647    """编码READY事实,并复用单块字符串表避免宽字段的重复JSON键膨胀。648649    参数: value为当前固定元数据。650    返回: CMT1 bytes,仍受原512KiB上限。651    异常: 字段、类型或包长错误。652    """653    value=_metadata(value)654    writer=_Writer()655    writer.text(value['dbc_path'])656    writer.optional_text(value['dbc_sha256'])657    writer.text(value['cantools_version'])658    writer.byte(('legacy','partial','exact').index(value['selected_length_check']))659    writer.byte(('source','selected_ids').index(value['record_check_scope']))660    writer.byte(('source','selected_ids','none').index(value['time_order_check_scope']))661    writer.uint(len(value['fields']))662    for item in value['fields']:663        locator=item['locator']664        writer.scalar(locator['channel'])665        writer.text(locator['signal'])666        writer.scalar(locator['frame_id'])667        for key in ('is_extended','is_fd'):668            writer.byte(2 if locator.get(key) is None else int(locator[key]))669        writer.optional_text(item['unit'])670        flags=sum(bit for name,bit in (('dbc_frames',1),('frame_format_compatibility',2),('cycle_ns',4)) if name in item)671        writer.byte(flags)672        if flags&1:673            frames=item['dbc_frames']674            if len(frames)>4:675                raise CANBlockError('DBC帧型组合超过四种')676            writer.byte(len(frames))677            for extended,fd in frames:678                writer.byte(int(extended)|(int(fd)<<1))679        if flags&4:680            writer.scalar(item['cycle_ns'])681    return writer.finish(b'CMT1')682683684def decode_metadata(packet, *, on_expand=None):685    """恢复固定READY事实;只按明确字段解释CMT1,不支持旧pickle。686687    参数: packet为完整CMT1 bytes;on_expand在新增固定模型分配前核父端实际额度。688    返回: 固定元数据dict,随后由既有模型冻结。689    异常: 版本、字段、包长、索引、展开或截断错误。690    """691    reader=None692    value=fields=item=locator=frames=None693    try:694        reader=_Reader(packet,b'CMT1',on_expand=on_expand)695        value=dict(profile='can-text-v1',dbc_path=reader.text(),dbc_sha256=reader.optional_text(),696            cantools_version=reader.text(),decode_choices=False,allow_truncated=True,decode_scope='requested')697        for name,choices in (698                ('selected_length_check',('legacy','partial','exact')),699                ('record_check_scope',('source','selected_ids')),700                ('time_order_check_scope',('source','selected_ids','none'))):701            index=reader.byte()702            if index>=len(choices):703                raise CANBlockError('CAN元数据策略索引非法')704            value[name]=choices[index]705        count=reader.uint()706        if count>4096 or count>reader.remaining//10:707            raise CANBlockError('CAN元数据字段数量非法')708        reader.charge(count*1024)709        fields=[]710        for _ in range(count):711            locator=dict(channel=reader.nonnegative(),signal=reader.text(),frame_id=reader.nonnegative())712            for key in ('is_extended','is_fd'):713                flag=reader.byte()714                if flag not in (0,1,2):715                    raise CANBlockError('CAN定位帧型标志非法')716                if flag!=2:717                    locator[key]=bool(flag)718            item=dict(locator=locator,unit=reader.optional_text())719            flags=reader.byte()720            if flags&~7:721                raise CANBlockError('CAN字段事实标志未知')722            if flags&1:723                combinations=reader.byte()724                if combinations>4:725                    raise CANBlockError('DBC帧型组合超过四种')726                frames=[]727                for _ in range(combinations):728                    pair=reader.byte()729                    if pair>3:730                        raise CANBlockError('DBC帧型组合非法')731                    frames.append([bool(pair&1),bool(pair&2)])732                item['dbc_frames']=frames733            if flags&2:734                item['frame_format_compatibility']=dict(code='dbc_fd_classic_selector',dbc_is_fd=True,requested_is_fd=False)735            if flags&4:736                item['cycle_ns']=reader.nonnegative()737            fields.append(item)738        value['fields']=fields739        if reader.remaining:740            raise CANBlockError('CAN元数据含未消费字节')741        return _metadata(value)742    finally:743        if reader is not None:744            reader.close()745        packet=value=fields=item=locator=frames=None746
Chronaxis 用户手册 · 离线阅读