Chronaxis用户手册English
本页目录

chronaxis.sources.can_updates

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""把三种原始报文入口的必要值交付给同一个逐消息计算模型。23投影只保存有限字段定位和可共享的来源引用。BLF交付的键是DBC信号名,4文本入口交付的键是规范字段;转换后均为原DBC信号名,不保留载荷副本、5原时间字符串或第二套时间。LegacyDecodedUpdate是已有计算模型的名称,6使用它不代表复现旧程序的缓存行为。7"""89from types import MappingProxyType1011from ..core.models import SourceRef12from ..core.validation import require_identifier13from ..legacy.records import LegacyCANField14from ..legacy.updates import LegacyDecodedUpdate, LegacyUntimedUpdate151617def require_message_keys(keys, *, channel=None):18    """校验有限触发声明。参数: keys完整定位tuple、可选来源通道。返回: 原tuple。异常: ValueError。"""19    if type(keys) is not tuple or len(keys) > 4096:20        raise ValueError('消息触发定位需要最多4096项tuple')21    for key in keys:22        if (type(key) is not tuple or len(key) != 4 or type(key[0]) is not int or key[0] < 023                or type(key[1]) is not int or key[2] is not None and type(key[2]) is not bool24                or key[3] is not None and type(key[3]) is not bool25                or not 0 <= key[1] <= (0x7ff if key[2] is False else 0x1fffffff)26                or channel is not None and key[0] != channel):27            raise ValueError('消息触发定位需要合法通道、ID、扩展及FD标志,且属于当前来源通道')28    return keys293031class CANUpdateProjection:32    """按完整报文身份索引所需字段;同一帧的多个别名只传一次物理值。"""3334    def __init__(self, source, session_id, fields, *, message_keys=(), clock_fields=()):35        """建立有限投影,不读取DBC或输入文件。3637        参数: source/session_id为来源和会话;fields为所需LegacyCANField元组;38            message_keys为无值公式仍需触发的(channel,id,extended,fd)元组;39            clock_fields只借DBC候选确定驱动帧,不加入值解码或值输出。40        返回: None;初始化实例状态。异常: TypeError/ValueError为字段、身份或同帧信号定位冲突。41        """42        if (type(fields) is not tuple or type(clock_fields) is not tuple43                or any(type(field) is not LegacyCANField for field in (*fields,*clock_fields))):44            raise TypeError('消息投影需要LegacyCANField tuple')45        require_identifier(source, 'message.source')46        require_identifier(session_id, 'message.session_id')47        groups = {}48        for key in require_message_keys(message_keys):49            groups.setdefault(key, {})50        self._value_fields = frozenset(fields)51        all_fields = tuple(dict.fromkeys((*fields, *clock_fields)))52        # 声明固定后规范键不再变化;在既有字段空间内共享,避免逐报文重新编码JSON。53        self._canonical_keys = MappingProxyType({field: field.key for field in all_fields})54        for field in all_fields:55            key = (field.channel, field.frame_id, field.is_extended, field.is_fd)56            group = groups.setdefault(key, {})57            if field.signal in group and group[field.signal] != field:58                raise ValueError('同一报文信号存在不同定位')59            group[field.signal] = field60        # 引用只标识必需的来源及代表字段,逐帧序号由已有更新模型保存一次。61        self._groups = MappingProxyType({key: (tuple(group.values()),62            SourceRef(session_id, source, self._canonical_keys[next(iter(group.values()))] if group else '__raw_message__'))63            for key, group in groups.items()})64        self.source = source65        self._seen = {}66        self._resolved = {}67        self._dbc_frames = {}68        self.compatibility_fields = {}6970    def configure_metadata(self, facts):71        """安装同次DBC的有限候选。参数: facts字段事实。返回: None。异常: 非法定位错误。72        """73        from ..legacy.records import field_from_dict74        self._dbc_frames = {field_from_dict(dict(fact['locator'])): tuple(tuple(pair) for pair in fact['dbc_frames'])75                            for fact in facts if 'dbc_frames' in fact}7677    def for_read(self, facts):78        """为单次读取隔离已见状态。参数: facts同次DBC事实。返回: 新投影。异常: 元数据错误。79        """80        from copy import copy81        result = copy(self)82        result._seen = {}83        result._resolved = {}84        result.compatibility_fields = {}85        result.configure_metadata(facts)86        return result8788    def _matching(self, frame):89        """匹配四种可选选择器,记录唯一实际身份。参数: frame。返回: 字段和引用或None。异常: 迟到歧义。90        """91        if frame.is_remote_frame or frame.is_error_frame:92            return None93        frame_key = (frame.channel, frame.arbitration_id, frame.is_extended_id, frame.is_fd)94        if frame_key in self._resolved:95            return self._resolved[frame_key]96        actual = (frame.is_extended_id, frame.is_fd)97        fields, reference = {}, None98        for extended in (frame.is_extended_id, None):99            for fd in (frame.is_fd, None):100                key = (frame.channel, frame.arbitration_id, extended, fd)101                item = self._groups.get(key)102                if item is None:103                    continue104                # 候选定义里没有该信号的另一种同ID帧属于无关输入,不制造失效更新。105                selected = []106                for field in item[0]:107                    candidates = self._dbc_frames.get(field)108                    if candidates is not None and not any(e == actual[0] and (f == actual[1] or f and not actual[1])109                                                          for e, f in candidates):110                        continue111                    selected.append(field)112                    if candidates is not None and not actual[1] and (actual[0], False) not in candidates:113                        self.compatibility_fields[field] = actual[0]114                if item[0] and not selected:115                    continue116                for identity_key in selected or (key,):117                    previous = self._seen.get(identity_key)118                    if previous is not None and previous != actual:119                        missing = 'is_extended' if previous[0] != actual[0] else 'is_fd'120                        raise ValueError('同一报文绑定匹配多种实际帧,请补充 ' + missing)121                    self._seen[identity_key] = actual122                fields.update((field, None) for field in selected)123                reference = reference or item[1]124        if reference is None:125            return None126        result = (tuple(fields), reference)127        # 只缓存已声明且成功匹配的身份,最多每声明四种,不随无关报文数量增长。128        self._resolved[frame_key] = result129        return result130131    def accepts_frame(self, frame):132        """判断是否有值或触发需求。参数: 原帧。返回: bool。异常: 非原帧属性错。"""133        return self._matching(frame) is not None134135    def fields_for(self, frame):136        """定位目标数据帧,未知ID/其他通道或帧型不参与本投影。137138        参数: frame为实际读取器交付且已校验的原帧。返回: 字段元组,非目标为空。139        异常: 非原帧对象缺少必需属性时AttributeError;不吞读取错误。140        """141        item = self._matching(frame)142        return () if item is None else item[0]143144    def _canonical_key(self, field):145        """借用当前投影已编码的字段键,不按原帧增加缓存。146147        参数: field为当前匹配出的已声明LegacyCANField。148        返回: 初始化时验证并生成的规范JSON字符串。149        异常: 非本投影字段时KeyError,不猜测或临时加入新的绑定。150        """151        return self._canonical_keys[field]152153    def build(self, frame, sequence, values, time_ns, *, canonical_keys=False):154        """只拣选计算字段,复用已有有时间/无时间更新及其严格值检查。155156        参数: frame为原帧,sequence为本来源原序号,values为已解码映射,157            time_ns为实际映射时间或明确未采集的None;canonical_keys说明文本字段键。158        返回: LegacyDecodedUpdate/LegacyUntimedUpdate;非目标数据帧返回None。159        异常: 类型/数值/身份错误原样传播,不缓存前条值补齐缺失。160        """161        if type(canonical_keys) is not bool:162            raise TypeError('canonical_keys需要bool')163        item = self._matching(frame)164        if item is None:165            return None166        fields, reference = item167        selected = {field.signal: values.get(self._canonical_keys[field] if canonical_keys else field.signal)168                    for field in fields if field in self._value_fields}169        update_type = LegacyUntimedUpdate if time_ns is None else LegacyDecodedUpdate170        return update_type(time_ns, self.source, frame.channel, frame.arbitration_id,171                    frame.is_extended_id, frame.is_fd, frame.is_remote_frame,172                    frame.is_error_frame, reference, sequence, sequence, selected)173
Chronaxis 用户手册 · 离线阅读