시작하기 전에
CAN은 메시지 단위의 통신을 한다. CAN 메시지들은 blf 파일에는 메시지 단위로 차곡차곡 저장된다. 아래 그림처럼. 이를 메시지 기반 파일 형식이라고 부르겠다.
blf 파일을 asc로 변환하였다. 파일 안에 데이터가 메시지별로 저장된 것을 볼 수 있다. 메시지 기반 파일 형식이라고 하겠다.
(위 그림에서 하이라이트 되어있는 첫 줄의 "3A 04 91 ..." 처럼) 바이너리로 저장된 데이터에서 신호를 추출해야 한다. 추출에 필요한 정보는 dbc 파일에 정의되어 있다. (dbc 파일이 무엇인지를 CAN 트레이스 보기 - 바퀴 속도 :: hsl's tsmaster 사용기 의 '트레이스 창'에서 부분에서 설명하였다.)
dbc 파일이 없으면 신호를 보고 싶은 사람에게 blf 파일은 쓸모가 없다.
blf 파일와 짝이 맞지 않는 dbc 파일을 적용하는 경우, 신호를 엉뚱하게 추출하게 된다. 추출하지 못하는 상황보다 더 나쁜 상황이 될 수 있다.
이 문제는 blf의 역사만큼 (2025년 기준으로 45년 정도된) 오래된 문제이다. 이렇게 오래된 문제에 해결책이 아직 없을까?
해결책들의 하나가 TSMaster에 구현되어 있다. blf를 MATLAB의 mat로 변환하는 기능이 있다. [주의]: 사용자는 짝이 맞는 dbc 파일을 사용해야 한다.
mat 파일에는 신호별로 데이터가 저장되어 있다. 아래 그림처럼. 이를 신호 기반 파일 형식이라고 부르겠다. (blf를 mat 파일로 변환하고, mat 파일을 읽어서 신호 데이터를 pandas 데이터프레임으로 만드는 방법을 mat 파일을 데이터프레임으로 변환하고 feather 파일로 저장하기 :: hsl's tsmaster 사용기 에서 설명하였다.)
mat 파일을 읽어서 pandas 데이터프레임으로 만들었다. ts는 타임스탬프이다. 메시지별로 주기가 다를 수 있다. 그렇다면 메시지별로 타임스탬프들이 다르다. 여러 메시지들에 있는 여러 신호들을 읽을 때 타임스탬프를 적절하게 처리해줘야 한다.
메시지 기반 파일 형식과 신호 기반 파일 형식은 각각의 장단점이 있다. 제동 성능 분석 같은 목적으로 데이터 파일을 주고받을 때는 신호 기반 형식의 파일이 편리하다. 한 파일만 전달하면 되고 변환이 필요하지 않으니까.
협업이 필수적이고 효율 향상에 진심인 자동차 산업에 데이터 파일 형식에 관한 표준이 있지 않을까? 라고 생각했다면, 맞았다. ASAM MDF 라는 파일 형식이 있다.
mdf 파일을 싶게 읽을 수 있는 Python 모듈도 존재한다. asammdf 모듈이다. TSMaster도 asammdf 모듈을 사용한다.
메인 메뉴/ Help/ About으로 About TSMaster 창을 열고 Acknowledgments 탭을 열면 asammdf를 찾을 수 있다. 아마 asammdf 바로 아래 h5py는 mat 파일을 다루기 위한 모듈일 것이다.
파이썬으로 mdf 파일을 읽어서 신호를 그래프로 표시하는 초간단한 뷰어를 만든다.
개요
코드
'''
ESC (Electronic Stability Control) 제동 시험에서 측정한 mdf 파일에서
제동 신호들을 추출하여 그래프로 표시하는 예제입니다.
'''
import streamlit as st
import asammdf
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# 페이지 설정
st.set_page_config(
page_title='pbm', # plot brake mdf
page_icon='📈',
layout="wide",
)
# constants
# TODO: 그래프 높이를 입력받는 콘트롤을 추가한다.
k_height_plot = 400
k_height_subplot = 300
def plot_signals(mdf, signal_names, title):
'''
Plot signals from MDF file
'''
fig = go.Figure()
y_title = ''
for signal_name in signal_names:
signal = mdf.get(signal_name)
fig.add_trace(go.Scatter(x=signal.timestamps, y=signal.samples, mode='lines', name=signal.name))
y_title += f'{signal.name} [{signal.unit}] '
fig.update_layout(
title=title,
xaxis_title='Time',
yaxis_title=y_title,
height=k_height_plot
)
return fig
def plot_subplots(mdf, plot_defs, title):
'''
Plot subplots of brake signals
'''
n_row = len(plot_defs)
fig = make_subplots(n_row, 1)
for i, signal_names in enumerate(plot_defs):
for signal_name in signal_names:
signal = mdf.get(signal_name)
fig.add_trace(go.Scatter(x=signal.timestamps, y=signal.samples, mode='lines', name=signal_name), row=i+1, col=1)
fig.update_layout(
title=title,
height=n_row * k_height_subplot
)
return fig
# main
st.title('Plot Brake Signals')
st.write('This is a simple example of plotting brake signals from an mdf file.')
# get user input
cols_menu = st.columns([3, 1])
file_path_mdf = cols_menu[0].file_uploader("Upload an mdf file", type=["mdf"])
# 버튼이 너무 위에 그려져서 빈 줄을 출력하여 위치를 조정한다.
cols_menu[1].write(' ')
cols_menu[1].write(' ')
cols_menu[1].write(' ')
button_plot_click = cols_menu[1].button('Plot Brake Signals')
# plot signals
if button_plot_click:
if not file_path_mdf:
st.warning('Please upload an mdf file.')
else:
mdf = asammdf.MDF(file_path_mdf)
# plot brake signals as subplots
plot_defs = [
['_wstr'],
['PFL', 'PFR', 'PRL', 'PRR', '_mpres'],
['_along', '_alat'],
['_yawm'],
['_vrdFL', '_vrdFR', '_vrdRL', '_vrdRR'],
]
fig_brake_subplots = plot_subplots(mdf, plot_defs, 'Brake and Motion Signals')
# plot grouped signals
signal_names = ['PFL', 'PFR', 'PRL', 'PRR', '_mpres']
fig_pressure = plot_signals(mdf, signal_names, '제동 압력')
signal_names = ['_along']
fig_long_accel = plot_signals(mdf, signal_names, '종가속도')
signal_names = ['_alat']
fig_lat_accel = plot_signals(mdf, signal_names, '횡가속도')
signal_names = ['_yawm']
fig_yaw = plot_signals(mdf, signal_names, '요-레이트')
signal_names = ['_vrdFL', '_vrdFR', '_vrdRL', '_vrdRR']
fig_velocity = plot_signals(mdf, signal_names, '바퀴 속도')
signal_names = ['_wstr']
fig_steering = plot_signals(mdf, signal_names, '조향각')
# show grouped signal plots
cols = st.columns(3)
with cols[0]:
st.plotly_chart(fig_velocity)
st.plotly_chart(fig_steering)
with cols[1]:
st.plotly_chart(fig_pressure)
st.plotly_chart(fig_yaw)
with cols[2]:
st.plotly_chart(fig_long_accel)
st.plotly_chart(fig_lat_accel)
# show subplots
st.plotly_chart(fig_brake_subplots)
실행
커맨드 창에서 아래 명령을 실행하여 필요한 모듈들을 설치한다.
pip install -U streamlit
pip install -U plotly
pip install asammdf
커맨드 창을 열고 파이썬 스크립트가 있는 경로로 이동한다.
커맨드 창에서 아래 명령을 실행하여 스크립트를 실행한다. "plot_mdf_rev_01.py"는 스크립트 파일 이름이다.
streamlit run plot_mdf_rev_01.py
인터넷 브라우저가 열리면서 프로그램이 실행된다.
대상 mdf 파일을 드래그&드롭하거나 "Browse files" 버튼을 클릭하여 선택한다.
"Plot Brake Signals" 버튼을 클릭하면 그래프들이 출력된다.
프로그램을 종료하려면 브라우저의 탭과 커맨드 창을 닫아야 한다.
프로그램 실행 화면이다.
결론
분석을 위해서 데이터를 파일로 주고받을 때는 mdf나 mat 처럼 신호 기반 형식의 파일이 편리하다.
Python으로 mdf 파일을 다룰 수 있는 asammdf 모듈이 있다.
asammdf, streamlit, plotly 모듈들을 이용하여 간단하게 데이터를 볼 수 있는 뷰어를 만들어봤다.
참조
plot_mdf_rev_01.py
0.00MB