Chronaxis用户手册English
本页目录

chronaxis.planning.dbc_defaults

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""仅在信号省略类型/单位或enum映射时读取DBC元数据,不读取报文。23每次计划编译按DBC路径共用一次加载;结果冻结为已有SignalDefinition字段。4数值信号不会因存在VAL表而变成文字枚举。重新加载配置才重新推导默认值。5"""6from contextlib import contextmanager7from contextvars import ContextVar8from pathlib import Path910from .checks import PlanValidationError11from ..legacy.binding import dbc_candidates12from ..can_dependencies import CANDependencyError, import_can_dependency13from ..dbc_loading import load_dbc_file141516_PREPARED_DBC = ContextVar('chronaxis_prepared_dbc', default=None)171819@contextmanager20def scoped_dbc_cache():21    """在一次 fast 准备期间共享 DBC,嵌套子计划沿用外层缓存,退出即释放。"""22    if _PREPARED_DBC.get() is not None:23        yield24        return25    token = _PREPARED_DBC.set({})26    try:27        yield28    finally:29        _PREPARED_DBC.reset(token)303132def load_dbc(path):33    """返回当前准备范围的数据库;普通单任务保持原加载行为。"""34    cache = _PREPARED_DBC.get()35    cantools = import_can_dependency('cantools')36    if cache is None:37        return load_dbc_file(path, cantools)38    key = Path(path).resolve()39    if key not in cache:40        cache[key] = load_dbc_file(path, cantools)41    return cache[key]424344def requested_enum_codes(config):45    """在加载 DBC 前定位 enum_code 使用的信号,只解析表达式位置和简单派生别名。4647    参数: config 为已归组的分析配置,可能含尚待完整校验的节点。48    返回: 原码别名集合。异常: 完整语法诊断由稍后的统一编译器报告。49    """50    import ast51    derived = config.get('derived', {})52    pending = [config.get(group, ()) for group in ('derived', 'rules', 'calculations', 'message_calculations')]53    names = set()54    while pending:55        item = pending.pop()56        if isinstance(item, (tuple, list)):57            pending.extend(item)58        elif isinstance(item, dict):59            for key, value in item.items():60                if isinstance(value, (list, dict)) and key not in ('metadata', 'parameters'):61                    pending.append(value)62                elif isinstance(value, str) and 'enum_code' in value and key in (63                        'expr', 'signal', 'condition', 'end_condition', 'start_condition', 'exit_condition', 'enter_condition'):64                    try:65                        tree = ast.parse(value, mode='eval')66                    except (SyntaxError, ValueError):67                        continue68                    for node in ast.walk(tree):69                        if (isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and70                                node.func.id == 'enum_code' and len(node.args) == 1 and isinstance(node.args[0], ast.Name)):71                            names.add(node.args[0].id)72    for name in tuple(names):73        seen = set()74        while isinstance(derived, dict) and isinstance(derived.get(name), dict) and name not in seen:75            seen.add(name)76            text = derived[name].get('expr')77            if not isinstance(text, str):78                break79            try:80                expression = ast.parse(text, mode='eval').body81            except (SyntaxError, ValueError):82                break83            if not isinstance(expression, ast.Name):84                break85            name = expression.id86            names.add(name)87    return names888990def _numeric_dtype(signal):91    """按解码后的数值范围选类型;有浮点缩放就使用float64,不丢弃小数。9293    参数: signal为DBC信号定义,包含原位宽、符号及解码倍率/偏移。94    返回: float64、int64或uint64文本。95    异常: ValueError为缩放后整数不能用64位类型表示。96    """97    if signal.is_float or type(signal.scale) is not int or type(signal.offset) is not int:98        return 'float64'99    low = -(2**(signal.length-1)) if signal.is_signed else 0100    high = 2**(signal.length-1)-1 if signal.is_signed else 2**signal.length-1101    limits = sorted((low*signal.scale+signal.offset, high*signal.scale+signal.offset))102    if -(2**63) <= limits[0] and limits[1] < 2**63:103        return 'int64'104    if 0 <= limits[0] and limits[1] < 2**64:105        return 'uint64'106    raise ValueError('DBC缩放后的整数超出64位,请显式选择dtype并核对数值范围')107108109def _enum_choices(signal):110    """把DBC原码表转换到既有解码值域,不对实际记录再施加一次DBC缩放。111112    参数: signal为含choices的DBC信号定义。113    返回: [已解码整数码, 标签]列表。114    异常: ValueError为缺少枚举、解码码值含小数或标签为空。115    """116    if not signal.choices:117        raise ValueError('DBC没有该信号的枚举表,请提供choices或选择数值dtype')118    result = []119    for code, label in signal.choices.items():120        value = signal.conversion.raw_to_scaled(code, decode_choices=False)121        # 核心枚举编码保持int/bool/str契约;不把浮点物理值截断成整数码。122        if type(value) is not int or signal.is_float:123            raise ValueError('DBC枚举缩放产生浮点值,当前枚举编码不支持;请使用数值dtype,不会截断或重复缩放')124        text = str(label)125        if not text:126            raise ValueError('DBC枚举标签为空,请提供明确choices')127        result.append([value,text])128    return result129130131class DBCDefaults:132    """一次计划编译内的临时数据库缓存,显式完整声明不打开DBC。"""133    def __init__(self):134        """创建空缓存,不访问磁盘。135136        参数: 无。返回: None。异常: 内存分配错误传播。137        """138        self.databases = {}139        self.failures = {}140141    def fill(self, raw, config, field, path, *, config_version=4, need_code=False):142        """补全DBC可证明的声明,不重复应用原解码缩放。143144        参数: raw为配置;config/field为来源与定位;path为诊断位置;config_version选择规则。145        返回: 独立dict,v4保留DBC原单位作为input_unit。146        异常: PlanValidationError为缺项、歧义、读取或依赖错误。147        """148        from ..core.unit_conversion import require_config_version149        require_config_version(config_version)150        result = dict(raw)151        # 手工映射已明确标签类型,可以直接得出逻辑dtype,不读取DBC作第二次猜测。152        choices = result.get('choices')153        if 'dtype' not in result and type(choices) is list and choices:154            if all(type(pair) is list and len(pair)==2 and type(pair[1]) is str for pair in choices):155                result['dtype']='enum'156            elif all(type(pair) is list and len(pair)==2 and type(pair[1]) is bool for pair in choices):157                result['dtype']='bool'158        needed = [key for key in ('dtype','unit') if key not in result]159        # 目标单位显式填写仍不代表DBC原值已经处于该尺度。160        if 'input_unit' not in result:161            needed.append('input_unit')162        if result.get('dtype') == 'enum' and 'choices' not in result:163            needed.append('choices')164        if need_code:165            needed.append('code_dtype')166        if not needed:167            return result168        key = str(config.dbc_path)169        if key in self.failures:170            raise PlanValidationError(path, 'DBC加载已失败,先修复 ' + self.failures[key], 'blocked_dependency')171        try:172            if key not in self.databases:173                try:174                    self.databases[key] = load_dbc(key)175                except Exception:176                    # 同一次失败文件不因另一个信号再次加载;不缓存跨任务的失败。177                    self.failures[key] = path178                    raise179            candidates = dbc_candidates(self.databases[key],field.frame_id,(field.signal,),field.is_extended,field.is_fd)180            signals = [next(s for s in message.signals if s.name==field.signal) for message in candidates]181            for name in needed:182                if name == 'dtype':183                    values = [_numeric_dtype(signal) for signal in signals]184                elif name == 'code_dtype':185                    if any(signal.is_float or signal.scale != 1 or signal.offset != 0 for signal in signals):186                        raise ValueError('enum_code 需要原整数;该 DBC 已缩放或提供浮点解码值')187                    values = ['int64' if signal.is_signed else 'uint64' for signal in signals]188                    if 'code_dtype' in raw and any(value != raw['code_dtype'] for value in values):189                        raise ValueError('code_dtype 与 DBC 原始整数有符号性不符')190                elif name in ('unit', 'input_unit'):191                    # 无单位定义时标未知,不冒称已核实为V/A或无量纲。192                    values = [signal.unit or ('1' if result.get('dtype') in ('enum','bool') else 'unknown') for signal in signals]193                else:194                    values = [_enum_choices(signal) for signal in signals]195                if any(value != values[0] for value in values[1:]):196                    raise ValueError(f'候选DBC的{name}不同,请补充帧类型或显式{name}')197                result[name] = values[0]198        except CANDependencyError as error:199            # 这是来源的安装环境缺项,不要求用户反复修改当前信号字段。200            raise PlanValidationError('$.sources', str(error), error.code,201                hint=error.hint, object_name='来源 ' + config.name) from error202        except Exception as error:203            # 不吞文件/解析错误,不用猜测值替代无法读取的元数据。204            raise PlanValidationError(path,'无法从DBC补充信号声明:'+str(error)) from error205        return result206
Chronaxis 用户手册 · 离线阅读