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 |
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
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)
| 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;
["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 |
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;
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
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
| Section | Visibility | Scrambled? | 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!
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
- Put signals that the recipe reads (SIGNAL_HELLJUS, SIGNAL_TANDNING, etc.) in
VAR_SIGNAL - Put enable flags (ENABLE_HELLJUS, ENABLE_BACKLJUS, etc.) in
VAR_SIGNAL - Put CAN buffers, timers, counters, internal state in
VAR - Put outputs in
VAR_OUTPUT(only in the main recipe) - Put configuration constants in
VAR_CONSTANT(local) orVAR_CONSTANT GLOBAL(shared)
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
- Prefer
TOFoverTON+ manual reset for pulse generation — fewer variables needed. - Write expressions inline:
TMR(IN := HW.BUTTON = 1, PT := T#250ms)instead of storing in intermediate variables. - Avoid unnecessary variables — every
VARdeclaration costs RAM. - Use
BYTEinstead ofDINTwhen the value range allows it (1 byte vs 4 bytes). - Reuse CAN buffers — multiple CAN_TX calls can share one
SENDDATAarray.
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.