J2534 specification and function reference ›
ELM327 specification and command reference ›
The ELM327 interface is available on the Nano ET. The other ScanDoc adapters use the J2534 PassThru protocol.
Changes in the J2534 DLL, ELM327 and ScanDoc adapter firmware that affect integration: new functions, protocols and parameters - with usage examples.
Download J2534 libraries 2.0.0.213 - Windows x86/x64/ARM64 (separate builds for Windows 7), macOS (universal), Linux (x64, x86, ARM, ARM64), Android (arm64-v8a, armeabi-v7a, x86, x86_64), iOS (XCFramework); the docs/ folder holds the SDK documentation (getting started, API reference, configuration, error handling, DoIP, firmware update, log format, Android, iOS). Static .a archives ship only for iOS and the Linux x64 server build; all other platforms load the library dynamically.
Fixed
CAN_PS, ISO15765_PS, J1939_PS and pin setting on _PS channels - connecting these three protocols was rejected by the device; they now work the same way as TP2_0_PS and ISO9141_PS. Pin setting has been brought in line with J2534-2 on three points:
PassThruConnect on the default pins, although a _PS channel must stay silent until SET_CONFIG(J1962_PINS). The channel now goes on the bus only after the pins are set.SET_CONFIG(J1962_PINS) switched a live channel to other contacts in the middle of a session. Per the standard, pins are set once per channel: a repeated call returns ERR_CHANNEL_IN_USE; other pins are possible only after PassThruDisconnect.ERR_PIN_NOT_SUPPORTED.uint32_t ch;
pt_config_t pins = { J1962_PINS, 0x0000060EU }; /* pins 6 and 14 */
pt_config_list_t cfg = { 1, &pins };
PassThruConnect(dev, ISO15765_PS, 0, 500000, &ch);
/* the channel is not on the bus yet */
if (PassThruIoctl(ch, SET_CONFIG, &cfg, NULL) != STATUS_NOERROR) {
/* ERR_PIN_NOT_SUPPORTED - this combination is not in the device wiring */
}
/* only now PassThruWriteMsgs / PassThruReadMsgs;
a repeated SET_CONFIG(J1962_PINS) - ERR_CHANNEL_IN_USE */
PassThruWriteMsgs returned success, PassThruReadMsgs returned nothing and the CONNECTION_LOST flag was never raised. ECU programming over TP2.0 verified on the bench.REQUEST_CONNECTION: a dozen attempts against a silent ECU used up all filter slots and muted the channel; TEARDOWN_CONNECTION on TP1_6_PS was rejected - the connection could not be closed from the application side; TP2.0 did not accept a connection established by the ECU and did not pass frames received outside a connection to the application (§19.3.1 J2534-2).PassThruReadMsgs. The connection here is point-to-point, the tester address is registered at routing activation, there is nothing to filter out: the channel passes every message to the application and PassThruStartMsgFilter answers ERR_NOT_SUPPORTED. A failure in the CAN driver rebooted the device in the middle of a DoIP session. The receive task watchdog is raised from 5 to 30 s - establishing a DoIP connection legitimately takes up to 20 s.PassThruConnect got a queue overflow on its very first message. Base CAN and ISO15765 went to the other CAN controller and never reached the bus. Reading of the receive queue did not recover after a corrupted entry - the flag was checked incorrectly in all CAN-based protocols.GET_NDIS_ADAPTER_INFO - uninitialised data was returned under STATUS_NOERROR. The reply now carries the adapter identifier, MAC, the IPv4 address at which the ECU sees the device, and the activation line state; on a device without Ethernet - ERR_NOT_SUPPORTED.GET_PROTOCOL_INFO - answered for only some protocols and in the wrong format. It now works on any open channel: timestamp resolution (1 µs), supported parity, UART data bits. A parameter the device cannot answer is flagged in the supported field while the call itself returns STATUS_NOERROR.PassThruDisconnect - accessing an already terminated channel task corrupted device memory.New
libj2534.xcframework contains slices for the device (arm64) and the simulator (arm64/x86_64), minimum iOS 12.0. Each slice carries the j2534.h and j2534_ota.h headers, a module map (Swift import J2534, CoreBluetooth autolinked) and a privacy manifest. The Pass-Thru API prototypes are declared in j2534.h itself - on all platforms. mbedTLS is compiled in, there are no external dependencies. Xcode setup: Embed = Do Not Embed (the library is static), -lc++ in Other Linker Flags, the NSBluetoothAlwaysUsageDescription (BLE) and NSLocalNetworkUsageDescription (WLAN) keys in Info.plist - without them iOS terminates the app on first use of the transport.
import J2534
var deviceId: UInt32 = 0
// PassThruOpen takes a mutable char* - pass a copy of the string
var cstr = Array("ScanDoc;b:N4999".utf8CString) // BLE by name prefix
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)
}
/* protocol and IOCTL IDs are macros with a cast and do not import into Swift:
use the numbers, let CAN: UInt32 = 5, let ISO15765: UInt32 = 6 */
ptOtaUpdate(devId, firmwarePath, callback) and ptOtaAbort(devId) are added to the JNI layer; previously OtaUpdate/OtaAbort were available only through the C API. Progress is reported in onProgress(current, total) - blocks, 1-based.
// update.bin has been copied into app storage beforehand.
// The call blocks - run it off the main thread.
val res = j2534.ptOtaUpdate(devId, file.absolutePath,
object : OtaProgressListener {
override fun onProgress(current: Int, total: Int) { /* progress bar */ }
})
if (res.status == 0) {
// firmware written, the device reboots: devId is invalid,
// reconnect with a new ptOpen (over BLE - allow ~10 s)
}
// res.status < 0 - an ota_result_t code (see j2534_ota.h)
// ptOtaAbort(devId) cancels the update without rebooting the device
log_level key in j2534.json: -1 off, 0 errors, 1 +warnings, 2 +info, 3 +debug, 4 +verbose; the default is 3, i.e. without configuration the log is written in full. At -1 neither the sdlogs folder nor the .qlog file is created and nothing is written to disk. On Android the level can also be set from code - ptSetLogLevel(int); a level set this way takes priority over the configuration file, so in a release build the log cannot be enabled from outside.
// Android: call before ptOpen
j2534.ptSetLogLevel(-1) // release build - log off
j2534.ptSetLogLevel(3) // support case - full log
// Other platforms: j2534.json in the configuration folder
// macOS ~/Library/Application Support/Quantex/
// Linux ~/.config/quantex/
// Windows %APPDATA%\Quantex\
{ "log_level": -1, "devices": [] }
Fixed
PassThruReadVersion and the .qlog header returned 2.0.0.0: the build number was not passed into the Android build. The version is now derived by the same rule as on the other platforms; this is the number to quote when contacting support.serial_* symbols: the USB transport is excluded from the iOS build, but calls to it remained. The transport is replaced by a stub - connecting with a c: string returns a regular port-open error..qlog log - message data was written up to 125 bytes only, and the record of a group of messages passed in a single PassThruReadMsgs or PassThruWriteMsgs call was limited by a fixed buffer. The message and the whole group are now written in full - important for long responses such as a DTC list..qlog log - the library and the device produced the log text with two independent implementations, and the decoding of the same values differed. The established-connection flag of TP2.0 and TP1.6 in the RxStatus field was printed as CONNECTION_ESTABLISHED by the library and as CONN_OK by the device; IOCTL names differed in 22 places. The names of protocols, TxFlags and RxStatus flags, IOCTLs and their parameters are now produced by a single implementation, so the application log and the device log of the same exchange read side by side.Download J2534 libraries 2.0.0.200 - Windows x86/x64/ARM64 (separate builds for Windows 7), macOS (universal), Linux (x64, x86, ARM, ARM64), Android (arm64-v8a, armeabi-v7a, x86, x86_64), iOS.
New
ISO13400_PS (0x8FFD) and HSFZ_PS (0x8FFC). They are not part of the SAE J2534 standard - this is a proprietary ScanDoc extension: diagnostics over Ethernet - vehicle discovery on the network (VIN, logical address), TCP connection, routing activation, UDS exchange. The default tester address is 0 - set ISO13400_SOURCE_ADDR before routing activation, otherwise the gateway refuses; the ECU address is passed in every message ([TA][SA][UDS]), ISO13400_TARGET_ADDR is not set via Set/GetConfig. Transmission is serialized by P2: one outstanding UDS request at a time, NRC 7F xx 78 extends the wait to P2*max (6 s). New channel parameter ISO13400_P3_DOIP (0x8108) - pause between messages.
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 - before routing activation */
PassThruIoctl(ch, ISO13400_DISCOVER_VEHICLES, NULL, NULL); /* the ECU IP is remembered automatically */
PassThruIoctl(ch, ISO13400_CONNECT_TCP, NULL, NULL);
PassThruIoctl(ch, ISO13400_ACTIVATE_ROUTING, NULL, &code); /* 0x10 = success */
/* then PassThruWriteMsgs / PassThruReadMsgs - regular UDS */
0x55 (J2534 frame marker) - J2534, anything else (text AT command) - ELM327.Fixed
PassThruStartMsgFilter compared only the 4 CAN ID bytes, ignoring the given filter length. Frames are now matched over the full length as the standard requires: PASS/BLOCK by frame content works.
/* Suppress TesterPresent responses (07E8 02 7E ...) in the receive queue */
pt_msg_t mask = {0}, pattern = {0};
mask.protocol_id = pattern.protocol_id = CAN;
mask.data_size = pattern.data_size = 6; /* 4 CAN ID bytes + 2 data bytes */
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 command on an active CAN channel broke Flow Control (the FC went out without padding, DLC=3 - the gateway sent no Consecutive Frames) and overwrote the receive filter with its own TX ID (reception without AT CRA broke). Per the datasheet, AT SH sets only the transmit header - the receive filter is now controlled only by AT CRA/CF/CM.PassThruStopPeriodicMsg could send one extra frame after stopping._PS - pin selection via SET_CONFIG(J1962_PINS) was not applied, frames never reached the bus.Fixed