- C 82.1%
- Python 15.9%
- CMake 2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| docs | ||
| examples | ||
| include | ||
| ports | ||
| src | ||
| tests | ||
| tools | ||
| .gitattributes | ||
| .gitignore | ||
| CMakeLists.txt | ||
| LICENSE | ||
| README.md | ||
| THIRD_PARTY_NOTICES.md | ||
kline-c
Portable C99 diagnostic library for microcontrollers connected to a K-line transceiver. The protocol core has no dynamic allocation, operating-system calls, Arduino dependency, or global mutable state. ESP-IDF and Raspberry Pi Pico SDK ports and read-only examples are included.
This is a standalone repository. No VAG-COM or Helldiag installation is required
to build or use the library. Python is needed only for host tests and optional
catalog generation. Its library-only history was extracted from the Helldiag
workspace with git subtree split --prefix=kline-c; earlier commit messages
may mention desktop changes, but their trees contain only this library.
What is implemented
| Area | Implemented behavior |
|---|---|
| KW1281 | 5-baud init, shared block counter, byte complements, identification sequences, DTC requests, measuring groups including raw group 0, keepalive, end session |
| ISO 9141-2 | OBD 5-baud init, 3-byte headers, additive checksum, response framing by inter-byte timeout |
| ISO 14230 / KWP2000 | Slow and fast init, negotiated header/length forms with normal timing, raw service requests, negative responses and bounded response-pending handling |
| Live data | 69 KW encodings (1–70 except unknown type 37), common scalar Mode 01 OBD PIDs, units, encoding descriptions, original KW bytes |
| Faults | VAG code/status triples, SAE packed codes and P/C/B/U formatting, small starter description catalog, extension via CSV |
| Local descriptions | Optional generated tables from the readable Labels files, retaining field names, notes, comments, and source audit metadata |
| Hardware | GPIO/UART abstraction, configurable echo, byte deadlines, ESP-IDF port, Pico SDK port |
Coverage is explicit, not universal. ECU-specific data identifiers, switch-bit
meanings, raw group-zero scaling, proprietary DTCs, and newer protocols cannot be
inferred from the transport. Unknown values remain raw or return KL_UNSUPPORTED.
No proprietary codes.dat file or recovered catalog is included. The bundled
DTC table is not the complete VAG-COM fault-code database. Use tools/import_dtcs.py with a
readable catalog you can use. CAN/UDS, flashing, and security access workflows are
outside this library's implemented scope.
This is a tested software implementation, not yet tested on a physical ECU. See verification evidence and protocol coverage and limits.
Build and test on a computer
Run from this directory:
cmake -S . -B build -DKLINE_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failure
This creates build/libkline.a and runs simulated wire exchanges and catalog
import tests. It never opens a serial port or contacts a vehicle. Release builds
keep the test assertions enabled.
For an existing C project, compile the five src/*.c files and add include to
the include path, or use add_subdirectory(kline-c) and link target kline.
Tests default OFF for embedded builds. The public API is in
include/kline.h, with callback, buffer, and lifetime comments.
snprintf is used only for short integer/text fields; the core does not require
floating-point printf. Float arithmetic is used for measurement scaling.
Connect with KW1281
kl_session session;
kl_hal hal = /* kl_pico_hal(&board) or kl_esp32_hal(&board) */;
kl_config config = kl_config_default(KL_KW1281);
config.target = 0x01; /* Engine; 0x17 is instruments, 0x46 convenience. */
config.baud = 10400; /* Set 9600 or another known baud if required by this ECU. */
kl_result result = kl_setup(&session, KL_KW1281, &hal, &config);
if (result != KL_OK) return; /* This snippet is inside your application function. */
result = kl_connect_slow(&session);
if (result == KL_OK) {
/* ACK all startup identification blocks before sending a diagnostic request.
Supply your callback instead of NULL to retain identification/coding. */
result = kl_kw_receive_sequence(&session, NULL, NULL);
}
if (result == KL_OK) {
kl_kw_block block;
result = kl_kw_read_group(&session, 3, &block);
if (result == KL_OK && block.title == KL_KW_VALUES && block.length % 3 == 0) {
for (size_t i = 0; i < block.length; i += 3) {
kl_value value;
kl_result decoded = kl_kw_decode_value(block.data[i], block.data[i+1],
block.data[i+2], &value);
/* Consume number/unit or text only when decoded == KL_OK.
Otherwise preserve block.data and show an unknown/raw field. */
(void)decoded;
}
}
}
/* Finish after your polling loop. */
if (session.active && session.counter_valid) (void)kl_kw_end(&session);
else (void)kl_disconnect(&session);
Do not use session if kl_setup fails. A complete error-handled example is in
examples/read_kw.c; it reads identification, fault codes,
and 20 samples of group 3. It does not clear faults or change coding.
Use kl_kw_command(..., KL_KW_READ_DTCS, NULL, 0, callback, user) to receive all
DTC blocks. In the callback, require title KL_KW_DTCS, then pass its payload to
kl_kw_decode_dtcs. FF FF 88 means no faults; FFFF with another status is a
real code and is retained. Lower seven status bits describe the failure subtype;
bit 7 marks an intermittent fault. Decimal VAG codes and packed SAE codes are
different namespaces: VAG 00575 is not P0575.
The generic command interface can send other service titles. KL_KW_CLEAR_DTCS
is an explicit destructive diagnostic request: sending it may erase useful
history. It is never sent automatically. This library does not implement or
automatically perform coding changes, basic settings, or ECU programming.
OBD data and KWP services
Initialize a separate session with KL_ISO9141 and kl_connect_slow, or with
KL_KWP2000 and the initialization required by the ECU. Default OBD wakeup address
is 0x33. For a VAG physical KWP session, explicitly set target,
functional=false, and the ECU's required slow-init parity (sometimes 7E1).
uint8_t pid = 0x0C; /* Engine RPM */
kl_message reply;
kl_result r = kl_obd_request(&session, 1, &pid, 1, &reply);
if (r == KL_OK && reply.length >= 2) {
kl_value rpm;
r = kl_obd_decode_pid(pid, reply.data + 2, reply.length - 2, &rpm);
/* rpm.number is valid only if r == KL_OK. */
}
First query supported-PID bitmap 00 and use kl_obd_pid_supported; follow the
bitmap continuation bits for higher ranges. A description table does not imply
that an ECU implements that PID. For stored/pending/permanent OBD DTCs, request
mode 03/07/0A with no parameters, then decode the reply after its SID
with kl_obd_decode_dtcs. Support for those modes is ECU-dependent.
kl_kwp_request takes an entire service payload, e.g. {0x21, local_id} or
{0x22, id_hi, id_lo}. It returns the positive-response SID plus raw data.
The format of manufacturer-specific KWP DTC and live-data services must be
selected from documentation for the actual ECU; it is not assumed to match
KW1281 triples. negative_code contains the last NRC on KL_NEGATIVE.
Use the local labels
Already generated here:
generated/all: 14,180 rows from 180 files, including adaptation/help rows.generated/passat: 113 rows from two explicitly selected files,038-906-018.lbland3B0-920-822.LBL.
The local CSV logs report 038 906 018 GA. The generic 038-906-018.lbl header
describes an ALH/BD ECU; it is a reference profile, not a verified GA match.
The selected cluster file is also an example, not an identification of your
installed cluster. Confirm group meanings against the actual ECU and its logs.
python3 tools/import_labels.py --source ../Labels --output generated/my_ecu \
--label '038-906-018.lbl'
cmake -S . -B build-labels -DKLINE_LABEL_DIR="$PWD/generated/my_ecu"
cmake --build build-labels
#include "local_labels.h"
const kl_label *label = kl_label_find(kl_local_labels, kl_local_labels_count,
"038-906-018", 'M', 3, 1);
/* label->name is the field title; ->details is range/help text.
->comment preserves preceding/inline source notes. All may be empty.
Missing rows return NULL. ECU/profile names are exact uppercase stems. */
M means measuring blocks; A, B, C retain source adaptation/basic-setting/
coding sections. Group 0 uses a controller-specific raw layout. Field 0 is a
title, 1–4 commonly label measured values, and other rows may be help text.
The parser also retains original file text, hashes, redirects, and unparsed lines
in labels-audit.json. Review this audit: it does not automatically resolve
redirect suffixes, wildcard ECU identities, or interpret arbitrary help text.
Do not use label ranges as an automatic diagnosis.
Generated label data retains the rights of its original authors and is excluded
by .gitignore. It is optional and not covered by this library's MIT license.
Include only needed profiles to save flash. The full host table is about 0.8 MB
of object sections; target pointer sizes and linker merging change this figure.
For an additional fault-code catalog:
system,code,description
vag,00575,Intake manifold pressure regulation
obd,P0300,Random or multiple cylinder misfire
python3 tools/import_dtcs.py my-codes.csv --output generated/my_codes
Compile local_dtcs.c, include local_dtcs.h, and pass kl_local_dtcs and
kl_local_dtcs_count to kl_dtc_description. The caller catalog overrides the
starter table. Unknown entries return an explicit unknown description.
ESP32 and Pico
Read the hardware guide before connecting a board. The L9637D can use a 3.3 V logic supply according to the supplied datasheet. The MC33660 requires the specified 5 V logic supply and appropriate level translation.
ESP-IDF example (set pins and target for your actual board):
cd examples/esp32
idf.py set-target esp32
idf.py build
The component is ports/kline_esp32. The core, adapter, and example sources were
cross-compiled using the installed ESP-IDF 5.5.1 headers and Xtensa toolchain.
A complete IDF firmware link and board run remain unverified. Use a dedicated
UART, one task per session, and enough task stack (start with 8 KiB for the
printing example). The ESP-IDF UART driver allocates buffers internally; the
allocation-free guarantee applies to the protocol core.
Pico SDK example:
export PICO_SDK_PATH=/path/to/pico-sdk
cmake -S examples/pico -B build-pico -DPICO_BOARD=pico
cmake --build build-pico
The example uses UART0 GPIO0 TX / GPIO1 RX and USB for console output. The core was cross-compiled for Cortex-M0+; a Pico SDK installation was not available for building the adapter or linking the example. Avoid concurrent Wi-Fi/display work that delays K-line timing until the port has been validated on your board.
References and provenance
- kw1281test: KW framing, services, coding layout, and measurement formulas; MIT attribution retained in THIRD_PARTY_NOTICES.md.
- The two user-supplied transceiver PDFs, with pages and electrical limits listed in HARDWARE.md.
- OBD9141: comparison of ISO9141/OBD framing.
- CSS Electronics PID table: cross-check for scalar Mode 01 PID scaling.
- Ross-Tech fault description example.
Implementation details and exclusions are documented in PROTOCOLS.md. New code is MIT-licensed; optional local label and user-imported DTC catalogs have separate data ownership.