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 约需 6.5 ms,而不是 16 ms。长于 248 字节的应答,设备按每块 248 字节、间隔 4 ms 分块发送,因此从设备到计算机的传输速度约为 59 KB/s:4 KB 的应答约需 70 ms。从计算机到设备的传输仍为 3 Mbit/s。TCP_NODELAY,应答一次完整发出。修复
PassThruConnect CAN 之后重启。打开 CAN 通道约 300 ms 后,设备偶尔会自行重启。另外,在同一个 PassThruOpen 内进行 CAN 会话后,OtaUpdate 无法开始更新:设备在 30 s 内不响应更新的开始命令。PassThruClose 时。此外,在 CAN 或 DoIP 会话期间及之后,打开或关闭端口时设备可能卡死。使用固件构建 86 和库 2.0.0.241 时,PassThruClose 之后可以安全地打开和关闭端口。只有在会话期间、PassThruClose 之前关闭端口,才可能卡死。在 macOS 和 Linux 上,PassThruClose 之后端口可供其他程序使用;在 Windows 上,端口与以前一样,在库卸载之前一直被占用。库使用端口期间,请勿自行打开端口,也不要更改 DTR 和 RTS。PassThruWriteMsgs 仍收到"全部已接受"的应答,放不下的消息则丢失。此外,每条这样的消息都会使设备泄漏内存,在密集流量下几十秒后设备就开始以内存不足为由拒绝请求,直到重启。现在设备在调用的超时时间内等待队列空出位置,并报告接受了多少条消息。未全部接受时,库在超时为零时返回 ERR_BUFFER_FULL,超时非零时返回 ERR_TIMEOUT,并把已接受的消息数写入 pNumMsgs。RxStatus 中带有 BUFFER_OVERFLOW 位。ETHERNET_NDIS。使用 PassThruConnect 返回的通道号调用 PassThruDisconnect 和 GET_NDIS_ADAPTER_INFO 时返回 ERR_INVALID_CHANNEL_ID,PassThruClose 也不关闭该通道。断开失败现在返回 ERR_FAILED,此前返回的是 0xFFFB 而不是 J2534 代码。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,OtaAbort 之后为 -510,其他代码见 FIRMWARE_UPDATE.md
};
修复
HSFZ_GET_VEHICLE_INFO 只返回每辆已发现车辆信息的前 8 个字节。应用得到的是不完整的 VIN,没有 ECU 地址和 IP 地址。现在每条 hsfz_vehicle_info_t 记录完整返回,共 32 字节:VIN、ECU 地址、MAC 和 IP。所有原生库均已修复。PassThruWriteMsgs 会使设备挂起。库把这样的调用作为一条命令发送,而设备不接受这样的命令。设备不应答,调用会等满整个超时;通过 LAN/WLAN 时,设备还会把该命令的数据字节当作后续命令解析。现在库把调用拆分为多条命令,每条命令获得调用超时的剩余时间。所有消息在发送第一条之前都会经过检查。固件构建 86 对旧版库发来的这种命令返回 ERR_INVALID_MSG。PassThruReadMsgs 被整体拒绝,因为设备每次最多返回 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 而不是名称。新增
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 连接时,库会关闭文件描述符 0,而 BLE 模式下根本没有套接字。通常这只是悄无声息地关闭了 stdin,但如果此时 fd 0 正被 JVM 的对象占用,fdsan 就会终止进程:attempted to close file descriptor 0 … owned by native object。因此崩溃并不规律。ptClose 都会多耗时一秒。库丢弃了设备对关闭命令的应答,等满 1 秒的接收超时,并在日志中写入 0x6E - Connection timed out,仿佛设备没有应答。现在关闭操作在收到设备应答后即完成。macOS、Windows 和 Linux 不受此问题影响。ptOpen 会崩溃。应用以 SIGSEGV 终止。在其他打开失败的情况下(配对被拒绝、连接超时),库未释放指向 BLEManager 的全局 JNI 引用,反复尝试时引用会不断累积。FAST_INIT。设备的应答被复制到应用的 pt_msg_t 结构体时没有检查长度:应答过短会读取未初始化的内存,应答中长度过大则会越界写入应用传入的结构体。在 Android 上,同一调用还会向总线发出异常帧,失败时使应用异常终止。输入长度的检查也一并修复:此前 0x20000000 量级的 NumOfBytes 会被视为合法。FIVE_BAUD_INIT。按 §11.3.3.3 的要求,输入限定为一个地址字节;此前接受任意长度的数据块,传入更多内容的应用会得到成功码而不是拒绝。.qlog 文件。只有 PassThruOpen 会打开日志,而 ptOpen 绕过了它。因此第三方 Android 应用无论日志级别如何,sdlogs 目录始终为空,记录只进入 logcat,也就无法从客户那里获取会话日志。FIVE_BAUD_INIT 与 FAST_INIT 的行中只打印 > ok:初始化地址和 ECU 的应答都没有写入日志,失败的初始化无从分析。现在该行同时包含请求与应答: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 对 ECU 编程。REQUEST_CONNECTION 失败后通道的内部过滤器未被移除:向无响应 ECU 尝试十余次即占满全部过滤器槽位并使通道静默;TP1_6_PS 上的 TEARDOWN_CONNECTION 返回拒绝 - 应用端无法关闭连接;TP2.0 不接受由 ECU 发起的连接,也不把连接外收到的帧交给应用(J2534-2 §19.3.1)。PassThruReadMsgs。此处是点对点连接,测试仪地址已在 routing activation 时登记,没有可过滤之物:通道把全部消息交给应用,PassThruStartMsgFilter 返回 ERR_NOT_SUPPORTED。CAN 驱动故障会在 DoIP 会话中途重启设备。接收任务看门狗从 5 s 提高到 30 s - 建立 DoIP 连接正常需要最长 20 s。PassThruConnect 在第一条消息就遇到队列溢出。基础 CAN 与 ISO15765 走到了另一个 CAN 控制器,到不了总线。接收队列在遇到损坏记录后无法恢复读取 - 所有基于 CAN 的协议都错误地检查了该标志。GET_NDIS_ADAPTER_INFO - 在 STATUS_NOERROR 下返回未初始化数据。现在返回适配器标识、MAC、ECU 看到设备的 IPv4 地址以及激活线状态;设备无 Ethernet 时 - ERR_NOT_SUPPORTED。GET_PROTOCOL_INFO - 只对部分协议应答且格式错误。现在在任何已打开通道上均可用:时间戳分辨率(1 µs)、支持的校验位、UART 数据位数。设备无法应答的参数在 supported 字段中标记,调用本身返回 STATUS_NOERROR。PassThruDisconnect - 访问已结束的通道任务会破坏设备内存。新增
libj2534.xcframework 包含设备(arm64)和模拟器(arm64/x86_64)切片,最低 iOS 12.0。每个切片含 j2534.h、j2534_ota.h 头文件、module map(Swift import J2534,自动链接 CoreBluetooth)及 privacy manifest。Pass-Thru API 原型已在 j2534.h 本身声明 - 所有平台一致。mbedTLS 已编入,无外部依赖。Xcode 设置:Embed = Do Not Embed(静态库),Other Linker Flags 中加 -lc++,Info.plist 中加 NSBluetoothAlwaysUsageDescription(BLE)与 NSLocalNetworkUsageDescription(WLAN)- 缺少时 iOS 会在首次使用传输层时终止应用。
import J2534
var deviceId: UInt32 = 0
// PassThruOpen 接受可变 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)
}
/* 协议和 IOCTL 的 ID 是带类型转换的宏,不会导入 Swift:
直接用数字,let CAN: UInt32 = 5, let ISO15765: UInt32 = 6 */
ptOtaUpdate(devId, firmwarePath, callback) 和 ptOtaAbort(devId);此前 OtaUpdate/OtaAbort 仅通过 C API 可用。进度通过 onProgress(current, total) 回传 - 以块计,从 1 开始。
// 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 s)
}
// res.status < 0 - ota_result_t 代码(见 j2534_ota.h)
// ptOtaAbort(devId) 中止更新且不重启设备
j2534.json 中的 log_level 键设置:-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 日志中的字段解析 - 库和设备用两套独立实现生成日志文本,同一数值的解析结果不一致。RxStatus 字段中 TP2.0 和 TP1.6 的连接已建立标志,库打印为 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 交换。tester 地址默认为 0 - 请在 routing activation 之前设置 ISO13400_SOURCE_ADDR,否则网关会拒绝;ECU 地址随每条消息传递([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); /* ECU 的 IP 会自动记住 */
PassThruIoctl(ch, ISO13400_CONNECT_TCP, NULL, NULL);
PassThruIoctl(ch, ISO13400_ACTIVATE_ROUTING, NULL, &code); /* 0x10 = 成功 */
/* 之后 PassThruWriteMsgs / PassThruReadMsgs - 常规 UDS */
0x55(J2534 帧标记)- J2534,其他(文本 AT 命令)- ELM327。修复
PassThruStartMsgFilter 只比较 CAN ID 的 4 个字节,忽略给定的过滤器长度。现在按标准要求对整个长度进行比较:按帧内容的 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 会破坏 Flow Control(FC 无填充发送,DLC=3 - 网关不发送 Consecutive Frames),并用自己的 TX ID 覆盖接收过滤器(不使用 AT CRA 时接收损坏)。按数据手册 AT SH 只设置发送标头 - 接收过滤器现在仅由 AT CRA/CF/CM 控制。PassThruStopPeriodicMsg 停止后可能多发送一帧。_PS - 通过 SET_CONFIG(J1962_PINS) 选择引脚未生效,帧未上总线。修复