🔍
v1.3.8

Recipe Structure

A recipe is the complete program that runs on an XBB dongle. It defines how the dongle reads vehicle signals, processes them, and drives outputs. Every recipe is composed of multiple layers (libraries + main code) that compile together into a single binary. Understanding the layer structure, recipe types, and naming conventions is essential for creating correct, maintainable recipes.

Recipe Types

The recipe type determines how the dongle detects ignition and reads vehicle signals. Choosing the correct type is the first decision when creating a new recipe:

Type Ignition Detection Signal Reading When to Use
UDS (Standard) UDS request UDS request Most vehicles - the default choice
Passive IGN Cyclic CAN UDS request BMW, Mercedes, Volvo PHEV - ignition visible on broadcast CAN
Full Passive Cyclic CAN Cyclic CAN Tesla, Ford, Citroen - all signals on broadcast CAN
PP-CAN Voltage Cyclic CAN Permanent internal mount - powered directly from vehicle harness
Special Varies Varies J1939 trucks, button readers, single-signal applications
Tip: When in doubt, start with a UDS recipe. UDS works with the widest range of vehicles and provides the most reliable signal reading. Switch to passive or PP-CAN only when the vehicle specifically requires it (or when you have already identified the needed CAN IDs through sniffing).

Layer Structure - UDS Recipe

A UDS recipe compiles as a stack of libraries plus your main code. The order matters - variables must be declared before they are used, so earlier layers define variables that later layers consume:

Layer 1:  NEW_FB_STANDARD_LIB_V3         // Standard library (add manually)
Layer 2:  NEW_UDS_DID_WAKE_UP_V2         // Wake-up library (add manually)
Layer 3:  <YOUR_DID_MODULE(S)>           // You create: signal reading via DID_EXT
Layer 4:  NEW_STANDARD_FUNCTIONS_V1      // Standard functions (add manually)
Layer 5:  Main recipe                    // You create: outputs, config, signal mapping
Important

Layers 1, 2, and 4 are shared standard libraries that you must add manually to your recipe’s library list. They are not added automatically. You only create layers 3 (DID modules) and 5 (main recipe). If starting from a template or copying an existing recipe, the standard libraries are usually already linked.

Layer Structure - PP-CAN Recipe

PP-CAN recipes typically use voltage-based wake-up and passive CAN reading, though the hardware supports active CAN transmission as well:

Layer 1:  NEW_FB_STANDARD_LIB_V3         // Standard library (add manually)
Layer 2:  NEW_PP_CAN_FD_WAKE_UP_V1       // PP-CAN wake-up (add manually)
Layer 3:  <YOUR_CAN_READING_LIBRARY>     // You create: passive CAN reading
Layer 4:  NEW_STANDARD_FUNCTIONS_V1      // Standard functions (add manually)
Layer 5:  Main recipe                    // You create: outputs, config

The key difference is layer 2: NEW_PP_CAN_FD_WAKE_UP_V1 replaces NEW_UDS_DID_WAKE_UP_V2. PP-CAN wake-up uses supply voltage to detect ignition rather than CAN requests.

Standard Libraries (Auto-Included)

Danger: Do NOT recreate these libraries. They are automatically fetched from reference recipes at compile time. Including duplicates will cause compilation errors.
Library Source Recipe Purpose
NEW_FB_STANDARD_LIB_V3 1332 / 1568 Core function blocks (DIM_MATRIX, BATT_MONITOR, etc.)
NEW_UDS_DID_WAKE_UP_V2 1332 UDS wake-up: gyro detection, CAN init, sleep management
NEW_PP_CAN_FD_WAKE_UP_V1 1568 PP-CAN wake-up: voltage detection, CAN mode management
NEW_STANDARD_FUNCTIONS_V1 1332 / 1568 Settings framework, flasher/blinker logic, worklight control

Naming Conventions

Internal Signals (Swedish)

Internal signal variables use Swedish names by convention. These are declared in VAR_SIGNAL and are used throughout the recipe for logic:

VAR_SIGNAL
    SIGNAL_HELLJUS : BOOL;       // High beam
    SIGNAL_HALVLJUS : BOOL;      // Low beam
    SIGNAL_BACKLJUS : BOOL;      // Reverse light
    SIGNAL_TANDNING : BOOL;      // Ignition
    SIGNAL_BLINKERS_V : BOOL;    // Turn signal left (vanster)
    SIGNAL_BLINKERS_H : BOOL;    // Turn signal right (hoger)
    SIGNAL_DIMLJUS : BOOL;       // Fog light
    SIGNAL_BROMS : BOOL;         // Brake
END_VAR;

App-Facing Outputs (English)

Output names use English because they are displayed in the XBB mobile app, which has an international user base:

VAR_OUTPUT
    HIGHBEAM ["Helljus"] : OUTPUT;
    LOWBEAM ["Halvljus"] : OUTPUT;
    REVERSE ["Backljus"] : OUTPUT;
    IGNITION ["Tandning"] : OUTPUT;
    LEFT_TURN ["Blinkers V"] : OUTPUT;
    RIGHT_TURN ["Blinkers H"] : OUTPUT;
END_VAR;
Note: The string in brackets (e.g., ["Helljus"]) is the display label shown in the app. It can be in any language. The variable name itself (e.g., HIGHBEAM) must be a valid identifier in English.

Library Naming

Libraries follow a strict naming convention that encodes the vehicle make, model, ECU, and version:

// DID Module library:
<MAKE>_<MODEL>_UDS_DID_<ECU>_MODULE_V<N>

// Examples:
BMW_G20_UDS_DID_LIGHT_MODULE_V1
VOLVO_SPA_UDS_DID_CEM_MODULE_V2
VW_MQB_UDS_DID_BCM_MODULE_V1

// Recipe naming:
<MAKE>_<MODEL>_RECIPE_V<N>

// Examples:
VOLVO_SPA_GEN2_RECIPE_V1
BMW_G20_RECIPE_V3
TESLA_MODEL3_RECIPE_V1

Recipe Display Name

The recipe name shown to end users follows this format:

*NEW 1.8.0* [Make] [Model] ([Generation]) [Headlight type] Headlights ([Years])

// Examples:
*NEW 1.8.0* Volvo XC90 (SPA) LED Headlights (2015-2024)
*NEW 1.8.0* BMW 3-Series (G20) LED Headlights (2019-2025)
*NEW 1.8.0* Tesla Model 3 LED Headlights (2017-2024)

Wake-Up Configuration

Wake-up determines how the dongle detects that the vehicle has been started or is in use. It is configured via boolean variables in the main recipe code (not in a separate library):

Variable Value When to Use
GYRO_WAKE_UP TRUE UDS recipes - dongle detects motion via gyroscope, then sends CAN requests
CAN_WAKE_UP TRUE Passive ignition - dongle listens for broadcast CAN traffic
BMW_WAKE_UP TRUE BMW only - special alarm-aware CAN mode switching
INPUT_WAKE_UP TRUE External wake-up signal via hardware input pin
Danger: CAN_WAKE_UP and GYRO_WAKE_UP are mutually exclusive - never set both to TRUE! Setting both will cause unpredictable wake/sleep behavior, potentially draining the vehicle battery or preventing the dongle from waking up at all.
// UDS recipe -- gyro-based wake-up (most common)
GYRO_WAKE_UP := TRUE;
CAN_WAKE_UP := FALSE;

// Passive recipe -- CAN-based wake-up
GYRO_WAKE_UP := FALSE;
CAN_WAKE_UP := TRUE;

// BMW -- special wake-up
GYRO_WAKE_UP := TRUE;
CAN_WAKE_UP := FALSE;
BMW_WAKE_UP := TRUE;
Note: GYRO_WAKE_UP, CAN_WAKE_UP, and BMW_WAKE_UP are variables declared by the wake-up library (NEW_UDS_DID_WAKE_UP_V2). This code belongs in the main recipe layer and requires the full standard library chain.

Required Signals

Every recipe needs a minimum set of signals to function correctly. The priority of each signal is based on customer expectations and hardware requirements:

Signal Priority Notes
Ignition (SIGNAL_TANDNING) CRITICAL Without ignition detection, the recipe cannot function. The system will not know when to activate or sleep.
High Beam (SIGNAL_HELLJUS) Required The primary user feature - this is why most customers buy the product.
Reverse (SIGNAL_BACKLJUS) 99% required Nearly every customer expects reverse light support. Omit only for vehicles where reverse detection is technically impossible.
Low Beam (SIGNAL_HALVLJUS) Recommended Useful for daytime running light control and status display.
Turn Signals (SIGNAL_BLINKERS_V/H) Recommended Enables sequential turn signal animations and side marker control.
Fog Light (SIGNAL_DIMLJUS) Optional Some vehicles use fog light signals for auxiliary lighting control.

Compilation Order

Libraries compile top-to-bottom. A variable declared in layer 3 is available in layers 4 and 5, but not in layers 1 or 2. This is why the layer order is fixed - the standard library declares base types first, wake-up logic declares system variables next, and your code builds on top:

// Layer 1: FB_STANDARD_LIB defines DID_EXT, CAN_RX, TON, etc.
// Layer 2: WAKE_UP declares SIGNAL_SYSTEM_ON, canReady, etc.
// Layer 3: YOUR DID MODULE uses DID_EXT (from L1) and SIGNAL_SYSTEM_ON (from L2)
// Layer 4: STANDARD_FUNCTIONS uses signals from L3
// Layer 5: YOUR RECIPE uses everything from L1-L4
Warning: If you reference a variable that is declared in a later layer, the compiler will report an “undeclared identifier” error. Check the layer order if you encounter this error.

How Libraries Work: Variable Scrambling

When the compiler processes a library, it scrambles (obfuscates) all local variable names to prevent naming collisions between libraries. This works similarly to name mangling in C/C++. Understanding this is critical for writing libraries that communicate with the main recipe.

VAR vs VAR_SIGNAL: The Key Difference

SectionVisibilityScrambled?Use For
VAR Local to library Yes - names are mangled Internal variables, CAN buffers, timers, counters
VAR_SIGNAL Global across all files No - names are preserved Signals shared between library and recipe
VAR_OUTPUT Global (displayed in app) No - names are preserved Physical outputs visible in the XBB app
VAR_CONSTANT Local to library Yes - names are mangled Constants used internally by the library
VAR_CONSTANT GLOBAL Global across all files No - names are preserved Constants available to all files

Practical Example

Consider a library that reads the high beam signal from CAN:

// Library: MY_CAN_LIBRARY

VAR
    CAN_DATA : ARRAY[0..7] OF BYTE;   // SCRAMBLED: becomes MY_CAN_LIBRARY_CAN_DATA
    CAN_RECV : CAN_RX;                // SCRAMBLED: becomes MY_CAN_LIBRARY_CAN_RECV
    CAN_TMR  : TON;                   // SCRAMBLED: becomes MY_CAN_LIBRARY_CAN_TMR
END_VAR;

VAR_SIGNAL
    SIGNAL_HELLJUS : BOOL;            // NOT scrambled: accessible everywhere
END_VAR;

CAN_RECV(ENABLE := TRUE, ID := 0x140, EXT := FALSE, DATA := CAN_DATA);
WHILE CAN_RECV.AVAILABLE DO
    SIGNAL_HELLJUS := CAN_DATA[0].7;
    CAN_TMR(IN := FALSE);
    CAN_RECV();
END_WHILE;

CAN_TMR(IN := TRUE, PT := T#5000ms);
IF CAN_TMR.Q THEN
    SIGNAL_HELLJUS := FALSE;
END_IF;

In the main recipe, you can use SIGNAL_HELLJUS directly because it is in VAR_SIGNAL. But you cannot access CAN_DATA, CAN_RECV, or CAN_TMR because they are local (VAR) and their names have been scrambled by the compiler:

// Main recipe

VAR_OUTPUT
    HIGHBEAM : OUTPUT;
END_VAR;

// This WORKS: SIGNAL_HELLJUS is in VAR_SIGNAL (global, not scrambled)
HIGHBEAM(VALUE := SIGNAL_HELLJUS * 1000, PERIOD := 1000);

// This would FAIL: CAN_DATA is in VAR (local, scrambled)
// HIGHBEAM(VALUE := CAN_DATA[0].7 * 1000, PERIOD := 1000);  // ERROR!
Common Mistake

If you declare a variable in VAR in a library and try to read it from the main recipe, you will get a “variable not found” compiler error. The variable exists, but its name has been scrambled and is no longer accessible by its original name.

Solution: Move the variable to VAR_SIGNAL if it needs to be shared between files. Keep it in VAR if it is truly internal to the library.

Why Scrambling Exists

Without scrambling, two libraries that both declare CAN_DATA : ARRAY[0..7] OF BYTE would conflict - the compiler would not know which CAN_DATA to use. By prefixing local variable names with the library name, each library gets its own isolated namespace. This is the same reason C compilers use name mangling for static variables.

Rules of Thumb

Flash & RAM Constraints

XBB devices have limited flash (16,384 bytes) and RAM (4,096 bytes). Every variable, function block instance, and code statement consumes these resources. Writing compact code is not just good practice — it can be the difference between a recipe that fits and one that doesn’t.

Optimization Tips

Size matters

A verbose 10-line solution vs a compact 3-line solution can mean 54% flash usage vs 11%. Always check the compile output to monitor your flash and RAM usage.