pythonでcanログを変換(BLF形式、ASCII形式→エクセル)

CAN,python,openpyxl,python,vector

初めに

前回に続いて、今回はCANログをエクセルファイルとして解析しやすい形に変換していきます。

サンプルのログファイルは以下から取得しました。

CANログのフォーマット

ASCII形式のCANログをベースに、CANログとして出力されるフレームごとの情報のフォーマット内容を確認しておきます。

ログフォーマットの詳細についてはCanalyzerインストール後、”C:\Program Files\Vector CANalyzer 16\Doc”に格納されているCAN_LOG_TRIGGER_ASC_Format.pdfを確認してください。

インストーラの取得は以下から。

クラシックCAN

以下はクラシックCANのログの一例です。

各要素の詳細については以下になります。

要素説明
TimestampCANメッセージが送信または受信された時点の時間情報
ChannelCANバスに接続された物理的な通信経路または回線
ID(Identifier)CANメッセージの識別子
Dir(Direction)CANメッセージの送信方向を示す
Tx:送信
Rx:受信
DLC(Data Length Code)CANメッセージのデータフィールドの長さを示すための値
Dataデータフィールド
Lengthバスを占有するフレームの送受信時間(ナノ秒単位)
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
BitCountフレームが送受信されたbit数
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
ID(末尾)前述のIDの十進数値

拡張フレームの場合は以下のようになり、差分はIDの末尾にxが付くだけです。

CANFD

続いてCANFDのログの一例です。

要素説明
TimestampCANメッセージが送信または受信された時点の時間情報
ChannelCANバスに接続された物理的な通信経路または回線
Dir(Direction)CANメッセージの送信方向を示す
Tx:送信
Rx:受信
ID(Identifier)CANメッセージの識別子
BRSCANフレームのBRS(Bit Rate Switch)ビットを意味する
BRSビットはデータフィールドのビットレートを切り替えるためのフラグ
0:データフィールドのビットレートは通常のCAN通信と同じ速度で転送される
1:データフィールドのビットレートが高速な速度で転送される
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
ESICANフレームのESI(Error State Indicator)を意味する
ESIはエラーステートを示すフラグ
0:エラーステートは通常の状態(異常なし)
1:エラーステートがアクティブ(異常あり)
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
DLC(Data Length Code)CANメッセージのデータフィールドの長さを示すための値
DataLengthメッセージの有効な長さ(byte単位)
Dataデータフィールド
MessageDurationバスを占有するフレームの送受信時間(ナノ秒単位)
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
MessageLengthフレームが送受信されたbit数
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
Flags各種CANフレームにおけるフラグ情報
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
CRCメッセージのチェックサム
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
BitTimingConfArbアービトレーション領域のビットタイミングパラメータ
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
BitTimingConfDataデータ領域のビットタイミングパラメータ
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
BitTimingConfExtArbアービトレーション領域のビットタイミング
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)
BitTimingConfExtDataデータ領域のビットタイミング
*今回の解析対象外(詳細についてはCAN_LOG_TRIGGER_ASC_Format.pdf参照)

CAN FDもClassickCAN同様、拡張フレームの場合はIDの末尾にxが付きます。

実装

今回は前述のログ情報すべてではなく、ライブラリを利用して抽出可能な情報のみエクセルへ出力します。

canライブラリ(can.io.ASCReader、can.io.BLFReader)

前述のCANログに含まれる各種情報を、フレーム単位で取り出せるようにしてくれる機能をcanライブラリは提供してくれています。

今回使用するのはcan.io.ASCReaderまたはcan.io.BLFReaderのいずれかとなり、ログの形式に応じて使いわけます。

本クラスを使用することで、任意のログファイルからメッセージごとに以下のような情報を抽出することが可能です。

要素説明
timestampCANメッセージが送信または受信された時点の時間情報
arbitration_idCANメッセージの識別子
channelCANバスに接続された物理的な通信経路または回線
dlcCANメッセージのデータフィールドの長さを示すための値
dataデータフィールド
error_state_indicaterFalse:エラーステートは通常の状態(異常なし)
True:エラーステートがアクティブ(異常あり)
is_extended_idFalse:標準ID
True:拡張ID
is_fdFalse:Classick CAN
True:CAN FD
is_error_frameFalse:正常フレーム
True:異常フレーム
is_remote_frameFalse:リモートフレーム以外
True:リモートフレーム(データなし)
is_rxFalse:Tx
True:Rx

詳細については以下公式から確認してください。

src

FILE_PATHのファイルをblfファイルにすれば、blfファイルからのエクセル生成も可能です。

import pandas as pd
import datetime
import can

FILE_PATH = "sample\\asc\\logfile_errorframes.asc"
TEMP_DIC = {"TIMESTAMP":"-","FRAME":"-","ID":"-","CHANNEL":"-","DIR":"-","DLC":"-",
            "D00":"-","D01":"-","D02":"-","D03":"-","D04":"-","D05":"-","D06":"-","D07":"-","D08":"-","D09":"-",
            "D10":"-","D11":"-","D12":"-","D13":"-","D14":"-","D15":"-","D16":"-","D17":"-","D18":"-","D19":"-",
            "D20":"-","D21":"-","D22":"-","D23":"-","D24":"-","D25":"-","D26":"-","D27":"-","D28":"-","D29":"-",
            "D30":"-","D31":"-","D32":"-","D33":"-","D34":"-","D35":"-","D36":"-","D37":"-","D38":"-","D39":"-",
            "D40":"-","D41":"-","D42":"-","D43":"-","D44":"-","D45":"-","D46":"-","D47":"-","D48":"-","D49":"-",
            "D50":"-","D51":"-","D52":"-","D53":"-","D54":"-","D55":"-","D56":"-","D57":"-","D58":"-","D59":"-",
            "D60":"-","D61":"-","D62":"-","D63":"-"}


def main():
    # ASCII形式
    if ".asc" in FILE_PATH:
        with open(FILE_PATH, 'r') as f_in:
            asciidata = can.io.ASCReader(f_in)
            read_log(asciidata)
    # BLF形式
    if ".blf" in FILE_PATH:
        with open(FILE_PATH, 'rb') as f_in:
            blfdata = can.io.BLFReader(f_in)
            read_log(blfdata)


def read_log(log_info):
    # 初期化
    excel = make_excel(TEMP_DIC)
    for msg in log_info:
        input_dic = TEMP_DIC
        # timestamp
        input_dic["TIMESTAMP"] = msg.timestamp
        if True == msg.is_error_frame:
            input_dic["FRAME"] = "Error Frame"
        else:
            # フレーム種別
            if False == msg.is_fd and False == msg.is_extended_id:
                input_dic["FRAME"] = "CAN標準フォーマット"
                input_dic["ID"] = '{:04x}'.format(msg.arbitration_id).upper()
            if True == msg.is_fd and False == msg.is_extended_id:
                input_dic["FRAME"] = "CAN FD標準フォーマット"
                input_dic["ID"] = '{:04x}'.format(msg.arbitration_id).upper()
            if False == msg.is_fd and True == msg.is_extended_id:
                input_dic["FRAME"] = "CAN拡張フォーマット"
                input_dic["ID"] = '{:08x}'.format(msg.arbitration_id).upper()
            if True == msg.is_fd and True == msg.is_extended_id:
                input_dic["FRAME"] = "CAN FD拡張フォーマット"
                input_dic["ID"] = '{:08x}'.format(msg.arbitration_id).upper()
            # channel
            input_dic["CHANNEL"] = msg.channel
            # dir
            if True == msg.is_rx:
                input_dic["DIR"] = "Rx"
            else:
                input_dic["DIR"] = "Tx"
            # dlc
            input_dic["DLC"] = msg.dlc
            # data
            for i, data in enumerate(msg.data):
                input_dic["D"+str('{:02d}'.format(i))] = '{:02x}'.format(data).upper()
            print(msg.equals)
        excel.add_inf(input_dic)
    # エクセルの保存
    now = datetime.datetime.now()
    file_name = 'canlog_convert_{}.xlsx'.format(now.strftime('%Y%m%d_%H%M%S'))
    excel.save_file(file_name,"sample1")
    return


class make_excel:
    # エクセルヘッダ記入
    def __init__( self , init_dic) :
        key_list = init_dic.keys()
        self.df = pd.DataFrame(columns=key_list)
    # 行追加
    def add_inf ( self , add_dict ) :
        self.df = self.df.append( add_dict , ignore_index=True)
        return
    # ファイル保存
    def save_file( self , file_name , title ):
        self.df.to_excel(file_name, sheet_name=title)
        return


if __name__ == "__main__":
    main()

実行結果

実行結果としてはこんな感じで出力されます。

変換前のASCIIファイルは以下。

date Sam Sep 30 15:06:13.191 2017
base hex  timestamps absolute
internal events logged
// version 9.0.0
Begin Triggerblock Sam Sep 30 15:06:13.191 2017
   0.000000 Start of measurement
   0.015991 CAN 1 Status:chip status error passive - TxErr: 132 RxErr: 0
   0.015991 CAN 2 Status:chip status error active
   2.501000 1 ErrorFrame
   2.501010 1 ErrorFrame ECC: 10100010
   2.501020 2 ErrorFrame Flags = 0xe CodeExt = 0x20a2 Code = 0x82 ID = 0 DLC = 0 Position = 5 Length = 11300
   2.520002 3 200 Tx r Length = 1704000 BitCount = 145 ID = 88888888x
   2.584921 4 300 Tx r 8 Length = 1704000 BitCount = 145 ID = 88888888x
   3.098426 1  18EBFF00x       Rx d 8 01 A0 0F A6 60 3B D1 40    Length = 273910 BitCount = 141 ID = 418119424x
   3.197693 1  18EBFF00x       Rx d 8 03 E1 00 4B FF FF 3C 0F    Length = 283910 BitCount = 146 ID = 418119424x
  17.876976 1  6F8             Rx   d 8 FF 00 0C FE 00 00 00 00  Length = 239910 BitCount = 124 ID = 1784
  20.105214 2  18EBFF00x       Rx   d 8 01 A0 0F A6 60 3B D1 40  Length = 273925 BitCount = 141 ID = 418119424x
  20.155119 2  18EBFF00x       Rx   d 8 02 1F DE 80 25 DF C0 2B  Length = 272152 BitCount = 140 ID = 418119424x
  20.204671 2  18EBFF00x       Rx   d 8 03 E1 00 4B FF FF 3C 0F  Length = 283910 BitCount = 146 ID = 418119424x
  20.248887 2  18EBFF00x       Rx   d 8 04 00 4B FF FF FF FF FF  Length = 283925 BitCount = 146 ID = 418119424x
End TriggerBlock