Спецификация и описание функций J2534 ›
Спецификация и описание команд ELM327 ›
Интерфейс ELM327 доступен в адаптере Nano ET. Остальные адаптеры ScanDoc используют протокол J2534 PassThru.
Изменения в J2534 DLL, ELM327 и прошивках адаптеров ScanDoc, которые касаются интеграции: новые функции, протоколы и параметры - с примерами использования.
Библиотеки поставляются одним архивом. Платформы: Windows x86/x64/ARM64 (отдельные сборки для Windows 7), macOS (universal), Linux (x64, x86, ARM, ARM64), Android (arm64-v8a, armeabi-v7a, x86, x86_64), iOS (XCFramework). В папке docs/ лежит документация SDK: начало работы, справочник API, конфигурация, обработка ошибок, DoIP, обновление прошивки, формат лога, Android, iOS.
Скачать библиотеки J2534 2.0.0.241 — Windows x86/x64/ARM64 (отдельные сборки для Windows 7), macOS (universal), Linux (x64, x86, ARM, ARM64), Android (arm64-v8a, armeabi-v7a, x86, x86_64), iOS (XCFramework), браузеры Chrome и Edge 137+ (WebAssembly); в папке docs/ — документация SDK (начало работы, справочник API, конфигурация, обработка ошибок, DoIP, DevHub, обновление прошивки, формат лога, Android, iOS, браузер). Статические .a поставляются только для iOS и серверной сборки Linux x64, остальные платформы загружают библиотеку динамически.
Новое
ConfigWrite молча пропускает ключ ble_name: ошибки нет, чтобы приложения, которые отправляют настройки целиком, продолжали работать. ConfigRead возвращает имя, как раньше.PassThruReadVersion по USB занимает около 6,5 мс вместо 16. Ответ длиннее 248 байт прибор отдаёт порциями по 248 байт с паузой 4 мс, поэтому передача из прибора в компьютер идёт со скоростью около 59 КБ/с: ответ в 4 КБ занимает примерно 70 мс. Передача из компьютера в прибор по-прежнему идёт на 3 Мбит/с.TCP_NODELAY, и ответ уходит целиком сразу.Исправлено
PassThruConnect CAN. Прибор иногда сам перезапускался примерно через 300 мс после открытия канала CAN. Кроме того, OtaUpdate после сеанса CAN в том же PassThruOpen не начинал обновление: прибор 30 с не отвечал на его начало.PassThruClose. Кроме того, во время сеанса CAN или DoIP и после него прибор мог зависнуть при открытии или закрытии порта. С прошивкой сборки 86 и библиотекой 2.0.0.241 после PassThruClose порт можно открывать и закрывать без риска. Зависание возможно, только если порт закрывается во время сеанса, до PassThruClose. На macOS и Linux после PassThruClose порт свободен для другой программы, на Windows он, как и раньше, занят до выгрузки библиотеки. Не открывайте порт сами и не меняйте DTR и RTS, пока им пользуется библиотека.PassThruWriteMsgs всё равно получал ответ "принято всё", а сообщения, которые не поместились, пропадали. Кроме того, на каждом таком сообщении прибор терял память и при плотном потоке через несколько десятков секунд начинал отвечать отказом по нехватке памяти до перезагрузки. Теперь прибор ждёт места в очереди в пределах таймаута вызова и сообщает, сколько сообщений принял. Недобор библиотека возвращает как ERR_BUFFER_FULL при нулевом таймауте и ERR_TIMEOUT при ненулевом, а в pNumMsgs записывает число принятых сообщений.BUFFER_OVERFLOW в RxStatus.ETHERNET_NDIS. PassThruDisconnect и GET_NDIS_ADAPTER_INFO с номером канала, который вернул PassThruConnect, отвечали ERR_INVALID_CHANNEL_ID, а PassThruClose этот канал не закрывал. Отказ отключения теперь возвращается как ERR_FAILED, раньше вместо кода J2534 приходило 0xFFFB.P4_MIN = 0 кадры обрывались с TX_FAILED.Новое
Promise объекта, в котором ret содержит код J2534, а остальные поля несут выходные параметры. Сведения о найденных машинах (ISO13400_GET_VEHICLE_INFO, HSFZ_GET_VEHICLE_INFO) приходят массивом объектов с полями vin, logicalAddress, ip и другими, а не байтами структур. Прибор по BLE пользователь выбирает по имени в окне браузера, а при первом подключении система спрашивает PIN. Неверный или отменённый PIN даёт ERR_FAILED и текст BLE pairing rejected, как в нативной библиотеке. Кроме функций PassThru доступны ConfigRead, ConfigWrite, ConfigReset, ConfigReboot и обновление прошивки: OtaUpdate берёт файл прошивки как ArrayBuffer и сообщает прогресс, а OtaAbort останавливает идущее обновление, и тогда OtaUpdate возвращает новый код OTA_RESULT_ERR_ABORTED (-510). На сайт кладутся два файла, j2534.js и j2534_web.mjs, а страница должна открываться по HTTPS. Демо-страница j2534_demo.html открывается прямо с диска. Нужна прошивка сборки 86. Закрытие USB-порта (releasePort() или закрытие вкладки) перезагружает Nano ET: при закрытии порта драйвер USB переключает линию DTR, а на Nano ET она управляет сбросом прибора. Нативная библиотека отпускает линии в безопасном порядке, а браузер такой возможности не даёт. Подробности в docs/WEB.md.
Пример
import { loadJ2534 } from './j2534.js';
const ISO15765 = 6;
connectButton.onclick = async () => {
// окно выбора прибора браузер показывает только в ответ на действие пользователя
const port = await navigator.serial.requestPort({ filters: [{ usbVendorId: 0x0403 }] });
const j2534 = await loadJ2534(port);
// по BLE вместо порта передаётся прибор BLE:
// const device = await navigator.bluetooth.requestDevice({ filters: [{ services: [0xfff0] }] });
// const j2534 = await loadJ2534(device);
const { ret, deviceId } = await j2534.PassThruOpen();
if (ret !== 0) {
const { errorDescription } = await j2534.PassThruGetLastError();
console.error(errorDescription);
return;
}
const { channelId } = await j2534.PassThruConnect(deviceId, ISO15765, 0, 500000);
const { msgs } = await j2534.PassThruReadMsgs(channelId, 10, 100);
// обновление прошивки из файла, выбранного пользователем
const fw = await fileInput.files[0].arrayBuffer();
const r = await j2534.OtaUpdate(deviceId, fw, (current, total) => {
progressBar.max = total;
progressBar.value = current;
});
// r.ret: 0 при успехе, -510 после OtaAbort, другие коды в FIRMWARE_UPDATE.md
};
Исправлено
HSFZ_GET_VEHICLE_INFO отдавал только первые 8 байт сведений о каждой найденной машине. Приложение получало обрывок VIN без адреса ЭБУ и IP-адреса. Теперь каждая запись hsfz_vehicle_info_t приходит целиком, 32 байта: VIN, адрес ЭБУ, MAC и IP. Исправлено во всех нативных библиотеках.PassThruWriteMsgs с числом сообщений больше 40 или объёмом больше 5115 байт подвешивал прибор. Библиотека отправляла такой вызов одной командой, а такую команду прибор не принимает. Прибор на неё не отвечал, и вызов ждал полный таймаут, а по LAN/WLAN прибор разбирал байты данных этой команды как следующие команды. Теперь библиотека делит вызов на несколько команд, и каждая получает остаток таймаута вызова. Все сообщения проверяются до отправки первого. Прошивка сборки 86 на такую команду от старых версий библиотеки отвечает ERR_INVALID_MSG.PassThruReadMsgs с таймаутом и запросом больше 128 сообщений получал отказ целиком, потому что прибор отдаёт не больше 128 сообщений за раз. Теперь вызов возвращает до 128 сообщений, остальные читаются следующим вызовом.CLEAR_RX_BUFFER не очищал сообщения, которые библиотека уже забрала из прибора, и следующий PassThruReadMsgs возвращал старые кадры. Теперь очищается и буфер библиотеки.ERR_DEVICE_NOT_CONNECTED. Дескриптор 0 свободен, когда процесс закрыл стандартный ввод, как это обычно делают службы.PassThruReadMsgs при закрытом приборе. Если прибор не был открыт, функция возвращала ошибку, но оставляла в *pNumMsgs число, которое запросило приложение. Приложение, которое проверяет число прочитанных сообщений, а не код возврата, обрабатывало столько пустых сообщений. Теперь в этом случае *pNumMsgs = 0.PassThruGetLastError. После обрыва связи функция возвращала текст ошибки одного из предыдущих вызовов, например ERR_BUFFER_EMPTY, хотя код возврата был верным. Теперь текст называет причину: LAN socket closed, BLE not connected или serial port not open. Для ERR_BUFFER_OVERFLOW (0x12) вместо названия выдавалось Unknown error: 18.Скачать библиотеки J2534 2.0.0.225
Новое
ConfigRead, а удаляет пару ConfigWrite. Новых вызовов в библиотеке для этого не появилось.
Пример
char json[2048];
if (ConfigRead(dev, json, sizeof(json)) == STATUS_NOERROR) {
/* в ответе, среди прочих настроек:
"ble_bonds":[{"name":"WS-07","mac":"A4:C1:38:11:22:33"}] */
}
ConfigWrite(dev, "{\"ble_bond_del\":\"A4:C1:38:11:22:33\"}");
/* применяется сразу, ConfigReboot не требуется */
Исправлено
reciv ack end single msg timeout SG, затем reciv ack counter error SG.TX_FAILED, и вернуть канал в работу можно было только переподключением.Новое
ConfigRead, ConfigWrite, ConfigReboot и ConfigReset (на Windows ординалы @52-@55) библиотека экспортировала и раньше, но в документации их не было, и воспользоваться ими со стороны было нельзя. Теперь в docs/API_REFERENCE.md описаны прототипы и поведение, а в docs/CONFIGURATION.md приведена таблица ключей с диапазонами, которые проверяет прошивка. Эти команды прибор обрабатывает до маршрутизации J2534, поэтому они работают на любом транспорте: LAN/WLAN, BLE и USB.
Пример
char json[2048];
ConfigRead(dev, json, sizeof(json)); /* все настройки одним объектом JSON */
ConfigWrite(dev, "{\"ble_name\":\"WS-07\"}"); /* изменяются только переданные ключи */
ConfigReboot(dev); /* настройки вступают в силу после перезагрузки;
device_id недействителен, переоткрыть через ~10 с */
ptConfigRead, ptConfigWrite, ptConfigReboot и ptConfigReset.
Пример
external fun ptConfigRead(devId: Int): String? // null при ошибке
external fun ptConfigWrite(devId: Int, json: String): Int
external fun ptConfigReboot(devId: Int): Int
external fun ptConfigReset(devId: Int): Int
val json = j2534.ptConfigRead(devId) ?: return // причина в ptGetLastError()
j2534.ptConfigWrite(devId, """{"ble_name":"WS-07"}""")
j2534.ptConfigReboot(devId)
PassThruOpen версия намеренно не проверяется, чтобы не тратить на это обмен с прибором при каждом открытии: установленную сборку возвращает PassThruReadVersion, а расхождение видно в .qlog по строке firmware build N is older than build 85 required by DLL ….Исправлено
ERR_FAILED, а PassThruGetLastError отдаёт текст BLE pairing rejected (wrong PIN or not paired). Код взят именно ERR_FAILED, потому что при ERR_DEVICE_NOT_CONNECTED приложения обычно текст ошибки не запрашивают. Работает на всех транспортах: Android, macOS, Windows и Linux. Остальные сбои BLE по-прежнему дают ERR_DEVICE_NOT_CONNECTED, но теперь с кодом транспорта: раньше этот код печатался как номер ошибки POSIX, и отказ записи выходил наружу строкой 0x7 - Argument list too long.ptClose по BLE. При закрытии BLE-соединения библиотека закрывала файловый дескриптор 0, хотя сокета в BLE-режиме нет. Обычно так молча закрывался stdin, но если fd 0 к этому моменту занимал объект JVM, fdsan завершал процесс: attempted to close file descriptor 0 … owned by native object. Поэтому падение и было нерегулярным.ptClose по BLE занимал лишнюю секунду. Ответ прибора на закрытие библиотека выбрасывала, выжидала таймаут приёма 1 с и писала в лог 0x6E - Connection timed out, будто прибор не ответил. Теперь закрытие завершается по ответу прибора. На macOS, Windows и Linux этой ошибки не было.ptOpen по BLE, когда прибор не ответил на открытие. Приложение завершалось с SIGSEGV. При других отказах открытия (отвергнутый пайринг, таймаут подключения) библиотека не освобождала глобальную JNI-ссылку на BLEManager, и при повторных попытках ссылки копились.FAST_INIT. Ответ прибора копировался в структуру pt_msg_t приложения без проверки длины: укороченный ответ приводил к чтению неинициализированной памяти, а завышенная длина в ответе приводила к записи за границы структуры, которую передало приложение. На Android тот же вызов отправлял в шину искажённый кадр, а при отказе аварийно завершал приложение. Исправлена и проверка объёма входных данных: значения NumOfBytes порядка 0x20000000 она считала допустимыми.FIVE_BAUD_INIT. Вход ограничен одним байтом адреса, как требует §11.3.3.3; раньше принимался блок произвольной длины, и приложение, передававшее больше, получало успешный код вместо отказа..qlog. Лог открывал только PassThruOpen, а вызов ptOpen идёт в обход него. Поэтому у стороннего приложения под Android папка sdlogs оставалась пустой при любом уровне логирования, записи попадали только в logcat, и получить от клиента лог сессии было нельзя.FIVE_BAUD_INIT и FAST_INIT печаталось только > ok: ни адрес инициализации, ни ответ ЭБУ в лог не попадали, и разобрать по логу неудачную инициализацию было невозможно. Теперь строка содержит и запрос, и ответ: io 1 FIVE_BAUD_INIT 33 > 8F6F 2850ms.Скачать библиотеки J2534 2.0.0.213 - Windows x86/x64/ARM64 (отдельные сборки для Windows 7), macOS (universal), Linux (x64, x86, ARM, ARM64), Android (arm64-v8a, armeabi-v7a, x86, x86_64), iOS (XCFramework); в папке docs/ - документация SDK (начало работы, справочник API, конфигурация, обработка ошибок, DoIP, обновление прошивки, формат лога, Android, iOS). Статические .a поставляются только для iOS и серверной сборки Linux x64, остальные платформы загружают библиотеку динамически.
Исправлено
CAN_PS, ISO15765_PS, J1939_PS и установка выводов на каналах _PS - при подключении этих трёх протоколов прибор отвечал отказом; теперь они работают наравне с TP2_0_PS и ISO9141_PS. Установка выводов приведена к J2534-2 по трём пунктам:
PassThruConnect на выводах по умолчанию, хотя _PS-канал обязан молчать до SET_CONFIG(J1962_PINS). Теперь на шину канал выходит только после установки выводов.SET_CONFIG(J1962_PINS) переключал живой канал на другие контакты посреди сеанса. По стандарту выводы задаются на канал один раз: повторный вызов возвращает ERR_CHANNEL_IN_USE, другие выводы - только после PassThruDisconnect.ERR_PIN_NOT_SUPPORTED.uint32_t ch;
pt_config_t pins = { J1962_PINS, 0x0000060EU }; /* выводы 6 и 14 */
pt_config_list_t cfg = { 1, &pins };
PassThruConnect(dev, ISO15765_PS, 0, 500000, &ch);
/* канал ещё не на шине */
if (PassThruIoctl(ch, SET_CONFIG, &cfg, NULL) != STATUS_NOERROR) {
/* ERR_PIN_NOT_SUPPORTED - такого сочетания нет в разводке прибора */
}
/* только теперь PassThruWriteMsgs / PassThruReadMsgs;
повторный SET_CONFIG(J1962_PINS) - ERR_CHANNEL_IN_USE */
PassThruWriteMsgs возвращал успех, PassThruReadMsgs - пусто, а признак CONNECTION_LOST не выставлялся. Программирование ЭБУ по TP2.0 проверено на стенде.REQUEST_CONNECTION: десяток попыток к молчащему ЭБУ занимал все слоты фильтров и глушил канал; TEARDOWN_CONNECTION на TP1_6_PS отвечал отказом - закрыть соединение со стороны приложения было нельзя; TP2.0 не принимал соединение, установленное со стороны ЭБУ, и не отдавал приложению кадры, пришедшие вне соединения (§19.3.1 J2534-2).PassThruReadMsgs. Соединение здесь точка-точка, адрес тестера зарегистрирован при routing activation, отбирать нечего: канал отдаёт приложению все сообщения, PassThruStartMsgFilter отвечает ERR_NOT_SUPPORTED. Отказ в драйвере CAN перезагружал прибор посреди сеанса DoIP. Сторожевой таймер приёмной задачи поднят с 5 до 30 с - установление соединения DoIP штатно занимает до 20 с.PassThruConnect получал переполнение очереди на первом же сообщении. Базовые CAN и ISO15765 уходили на другой CAN-контроллер и не доходили до шины. Чтение приёмной очереди не восстанавливалось после испорченной записи - признак проверялся неверно во всех протоколах на базе CAN.GET_NDIS_ADAPTER_INFO - под кодом STATUS_NOERROR возвращались неинициализированные данные. Теперь приходят идентификатор адаптера, MAC, адрес IPv4, по которому прибор виден ЭБУ, и состояние линии активации; если Ethernet на приборе нет - ERR_NOT_SUPPORTED.GET_PROTOCOL_INFO - отвечал не за все протоколы и не тем форматом. Теперь работает на любом открытом канале: разрешение метки времени (1 мкс), поддерживаемая чётность, разрядность данных UART. Параметр, на который прибор ответить не может, помечается в поле supported, вызов при этом возвращает STATUS_NOERROR.PassThruDisconnect - обращение к уже завершившейся задаче канала портило память прибора.Новое
libj2534.xcframework содержит срезы для устройства (arm64) и симулятора (arm64/x86_64), минимальная версия iOS 12.0. В каждом срезе - заголовки j2534.h и j2534_ota.h, модульная карта (Swift import J2534, автолинковка CoreBluetooth) и privacy manifest. Прототипы Pass-Thru API объявлены в самом j2534.h - на всех платформах. mbedTLS вкомпилирован, внешних зависимостей нет. Подключение в Xcode: Embed = Do Not Embed (библиотека статическая), -lc++ в Other Linker Flags, ключи NSBluetoothAlwaysUsageDescription (BLE) и NSLocalNetworkUsageDescription (WLAN) в Info.plist - без них iOS завершает приложение при первом обращении к транспорту.
import J2534
var deviceId: UInt32 = 0
// PassThruOpen принимает mutable char* - передаём копию строки
var cstr = Array("ScanDoc;b:N4999".utf8CString) // BLE по префиксу имени
let ret = cstr.withUnsafeMutableBufferPointer { PassThruOpen($0.baseAddress, &deviceId) }
if ret == 0 {
var fw = [CChar](repeating: 0, count: 80)
var dll = [CChar](repeating: 0, count: 80)
var api = [CChar](repeating: 0, count: 80)
PassThruReadVersion(deviceId, &fw, &dll, &api)
PassThruClose(deviceId)
}
/* ID протоколов и IOCTL - макросы с приведением типа, в Swift не импортируются:
задавайте числом, let CAN: UInt32 = 5, let ISO15765: UInt32 = 6 */
ptOtaUpdate(devId, firmwarePath, callback) и ptOtaAbort(devId); ранее OtaUpdate/OtaAbort были доступны только через C API. Прогресс передаётся в onProgress(current, total) - блоки, нумерация с единицы.
// update.bin заранее скопирован в хранилище приложения.
// Вызов блокирующий - выполнять вне main thread.
val res = j2534.ptOtaUpdate(devId, file.absolutePath,
object : OtaProgressListener {
override fun onProgress(current: Int, total: Int) { /* прогресс-бар */ }
})
if (res.status == 0) {
// прошивка записана, прибор перезагружается: devId недействителен,
// подключение восстанавливается новым ptOpen (по BLE - ожидание ~10 с)
}
// res.status < 0 - код ota_result_t (см. j2534_ota.h)
// ptOtaAbort(devId) прерывает обновление без перезагрузки прибора
log_level в j2534.json: -1 выключено, 0 ошибки, 1 +предупреждения, 2 +info, 3 +debug, 4 +verbose; по умолчанию - 3, то есть без настройки лог пишется полностью. На уровне -1 папка sdlogs и файл .qlog не создаются, данные на диск не записываются. На Android уровень задаётся и из кода - ptSetLogLevel(int); заданный так уровень имеет приоритет над файлом конфигурации, поэтому в релизной сборке лог не может быть включён извне.
// Android: вызвать до ptOpen
j2534.ptSetLogLevel(-1) // релизная сборка - лог выключен
j2534.ptSetLogLevel(3) // обращение в поддержку - полный лог
// Остальные платформы: j2534.json в папке конфигурации
// macOS ~/Library/Application Support/Quantex/
// Linux ~/.config/quantex/
// Windows %APPDATA%\Quantex\
{ "log_level": -1, "devices": [] }
Исправлено
PassThruReadVersion и заголовок .qlog возвращали 2.0.0.0: номер сборки не передавался в сборку под Android. Версия определяется тем же правилом, что и на остальных платформах; этот номер указывается при обращении в поддержку.serial_*: USB-транспорт из iOS-сборки исключён, а вызовы к нему оставались. Транспорт заменён заглушкой - подключение строкой c: возвращает штатную ошибку открытия порта..qlog - данные сообщения записывались не далее 125 байт, а запись группы сообщений, переданных одним вызовом PassThruReadMsgs или PassThruWriteMsgs, ограничивалась фиксированным буфером. Сообщение и вся группа записываются полностью - это важно для длинных ответов, например списка DTC..qlog - библиотека и прибор формировали текст лога двумя независимыми реализациями, и расшифровка одних и тех же значений различалась. Флаг установленного соединения TP2.0 и TP1.6 в поле RxStatus библиотека печатала как CONNECTION_ESTABLISHED, прибор - как CONN_OK; имена IOCTL различались в 22 позициях. Теперь имена протоколов, флагов TxFlags и RxStatus, IOCTL и их параметров формируются единой реализацией, поэтому лог приложения и лог прибора по одному и тому же обмену читаются рядом.Скачать библиотеки J2534 2.0.0.200 - Windows x86/x64/ARM64 (отдельные сборки для Windows 7), macOS (universal), Linux (x64, x86, ARM, ARM64), Android (arm64-v8a, armeabi-v7a, x86, x86_64), iOS.
Новое
ISO13400_PS (0x8FFD) и HSFZ_PS (0x8FFC). В стандарт SAE J2534 они не входят - это собственное расширение ScanDoc: диагностика по Ethernet - обнаружение автомобилей в сети (VIN, логический адрес), TCP-подключение, routing activation, обмен UDS. Тестерный адрес по умолчанию 0 - задайте ISO13400_SOURCE_ADDR до активации маршрутизации, иначе шлюз ответит отказом; адрес ЭБУ передаётся в каждом сообщении ([TA][SA][UDS]), ISO13400_TARGET_ADDR через Set/GetConfig не задаётся. Отправка сериализуется по P2: один незакрытый UDS-запрос за раз, NRC 7F xx 78 продлевает ожидание до P2*max (6 с). Новый параметр канала ISO13400_P3_DOIP (0x8108) - пауза между сообщениями.
uint32_t ch, code = 0;
pt_config_t sa = { ISO13400_SOURCE_ADDR, 0x0E80 };
pt_config_list_t cfg = { 1, &sa };
PassThruConnect(dev, ISO13400_PS, 0, 0, &ch);
PassThruIoctl(ch, SET_CONFIG, &cfg, NULL); /* SA - до routing activation */
PassThruIoctl(ch, ISO13400_DISCOVER_VEHICLES, NULL, NULL); /* IP ЭБУ запомнится сам */
PassThruIoctl(ch, ISO13400_CONNECT_TCP, NULL, NULL);
PassThruIoctl(ch, ISO13400_ACTIVATE_ROUTING, NULL, &code); /* 0x10 = success */
/* дальше PassThruWriteMsgs / PassThruReadMsgs - обычный UDS */
0x55 (маркер кадра J2534) - J2534, любой другой (текстовая AT-команда) - ELM327.Исправлено
PassThruStartMsgFilter сравнивал только 4 байта CAN ID, игнорируя заданную длину фильтра. Теперь кадр сравнивается на всю длину, как требует стандарт: PASS/BLOCK по содержимому кадра работают.
/* Подавить ответы TesterPresent (07E8 02 7E ...) в приёмной очереди */
pt_msg_t mask = {0}, pattern = {0};
mask.protocol_id = pattern.protocol_id = CAN;
mask.data_size = pattern.data_size = 6; /* 4 байта CAN ID + 2 байта данных */
memcpy(mask.data, "\xFF\xFF\xFF\xFF\xFF\xFF", 6);
memcpy(pattern.data, "\x00\x00\x07\xE8\x02\x7E", 6);
uint32_t fid;
PassThruStartMsgFilter(ch, BLOCK_FILTER, &mask, &pattern, NULL, &fid);
AT SH при активном CAN-канале ломала Flow Control (FC уходил без паддинга, DLC=3 - шлюз не слал Consecutive Frames) и затирала приёмный фильтр своим TX ID (приём ответов без AT CRA ломался). По даташиту AT SH задаёт только заголовок передачи - приёмный фильтр теперь управляют только AT CRA/CF/CM.PassThruStopPeriodicMsg мог отправить лишний кадр после остановки._PS - выбор пинов через SET_CONFIG(J1962_PINS) не применялся, кадры не уходили на шину.Исправлено