Embedded

Имитация CAN-шины для тестирования без реального оборудования

Введение

Разработка и тестирование CAN-кода, разобранного в предыдущих статьях, не всегда возможны с реальным автомобилем или физическим CAN-оборудованием — для этого Linux предоставляет виртуальный CAN-интерфейс (vcan), позволяющий полностью имитировать CAN-шину программно, без какого-либо физического оборудования, аналогично тому, как имитация MQTT-брокера (статья 367 концептуально аналогична статье про тестирование из основного цикла) позволяет тестировать логику без реальной инфраструктуры.

Концепция

vcan — модуль ядра Linux, создающий виртуальный CAN-интерфейс, который ведёт себя для приложений идентично реальному физическому CAN-адаптеру через тот же SocketCAN API, но без какой-либо физической шины — все кадры, отправленные одним приложением в vcan0, немедленно доступны для приёма любым другим приложением, подписанным на тот же виртуальный интерфейс, в той же программной среде. Это даёт возможность полноценно разрабатывать и тестировать как код, отправляющий сообщения (имитация ECU автомобиля), так и код, принимающий их (тестируемое Qt-приложение), на одной разработческой машине, без физического CAN-оборудования вовсе.

Пример кода: Имитация CAN-шины для

# Создание и настройка виртуального CAN-интерфейса
sudo modprobe vcan
sudo ip link add dev vcan0 type vcan
sudo ip link set up vcan0

# Простая проверка через утилиты командной строки can-utils
sudo apt install can-utils
candump vcan0 &     # прослушивание всех кадров на виртуальной шине в фоне
cansend vcan0 123#DEADBEEF  # отправка тестового кадра с идентификатором 0x123 и данными DE AD BE EF
// Qt-код тестируемого приложения работает с vcan0 абсолютно так же, как с реальным can0 —
// единственное отличие — имя интерфейса, передаваемое в createDevice()
QCanBusDevice *device = QCanBus::instance()->createDevice("socketcan", "vcan0", &errorString);
// Автоматизированный тест, имитирующий ECU автомобиля для проверки CanReceiver из статьи 363
#include <QtTest>

class CanReceiverTest : public QObject
{
    Q_OBJECT
private slots:
    void testEngineRpmParsing()
    {
        QString errorString;
        auto *senderDevice = QCanBus::instance()->createDevice("socketcan", "vcan0", &errorString);
        auto *receiverDevice = QCanBus::instance()->createDevice("socketcan", "vcan0", &errorString);
        senderDevice->connectDevice();
        receiverDevice->connectDevice();

        CanReceiver receiver(receiverDevice);
        QSignalSpy spy(&receiver, &CanReceiver::engineDataReceived); // предполагаемый сигнал

        // Отправка тестового кадра, имитирующего реальный сигнал от автомобиля
        QCanBusFrame testFrame;
        testFrame.setFrameId(0x0C0);
        testFrame.setPayload(QByteArray::fromHex("1A2B"));
        senderDevice->writeFrame(testFrame);

        QVERIFY(spy.wait(1000)); // ожидание получения и обработки кадра приёмником
        QCOMPARE(spy.count(), 1);
    }
};

Пояснения к коду

Ключевое свойство, делающее тестирование возможным — идентичность API для vcan0 и реального can0: единственное различие в коде Qt-приложения — строка с именем интерфейса, передаваемая в createDevice(), и весь остальной код (отправка, приём, разбор кадров) работает абсолютно одинаково независимо от того, виртуальная это шина или реальная, подключённая к настоящему автомобилю. Автоматизированный тест показывает практичное применение этой эквивалентности — senderDevice в тестовом коде имитирует поведение реального ECU автомобиля, отправляя заранее известный, контролируемый кадр, что позволяет детерминированно (без зависимости от реального, переменчивого поведения настоящего автомобиля) проверить корректность логики разбора CanReceiver, аналогично общему подходу к автоматизированному тестированию из основного цикла статей про тестирование (статья 6).

Подводные камни

  • Тестирование исключительно на виртуальной шине без какой-либо последующей проверки на реальном оборудовании перед развёртыванием в production — виртуальная шина идеально подходит для проверки логики разбора и обработки сообщений, но не может выявить проблемы, специфичные именно для реального физического уровня (помехи, неправильное терминирование, проблемы конкретного драйвера используемого USB-CAN адаптера), и финальная проверка на реальном оборудовании остаётся необходимым, не заменяемым виртуальным тестированием шагом.
  • Забытое создание/поднятие виртуального интерфейса vcan0 перед запуском тестов (особенно в CI/CD-окружении, основной цикл статей про автотесты, не имеющем постоянно настроенного vcan0 «по умолчанию») — для автоматизированного запуска тестов в CI-системе настройка vcan0 должна быть явным шагом подготовки тестового окружения (modprobe vcan, ip link add), выполняемым перед запуском самих тестов, а не предполагаться как нечто, уже существующее в окружении.
  • Использование одного и того же виртуального интерфейса vcan0 одновременно несколькими независимыми, параллельно выполняющимися тестами без должной изоляции — поскольку все приложения, подключённые к одному vcan0, делят одну и ту же «шину», параллельный запуск нескольких независимых тестов, использующих один и тот же виртуальный интерфейс, может привести к тому, что один тест получит кадры, отправленные другим, не связанным с ним тестом, что требует либо создания отдельных виртуальных интерфейсов для разных тестов (vcan0, vcan1), либо явной сериализации (запуска по одному, не параллельно) тестов, использующих общую виртуальную шину.
  • Отсутствие у виртуальной шины каких-либо реальных физических ограничений (скорость, помехи, коллизии), что означает, что код, успешно работающий на vcan0 даже при намеренно некорректных настройках скорости (которая на виртуальной шине просто не имеет физического значения), может не сразу выявить ошибки конфигурации, проявляющиеся только на реальной, физической шине с её реальными ограничениями — виртуальное тестирование проверяет корректность логики обработки сообщений, но не заменяет полностью проверку правильности физической конфигурации реального оборудования.