Chronaxis用户手册English
本页目录

chronaxis.planning.text_inputs

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""静态解释新的ASC和原始CAN表配置,不打开输入或DBC。23来源使用既有name/time/dbc_by_channel及path或有序paths;parser明确ASC版本4或CSV布局/时间模式。offsets可逐文件声明相对于共同time.offset的额外平移,5不自动猜跨日、设备同步或日期。返回CANTextSpec及规范来源身份供公共AnalysisRunner。6"""78from hashlib import sha256910from ..core.models import TimebaseSpec11from ..core.types import TimeType12from ..sources.asc import ASCSpec13from ..sources.blf import BLFSpec14from ..sources.raw_csv import CANCSVSpec,CSVTimeSpec15from ..sources.raw_csv_layout import CANCSVLayout16from ..sources.text_can_spec import CANTextSpec17from ..timebase.conversion import checked_add_ns18from .checks import (PlanValidationError,absolute_path,array_at,canonical,choice_at,coordinate_at,19    duration_at,metadata_at,model_at,object_at,text_at,timezone_at)202122def parse_text_source(raw,base,path):23    """建立一个明确的新文本CAN来源。参数: raw原对象、base配置目录、path错误位置。24    返回: SourcePlan。异常: PlanValidationError定位配置问题;不发生原始输入I/O。25    """26    from .models import SourcePlan27    raw=object_at(raw,path,('name','source_type','time','dbc_by_channel'),('path','paths','parser','offsets','metadata','input_check','file_merge') + (('reader_options',)))28    source_type=choice_at(raw['source_type'],('can_asc','asc_or_blf','raw_can_csv'),path+'.source_type')29    name=text_at(raw['name'],path+'.name',identifier=True)30    clock=object_at(raw['time'],path+'.time',('time_type','unit'),('origin','offset','timezone'))31    time_type=choice_at(clock['time_type'],('relative','absolute'),path+'.time.time_type')32    unit=choice_at(clock['unit'],('s','ms','us','ns','ISO8601'),path+'.time.unit')33    if time_type=='relative':34        if 'origin' not in clock or 'timezone' in clock or unit=='ISO8601':35            raise PlanValidationError(path+'.time','relative必须声明共同origin,且不补日历时区')36        origin=text_at(clock['origin'],path+'.time.origin')37        clock_id='relative_'+sha256(origin.encode('utf-8')).hexdigest()[:16]38    else:39        if 'origin' in clock:40            raise PlanValidationError(path+'.time.origin','absolute不接受relative origin')41        origin=None; clock_id='utc'42    timebase=model_at(TimebaseSpec,path+'.time',clock_id,TimeType(time_type),origin)43    offset=coordinate_at(clock.get('offset','0s'),path+'.time.offset')44    zone=None45    if 'timezone' in clock:46        tz=timezone_at(clock['timezone'],path+'.time.timezone')47        delta=None if tz is None else tz.utcoffset(None)48        if delta is None or delta.total_seconds()%60:49            raise PlanValidationError(path+'.time.timezone','此原始报文读取器需要固定时区:UTC或±HH:MM')50        zone=int(delta.total_seconds()//60)51    if ('path' in raw)==('paths' in raw):52        raise PlanValidationError(path,'path和paths必须且只能提供一种')53    names=[raw['path']] if 'path' in raw else array_at(raw['paths'],path+'.paths')54    if not 1<=len(names)<=4096:55        raise PlanValidationError(path+'.paths','需要1至4096个有序文件')56    paths=tuple(absolute_path(item,base,f'{path}.paths[{i}]') for i,item in enumerate(names))57    if source_type=='asc_or_blf' and any(p.suffix.lower() not in {'.asc','.blf'} for p in paths):58        raise PlanValidationError(path+'.paths','自动读取只支持ASC/BLF后缀')59    offsets=array_at(raw.get('offsets',['0s']*len(paths)),path+'.offsets')60    if len(offsets)!=len(paths):61        raise PlanValidationError(path+'.offsets','逐文件偏移数量必须与paths相同')62    shifted=tuple(model_at(checked_add_ns,f'{path}.offsets[{i}]',offset,coordinate_at(value,f'{path}.offsets[{i}]'))63        for i,value in enumerate(offsets))64    dbc=raw['dbc_by_channel']65    if type(dbc) is not dict or len(dbc)!=1:66        raise PlanValidationError(path+'.dbc_by_channel','当前文本CAN需要一个明确通道到DBC路径')67    channel=next(iter(dbc))68    from .channels import channel_index69    channel_number=channel_index(channel,path+'.dbc_by_channel',key=True)70    dbc_path=absolute_path(dbc[channel],base,path+'.dbc_by_channel.'+channel)71    parser=raw.get('parser',{})72    if source_type in ('can_asc','asc_or_blf'):73        parser=object_at(parser,path+'.parser',(),('version','encoding'))74        if unit!='s':75            raise PlanValidationError(path+'.time.unit','ASC原文字以秒表示')76        inputs=tuple(model_at(BLFSpec,path+'.parser',p,shift) if source_type=='asc_or_blf' and p.suffix.lower()=='.blf' else77            model_at(ASCSpec,path+'.parser',p,parser.get('version','vector-asc-v1'),78            parser.get('encoding','utf-8-sig'),'epoch' if time_type=='absolute' else 'relative',zone,shift)79            for p,shift in zip(paths,shifted))80    else:81        parser=object_at(parser,path+'.parser',('layout','time_format'),82            ('id_base','length_base','channel_origin','payload_style','rollover_min_drop'))83        layout_raw=parser['layout']84        if type(layout_raw) is dict and type(layout_raw.get('fields')) is dict:85            fields=dict(layout_raw['fields'])86            # 通道列缺省时继承当前来源唯一通道;先还原输入起点,reader只减一次。87            origin=parser.get('channel_origin',0)88            if type(origin) is not int or origin not in (0,1):89                raise PlanValidationError(path+'.parser.channel_origin','通道起点须为0或1')90            fields.setdefault('channel',{'fixed':channel_number+origin})91            layout_raw=dict(layout_raw,fields=fields)92        layout=model_at(CANCSVLayout.from_dict,path+'.parser.layout',layout_raw)93        time_format=choice_at(parser['time_format'],('numeric','clock','clock-ms-us-v1','iso8601'),path+'.parser.time_format')94        if ((time_format in ('clock','clock-ms-us-v1') and (time_type!='relative' or unit!='s'))95                or time_format=='iso8601' and (time_type!='absolute' or unit!='ISO8601')96                or time_format=='numeric' and (unit=='ISO8601' or zone is not None)):97            raise PlanValidationError(path+'.time','时间种类/单位/时区与原表解析模式不符')98        rollover=duration_at(parser['rollover_min_drop'],path+'.parser.rollover_min_drop',positive=True) if 'rollover_min_drop' in parser else None99        inputs=tuple(model_at(CANCSVSpec,path+'.parser',p,layout,100            model_at(CSVTimeSpec,path+'.parser',time_format,unit if time_format=='numeric' else 's',shift,zone,rollover),101            parser.get('id_base','auto'),parser.get('length_base',10),parser.get('channel_origin',0),parser.get('payload_style','spaced'))102            for p,shift in zip(paths,shifted))103    options=object_at(raw.get('reader_options',{}),path+'.reader_options',(),('decode_policy',))104    policy=choice_at(options.get('decode_policy','strict'),105        ('strict','partial','legacy'),path+'.reader_options.decode_policy')106    input_check=choice_at(raw.get('input_check','auto'),('auto','full'),path+'.input_check')107    merge=choice_at(raw.get('file_merge','concatenate'),('concatenate','time_order'),path+'.file_merge')108    if source_type=='raw_can_csv' and 'file_merge' in raw:109        raise PlanValidationError(path+'.file_merge','file_merge仅适用于ASC/BLF来源')110    spec=model_at(CANTextSpec,path,name,inputs,dbc_path,channel_number,timebase,policy,input_check,merge)111    identity=dict(raw,paths=tuple(str(p) for p in paths),dbc_by_channel={channel:str(dbc_path)})112    identity.pop('path',None)113    return SourcePlan(name,source_type,spec,unit,clock.get('timezone'),canonical(identity),metadata_at(raw.get('metadata',{}),path+'.metadata'))114
Chronaxis 用户手册 · 离线阅读