No description
  • C 82.1%
  • Python 15.9%
  • CMake 2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-26 19:26:44 +01:00
docs Add Passat controller functions, graph logging and merged instrument tools 2026-10-26 19:02:21 +01:00
examples Add portable K-line library, MCU ports, import tools and documentation 2026-10-26 16:12:02 +01:00
include Fix live KKL connections with automatic baud and VAG-COM timing 2026-10-26 17:55:00 +01:00
ports Add portable K-line library, MCU ports, import tools and documentation 2026-10-26 16:12:02 +01:00
src Add Passat controller functions, graph logging and merged instrument tools 2026-10-26 19:02:21 +01:00
tests Add portable K-line library, MCU ports, import tools and documentation 2026-10-26 16:12:02 +01:00
tools Add portable K-line library, MCU ports, import tools and documentation 2026-10-26 16:12:02 +01:00
.gitattributes Prepare standalone kline-c repository 2026-10-26 19:26:44 +01:00
.gitignore Prepare standalone kline-c repository 2026-10-26 19:26:44 +01:00
CMakeLists.txt Add portable K-line library, MCU ports, import tools and documentation 2026-10-26 16:12:02 +01:00
LICENSE Add portable K-line library, MCU ports, import tools and documentation 2026-10-26 16:12:02 +01:00
README.md Prepare standalone kline-c repository 2026-10-26 19:26:44 +01:00
THIRD_PARTY_NOTICES.md Add portable K-line library, MCU ports, import tools and documentation 2026-10-26 16:12:02 +01:00

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.lbl and 3B0-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

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.