Browse Source

add tool_logtoasc. for convert log to asc file ,have 2 versions, gtk or qt.

yuchuli 4 months ago
parent
commit
dccc6d0d40

+ 74 - 0
src/tool/tool_logtoasc/.gitignore

@@ -0,0 +1,74 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+
+# qtcreator generated files
+*.pro.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+

+ 15 - 0
src/tool/tool_logtoasc/gtkversion/Makefile

@@ -0,0 +1,15 @@
+all: gtkfile.o lib.o
+
+	gcc -o log2asc gtkfile.o lib.o `pkg-config --libs gtk+-3.0`
+
+gtkfile.o: gtkfile.c
+
+	gcc -o gtkfile.o -c gtkfile.c `pkg-config --cflags gtk+-3.0`
+	
+lib.o: ./../lib.c
+	gcc -o lib.o -c ./../lib.c
+
+
+clean:
+
+	rm gtkfile.o lib.o log2asc

+ 444 - 0
src/tool/tool_logtoasc/gtkversion/gtkfile.c

@@ -0,0 +1,444 @@
+#include <gtk/gtk.h>  
+
+
+#include <libgen.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/can.h>
+#include <net/if.h>
+#include <sys/time.h>
+
+#include "./../lib.h"
+  
+// 全局变量,用于存储选择的文件路径和保存的文件名称  
+gchar *selected_file_path = NULL;  
+gchar *save_file_name = NULL;  
+
+
+
+#define DEVSZ 22
+#define EXTRASZ 20
+#define TIMESZ sizeof("(1345212884.318850)   ")
+#define BUFSZ (DEVSZ + AFRSZ + EXTRASZ + TIMESZ)
+/* adapt sscanf() functions below on error */
+#if (AFRSZ != 6300)
+#error "AFRSZ value does not fit sscanf restrictions!"
+#endif
+#if (DEVSZ != 22)
+#error "DEVSZ value does not fit sscanf restrictions!"
+#endif
+#if (EXTRASZ != 20)
+#error "EXTRASZ value does not fit sscanf restrictions!"
+#endif
+
+static void can_asc(struct canfd_frame *cfd, int devno, int nortrdlc,
+                    char *extra_info, FILE *outfile)
+{
+    int i;
+    char id[10];
+    char *dir = "Rx";
+    int dlc;
+    struct can_frame *cf = (struct can_frame *)cfd; /* for len8_dlc */
+
+    fprintf(outfile, "%-2d ", devno); /* channel number left aligned */
+
+    if (cf->can_id & CAN_ERR_FLAG)
+        fprintf(outfile, "ErrorFrame");
+    else {
+        sprintf(id, "%X%c", cf->can_id & CAN_EFF_MASK,
+                (cf->can_id & CAN_EFF_FLAG)?'x':' ');
+
+        /* check for extra info */
+        if (strlen(extra_info) > 0) {
+            /* only the first char is defined so far */
+            if (extra_info[0] == 'T')
+                dir = "Tx";
+        }
+
+        fprintf(outfile, "%-15s %s   ", id, dir);
+
+        if (cf->len == CAN_MAX_DLC &&
+            cf->len8_dlc > CAN_MAX_DLC &&
+            cf->len8_dlc <= CAN_MAX_RAW_DLC)
+            dlc = cf->len8_dlc;
+        else
+            dlc = cf->len;
+
+        if (cf->can_id & CAN_RTR_FLAG) {
+            if (nortrdlc)
+                fprintf(outfile, "r"); /* RTR frame */
+            else
+                fprintf(outfile, "r %X", dlc); /* RTR frame */
+        } else {
+            fprintf(outfile, "d %X", dlc); /* data frame */
+
+            for (i = 0; i < cf->len; i++) {
+                fprintf(outfile, " %02X", cf->data[i]);
+            }
+        }
+    }
+}
+
+static void canfd_asc(struct canfd_frame *cf, int devno, int mtu,
+                      char *extra_info, FILE *outfile)
+{
+    int i;
+    char id[10];
+    char *dir = "Rx";
+    unsigned int flags = 0;
+    unsigned int dlen = cf->len;
+    unsigned int dlc = can_fd_len2dlc(dlen);
+
+    /* relevant flags in Flags field */
+#define ASC_F_RTR 0x00000010
+#define ASC_F_FDF 0x00001000
+#define ASC_F_BRS 0x00002000
+#define ASC_F_ESI 0x00004000
+
+    /* check for extra info */
+    if (strlen(extra_info) > 0) {
+        /* only the first char is defined so far */
+        if (extra_info[0] == 'T')
+            dir = "Tx";
+    }
+
+    fprintf(outfile, "CANFD %3d %s ", devno, dir); /* 3 column channel number right aligned */
+
+    sprintf(id, "%X%c", cf->can_id & CAN_EFF_MASK,
+            (cf->can_id & CAN_EFF_FLAG)?'x':' ');
+    fprintf(outfile, "%11s                                  ", id);
+    fprintf(outfile, "%c ", (cf->flags & CANFD_BRS)?'1':'0');
+    fprintf(outfile, "%c ", (cf->flags & CANFD_ESI)?'1':'0');
+
+    /* check for extra DLC when having a Classic CAN with 8 bytes payload */
+    if ((mtu == CAN_MTU) && (dlen == CAN_MAX_DLEN)) {
+        struct can_frame *ccf = (struct can_frame *)cf;
+
+        if ((ccf->len8_dlc > CAN_MAX_DLEN) && (ccf->len8_dlc <= CAN_MAX_RAW_DLC))
+            dlc = ccf->len8_dlc;
+    }
+
+    fprintf(outfile, "%x ", dlc);
+
+    if (mtu == CAN_MTU) {
+        if (cf->can_id & CAN_RTR_FLAG) {
+            /* no data length but dlc for RTR frames */
+            dlen = 0;
+            flags = ASC_F_RTR;
+        }
+    } else {
+        flags = ASC_F_FDF;
+        if (cf->flags & CANFD_BRS)
+            flags |= ASC_F_BRS;
+        if (cf->flags & CANFD_ESI)
+            flags |= ASC_F_ESI;
+    }
+
+    fprintf(outfile, "%2d", dlen);
+
+    for (i = 0; i < (int)dlen; i++) {
+        fprintf(outfile, " %02X", cf->data[i]);
+    }
+
+    fprintf(outfile, " %8d %4d %8X 0 0 0 0 0", 130000, 130, flags);
+}
+
+
+int log2asc(const char * strinfile, const char * stroutfile)
+{
+    static char buf[BUFSZ], device[DEVSZ], afrbuf[AFRSZ], extra_info[EXTRASZ];
+
+    static cu_t cu;
+    static struct timeval tv, start_tv;
+    FILE *infile = stdin;
+    FILE *outfile = stdout;
+    static int maxdev, devno, i, crlf, fdfmt, nortrdlc, d4, opt, mtu;
+    (void)maxdev; (void)i; (void)opt;
+    int print_banner = 1;
+    unsigned long long sec, usec;
+    infile = fopen(strinfile, "r");
+    outfile = fopen(stroutfile, "w");
+
+    //printf("Found %d CAN devices!\n", maxdev);
+
+    while (fgets(buf, BUFSZ-1, infile)) {
+
+        if (strlen(buf) >= BUFSZ-2) {
+            fprintf(stderr, "line too long for input buffer\n");
+            return 1;
+        }
+
+        /* check for a comment line */
+        if (buf[0] != '(')
+            continue;
+
+        if (sscanf(buf, "(%llu.%llu) %21s %6299s %19s", &sec, &usec,
+                   device, afrbuf, extra_info) != 5) {
+
+            /* do not evaluate the extra info */
+            extra_info[0] = 0;
+
+            if (sscanf(buf, "(%llu.%llu) %21s %6299s", &sec, &usec,
+                       device, afrbuf) != 4) {
+                fprintf(stderr, "incorrect line format in logfile\n");
+                return 1;
+            }
+        }
+        tv.tv_sec = sec;
+        tv.tv_usec = usec;
+
+        if (print_banner) { /* print banner */
+            print_banner = 0;
+            start_tv = tv;
+            fprintf(outfile, "date %s", ctime(&start_tv.tv_sec));
+            fprintf(outfile, "base hex  timestamps absolute%s",
+                    (crlf)?"\r\n":"\n");
+            fprintf(outfile, "no internal events logged%s",
+                    (crlf)?"\r\n":"\n");
+        }
+
+        // for (i = 0, devno = 0; i < maxdev; i++) {
+        //     if (!strcmp(device, argv[optind+i])) {
+        //         devno = i + 1; /* start with channel '1' */
+        //         break;
+        //     }
+        // }
+
+        devno = 1;
+
+        if (devno) { /* only convert for selected CAN devices */
+
+            mtu = parse_canframe(afrbuf, &cu);
+
+            /* convert only CAN CC and CAN FD frames */
+            if ((mtu != CAN_MTU) && (mtu != CANFD_MTU)) {
+                printf("no valid CAN CC/FD frame\n");
+                return 1;
+            }
+
+            /* we don't support error message frames in CAN FD */
+            if ((mtu == CANFD_MTU) && (cu.cc.can_id & CAN_ERR_FLAG))
+                continue;
+
+            tv.tv_sec  = tv.tv_sec - start_tv.tv_sec;
+            tv.tv_usec = tv.tv_usec - start_tv.tv_usec;
+            if (tv.tv_usec < 0)
+                tv.tv_sec--, tv.tv_usec += 1000000;
+            if (tv.tv_sec < 0)
+                tv.tv_sec = tv.tv_usec = 0;
+
+            if (d4)
+                fprintf(outfile, "%4llu.%04llu ", (unsigned long long)tv.tv_sec, (unsigned long long)tv.tv_usec/100);
+            else
+                fprintf(outfile, "%4llu.%06llu ", (unsigned long long)tv.tv_sec, (unsigned long long)tv.tv_usec);
+
+            if ((mtu == CAN_MTU) && (fdfmt == 0))
+                can_asc(&cu.fd, devno, nortrdlc, extra_info, outfile);
+            else
+                canfd_asc(&cu.fd, devno, mtu, extra_info, outfile);
+
+            if (crlf)
+                fprintf(outfile, "\r");
+            fprintf(outfile, "\n");
+        }
+    }
+    fflush(outfile);
+    fclose(outfile);
+    fclose(infile);
+
+    return 1;
+}
+  
+// 回调函数,处理打开按钮点击事件  
+static void on_open_button_clicked(GtkWidget *widget, gpointer data) {  
+    GtkWidget *window = gtk_widget_get_toplevel(widget);  
+  
+    // 创建一个文件选择器对话框  
+    GtkWidget *file_chooser_dialog = gtk_file_chooser_dialog_new(  
+        "Open File",  
+        GTK_WINDOW(window),  
+        GTK_FILE_CHOOSER_ACTION_OPEN,  
+        "_Cancel", GTK_RESPONSE_CANCEL,  
+        "_Open", GTK_RESPONSE_ACCEPT,  
+        NULL  
+    );  
+    
+    int nrtn = 0;
+    
+    GtkFileFilter *filter = gtk_file_filter_new();  
+    gtk_file_filter_set_name(filter, "log files");  
+    gtk_file_filter_add_pattern(filter, "*.log"); // 只允许选择 .log 文件  
+    // 将过滤器添加到文件选择器  
+    gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser_dialog), filter);  
+    // 设置默认过滤器(可选)  
+    gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser_dialog), filter);  
+  
+    // 运行文件选择器对话框  
+    if (gtk_dialog_run(GTK_DIALOG(file_chooser_dialog)) == GTK_RESPONSE_ACCEPT) {  
+        // 获取选择的文件路径  
+        g_free(selected_file_path); // 释放之前可能分配的内存  
+        selected_file_path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(file_chooser_dialog));  
+  
+        // 创建一个保存文件对话框  
+        GtkWidget *save_dialog = gtk_file_chooser_dialog_new(  
+            "Save File",  
+            GTK_WINDOW(window),  
+            GTK_FILE_CHOOSER_ACTION_SAVE,  
+            "_Cancel", GTK_RESPONSE_CANCEL,  
+            "_Save", GTK_RESPONSE_ACCEPT,  
+            NULL  
+        );  
+  
+        // 设置默认的文件名称(这里简单地使用选择的文件名,不带路径)  
+        gchar *basename = g_path_get_basename(selected_file_path);  
+        char * str1 = basename;
+        char str2[256];
+        strncpy(str2,str1,256);
+        if(strnlen(str2,256)>4)
+        {
+        	int nlen = strnlen(str2,256);
+        	if((str2[nlen-4]=='.')&&(str2[nlen-3]=='l')&&(str2[nlen-2]=='o')&&(str2[nlen-1]=='g'))
+        	{
+        		str2[nlen-4] = 0;
+        	}
+        }
+        gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(save_dialog), str2);  
+        g_free(basename);  
+        
+         GtkFileFilter *filter2 = gtk_file_filter_new();  
+         gtk_file_filter_set_name(filter2, "asc files");  
+         gtk_file_filter_add_pattern(filter2, "*.asc"); // 只允许选择 .log 文件  
+    // 将过滤器添加到文件选择器  
+         gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(save_dialog), filter2);  
+    // 设置默认过滤器(可选)  
+        gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(save_dialog), filter2);  
+
+  
+  
+        // 运行保存文件对话框  
+        if (gtk_dialog_run(GTK_DIALOG(save_dialog)) == GTK_RESPONSE_ACCEPT) {  
+            // 获取输入的保存文件名称(带路径)  
+            g_free(save_file_name); // 释放之前可能分配的内存  
+            save_file_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(save_dialog));  
+  
+            // 打印选择的文件和保存的文件名称  
+            g_print("Selected file: %s\n", selected_file_path);  
+            g_print("Save file name: %s\n", save_file_name); 
+            
+            char * str1 = save_file_name;
+            char str2[256];
+            strncpy(str2,str1,256);
+            if(strnlen(str2,256)>4)
+            {
+        	int nlen = strnlen(str2,256);
+        	if((str2[nlen-4]=='.')&&(str2[nlen-3]=='a')&&(str2[nlen-2]=='s')&&(str2[nlen-1]=='c'))
+        	{
+        		
+        	}
+        	else
+        	{
+        		strncat(str2,".asc",256);
+        	}
+            }
+            
+            nrtn = log2asc( selected_file_path,str2);
+
+        }  
+  
+        // 销毁保存文件对话框  
+        gtk_widget_destroy(save_dialog);  
+    }  
+  
+    // 销毁文件选择器对话框  
+    gtk_widget_destroy(file_chooser_dialog);  
+    
+    
+    if(nrtn == 1)
+            {
+            	GtkWidget *dialog;  
+    		GtkMessageType type;  
+    		GtkButtonsType buttons;  
+    		gint response;  
+  
+    		// 设置消息框的类型和按钮  
+    		type = GTK_MESSAGE_INFO; // 可以是 GTK_MESSAGE_INFO, GTK_MESSAGE_WARNING, GTK_MESSAGE_ERROR, GTK_MESSAGE_QUESTION 等  
+    		buttons = GTK_BUTTONS_OK_CANCEL; // 可以是 GTK_BUTTONS_NONE, GTK_BUTTONS_OK, GTK_BUTTONS_CLOSE, GTK_BUTTONS_YES_NO 等  
+  
+    		// 创建消息框  
+    		dialog = gtk_message_dialog_new(GTK_WINDOW(window),  
+                                   GTK_DIALOG_DESTROY_WITH_PARENT,  
+                                   type,  
+                                   buttons,  
+                                   "Convert Result"); // 消息框的标题  
+  
+    		// 设置消息框的主要内容(可以使用 gtk_message_dialog_set_markup 来设置富文本)  
+    		gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),  
+                                            "Convert log to asc successfully.");  
+  
+    		// 运行消息框并等待用户响应  
+    		response = gtk_dialog_run(GTK_DIALOG(dialog));  
+  
+    		// 根据用户响应执行操作  
+    		if (response == GTK_RESPONSE_OK) {  
+       			g_print("User clicked OK\n");  
+    		} else if (response == GTK_RESPONSE_CANCEL) {  
+        		g_print("User clicked Cancel\n");  
+    		}  
+  
+    		// 销毁消息框  
+    		gtk_widget_destroy(dialog);  
+            }
+}  
+  
+// 应用程序激活回调(对于 GTK 应用程序)  
+static void on_activate(GtkApplication *app, gpointer user_data) {  
+    // 创建一个新窗口  
+    GtkWidget *fixed;
+    GtkWidget *label;
+    GtkWidget *window = gtk_application_window_new(app);  
+    gtk_window_set_title(GTK_WINDOW(window), "log2asc");  
+    gtk_window_set_default_size(GTK_WINDOW(window), 300, 150);  
+  
+    fixed = gtk_fixed_new ();
+    gtk_container_add (GTK_CONTAINER (window), fixed);
+ //   gtk_widget_show (fixed);
+    // 创建一个按钮  
+    GtkWidget *open_button = gtk_button_new_with_label("转换");  
+  
+    // 将按钮添加到窗口中  
+//    gtk_container_add(GTK_CONTAINER(window), open_button);  
+    label = gtk_label_new ("Use candump -l,eg, candump -l can0 save log data.");
+    gtk_fixed_put (GTK_FIXED (fixed), label, 10, 30);
+    gtk_widget_set_size_request(label,260,30);
+
+    
+    gtk_fixed_put (GTK_FIXED (fixed), open_button, 100, 90);
+    gtk_widget_set_size_request(open_button,100,30);
+  
+    // 连接按钮点击事件到回调函数  
+    g_signal_connect(open_button, "clicked", G_CALLBACK(on_open_button_clicked), NULL);  
+  
+    // 显示所有窗口内容  
+    gtk_widget_show_all(window);  
+}  
+  
+int main(int argc, char **argv) {  
+    // 初始化 GTK 应用程序  
+    GtkApplication *app = gtk_application_new("com.adc.log2asc", G_APPLICATION_FLAGS_NONE);  
+  
+    // 连接应用程序激活回调  
+    g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);  
+  
+    // 运行应用程序  
+    int status = g_application_run(G_APPLICATION(app), argc, argv);  
+  
+    // 释放资源  
+    g_free(selected_file_path);  
+    g_free(save_file_name);  
+  
+    // 返回状态码  
+    return status;  
+}

+ 901 - 0
src/tool/tool_logtoasc/lib.c

@@ -0,0 +1,901 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * lib.c - library for command line tools
+ *
+ * Copyright (c) 2002-2007 Volkswagen Group Electronic Research
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Volkswagen nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this
+ * software may be distributed under the terms of the GNU General
+ * Public License ("GPL") version 2, in which case the provisions of the
+ * GPL apply INSTEAD OF those given above.
+ *
+ * The provided data structures and external interfaces from this code
+ * are not restricted to be used by modules with a GPL compatible license.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Send feedback to <linux-can@vger.kernel.org>
+ *
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include <linux/can.h>
+#include <linux/can/error.h>
+#include <sys/socket.h> /* for sa_family_t */
+
+#include "lib.h"
+
+#define CANID_DELIM '#'
+#define CC_DLC_DELIM '_'
+#define XL_HDR_DELIM ':'
+#define DATA_SEPERATOR '.'
+
+const char hex_asc_upper[] = "0123456789ABCDEF";
+
+#define hex_asc_upper_lo(x) hex_asc_upper[((x)&0x0F)]
+#define hex_asc_upper_hi(x) hex_asc_upper[((x)&0xF0) >> 4]
+
+static inline void put_hex_byte(char *buf, __u8 byte)
+{
+	buf[0] = hex_asc_upper_hi(byte);
+	buf[1] = hex_asc_upper_lo(byte);
+}
+
+static inline void _put_id(char *buf, int end_offset, canid_t id)
+{
+	/* build 3 (SFF) or 8 (EFF) digit CAN identifier */
+	while (end_offset >= 0) {
+		buf[end_offset--] = hex_asc_upper_lo(id);
+		id >>= 4;
+	}
+}
+
+#define put_sff_id(buf, id) _put_id(buf, 2, id)
+#define put_eff_id(buf, id) _put_id(buf, 7, id)
+
+/* CAN DLC to real data length conversion helpers */
+
+static const unsigned char dlc2len[] = {0, 1, 2, 3, 4, 5, 6, 7,
+					8, 12, 16, 20, 24, 32, 48, 64};
+
+/* get data length from raw data length code (DLC) */
+unsigned char can_fd_dlc2len(unsigned char dlc)
+{
+	return dlc2len[dlc & 0x0F];
+}
+
+static const unsigned char len2dlc[] = {0, 1, 2, 3, 4, 5, 6, 7, 8,		/* 0 - 8 */
+					9, 9, 9, 9,				/* 9 - 12 */
+					10, 10, 10, 10,				/* 13 - 16 */
+					11, 11, 11, 11,				/* 17 - 20 */
+					12, 12, 12, 12,				/* 21 - 24 */
+					13, 13, 13, 13, 13, 13, 13, 13,		/* 25 - 32 */
+					14, 14, 14, 14, 14, 14, 14, 14,		/* 33 - 40 */
+					14, 14, 14, 14, 14, 14, 14, 14,		/* 41 - 48 */
+					15, 15, 15, 15, 15, 15, 15, 15,		/* 49 - 56 */
+					15, 15, 15, 15, 15, 15, 15, 15};	/* 57 - 64 */
+
+/* map the sanitized data length to an appropriate data length code */
+unsigned char can_fd_len2dlc(unsigned char len)
+{
+	if (len > 64)
+		return 0xF;
+
+	return len2dlc[len];
+}
+
+unsigned char asc2nibble(char c)
+{
+	if ((c >= '0') && (c <= '9'))
+		return c - '0';
+
+	if ((c >= 'A') && (c <= 'F'))
+		return c - 'A' + 10;
+
+	if ((c >= 'a') && (c <= 'f'))
+		return c - 'a' + 10;
+
+	return 16; /* error */
+}
+
+int hexstring2data(char *arg, unsigned char *data, int maxdlen)
+{
+	int len = strlen(arg);
+	int i;
+	unsigned char tmp;
+
+	if (!len || len % 2 || len > maxdlen * 2)
+		return 1;
+
+	memset(data, 0, maxdlen);
+
+	for (i = 0; i < len / 2; i++) {
+		tmp = asc2nibble(*(arg + (2 * i)));
+		if (tmp > 0x0F)
+			return 1;
+
+		data[i] = (tmp << 4);
+
+		tmp = asc2nibble(*(arg + (2 * i) + 1));
+		if (tmp > 0x0F)
+			return 1;
+
+		data[i] |= tmp;
+	}
+
+	return 0;
+}
+
+/* the 8-bit VCID is optionally placed in the canxl_frame.prio element */
+#define CANXL_VCID_OFFSET 16 /* bit offset of VCID in prio element */
+#define CANXL_VCID_VAL_MASK 0xFFUL /* VCID is an 8-bit value */
+#define CANXL_VCID_MASK (CANXL_VCID_VAL_MASK << CANXL_VCID_OFFSET)
+
+int parse_canframe(char *cs, cu_t *cu)
+{
+	/* documentation see lib.h */
+
+	int i, idx, dlen, len;
+	int maxdlen = CAN_MAX_DLEN;
+	int mtu = CAN_MTU;
+	__u8 *data = cu->fd.data; /* fill CAN CC/FD data by default */
+	canid_t tmp;
+
+	len = strlen(cs);
+	//printf("'%s' len %d\n", cs, len);
+
+	memset(cu, 0, sizeof(*cu)); /* init CAN CC/FD/XL frame, e.g. LEN = 0 */
+
+	if (len < 4)
+		return 0;
+
+	if (cs[3] == CANID_DELIM) { /* 3 digits SFF */
+
+		idx = 4;
+		for (i = 0; i < 3; i++) {
+			if ((tmp = asc2nibble(cs[i])) > 0x0F)
+				return 0;
+			cu->cc.can_id |= tmp << (2 - i) * 4;
+		}
+
+	} else if (cs[5] == CANID_DELIM) { /* 5 digits CAN XL VCID/PRIO*/
+
+		idx = 6;
+		for (i = 0; i < 5; i++) {
+			if ((tmp = asc2nibble(cs[i])) > 0x0F)
+				return 0;
+			cu->xl.prio |= tmp << (4 - i) * 4;
+		}
+
+		/* the VCID starts at bit position 16 */
+		tmp = (cu->xl.prio << 4) & CANXL_VCID_MASK;
+		cu->xl.prio &= CANXL_PRIO_MASK;
+		cu->xl.prio |= tmp;
+
+	} else if (cs[8] == CANID_DELIM) { /* 8 digits EFF */
+
+		idx = 9;
+		for (i = 0; i < 8; i++) {
+			if ((tmp = asc2nibble(cs[i])) > 0x0F)
+				return 0;
+			cu->cc.can_id |= tmp << (7 - i) * 4;
+		}
+		if (!(cu->cc.can_id & CAN_ERR_FLAG)) /* 8 digits but no errorframe?  */
+			cu->cc.can_id |= CAN_EFF_FLAG;   /* then it is an extended frame */
+
+	} else
+		return 0;
+
+	if ((cs[idx] == 'R') || (cs[idx] == 'r')) { /* RTR frame */
+		cu->cc.can_id |= CAN_RTR_FLAG;
+
+		/* check for optional DLC value for CAN 2.0B frames */
+		if (cs[++idx] && (tmp = asc2nibble(cs[idx++])) <= CAN_MAX_DLEN) {
+			cu->cc.len = tmp;
+
+			/* check for optional raw DLC value for CAN 2.0B frames */
+			if ((tmp == CAN_MAX_DLEN) && (cs[idx++] == CC_DLC_DELIM)) {
+				tmp = asc2nibble(cs[idx]);
+				if ((tmp > CAN_MAX_DLEN) && (tmp <= CAN_MAX_RAW_DLC))
+					cu->cc.len8_dlc = tmp;
+			}
+		}
+		return mtu;
+	}
+
+	if (cs[idx] == CANID_DELIM) { /* CAN FD frame escape char '##' */
+		maxdlen = CANFD_MAX_DLEN;
+		mtu = CANFD_MTU;
+
+		/* CAN FD frame <canid>##<flags><data>* */
+		if ((tmp = asc2nibble(cs[idx + 1])) > 0x0F)
+			return 0;
+
+		cu->fd.flags = tmp;
+		cu->fd.flags |= CANFD_FDF; /* dual-use */
+		idx += 2;
+
+	} else if (cs[idx + 14] == CANID_DELIM) { /* CAN XL frame '#80:00:11223344#' */
+		maxdlen = CANXL_MAX_DLEN;
+		mtu = CANXL_MTU;
+		data = cu->xl.data; /* fill CAN XL data */
+
+		if ((cs[idx + 2] != XL_HDR_DELIM) || (cs[idx + 5] != XL_HDR_DELIM))
+			return 0;
+
+		if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
+			return 0;
+		cu->xl.flags = tmp << 4;
+		if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
+			return 0;
+		cu->xl.flags |= tmp;
+
+		/* force CAN XL flag if it was missing in the ASCII string */
+		cu->xl.flags |= CANXL_XLF;
+
+		idx++; /* skip XL_HDR_DELIM */
+
+		if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
+			return 0;
+		cu->xl.sdt = tmp << 4;
+		if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
+			return 0;
+		cu->xl.sdt |= tmp;
+
+		idx++; /* skip XL_HDR_DELIM */
+
+		for (i = 0; i < 8; i++) {
+			if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
+				return 0;
+			cu->xl.af |= tmp << (7 - i) * 4;
+		}
+
+		idx++; /* skip CANID_DELIM */
+	}
+
+	for (i = 0, dlen = 0; i < maxdlen; i++) {
+		if (cs[idx] == DATA_SEPERATOR) /* skip (optional) separator */
+			idx++;
+
+		if (idx >= len) /* end of string => end of data */
+			break;
+
+		if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
+			return 0;
+		data[i] = tmp << 4;
+		if ((tmp = asc2nibble(cs[idx++])) > 0x0F)
+			return 0;
+		data[i] |= tmp;
+		dlen++;
+	}
+
+	if (mtu == CANXL_MTU)
+		cu->xl.len = dlen;
+	else
+		cu->fd.len = dlen;
+
+	/* check for extra DLC when having a Classic CAN with 8 bytes payload */
+	if ((maxdlen == CAN_MAX_DLEN) && (dlen == CAN_MAX_DLEN) && (cs[idx++] == CC_DLC_DELIM)) {
+		unsigned char dlc = asc2nibble(cs[idx]);
+
+		if ((dlc > CAN_MAX_DLEN) && (dlc <= CAN_MAX_RAW_DLC))
+			cu->cc.len8_dlc = dlc;
+	}
+
+	return mtu;
+}
+
+int snprintf_canframe(char *buf, size_t size, cu_t *cu, int sep)
+{
+	/* documentation see lib.h */
+
+	unsigned char is_canfd = cu->fd.flags;
+	int i, offset;
+	int len;
+
+	/* ensure space for string termination */
+	if (size < 1)
+		return size;
+
+	/* handle CAN XL frames */
+	if (cu->xl.flags & CANXL_XLF) {
+		len = cu->xl.len;
+
+		/* check if the CAN frame fits into the provided buffer */
+		if (sizeof("00123#11:22:12345678#") + 2 * len + (sep ? len : 0) > size - 1) {
+			/* mark buffer overflow in output */
+			memset(buf, '-', size - 1);
+			buf[size - 1] = 0;
+			return size;
+		}
+
+		/* print prio and CAN XL header content */
+		offset = sprintf(buf, "%02X%03X#%02X:%02X:%08X#",
+				 (canid_t)(cu->xl.prio & CANXL_VCID_MASK) >> CANXL_VCID_OFFSET,
+				 (canid_t)(cu->xl.prio & CANXL_PRIO_MASK),
+				 cu->xl.flags, cu->xl.sdt, cu->xl.af);
+
+		/* data */
+		for (i = 0; i < len; i++) {
+			put_hex_byte(buf + offset, cu->xl.data[i]);
+			offset += 2;
+			if (sep && (i + 1 < len))
+				buf[offset++] = '.';
+		}
+
+		buf[offset] = 0;
+
+		return offset;
+	}
+
+	/* handle CAN CC/FD frames - ensure max length values */
+	if (is_canfd)
+		len = (cu->fd.len > CANFD_MAX_DLEN) ? CANFD_MAX_DLEN : cu->fd.len;
+	else
+		len = (cu->fd.len > CAN_MAX_DLEN) ? CAN_MAX_DLEN : cu->fd.len;
+
+	/* check if the CAN frame fits into the provided buffer */
+	if (sizeof("12345678#_F") + 2 * len + (sep ? len : 0) +	\
+	    (cu->fd.can_id & CAN_RTR_FLAG ? 2 : 0) > size - 1) {
+		/* mark buffer overflow in output */
+		memset(buf, '-', size - 1);
+		buf[size - 1] = 0;
+		return size;
+	}
+
+	if (cu->fd.can_id & CAN_ERR_FLAG) {
+		put_eff_id(buf, cu->fd.can_id & (CAN_ERR_MASK | CAN_ERR_FLAG));
+		buf[8] = '#';
+		offset = 9;
+	} else if (cu->fd.can_id & CAN_EFF_FLAG) {
+		put_eff_id(buf, cu->fd.can_id & CAN_EFF_MASK);
+		buf[8] = '#';
+		offset = 9;
+	} else {
+		put_sff_id(buf, cu->fd.can_id & CAN_SFF_MASK);
+		buf[3] = '#';
+		offset = 4;
+	}
+
+	/* CAN CC frames may have RTR enabled. There are no ERR frames with RTR */
+	if (!is_canfd && cu->fd.can_id & CAN_RTR_FLAG) {
+		buf[offset++] = 'R';
+		/* print a given CAN 2.0B DLC if it's not zero */
+		if (len && len <= CAN_MAX_DLEN) {
+			buf[offset++] = hex_asc_upper_lo(cu->fd.len);
+
+			/* check for optional raw DLC value for CAN 2.0B frames */
+			if (len == CAN_MAX_DLEN) {
+				if ((cu->cc.len8_dlc > CAN_MAX_DLEN) && (cu->cc.len8_dlc <= CAN_MAX_RAW_DLC)) {
+					buf[offset++] = CC_DLC_DELIM;
+					buf[offset++] = hex_asc_upper_lo(cu->cc.len8_dlc);
+				}
+			}
+		}
+
+		buf[offset] = 0;
+		return offset;
+	}
+
+	/* any CAN FD flags */
+	if (is_canfd) {
+		/* add CAN FD specific escape char and flags */
+		buf[offset++] = '#';
+		buf[offset++] = hex_asc_upper_lo(cu->fd.flags);
+		if (sep && len)
+			buf[offset++] = '.';
+	}
+
+	/* data */
+	for (i = 0; i < len; i++) {
+		put_hex_byte(buf + offset, cu->fd.data[i]);
+		offset += 2;
+		if (sep && (i + 1 < len))
+			buf[offset++] = '.';
+	}
+
+	/* check for extra DLC when having a Classic CAN with 8 bytes payload */
+	if (!is_canfd && (len == CAN_MAX_DLEN)) {
+		unsigned char dlc = cu->cc.len8_dlc;
+
+		if ((dlc > CAN_MAX_DLEN) && (dlc <= CAN_MAX_RAW_DLC)) {
+			buf[offset++] = CC_DLC_DELIM;
+			buf[offset++] = hex_asc_upper_lo(dlc);
+		}
+	}
+
+	buf[offset] = 0;
+
+	return offset;
+}
+
+int snprintf_long_canframe(char *buf, size_t size, cu_t *cu, int view)
+{
+	/* documentation see lib.h */
+
+	unsigned char is_canfd = cu->fd.flags;
+	int i, j, dlen, offset;
+	size_t maxsize;
+	int len;
+
+	/* ensure space for string termination */
+	if (size < 1)
+		return size;
+
+	/* handle CAN XL frames */
+	if (cu->xl.flags & CANXL_XLF) {
+		len = cu->xl.len;
+
+		/* crop to CANFD_MAX_DLEN */
+		if (len > CANFD_MAX_DLEN)
+			dlen = CANFD_MAX_DLEN;
+		else
+			dlen = len;
+
+		/* check if the CAN frame fits into the provided buffer */
+		if (sizeof(".....123 [2048] (00|11:22:12345678)  ...") + 3 * dlen > size - 1) {
+			/* mark buffer overflow in output */
+			memset(buf, '-', size - 1);
+			buf[size - 1] = 0;
+			return size;
+		}
+
+		if (view & CANLIB_VIEW_INDENT_SFF) {
+			memset(buf, ' ', 5);
+			put_sff_id(buf + 5, cu->xl.prio & CANXL_PRIO_MASK);
+			offset = 8;
+		} else {
+			put_sff_id(buf, cu->xl.prio & CANXL_PRIO_MASK);
+			offset = 3;
+		}
+
+		/* print prio and CAN XL header content */
+		offset += sprintf(buf + offset, " [%04d] (%02X|%02X:%02X:%08X) ",
+				  len,
+				  (canid_t)(cu->xl.prio & CANXL_VCID_MASK) >> CANXL_VCID_OFFSET,
+				  cu->xl.flags, cu->xl.sdt, cu->xl.af);
+
+		for (i = 0; i < dlen; i++) {
+			put_hex_byte(buf + offset, cu->xl.data[i]);
+			offset += 2;
+			if (i + 1 < dlen)
+				buf[offset++] = ' ';
+		}
+
+		/* indicate cropped output */
+		if (cu->xl.len > dlen)
+			offset += sprintf(buf + offset, " ...");
+
+		buf[offset] = 0;
+
+		return offset;
+	}
+
+	/* ensure max length values */
+	if (is_canfd)
+		len = (cu->fd.len > CANFD_MAX_DLEN) ? CANFD_MAX_DLEN : cu->fd.len;
+	else
+		len = (cu->fd.len > CAN_MAX_DLEN) ? CAN_MAX_DLEN : cu->fd.len;
+
+	/* check if the CAN frame fits into the provided buffer */
+	maxsize = sizeof("12345678  [12]  ");
+	if (view & CANLIB_VIEW_BINARY)
+		dlen = 9; /* _10101010 */
+	else
+		dlen = 3; /* _AA */
+
+	if (cu->fd.can_id & CAN_RTR_FLAG) {
+		maxsize += sizeof("    remote request");
+	} else {
+		maxsize += len * dlen;
+
+		if (len <= CAN_MAX_DLEN) {
+			if (cu->fd.can_id & CAN_ERR_FLAG) {
+				maxsize += sizeof("    ERRORFRAME");
+				maxsize += (8 - len) * dlen;
+			} else if (view & CANLIB_VIEW_ASCII) {
+				maxsize += sizeof("    'a.b.CDEF'");
+				maxsize += (8 - len) * dlen;
+			}
+		}
+	}
+
+	if (maxsize > size - 1) {
+		/* mark buffer overflow in output */
+		memset(buf, '-', size - 1);
+		buf[size - 1] = 0;
+		return size;
+	}
+
+	/* initialize space for CAN-ID and length information */
+	memset(buf, ' ', 15);
+
+	if (cu->cc.can_id & CAN_ERR_FLAG) {
+		put_eff_id(buf, cu->cc.can_id & (CAN_ERR_MASK | CAN_ERR_FLAG));
+		offset = 10;
+	} else if (cu->fd.can_id & CAN_EFF_FLAG) {
+		put_eff_id(buf, cu->fd.can_id & CAN_EFF_MASK);
+		offset = 10;
+	} else {
+		if (view & CANLIB_VIEW_INDENT_SFF) {
+			put_sff_id(buf + 5, cu->fd.can_id & CAN_SFF_MASK);
+			offset = 10;
+		} else {
+			put_sff_id(buf, cu->fd.can_id & CAN_SFF_MASK);
+			offset = 5;
+		}
+	}
+
+	/* The len value is sanitized (see above) */
+	if (!is_canfd) {
+		if (view & CANLIB_VIEW_LEN8_DLC) {
+			unsigned char dlc = cu->cc.len8_dlc;
+
+			/* fall back to len if we don't have a valid DLC > 8 */
+			if (!((len == CAN_MAX_DLEN) && (dlc > CAN_MAX_DLEN) &&
+			      (dlc <= CAN_MAX_RAW_DLC)))
+				dlc = len;
+
+			buf[offset + 1] = '{';
+			buf[offset + 2] = hex_asc_upper[dlc];
+			buf[offset + 3] = '}';
+		} else {
+			buf[offset + 1] = '[';
+			buf[offset + 2] = len + '0';
+			buf[offset + 3] = ']';
+		}
+
+		/* standard CAN frames may have RTR enabled */
+		if (cu->fd.can_id & CAN_RTR_FLAG) {
+			offset += sprintf(buf + offset + 5, " remote request");
+			return offset + 5;
+		}
+	} else {
+		buf[offset] = '[';
+		buf[offset + 1] = (len / 10) + '0';
+		buf[offset + 2] = (len % 10) + '0';
+		buf[offset + 3] = ']';
+	}
+	offset += 5;
+
+	if (view & CANLIB_VIEW_BINARY) {
+		/* _10101010 - dlen = 9, see above */
+		if (view & CANLIB_VIEW_SWAP) {
+			for (i = len - 1; i >= 0; i--) {
+				buf[offset++] = (i == len - 1) ? ' ' : SWAP_DELIMITER;
+				for (j = 7; j >= 0; j--)
+					buf[offset++] = (1 << j & cu->fd.data[i]) ? '1' : '0';
+			}
+		} else {
+			for (i = 0; i < len; i++) {
+				buf[offset++] = ' ';
+				for (j = 7; j >= 0; j--)
+					buf[offset++] = (1 << j & cu->fd.data[i]) ? '1' : '0';
+			}
+		}
+	} else {
+		/* _AA - dlen = 3, see above */
+		if (view & CANLIB_VIEW_SWAP) {
+			for (i = len - 1; i >= 0; i--) {
+				if (i == len - 1)
+					buf[offset++] = ' ';
+				else
+					buf[offset++] = SWAP_DELIMITER;
+
+				put_hex_byte(buf + offset, cu->fd.data[i]);
+				offset += 2;
+			}
+		} else {
+			for (i = 0; i < len; i++) {
+				buf[offset++] = ' ';
+				put_hex_byte(buf + offset, cu->fd.data[i]);
+				offset += 2;
+			}
+		}
+	}
+
+	buf[offset] = 0; /* terminate string */
+
+	/*
+	 * The ASCII & ERRORFRAME output is put at a fixed len behind the data.
+	 * For now we support ASCII output only for payload length up to 8 bytes.
+	 * Does it make sense to write 64 ASCII byte behind 64 ASCII HEX data on the console?
+	 */
+	if (len > CAN_MAX_DLEN)
+		return offset;
+
+	if (cu->fd.can_id & CAN_ERR_FLAG)
+		offset += sprintf(buf + offset, "%*s", dlen * (8 - len) + 13, "ERRORFRAME");
+	else if (view & CANLIB_VIEW_ASCII) {
+		j = dlen * (8 - len) + 4;
+		if (view & CANLIB_VIEW_SWAP) {
+			sprintf(buf + offset, "%*s", j, "`");
+			offset += j;
+			for (i = len - 1; i >= 0; i--)
+				if ((cu->fd.data[i] > 0x1F) && (cu->fd.data[i] < 0x7F))
+					buf[offset++] = cu->fd.data[i];
+				else
+					buf[offset++] = '.';
+
+			offset += sprintf(buf + offset, "`");
+		} else {
+			sprintf(buf + offset, "%*s", j, "'");
+			offset += j;
+			for (i = 0; i < len; i++)
+				if ((cu->fd.data[i] > 0x1F) && (cu->fd.data[i] < 0x7F))
+					buf[offset++] = cu->fd.data[i];
+				else
+					buf[offset++] = '.';
+
+			offset += sprintf(buf + offset, "'");
+		}
+	}
+
+	return offset;
+}
+
+static const char *error_classes[] = {
+	"tx-timeout",
+	"lost-arbitration",
+	"controller-problem",
+	"protocol-violation",
+	"transceiver-status",
+	"no-acknowledgement-on-tx",
+	"bus-off",
+	"bus-error",
+	"restarted-after-bus-off",
+	"error-counter-tx-rx",
+};
+
+static const char *controller_problems[] = {
+	"rx-overflow",
+	"tx-overflow",
+	"rx-error-warning",
+	"tx-error-warning",
+	"rx-error-passive",
+	"tx-error-passive",
+	"back-to-error-active",
+};
+
+static const char *protocol_violation_types[] = {
+	"single-bit-error",
+	"frame-format-error",
+	"bit-stuffing-error",
+	"tx-dominant-bit-error",
+	"tx-recessive-bit-error",
+	"bus-overload",
+	"active-error",
+	"error-on-tx",
+};
+
+static const char *protocol_violation_locations[] = {
+	"unspecified",
+	"unspecified",
+	"id.28-to-id.21",
+	"start-of-frame",
+	"bit-srtr",
+	"bit-ide",
+	"id.20-to-id.18",
+	"id.17-to-id.13",
+	"crc-sequence",
+	"reserved-bit-0",
+	"data-field",
+	"data-length-code",
+	"bit-rtr",
+	"reserved-bit-1",
+	"id.4-to-id.0",
+	"id.12-to-id.5",
+	"unspecified",
+	"active-error-flag",
+	"intermission",
+	"tolerate-dominant-bits",
+	"unspecified",
+	"unspecified",
+	"passive-error-flag",
+	"error-delimiter",
+	"crc-delimiter",
+	"acknowledge-slot",
+	"end-of-frame",
+	"acknowledge-delimiter",
+	"overload-flag",
+	"unspecified",
+	"unspecified",
+	"unspecified",
+};
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#endif
+
+static int snprintf_error_data(char *buf, size_t len, uint8_t err,
+			       const char **arr, int arr_len)
+{
+	int i, n = 0, count = 0;
+
+	if (!err || len <= 0)
+		return 0;
+
+	for (i = 0; i < arr_len; i++) {
+		if (err & (1 << i)) {
+			int tmp_n = 0;
+			if (count) {
+				/* Fix for potential buffer overflow https://lgtm.com/rules/1505913226124/ */
+				tmp_n = snprintf(buf + n, len - n, ",");
+				if (tmp_n < 0 || (size_t)tmp_n >= len - n) {
+					return n;
+				}
+				n += tmp_n;
+			}
+			tmp_n = snprintf(buf + n, len - n, "%s", arr[i]);
+			if (tmp_n < 0 || (size_t)tmp_n >= len - n) {
+				return n;
+			}
+			n += tmp_n;
+			count++;
+		}
+	}
+
+	return n;
+}
+
+static int snprintf_error_lostarb(char *buf, size_t len, const struct canfd_frame *cf)
+{
+	if (len <= 0)
+		return 0;
+	return snprintf(buf, len, "{at bit %d}", cf->data[0]);
+}
+
+static int snprintf_error_ctrl(char *buf, size_t len, const struct canfd_frame *cf)
+{
+	int n = 0;
+
+	if (len <= 0)
+		return 0;
+
+	n += snprintf(buf + n, len - n, "{");
+	n += snprintf_error_data(buf + n, len - n, cf->data[1],
+				controller_problems,
+				ARRAY_SIZE(controller_problems));
+	n += snprintf(buf + n, len - n, "}");
+
+	return n;
+}
+
+static int snprintf_error_prot(char *buf, size_t len, const struct canfd_frame *cf)
+{
+	int n = 0;
+
+	if (len <= 0)
+		return 0;
+
+	n += snprintf(buf + n, len - n, "{{");
+	n += snprintf_error_data(buf + n, len - n, cf->data[2],
+				protocol_violation_types,
+				ARRAY_SIZE(protocol_violation_types));
+	n += snprintf(buf + n, len - n, "}{");
+	if (cf->data[3] > 0 &&
+	    cf->data[3] < ARRAY_SIZE(protocol_violation_locations))
+		n += snprintf(buf + n, len - n, "%s",
+			      protocol_violation_locations[cf->data[3]]);
+	n += snprintf(buf + n, len - n, "}}");
+
+	return n;
+}
+
+static int snprintf_error_cnt(char *buf, size_t len, const struct canfd_frame *cf)
+{
+	int n = 0;
+
+	if (len <= 0)
+		return 0;
+
+	n += snprintf(buf + n, len - n, "{{%d}{%d}}",
+		      cf->data[6], cf->data[7]);
+
+	return n;
+}
+
+int snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf,
+                  const char* sep)
+{
+	canid_t class, mask;
+	int i, n = 0, classes = 0;
+	char *defsep = ",";
+
+	if (!(cf->can_id & CAN_ERR_FLAG))
+		return 0;
+
+	class = cf->can_id & CAN_EFF_MASK;
+	if (class > (1 << ARRAY_SIZE(error_classes))) {
+		fprintf(stderr, "Error class %#x is invalid\n", class);
+		return 0;
+	}
+
+	if (!sep)
+		sep = defsep;
+
+	for (i = 0; i < (int)ARRAY_SIZE(error_classes); i++) {
+		mask = 1 << i;
+		if (class & mask) {
+			int tmp_n = 0;
+			if (classes) {
+				/* Fix for potential buffer overflow https://lgtm.com/rules/1505913226124/ */
+				tmp_n = snprintf(buf + n, len - n, "%s", sep);
+				if (tmp_n < 0 || (size_t)tmp_n >= len - n) {
+					buf[0] = 0; /* empty terminated string */
+					return 0;
+				}
+				n += tmp_n;
+			}
+			tmp_n = snprintf(buf + n, len - n, "%s", error_classes[i]);
+			if (tmp_n < 0 || (size_t)tmp_n >= len - n) {
+				buf[0] = 0; /* empty terminated string */
+				return 0;
+			}
+			n += tmp_n;
+			if (mask == CAN_ERR_LOSTARB)
+				n += snprintf_error_lostarb(buf + n, len - n,
+							   cf);
+			if (mask == CAN_ERR_CRTL)
+				n += snprintf_error_ctrl(buf + n, len - n, cf);
+			if (mask == CAN_ERR_PROT)
+				n += snprintf_error_prot(buf + n, len - n, cf);
+			if (mask == CAN_ERR_CNT)
+				n += snprintf_error_cnt(buf + n, len - n, cf);
+			classes++;
+		}
+	}
+
+	if (!(cf->can_id & CAN_ERR_CNT) && (cf->data[6] || cf->data[7])) {
+		n += snprintf(buf + n, len - n, "%serror-counter-tx-rx", sep);
+		n += snprintf_error_cnt(buf + n, len - n, cf);
+	}
+
+	return n;
+}
+
+int64_t timespec_diff_ms(struct timespec *ts1,
+					  struct timespec *ts2)
+{
+	int64_t diff = (ts1->tv_sec - ts2->tv_sec) * 1000;
+
+	diff += (ts1->tv_nsec - ts2->tv_nsec) / 1000000;
+
+	return diff;
+}
+
+void timespec_add_ms(struct timespec *ts, uint64_t milliseconds)
+{
+	uint64_t total_ns = ts->tv_nsec + (milliseconds * 1000000);
+
+	ts->tv_sec += total_ns / 1000000000;
+	ts->tv_nsec = total_ns % 1000000000;
+}

+ 261 - 0
src/tool/tool_logtoasc/lib.h

@@ -0,0 +1,261 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * lib.h - library include for command line tools
+ *
+ * Copyright (c) 2002-2007 Volkswagen Group Electronic Research
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Volkswagen nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this
+ * software may be distributed under the terms of the GNU General
+ * Public License ("GPL") version 2, in which case the provisions of the
+ * GPL apply INSTEAD OF those given above.
+ *
+ * The provided data structures and external interfaces from this code
+ * are not restricted to be used by modules with a GPL compatible license.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * Send feedback to <linux-can@vger.kernel.org>
+ *
+ */
+
+#ifndef CAN_UTILS_LIB_H
+#define CAN_UTILS_LIB_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#ifdef DEBUG
+#define pr_debug(fmt, args...) printf(fmt, ##args)
+#else
+__attribute__((format (printf, 1, 2)))
+static inline int pr_debug(const char* fmt, ...) {return 0;}
+#endif
+
+/* CAN CC/FD/XL frame union */
+typedef union {
+	struct can_frame cc;
+	struct canfd_frame fd;
+	struct canxl_frame xl;
+} cu_t;
+
+/*
+ * The buffer size for ASCII CAN frame string representations
+ * covers also the 'long' CAN frame output from sprint_long_canframe()
+ * including (swapped) binary represetations, timestamps, netdevice names,
+ * lengths and error message details as the CAN XL data is cropped to 64
+ * byte (the 'long' CAN frame output is only for display on terminals).
+ */
+#define AFRSZ 6300 /* 3*2048 (data) + 22 (timestamp) + 18 (netdev) + ID/HDR */
+
+/* CAN DLC to real data length conversion helpers especially for CAN FD */
+
+/* get data length from raw data length code (DLC) */
+unsigned char can_fd_dlc2len(unsigned char dlc);
+
+/* map the sanitized data length to an appropriate data length code */
+unsigned char can_fd_len2dlc(unsigned char len);
+
+unsigned char asc2nibble(char c);
+/*
+ * Returns the decimal value of a given ASCII hex character.
+ *
+ * While 0..9, a..f, A..F are valid ASCII hex characters.
+ * On invalid characters the value 16 is returned for error handling.
+ */
+
+int hexstring2data(char *arg, unsigned char *data, int maxdlen);
+/*
+ * Converts a given ASCII hex string to a (binary) byte string.
+ *
+ * A valid ASCII hex string consists of an even number of up to 16 chars.
+ * Leading zeros '00' in the ASCII hex string are interpreted.
+ *
+ * Examples:
+ *
+ * "1234"   => data[0] = 0x12, data[1] = 0x34
+ * "001234" => data[0] = 0x00, data[1] = 0x12, data[2] = 0x34
+ *
+ * Return values:
+ * 0 = success
+ * 1 = error (in length or the given characters are no ASCII hex characters)
+ *
+ * Remark: The not written data[] elements are initialized with zero.
+ *
+ */
+
+int parse_canframe(char *cs, cu_t *cu);
+/*
+ * Transfers a valid ASCII string describing a CAN frame into the CAN union
+ * containing CAN CC/FD/XL structs.
+ *
+ * CAN CC frames (aka Classical CAN, CAN 2.0B)
+ * - string layout <can_id>#{R{len}|data}{_len8_dlc}
+ * - {data} has 0 to 8 hex-values that can (optionally) be separated by '.'
+ * - {len} can take values from 0 to 8 and can be omitted if zero
+ * - {_len8_dlc} can take hex values from '_9' to '_F' when len is CAN_MAX_DLEN
+ * - return value on successful parsing: CAN_MTU
+ *
+ * CAN FD frames
+ * - string layout <can_id>##<flags>{data}
+ * - <flags> a single ASCII Hex value (0 .. F) which defines canfd_frame.flags
+ * - {data} has 0 to 64 hex-values that can (optionally) be separated by '.'
+ * - return value on successful parsing: CANFD_MTU
+ *
+ * CAN XL frames
+ * - string layout <vcid><prio>#<flags>:<sdt>:<af>#{data}
+ * - <vcid> a two ASCII Hex value (00 .. FF) which defines the VCID
+ * - <prio> a three ASCII Hex value (000 .. 7FF) which defines the 11 bit PRIO
+ * - <flags> a two ASCII Hex value (00 .. FF) which defines canxl_frame.flags
+ * - <sdt> a two ASCII Hex value (00 .. FF) which defines canxl_frame.sdt
+ * - <af> a 8 digit ASCII Hex value which defines the 32 bit canxl_frame.af
+ * - {data} has 1 to 2048 hex-values that can (optionally) be separated by '.'
+ * - return value on successful parsing: CANXL_MTU
+ *
+ * Return value on detected problems: 0
+ *
+ * <can_id> can have 3 (standard frame format) or 8 (extended frame format)
+ * hexadecimal chars
+ *
+ *
+ * Examples:
+ *
+ * 123# -> standard CAN-Id = 0x123, len = 0
+ * 12345678# -> extended CAN-Id = 0x12345678, len = 0
+ * 123#R -> standard CAN-Id = 0x123, len = 0, RTR-frame
+ * 123#R0 -> standard CAN-Id = 0x123, len = 0, RTR-frame
+ * 123#R7 -> standard CAN-Id = 0x123, len = 7, RTR-frame
+ * 123#R8_9 -> standard CAN-Id = 0x123, len = 8, dlc = 9, RTR-frame
+ * 7A1#r -> standard CAN-Id = 0x7A1, len = 0, RTR-frame
+ *
+ * 123#00 -> standard CAN-Id = 0x123, len = 1, data[0] = 0x00
+ * 123#1122334455667788 -> standard CAN-Id = 0x123, len = 8
+ * 123#1122334455667788_E -> standard CAN-Id = 0x123, len = 8, dlc = 14
+ * 123#11.22.33.44.55.66.77.88 -> standard CAN-Id = 0x123, len = 8
+ * 123#11.2233.44556677.88 -> standard CAN-Id = 0x123, len = 8
+ * 32345678#112233 -> error frame with CAN_ERR_FLAG (0x2000000) set
+ *
+ * 123##0112233 -> CAN FD frame standard CAN-Id = 0x123, flags = 0, len = 3
+ * 123##1112233 -> CAN FD frame, flags = CANFD_BRS, len = 3
+ * 123##2112233 -> CAN FD frame, flags = CANFD_ESI, len = 3
+ * 123##3 -> CAN FD frame, flags = (CANFD_ESI | CANFD_BRS), len = 0
+ *     ^^
+ *     CAN FD extension to handle the canfd_frame.flags content
+ *
+ * 45123#81:00:12345678#11223344.556677 -> CAN XL frame with len = 7,
+ *   VCID = 0x45, PRIO = 0x123, flags = 0x81, sdt = 0x00, af = 0x12345678
+ *
+ * Simple facts on this compact ASCII CAN frame representation:
+ *
+ * - 3 digits: standard frame format
+ * - 8 digits: extendend frame format OR error frame
+ * - 8 digits with CAN_ERR_FLAG (0x2000000) set: error frame
+ * - an error frame is never a RTR frame
+ * - CAN FD frames do not have a RTR bit
+ */
+
+int snprintf_canframe(char *buf, size_t size, cu_t *cu, int sep);
+/*
+ * Creates a CAN frame hexadecimal output in compact format.
+ * The CAN data[] is separated by '.' when sep != 0.
+ *
+ * A CAN XL frame is detected when CANXL_XLF is set in the struct
+ * cu.canxl_frame.flags. Otherwise the type of the CAN frame (CAN CC/FD)
+ * is specified by the dual-use struct cu.canfd_frame.flags element:
+ * w/o  CAN FD flags (== 0) -> CAN CC frame (aka Classical CAN, CAN2.0B)
+ * with CAN FD flags (!= 0) -> CAN FD frame (with CANFD_[FDF/BRS/ESI])
+ *
+ * 12345678#112233 -> extended CAN-Id = 0x12345678, len = 3, data, sep = 0
+ * 123#1122334455667788_E -> standard CAN-Id = 0x123, len = 8, dlc = 14, data, sep = 0
+ * 12345678#R -> extended CAN-Id = 0x12345678, RTR, len = 0
+ * 12345678#R5 -> extended CAN-Id = 0x12345678, RTR, len = 5
+ * 123#11.22.33.44.55.66.77.88 -> standard CAN-Id = 0x123, dlc = 8, sep = 1
+ * 32345678#112233 -> error frame with CAN_ERR_FLAG (0x2000000) set
+ * 123##0112233 -> CAN FD frame standard CAN-Id = 0x123, flags = 0, len = 3
+ * 123##2112233 -> CAN FD frame, flags = CANFD_ESI, len = 3
+ * 45123#81:00:12345678#11223344.556677 -> CAN XL frame with len = 7,
+ *   VCID = 0x45, PRIO = 0x123, flags = 0x81, sdt = 0x00, af = 0x12345678
+ *
+ */
+
+#define CANLIB_VIEW_ASCII	0x1
+#define CANLIB_VIEW_BINARY	0x2
+#define CANLIB_VIEW_SWAP	0x4
+#define CANLIB_VIEW_ERROR	0x8
+#define CANLIB_VIEW_INDENT_SFF	0x10
+#define CANLIB_VIEW_LEN8_DLC	0x20
+
+#define SWAP_DELIMITER '`'
+
+int snprintf_long_canframe(char *buf, size_t size, cu_t *cu, int view);
+/*
+ * Creates a CAN frame hexadecimal output in user readable format.
+ *
+ * A CAN XL frame is detected when CANXL_XLF is set in the struct
+ * cu.canxl_frame.flags. Otherwise the type of the CAN frame (CAN CC/FD)
+ * is specified by the dual-use struct cu.canfd_frame.flags element:
+ * w/o  CAN FD flags (== 0) -> CAN CC frame (aka Classical CAN, CAN2.0B)
+ * with CAN FD flags (!= 0) -> CAN FD frame (with CANFD_[FDF/BRS/ESI])
+ *
+ * 12345678   [3]  11 22 33 -> extended CAN-Id = 0x12345678, len = 3, data
+ * 12345678   [0]  remote request -> extended CAN-Id = 0x12345678, RTR
+ * 14B0DC51   [8]  4A 94 E8 2A EC 58 55 62   'J..*.XUb' -> (with ASCII output)
+ * 321   {B}  11 22 33 44 55 66 77 88 -> Classical CAN with raw '{DLC}' value B
+ * 20001111   [7]  C6 23 7B 32 69 98 3C      ERRORFRAME -> (CAN_ERR_FLAG set)
+ * 12345678  [03]  11 22 33 -> CAN FD with extended CAN-Id = 0x12345678, len = 3
+ *      123 [0003] (45|81:00:12345678) 11 22 33 -> CAN XL frame with VCID 0x45
+ *
+ * 123   [3]  11 22 33         -> CANLIB_VIEW_INDENT_SFF == 0
+ *      123   [3]  11 22 33    -> CANLIB_VIEW_INDENT_SFF == set
+ *
+ * There are no binary or ASCII view modes for CAN XL and the number of displayed
+ * data bytes is limited to 64 to fit terminal output use-cases.
+ */
+
+int snprintf_can_error_frame(char *buf, size_t len, const struct canfd_frame *cf,
+			     const char *sep);
+/*
+ * Creates a CAN error frame output in user readable format.
+ */
+
+/**
+ * timespec_diff_ms - calculate timespec difference in milliseconds
+ * @ts1: first timespec
+ * @ts2: second timespec
+ *
+ * Return negative difference if in the past.
+ */
+int64_t timespec_diff_ms(struct timespec *ts1, struct timespec *ts2);
+
+/**
+ * timespec_add_ms - add milliseconds to timespec
+ * @ts: timespec
+ * @milliseconds: milliseconds to add
+ */
+void timespec_add_ms(struct timespec *ts, uint64_t milliseconds);
+
+#endif

+ 11 - 0
src/tool/tool_logtoasc/main.cpp

@@ -0,0 +1,11 @@
+#include "mainwindow.h"
+
+#include <QApplication>
+
+int main(int argc, char *argv[])
+{
+    QApplication a(argc, argv);
+    MainWindow w;
+    w.show();
+    return a.exec();
+}

+ 309 - 0
src/tool/tool_logtoasc/mainwindow.cpp

@@ -0,0 +1,309 @@
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include <QFileDialog>
+#include <QMessageBox>
+
+#include <iostream>
+
+#include <libgen.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/can.h>
+#include <net/if.h>
+#include <sys/time.h>
+
+
+extern "C"
+{
+#include "lib.h"
+}
+
+MainWindow::MainWindow(QWidget *parent)
+    : QMainWindow(parent)
+    , ui(new Ui::MainWindow)
+{
+    ui->setupUi(this);
+
+    setWindowTitle("Log To ASC");
+}
+
+MainWindow::~MainWindow()
+{
+    delete ui;
+}
+
+void MainWindow::on_pushButton_convert_clicked()
+{
+    QString strinfilename = QFileDialog::getOpenFileName(this,"Open log File",".","*.log");
+
+    if(strinfilename.isEmpty())
+    {
+        std::cout<<" not select input file."<<std::endl;
+        return;
+    }
+
+    int indexsplash = strinfilename.lastIndexOf('/');
+    QString lastPath = strinfilename.left(indexsplash);
+
+
+    std::cout<<" input file name: "<<strinfilename.toStdString()<<std::endl;
+
+    QString stroutfilename = QFileDialog::getSaveFileName(this,"Save asc File",lastPath,"*.asc");
+
+    if(stroutfilename.isEmpty())
+    {
+        std::cout<<" not select output file."<<std::endl;
+        return;
+    }
+    if(stroutfilename.right(4) != ".asc")
+    {
+        std::cout<<" append .asc"<<std::endl;
+        stroutfilename.append(".asc");
+    }
+
+    std::cout<<" out file name: "<<stroutfilename.toStdString()<<std::endl;
+
+    int nrtn = log2asc(strinfilename,stroutfilename);
+
+    if(nrtn == 1){
+        QMessageBox::information(this,"Success","Convert Successfully.",QMessageBox::YesAll);
+    }
+}
+
+#define DEVSZ 22
+#define EXTRASZ 20
+#define TIMESZ sizeof("(1345212884.318850)   ")
+#define BUFSZ (DEVSZ + AFRSZ + EXTRASZ + TIMESZ)
+/* adapt sscanf() functions below on error */
+#if (AFRSZ != 6300)
+#error "AFRSZ value does not fit sscanf restrictions!"
+#endif
+#if (DEVSZ != 22)
+#error "DEVSZ value does not fit sscanf restrictions!"
+#endif
+#if (EXTRASZ != 20)
+#error "EXTRASZ value does not fit sscanf restrictions!"
+#endif
+
+static void can_asc(struct canfd_frame *cfd, int devno, int nortrdlc,
+                    char *extra_info, FILE *outfile)
+{
+    int i;
+    char id[10];
+    char *dir = "Rx";
+    int dlc;
+    struct can_frame *cf = (struct can_frame *)cfd; /* for len8_dlc */
+
+    fprintf(outfile, "%-2d ", devno); /* channel number left aligned */
+
+    if (cf->can_id & CAN_ERR_FLAG)
+        fprintf(outfile, "ErrorFrame");
+    else {
+        sprintf(id, "%X%c", cf->can_id & CAN_EFF_MASK,
+                (cf->can_id & CAN_EFF_FLAG)?'x':' ');
+
+        /* check for extra info */
+        if (strlen(extra_info) > 0) {
+            /* only the first char is defined so far */
+            if (extra_info[0] == 'T')
+                dir = "Tx";
+        }
+
+        fprintf(outfile, "%-15s %s   ", id, dir);
+
+        if (cf->len == CAN_MAX_DLC &&
+            cf->len8_dlc > CAN_MAX_DLC &&
+            cf->len8_dlc <= CAN_MAX_RAW_DLC)
+            dlc = cf->len8_dlc;
+        else
+            dlc = cf->len;
+
+        if (cf->can_id & CAN_RTR_FLAG) {
+            if (nortrdlc)
+                fprintf(outfile, "r"); /* RTR frame */
+            else
+                fprintf(outfile, "r %X", dlc); /* RTR frame */
+        } else {
+            fprintf(outfile, "d %X", dlc); /* data frame */
+
+            for (i = 0; i < cf->len; i++) {
+                fprintf(outfile, " %02X", cf->data[i]);
+            }
+        }
+    }
+}
+
+static void canfd_asc(struct canfd_frame *cf, int devno, int mtu,
+                      char *extra_info, FILE *outfile)
+{
+    int i;
+    char id[10];
+    char *dir = "Rx";
+    unsigned int flags = 0;
+    unsigned int dlen = cf->len;
+    unsigned int dlc = can_fd_len2dlc(dlen);
+
+    /* relevant flags in Flags field */
+#define ASC_F_RTR 0x00000010
+#define ASC_F_FDF 0x00001000
+#define ASC_F_BRS 0x00002000
+#define ASC_F_ESI 0x00004000
+
+    /* check for extra info */
+    if (strlen(extra_info) > 0) {
+        /* only the first char is defined so far */
+        if (extra_info[0] == 'T')
+            dir = "Tx";
+    }
+
+    fprintf(outfile, "CANFD %3d %s ", devno, dir); /* 3 column channel number right aligned */
+
+    sprintf(id, "%X%c", cf->can_id & CAN_EFF_MASK,
+            (cf->can_id & CAN_EFF_FLAG)?'x':' ');
+    fprintf(outfile, "%11s                                  ", id);
+    fprintf(outfile, "%c ", (cf->flags & CANFD_BRS)?'1':'0');
+    fprintf(outfile, "%c ", (cf->flags & CANFD_ESI)?'1':'0');
+
+    /* check for extra DLC when having a Classic CAN with 8 bytes payload */
+    if ((mtu == CAN_MTU) && (dlen == CAN_MAX_DLEN)) {
+        struct can_frame *ccf = (struct can_frame *)cf;
+
+        if ((ccf->len8_dlc > CAN_MAX_DLEN) && (ccf->len8_dlc <= CAN_MAX_RAW_DLC))
+            dlc = ccf->len8_dlc;
+    }
+
+    fprintf(outfile, "%x ", dlc);
+
+    if (mtu == CAN_MTU) {
+        if (cf->can_id & CAN_RTR_FLAG) {
+            /* no data length but dlc for RTR frames */
+            dlen = 0;
+            flags = ASC_F_RTR;
+        }
+    } else {
+        flags = ASC_F_FDF;
+        if (cf->flags & CANFD_BRS)
+            flags |= ASC_F_BRS;
+        if (cf->flags & CANFD_ESI)
+            flags |= ASC_F_ESI;
+    }
+
+    fprintf(outfile, "%2d", dlen);
+
+    for (i = 0; i < (int)dlen; i++) {
+        fprintf(outfile, " %02X", cf->data[i]);
+    }
+
+    fprintf(outfile, " %8d %4d %8X 0 0 0 0 0", 130000, 130, flags);
+}
+
+
+int MainWindow::log2asc(QString strinfile, QString stroutfile)
+{
+    static char buf[BUFSZ], device[DEVSZ], afrbuf[AFRSZ], extra_info[EXTRASZ];
+
+    static cu_t cu;
+    static struct timeval tv, start_tv;
+    FILE *infile = stdin;
+    FILE *outfile = stdout;
+    static int maxdev, devno, i, crlf, fdfmt, nortrdlc, d4, opt, mtu;
+    (void)maxdev; (void)i; (void)opt;
+    int print_banner = 1;
+    unsigned long long sec, usec;
+    infile = fopen(strinfile.toStdString().data(), "r");
+    outfile = fopen(stroutfile.toStdString().data(), "w");
+
+    //printf("Found %d CAN devices!\n", maxdev);
+
+    while (fgets(buf, BUFSZ-1, infile)) {
+
+        if (strlen(buf) >= BUFSZ-2) {
+            fprintf(stderr, "line too long for input buffer\n");
+            return 1;
+        }
+
+        /* check for a comment line */
+        if (buf[0] != '(')
+            continue;
+
+        if (sscanf(buf, "(%llu.%llu) %21s %6299s %19s", &sec, &usec,
+                   device, afrbuf, extra_info) != 5) {
+
+            /* do not evaluate the extra info */
+            extra_info[0] = 0;
+
+            if (sscanf(buf, "(%llu.%llu) %21s %6299s", &sec, &usec,
+                       device, afrbuf) != 4) {
+                fprintf(stderr, "incorrect line format in logfile\n");
+                return 1;
+            }
+        }
+        tv.tv_sec = sec;
+        tv.tv_usec = usec;
+
+        if (print_banner) { /* print banner */
+            print_banner = 0;
+            start_tv = tv;
+            fprintf(outfile, "date %s", ctime(&start_tv.tv_sec));
+            fprintf(outfile, "base hex  timestamps absolute%s",
+                    (crlf)?"\r\n":"\n");
+            fprintf(outfile, "no internal events logged%s",
+                    (crlf)?"\r\n":"\n");
+        }
+
+        // for (i = 0, devno = 0; i < maxdev; i++) {
+        //     if (!strcmp(device, argv[optind+i])) {
+        //         devno = i + 1; /* start with channel '1' */
+        //         break;
+        //     }
+        // }
+
+        devno = 1;
+
+        if (devno) { /* only convert for selected CAN devices */
+
+            mtu = parse_canframe(afrbuf, &cu);
+
+            /* convert only CAN CC and CAN FD frames */
+            if ((mtu != CAN_MTU) && (mtu != CANFD_MTU)) {
+                printf("no valid CAN CC/FD frame\n");
+                return 1;
+            }
+
+            /* we don't support error message frames in CAN FD */
+            if ((mtu == CANFD_MTU) && (cu.cc.can_id & CAN_ERR_FLAG))
+                continue;
+
+            tv.tv_sec  = tv.tv_sec - start_tv.tv_sec;
+            tv.tv_usec = tv.tv_usec - start_tv.tv_usec;
+            if (tv.tv_usec < 0)
+                tv.tv_sec--, tv.tv_usec += 1000000;
+            if (tv.tv_sec < 0)
+                tv.tv_sec = tv.tv_usec = 0;
+
+            if (d4)
+                fprintf(outfile, "%4llu.%04llu ", (unsigned long long)tv.tv_sec, (unsigned long long)tv.tv_usec/100);
+            else
+                fprintf(outfile, "%4llu.%06llu ", (unsigned long long)tv.tv_sec, (unsigned long long)tv.tv_usec);
+
+            if ((mtu == CAN_MTU) && (fdfmt == 0))
+                can_asc(&cu.fd, devno, nortrdlc, extra_info, outfile);
+            else
+                canfd_asc(&cu.fd, devno, mtu, extra_info, outfile);
+
+            if (crlf)
+                fprintf(outfile, "\r");
+            fprintf(outfile, "\n");
+        }
+    }
+    fflush(outfile);
+    fclose(outfile);
+    fclose(infile);
+
+    return 1;
+}
+

+ 29 - 0
src/tool/tool_logtoasc/mainwindow.h

@@ -0,0 +1,29 @@
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+
+QT_BEGIN_NAMESPACE
+namespace Ui {
+class MainWindow;
+}
+QT_END_NAMESPACE
+
+class MainWindow : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    MainWindow(QWidget *parent = nullptr);
+    ~MainWindow();
+
+private slots:
+    void on_pushButton_convert_clicked();
+
+private:
+    Ui::MainWindow *ui;
+
+private:
+    int log2asc(QString strinfile,QString stroutfile);
+};
+#endif // MAINWINDOW_H

+ 58 - 0
src/tool/tool_logtoasc/mainwindow.ui

@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>477</width>
+    <height>258</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <widget class="QLabel" name="label">
+    <property name="geometry">
+     <rect>
+      <x>64</x>
+      <y>30</y>
+      <width>501</width>
+      <height>61</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Tip:  使用candump记录log数据:candump -l can0</string>
+    </property>
+   </widget>
+   <widget class="QPushButton" name="pushButton_convert">
+    <property name="geometry">
+     <rect>
+      <x>140</x>
+      <y>110</y>
+      <width>191</width>
+      <height>61</height>
+     </rect>
+    </property>
+    <property name="text">
+     <string>Convert</string>
+    </property>
+   </widget>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>477</width>
+     <height>27</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 26 - 0
src/tool/tool_logtoasc/tool_logtoasc.pro

@@ -0,0 +1,26 @@
+QT       += core gui
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+CONFIG += c++17
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+    lib.c \
+    main.cpp \
+    mainwindow.cpp
+
+HEADERS += \
+    lib.h \
+    mainwindow.h
+
+FORMS += \
+    mainwindow.ui
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target