技术博客

29/05/2015 作者 Magnus Carlsson

使用kvmlib和Python检查记录的数据

今天我们来看看如何读取记录的数据,以及如何使用kvmlib将数据与监视器关联起来。我们通过记录一些样本数据,然后使用Python包装器尝试分析它。

1.1 生成和记录数据

让我们开始生成一些数据。我们使用t样本程序histogram_stimuli.t,它包含在CANlib SDK(Samples\tScript\histogram\histogram_stimuli.t).中。这个程序使用四个报文; ECM_001,ECM_003,ECM_004和LIM_002。这些报文包含了EngineSpeed,EngineTemp,Fuel和Load等信号。

used-signals-and-their-main-attributes

表1:使用的信号及其主要属性

t程序在报文之间生成随机数值和延迟(在init_stimuli_ctrl()中设置的范围内)。现在配置Eagle用来“记录所有”,启动t程序并让它记录几分钟。

1.2 发动机温度何时约为161°C?

我们想回答的第一个问题是“发动机温度何时为161°C?”。 (这可能不是现实世界中最具工程学的问题,但是由于我们的数据是完全随机的,所以我相信可以直观地看出我们如何在读取日志文件时获得绝对的时间。)

如果我们从一开始就知道这个问题,我们可以添加几个触发器,只要发动机温度介于159°C到163°C之间,就会开始记录(当超出此范围时停止记录)。但现在让我们读出所有记录的数据,并在发动机温度在159°C和163°C之间时输出数据。

在运行程序1之前,让我们先来看看主要部分。我们首先定义一个函数,找到我们进行会话的设备,然后打开它来获取一些句柄。

def readEventsFromDevice(ean, channel, msgId):
# Read all events from device matching ean, channel and msgId
print "Open device..."
# Create a device with the selected EAN number
dev = kvDevice.kvDevice(ean=ean)
# Open a device that matches our criteria (EAN)
dev.memoOpen()
# Mount the log files so we can acces them
dev.memo.deviceMountKmf()
# Read out how many log files that are availible on the card
fileCount = dev.memo.logFileGetCount()
print "Found %d file%s on card:" % (fileCount, "s" if fileCount > 1 else "")

从上面的代码片段中可以看出,我们首先使用kvDevice帮助程序模块来定义我们的设备并打开一个句柄。然后我们打开设备的kvmhandle (使用memoOpen()),然后我们通过deviceMountKmf()获取设备上的日志文件。然后我们读出卡上的日志文件数。2

       # Now we read all events from each file found on the card.
       for fileIndx in range (fileCount):
           # When mounting the logfile, we get an aproximate value back
           eventCount = dev.memo.logFileMount(fileIndx)
           print "File %3d: Contains less than %d events" % (fileIndx, eventCount)
           # We read out when the logging was started
           startTime = dev.memo.logFileGetStartTime()
           print "Logging started at %s\n" % startTime

我们依次通过每个日志文件并挂载它。3 挂载日志文件还会返回一个事件计数,其中包含“日志文件中的大概事件数”。4 然后,我们读出日志文件启动的时间,记住这点以便后来使用。

           while True:
               # Read events from the log file, when no more events are availible,
               # 'None' will be returned
               event = dev.memo.logReadEventEx()
               if event is None:
                   break

我们现在在一个循环中逐个读出事件,直到没有事件可读。

               # We are only interested in events that are log messages
               if type(event) is kvmlib.logMsg:
                   # Also filter on message id and channel number
                   if event.id == msgId and event.channel == channel:

因为我们只对一种类型的日志报文感兴趣,我们将其它报文都过滤掉。5 我们还可过滤掉我们感兴趣的卡上的通道号。6

                       # We know the message data is a float, so convert it to a
                       # more usable format
                       value = raw2float(event.data)
                       # Now filter on the value
                       if value > 159 and value < 163:
                           # Get the time of the event
                           eventTime = getEventTime(startTime, event)
                           print "%s, Msg id %d value %f on channel %d:" % (eventTime, msgId, value, channel)
                           #print event

我们将原始值转换为函数raw2float()中的float。查看数据库,我们看到这是一个直接映射(类型float,格式Intel,开始位0,长度32,因子1偏移量0)。如果值在159和163之间,我们在函数getEventTime()中计算事件的绝对时间,并最终输出数据。

           print "n"
           # Dismount to free up resources
           dev.memo.logFileDismount()
       # We are done, close the kvmlib handle to device
       dev.memoClose()

当读取所有事件后,我们卸载日志文件并继续读取下一个。在读取所有日志文件后,我们关闭设备的句柄。

通过将日志文件的开始时间添加到事件的时间戳,在函数getEventTime()中计算事件的绝对时间。

   def getEventTime(startTime, event):
       # The event timestamp is given in nanoseconds. This function converts it to
       # seconds, and returns the sum of startTime and event time (as a Python
       # datetime object).
       offsetInSeconds = event.timeStamp/1000000000.0
       return startTime + datetime.timedelta(seconds=offsetInSeconds)

请注意,时间戳以纳秒为单位,因此我们需要将其转换为秒,然后再使用Python datetime包将其添加到日志文件的开始时间。

   # Read all events with message id 503, on the first channel, from the first
   # device with EAN 73-30130-00567-9.
   readEventsFromDevice(ean="73-30130-00567-9", channel=0, msgId=503)

如上面代码所示调用我们的函数会产生以下输出:

Open device...
Found 1 file on card:

File   0: Contains less than 960 events
Logging started at 2015-05-17 12:48:26

2015-05-17 12:49:07.510234, Msg id 503 value 160.418518 on channel 0:
2015-05-17 12:49:39.647368, Msg id 503 value 159.750595 on channel 0:
2015-05-17 12:49:44.642380, Msg id 503 value 161.818268 on channel 0:
2015-05-17 12:49:59.753440, Msg id 503 value 160.477036 on channel 0:
2015-05-17 12:50:10.433490, Msg id 503 value 162.389267 on channel 0:
2015-05-17 12:50:16.684522, Msg id 503 value 159.377579 on channel 0:
2015-05-17 12:50:53.411675, Msg id 503 value 160.748947 on channel 0:
2015-05-17 12:51:00.961706, Msg id 503 value 159.282867 on channel 0:

在打印输出中,我们可以看到,在2015-05-17 12:49:59.753440中已识别到160.477°C。

如果我们没有太多的数据,另一种方式是提取到.csv并对列进行排序以找到正确值。现在让我们就这样做,因为这将使我们在时间领域中洞察更多

  1. 将Memorator连接到PC。
  2. 启动相应的Kvaser Memorator配置工具(例如使用Eagle快捷方式)并连接到你的设备。
  3. 转到选项卡日志文件。
  4. 单击“列出文件”(List files)以刷新列表。
  5. 单击“提取文件…”(Extract files…)。
  6. 设置文件存储位置和文件名选项,单击“下一步”。
  7. 将提取的文件格式设置为“CSV格式选定信号”(Selected signals in CSV format),单击“下一步”。
  8. 单击添加数据库(Add Database)并选择histogram.dbc(在SamplestScript histogramhistogram.dbc中找到)。
  9. 双击信号“EngineTemp”,单击下一步。
  10. 确保“时间戳偏移量”(Time stamp offset)设置为“开始测量”(Start of measurement),并选中“高分辨率AbsTime”(High resolution AbsTime)(在CSV文件选项下,参见图1)。如果你像我一样使用非英语版本的MS Excel,请根据需要调整“分隔符”(;)和“十进制分隔符”(,),以便电子表格程序将数值视为数字(而不是文本)。单击“完成”。
   # Filter out log messages that are outside of our time range
   time = startTime + datetime.timedelta(seconds=event.timeStamp/1000000000.0)
   if time >= firstTime and time <= lastTime:       #
       msgId = event.id
       # We know the message data is a float, so convert it to a
       # more usable format
       value = raw2float(event.data)
       # Get the time of the event
       eventTime = getEventTime(startTime, event)
       print "%s, Msg id %d value %f on channel %d:" % (eventTime, msgId, value, channel)

这次我们命名函数readTimedEventsFromDevice()并调用它,如下所示。

   # Read all events with message id 503, on the first channel, from the first
   # device with EAN 73-30130-00567-9 that was recorded between 2015-05-17 12:49:10
   # and 2015-05-17 12:49:20.
   startTime = datetime.datetime.strptime("2015-05-17 12:49:10", "%Y-%m-%d %H:%M:%S")
   endTime = datetime.datetime.strptime("2015-05-17 12:49:20", "%Y-%m-%d %H:%M:%S")
   print "nLooking at %s - %s" % (startTime,endTime)
   readTimedEventsFromDevice(ean="73-30130-00567-9", channel=0, msgId=503, firstTime=startTime, lastTime=endTime)

调用该函数将产生以下输出:

Looking at 2015-05-17 12:49:10 - 2015-05-17 12:49:20
Open device...
Found 1 file on card:

File   0: Contains less than 960 events
Logging started at 2015-05-17 12:48:26

2015-05-17 12:49:10.189224, Msg id 503 value 8.356499 on channel 0:
2015-05-17 12:49:10.425245, Msg id 503 value 148.579849 on channel 0:
2015-05-17 12:49:10.683234, Msg id 503 value -21.685257 on channel 0:
2015-05-17 12:49:10.853234, Msg id 503 value 33.064117 on channel 0:
2015-05-17 12:49:10.935236, Msg id 503 value -29.684952 on channel 0:
2015-05-17 12:49:11.320254, Msg id 503 value 54.329140 on channel 0:
2015-05-17 12:49:11.692243, Msg id 503 value -28.083881 on channel 0:
2015-05-17 12:49:11.770270, Msg id 503 value -48.805271 on channel 0:
2015-05-17 12:49:12.234247, Msg id 503 value 117.977112 on channel 0:
2015-05-17 12:49:12.294250, Msg id 503 value 72.071869 on channel 0:
2015-05-17 12:49:12.690248, Msg id 503 value 26.443817 on channel 0:
2015-05-17 12:49:13.132250, Msg id 503 value 74.562210 on channel 0:
2015-05-17 12:49:13.146244, Msg id 503 value 84.493378 on channel 0:
2015-05-17 12:49:13.184243, Msg id 503 value -30.005280 on channel 0:
2015-05-17 12:49:13.375251, Msg id 503 value -10.720951 on channel 0:
2015-05-17 12:49:13.821248, Msg id 503 value 58.475220 on channel 0:
2015-05-17 12:49:14.184254, Msg id 503 value 176.979965 on channel 0:
2015-05-17 12:49:14.427251, Msg id 503 value -37.418663 on channel 0:
2015-05-17 12:49:14.624254, Msg id 503 value 197.064117 on channel 0:
2015-05-17 12:49:14.646256, Msg id 503 value 175.246704 on channel 0:
2015-05-17 12:49:14.715250, Msg id 503 value -54.334503 on channel 0:
2015-05-17 12:49:15.017257, Msg id 503 value 18.197182 on channel 0:
2015-05-17 12:49:15.303258, Msg id 503 value 110.723022 on channel 0:
2015-05-17 12:49:15.337259, Msg id 503 value 86.609604 on channel 0:
2015-05-17 12:49:15.758269, Msg id 503 value 40.770157 on channel 0:
2015-05-17 12:49:15.975262, Msg id 503 value 71.019363 on channel 0:
2015-05-17 12:49:16.342263, Msg id 503 value 95.364639 on channel 0:
2015-05-17 12:49:16.475266, Msg id 503 value 124.447952 on channel 0:
2015-05-17 12:49:16.740282, Msg id 503 value 81.019073 on channel 0:
2015-05-17 12:49:16.796267, Msg id 503 value 171.284988 on channel 0:
2015-05-17 12:49:16.911271, Msg id 503 value 189.340149 on channel 0:
2015-05-17 12:49:16.984273, Msg id 503 value 163.904785 on channel 0:
2015-05-17 12:49:17.276268, Msg id 503 value -33.255932 on channel 0:
2015-05-17 12:49:17.434269, Msg id 503 value -52.797394 on channel 0:
2015-05-17 12:49:17.470280, Msg id 503 value -10.525452 on channel 0:
2015-05-17 12:49:17.929274, Msg id 503 value 49.282883 on channel 0:
2015-05-17 12:49:17.973638, Msg id 503 value 184.748627 on channel 0:
2015-05-17 12:49:18.109271, Msg id 503 value 36.713226 on channel 0:
2015-05-17 12:49:18.543270, Msg id 503 value -41.598648 on channel 0:
2015-05-17 12:49:18.617276, Msg id 503 value 172.797897 on channel 0:
2015-05-17 12:49:18.686271, Msg id 503 value 19.661766 on channel 0:
2015-05-17 12:49:18.974271, Msg id 503 value 187.943726 on channel 0:
2015-05-17 12:49:19.374277, Msg id 503 value 39.316628 on channel 0:
2015-05-17 12:49:19.657277, Msg id 503 value 79.659393 on channel 0:

所以,我们得到,EngineTemp的所有值在12:49:10和12:49:20之间。


图1:Kvaser Memorator配置工具中提取向导的最后一步

在电子表格程序中打开导出的.csv,对包含EngineTemp的列进行排序,并向下滚动到EngineTemp为160°C时,参见图2。读取AbsTime列,我们现在可以看到EngineTemp为例如160.477°C at 2015-05-17T11:49:59.6651。

图2:使用“高分辨率AbsTime”解压缩后生成的.csv文件的一部分

但是,Python程序不是已经显示“12:49:59.753440”? 是的,你发现得好,这是正确的。

首先,11:49和12:49时间差是基于Unix时间和当地时间的差异。 Unix时间是“从世界协调时间(UTC)1970年1月1日星期四00:00:00起已经过去的秒数”。7 这意味着在将Unix时间转换到本地时间时应考虑时区和夏令时。 Memorator中的实时时钟是以Unix时间设置的,Python相对而言比较方便,是本地时间。 .csv表也是基于UTC时间,但这点没有向电子表格程序说明,因此这其中就有一个小时的差异。

那么剩下的883 ms的差异呢?这是由于时间戳偏移导致的。让我们通过提取到“纯文本格式的CAN帧”来查看文件中的第一个原始值,并将“时间戳偏移量”设置为“开始测量”。我们现在得到以下内容:

                                Kvaser Memorator Log
                                ====================

  Converted from Memorator Binary format at:  5/19/2015 07:41:03

  Settings:
     Format of data field: DEC
     Format of id field:   DEC
     Timestamp Offset:     0,000000 s
     CAN channel:          1 2

          Time        CAN Identifier  Flags  DLC Data                          Counter
  =====================================================================================
  DateTime: 2015-05-17 11:48:26
      0,088284  Trigger (type=0x1, active=0x00, pre-trigger=0, post-trigger=-1)
     18,323147  1         503    Rx         8    8 206  37  66   0   0   0   0     1
     18,430094  1         501    Rx         8  202  19   0   0   0   0   0   0     2
     18,516129  1         503    Rx         8  206 208   2  67   0   0   0   0     3
     18,529129  1         503    Rx         8   60 184 254  66   0   0   0   0     4

请注意,第一个触发事件在883 ms之后到达。现在,通过再次提取到“纯文本格式的CAN帧”并将“时间戳偏移量”设置为“第一触发器”来补偿。我们现在得到以下内容:

                                Kvaser Memorator Log
                                ====================

  Converted from Memorator Binary format at:  5/19/2015 07:40:51

  Settings:
     Format of data field: DEC
     Format of id field:   DEC
     Timestamp Offset:     -0,088284 s
     CAN channel:          1 2

          Time        CAN Identifier  Flags  DLC Data                          Counter
  =====================================================================================
  DateTime: 2015-05-17 11:48:26
      0,000000  Trigger (type=0x1, active=0x00, pre-trigger=0, post-trigger=-1)
     18,234863  1         503    Rx         8    8 206  37  66   0   0   0   0     1
     18,341810  1         501    Rx         8  202  19   0   0   0   0   0   0     2
     18,427845  1         503    Rx         8  206 208   2  67   0   0   0   0     3
     18,440845  1         503    Rx         8   60 184 254  66   0   0   0   0     4

现在“时间戳偏移”-883ms,所有的时间戳都被调整了这么多。那么为什么我们不能在Python程序中补偿这一点呢?实时时钟具有2 s(±1 s)的精度,因此在这种情况下,实际上可能会过度弥补几十秒。

1.3 在12:49:15时间对应的值是多少?

假设有人说:“当时钟显示12:49:15,驱动程序标记到一个声音,那这个时刻的EngineTemp的值是什么?”再次,如果我们早就知道驱动程序需要标记一些东西,那我们会给他提供一个按钮去点击。此按钮将被连接到Memorator的触发器输入,如图2所示,在任何时候按钮被点击后,会提供给我们非常容易找到的触发器。

图3:如何将按钮连接到Memorator的外部触发输入的示例

在我们深入到日志文件之前,我们必须考虑驱动程序注意到的时间,12:49:15。因为他说他看着时钟,这是本地时间并知道Python kvmlib包装器可将时间戳转换为本地时间,让我们写一个小程序来输出相关的数据。我们也知道精度是2秒,所以让我们看看12:49:10 – 12:49:20这个间隔。该程序非常像以前的程序,除了我们对时间戳而不是记录值进行过滤。


脚注:

1 完整的程序列表见 http://github.com/Kvaser/developer-blog

2 日志文件数与.kmf容器数量不同(log000000.kmf,log000001.kmf等),如果我们将SD卡放到SD卡读卡器内并通过电脑查看内容,则可以看到这些SD卡上的数量。每次触发器开始记录时都会创建一个新的日志文件。在我们的示例中,由于我们使用配置选项“记录所有”(Log everything),当我们给CAN总线供电时(同时断开USB连接),将创建一个新的日志文件。

3 请注意,kvmlib中的函数的命名在最新版本中已进行了变更,以使它们更加统一,更容易理解。 Python kvmlib包装器中的命名尚未完全变更。

4 我们实际读取的是该日志文件在磁盘上使用的扇区数,并知道在一个扇区中可拥有事件的数量并将其作为一系列事件进行返回。

5 在我们的示例中,我们对报文标识 503进行过滤,因为这是我们的信号EngineTemp保存的位置。变量msgId实际上是在主代码中设置并作为参数传递。

6 通常在我的测试设置中连接到同一CAN总线的设备上有两个通道,并且对通道号的过滤将减少记录CAN报文的冗余。

7 有关Unix时间的更多信息,请参阅http://en.wikipedia.org/wiki/Unix_time

Author Image

Magnus Carlsson

Margus Carlsson是Kvaser AB公司的软件开发人员,从2007年以来深度参与了Kvaser固件和软件的开发。他还为Kvaser的技术博客撰写了许多用流行的Python语言编写应用程序的文章。