Введение
Для диагностики проблем, обратного проектирования (reverse engineering) незадокументированных протоколов конкретного автомобиля, и долговременного анализа поведения системы нужен инструмент логирования всего трафика CAN-шины — в статье разберём запись и последующий анализ CAN-трафика, объединяя темы логирования (spdlog-цикл, статьи 333-340) и CAN-специфичных инструментов.
Концепция
Полный лог CAN-трафика обычно записывается в стандартизированном текстовом или бинарном формате (например, ASC-формат от Vector, или более простой CSV) с привязкой каждого кадра к точной временной метке — это даёт возможность позже воспроизвести (replay) записанный трафик для тестирования без реального оборудования (продолжая тему статьи 367), либо проанализировать паттерны сообщений для понимания незадокументированного протокола конкретного устройства. Реального времени для анализа критичный фактор — высокая интенсивность трафика на реальной автомобильной шине (сотни сообщений в секунду) требует эффективной записи без потери кадров и без заметного влияния самого процесса логирования на остальную работу системы.
Пример кода: Логирование и анализ
// canlogger.h — логирование всего CAN-трафика в файл с временными метками
#pragma once
#include <QCanBusDevice>
#include <QFile>
#include <QTextStream>
class CanLogger : public QObject
{
Q_OBJECT
public:
CanLogger(QCanBusDevice *device, const QString &logFilePath, QObject *parent = nullptr)
: QObject(parent), m_device(device)
{
m_logFile.setFileName(logFilePath);
m_logFile.open(QIODevice::WriteOnly | QIODevice::Append);
m_stream.setDevice(&m_logFile);
connect(m_device, &QCanBusDevice::framesReceived, this, &CanLogger::onFramesReceived);
}
private slots:
void onFramesReceived()
{
while (m_device->framesAvailable() > 0) {
QCanBusFrame frame = m_device->readFrame();
// Простой CSV-формат: временная метка, идентификатор, длина, данные в hex
m_stream << frame.timeStamp().seconds() << "."
<< QString::number(frame.timeStamp().microSeconds()).rightJustified(6, '0') << ","
<< Qt::hex << frame.frameId() << Qt::dec << ","
<< frame.payload().size() << ","
<< frame.payload().toHex() << "\n";
}
m_stream.flush(); // явный flush для надёжности — компромисс с производительностью при высоком трафике
}
private:
QCanBusDevice *m_device;
QFile m_logFile;
QTextStream m_stream;
};
// Воспроизведение записанного лога — отправка тех же кадров в той же временной последовательности
void replayCanLog(QCanBusDevice *device, const QString &logFilePath)
{
QFile file(logFilePath);
file.open(QIODevice::ReadOnly);
QTextStream stream(&file);
double previousTimestamp = -1.0;
while (!stream.atEnd()) {
QString line = stream.readLine();
QStringList parts = line.split(',');
if (parts.size() < 4) continue;
double timestamp = parts[0].toDouble();
uint32_t frameId = parts[1].toUInt(nullptr, 16);
QByteArray payload = QByteArray::fromHex(parts[3].toUtf8());
if (previousTimestamp >= 0) {
// Воспроизведение с сохранением оригинальных временных интервалов между кадрами
double delay = timestamp - previousTimestamp;
QThread::msleep(static_cast<int>(delay * 1000));
}
previousTimestamp = timestamp;
QCanBusFrame frame;
frame.setFrameId(frameId);
frame.setPayload(payload);
device->writeFrame(frame);
}
}
Пояснения к коду
CanLogger подписывается на тот же сигнал framesReceived(), что использовался для обычной обработки сообщений в статье 363, но вместо интерпретации содержимого просто записывает каждый кадр в файл с точной временной меткой (frame.timeStamp(), предоставляемой самим QCanBusFrame на основе момента фактического приёма кадра) — этот подход позволяет логировать трафик независимо от того, понимает ли приложение смысл конкретных сообщений, что особенно ценно именно для анализа незадокументированных протоколов, когда смысл сообщений изначально неизвестен. replayCanLog() восстанавливает оригинальные временные интервалы между кадрами через QThread::msleep() между отправками — это важно для воспроизведения реалистичного, временно корректного паттерна трафика (а не мгновенной отправки всех записанных сообщений подряд без задержек), что критично, если воспроизведение используется для тестирования системы, чувствительной к частоте/тайминга поступления сообщений.
Подводные камни
- Использование
QThread::msleep()непосредственно в коде воспроизведения, выполняемом в основном потоке приложения, что блокирует весь интерфейс на время каждой паузы между кадрами — для воспроизведения с GUI, отображающим прогресс или позволяющим прервать процесс воспроизведения, эта функция должна выполняться в отдельном потоке (аналогично паттернуGrpcWorkerиз статьи 327), а не блокировать основной поток интерфейса серией последовательныхmsleep(). - Недостаточно быстрая запись лога при очень высокой интенсивности реального трафика CAN-шины, особенно при использовании
QTextStreamс частымflush()(как в примере, ради надёжности) — на шинах с интенсивным трафиком (сотни сообщений в секунду) накладные расходы на текстовое форматирование и частый сброс буфера на диск могут не успевать за скоростью поступления новых кадров, что приводит к нарастающему отставанию и потенциальной потере данных; для высокоинтенсивного трафика стоит рассмотреть более компактный бинарный формат логирования и менее частый, но достаточно надёжный (например, через буферизованную, асинхронную запись, аналогично spdlog-циклу) сброс на диск. - Использование простого, самостоятельно изобретённого CSV-формата (как в примере) вместо одного из стандартизированных форматов CAN-логов (ASC, BLF от Vector, или другие), если предполагается совместная работа с существующими, готовыми инструментами анализа CAN-трафика — собственный формат проще реализовать с нуля, но лишает возможности использовать богатую существующую экосистему инструментов анализа, изначально рассчитанных на стандартные форматы; выбор формата стоит делать с учётом того, нужна ли совместимость с внешними инструментами анализа.
- Хранение записанных логов CAN-трафика без учёта того, что они могут содержать потенциально чувствительную информацию (например, точные данные о манере вождения конкретного человека, местоположение через данные GPS-модуля, переданные по той же шине) — для логов, записываемых с реальных, эксплуатируемых автомобилей конкретных людей (а не исключительно тестовых стендов), стоит рассматривать вопросы конфиденциальности данных аналогично общим принципам защиты персональных данных, разбиравшимся для других типов приложений в основном цикле статей про безопасность.