Variable Declarations
Variables in TSharkRex are declared inside dedicated VAR sections. Each section type serves a different purpose - from local scratch variables to globally shared signals and app-visible outputs. Getting the right section type is one of the most important decisions you make when writing a recipe or library.
VAR Sections Overview
TSharkRex provides six types of variable sections. Each one controls the scope, visibility, and lifetime of the variables declared within it.
| Section | Purpose | Scope |
|---|---|---|
VAR |
Local variables | Current file |
VAR_INPUT |
Function parameters | Function only |
VAR_OUTPUT |
Recipe outputs (visible in the app) | Current file |
VAR_SIGNAL |
Global signals (shared between libraries) | All files |
VAR_CONSTANT |
Constants (file-local) | Current file |
VAR_CONSTANT GLOBAL |
Constants (global) | All files |
END_VAR; (with a semicolon).
VAR - Local Variables
The VAR section declares variables that are local to the current file. These
are the most common variables in any TSharkRex program. Use them for counters, buffers,
intermediate calculations, and function block instances.
VAR
myVar : INT;
counter : DINT := 0; // With initial value
buffer : ARRAY[0..7] OF BYTE;
pData : POINTER TO BYTE;
TMR : TON; // Function block instance
END_VAR;
Multiple VAR Sections
You can have multiple VAR sections in the same file. They can appear anywhere
in your code - at the top, between functions, or even interleaved with executable
statements. This lets you declare variables close to where they are used:
VAR
canId : DINT := 0x320;
dlc : BYTE := 8;
END_VAR;
// ... some code ...
VAR
responseBuffer : ARRAY[0..63] OF BYTE;
responseLen : INT := 0;
END_VAR;
// ... more code using responseBuffer ...
Initialization Behavior
Variables declared with an initial value are initialized once when the program starts. Code placed inside a VAR block (between the declarations) also executes only once. On subsequent scan cycles, the variables retain their values from the previous cycle.
VAR
cycleCount : DINT := 0; // Set to 0 once at startup
isFirstRun : BOOL := TRUE;
END_VAR;
cycleCount := cycleCount + 1; // Increments every cycle
IF isFirstRun THEN
// Runs only on the first cycle
isFirstRun := FALSE;
END_IF;
VAR_INPUT - Function Parameters
VAR_INPUT declares the input parameters of a FUNCTION. These
variables receive their values from the caller when the function is invoked.
FUNCTION my_func : INT
VAR_INPUT
param1 : INT;
param2 : BYTE;
END_VAR;
my_func := param1 + param2;
END_FUNCTION;
Calling the function:
FUNCTION my_func : INT
VAR_INPUT
param1 : INT;
param2 : BYTE;
END_VAR;
my_func := param1 + param2;
END_FUNCTION;
VAR
result : INT;
END_VAR;
result := my_func(param1 := 100, param2 := 0x0A);
VAR_INPUT is only used inside
FUNCTION definitions. If you are writing a FUNCTION_BLOCK, use
plain VAR for all variables - inputs, outputs, and internal
state alike. This is a key difference from standard IEC 61131-3, where
FUNCTION_BLOCK uses VAR_INPUT, VAR_OUTPUT, and
VAR separately.
A more complete function example:
FUNCTION scale_value : INT
VAR_INPUT
rawValue : INT;
minIn : INT;
maxIn : INT;
minOut : INT;
maxOut : INT;
END_VAR;
VAR
range_in : INT;
range_out : INT;
END_VAR;
range_in := maxIn - minIn;
range_out := maxOut - minOut;
IF range_in <> 0 THEN
scale_value := minOut + (rawValue - minIn) * range_out / range_in;
ELSE
scale_value := minOut;
END_IF;
END_FUNCTION;
VAR_OUTPUT - Recipe Outputs
VAR_OUTPUT declares outputs that are visible in the XBB app. These represent
the physical outputs (lights, relays) and informational values that the end user can see
and interact with on their device.
VAR_OUTPUT
HIGHBEAM : OUTPUT;
LOWBEAM : OUTPUT;
DRL : OUTPUT;
VOLTAGE : INFO_VALUE;
END_VAR;
The OUTPUT type maps to a controllable output channel on the XBB hardware.
INFO_VALUE displays a read-only value in the app.
VAR_OUTPUT
section in your recipe is displayed on the platform UI. If you need all outputs visible,
declare them in a single section.
A typical recipe output section with labels for the app:
VAR_OUTPUT
HELLJUS ["Helljus"] : OUTPUT;
HALVLJUS ["Halvljus"] : OUTPUT;
DIMLJUS ["Dimljus"] : OUTPUT;
BAKLJUS ["Bakljus"] : OUTPUT;
BROMSLJUS ["Bromsljus"] : OUTPUT;
BLINKERS_V ["Blinkers V"] : OUTPUT;
BLINKERS_H ["Blinkers H"] : OUTPUT;
BATTERI_VOLT ["Batterispanning"] : INFO_VALUE;
END_VAR;
VAR_SIGNAL - Global Signals
VAR_SIGNAL declares variables that are shared across all files in
a recipe - including all attached libraries. This is the primary mechanism for
communication between a CAN library (which reads vehicle signals) and the main recipe
(which controls outputs based on those signals).
VAR_SIGNAL
SIGNAL_HELLJUS : BOOL; // High beam detected on CAN
SIGNAL_HALVLJUS : BOOL; // Low beam detected on CAN
SIGNAL_TANDNING : BOOL; // Ignition on
SIGNAL_BLINKER_V : BOOL; // Left turn signal
SIGNAL_BLINKER_H : BOOL; // Right turn signal
HW : HARDWARE; // Hardware abstraction
END_VAR;
How VAR_SIGNAL Works
When both a library and a recipe declare the same VAR_SIGNAL variable, they
share the same memory. The library writes to the signal, and the recipe reads it:
// --- In the CAN library ---
VAR_SIGNAL
SIGNAL_HELLJUS : BOOL;
END_VAR;
VAR
canData : ARRAY[0..7] OF BYTE;
END_VAR;
// Library sets the signal based on CAN data
IF canData[3].5 = TRUE THEN
SIGNAL_HELLJUS := TRUE;
ELSE
SIGNAL_HELLJUS := FALSE;
END_IF;
// --- In the main recipe ---
VAR_SIGNAL
SIGNAL_HELLJUS : BOOL;
END_VAR;
VAR_OUTPUT
HIGHBEAM ["Helljus"] : OUTPUT;
END_VAR;
// Recipe uses the signal to control an output
IF SIGNAL_HELLJUS THEN
HIGHBEAM(VALUE := 1000, PERIOD := 1000); // Full brightness
ELSE
HIGHBEAM(VALUE := 0, PERIOD := 1000); // Off
END_IF;
VAR_SIGNAL sections
are shown in the platform UI. Use them to document what each signal represents - it
helps users understand what the library provides.
VAR_OUTPUT, only the first
VAR_SIGNAL section in a file is displayed on the platform.
VAR_CONSTANT - Constants
VAR_CONSTANT declares values that are evaluated at compile time
and substituted directly into the code. They do not use any RAM at runtime - the
compiler replaces every reference to the constant with its actual value. This is similar
to #define in C.
VAR_CONSTANT
MAX_MODULES : BYTE := 4;
TIMEOUT_MS : TIME := T#500ms;
CAN_ID_ENGINE : DINT := 0x7E0;
BAUD_RATE : INT := 500;
VERSION : BYTE := 3;
END_VAR;
Compile-Time Evaluation
Constants can be used in expressions, and the compiler evaluates them at compile time:
VAR_CONSTANT
BASE_TIMEOUT : INT := 100;
MULTIPLIER : INT := 5;
TOTAL_TIMEOUT : INT := BASE_TIMEOUT * MULTIPLIER; // = 500 at compile time
END_VAR;
VAR
remaining : INT;
END_VAR;
// The compiler replaces TOTAL_TIMEOUT with 500 in the generated code.
// No runtime calculation happens - it's as if you wrote "500" directly.
remaining := TOTAL_TIMEOUT + 10; // Compiler sees: remaining := 510;
This means constants are ideal for:
- CAN IDs -
CAN_ID_ENGINE : DINT := 0x7E0; - Array sizes -
BUFFER_SIZE : BYTE := 8;thenARRAY[0..BUFFER_SIZE - 1] OF BYTE - Thresholds -
MAX_SPEED : INT := 250; - Hardware config -
MODULE_MSG_SHIFT : BYTE := 1;(BMW)
Use constants instead of magic numbers in your code. Instead of writing
IF speed > 250 THEN, declare MAX_SPEED : INT := 250 and
write IF speed > MAX_SPEED THEN. This makes the code self-documenting
and easy to adjust - change the value in one place and it updates everywhere.
Using Constants for Array Sizes
A powerful use of constants is defining array sizes and loop bounds:
VAR_CONSTANT
TOTALMSG : BYTE := 3; // Number of DID requests
MODULE_0_DATA_LEN : INT := 19; // Response size for module 0
END_VAR;
VAR
MODULE_0_DATA : ARRAY[0..MODULE_0_DATA_LEN - 1] OF BYTE; // 19-byte array
PAYLOAD : ARRAY[0..TOTALMSG - 1, 0..7] OF BYTE := [
0x03, 0x22, 0xD5, 0x42, 0x00, 0x00, 0x00, 0x00,
0x03, 0x22, 0x42, 0x1B, 0x00, 0x00, 0x00, 0x00,
0x03, 0x22, 0xF1, 0x90, 0x00, 0x00, 0x00, 0x00
];
END_VAR;
// TOTALMSG is replaced with 3 at compile time
FOR i : BYTE := 0 TO TOTALMSG - 1 DO
MODULE_0_DATA[i] := PAYLOAD[i, 0]; // Copy first byte of each request
END_FOR;
VAR_CONSTANT GLOBAL - Global Constants
Adding the GLOBAL keyword makes the constants accessible from all files
in the recipe (libraries + main recipe), not just the current file. Without GLOBAL,
constants are local and will be scrambled during library compilation
(see Library Scrambling).
VAR_CONSTANT GLOBAL
PROTOCOL_VERSION : BYTE := 0x02;
MAX_RETRY : BYTE := 3;
DIAG_CAN_ID : DINT := 0x7DF; // Broadcast UDS address
END_VAR;
Use VAR_CONSTANT GLOBAL when other files need the value (e.g., a CAN ID
that both the library and recipe reference). Use plain VAR_CONSTANT for
values that only matter within the current file.
Declaration Syntax - Full Reference
This section covers every form of variable declaration supported by TSharkRex.
Simple Declaration
name : TYPE;
Declares a variable with the default initial value (typically 0 or FALSE).
VAR
counter : INT;
flag : BOOL;
data : BYTE;
END_VAR;
Declaration with Initial Value
name : TYPE := value;
Assigns an initial value that is set once when the program starts.
VAR
counter : DINT := 0;
maxRetries : BYTE := 5;
scaleFactor : REAL; // REAL cannot be initialized in VAR
isEnabled : BOOL := TRUE;
targetId : DINT := 0x7E0;
END_VAR;
scaleFactor := 0.001; // Assign REAL values in the code body
Array Declaration
name : ARRAY[start..end] OF TYPE;
Declares a one-dimensional array. Indices are inclusive.
VAR
buffer : ARRAY[0..7] OF BYTE;
readings : ARRAY[0..99] OF INT;
flags : ARRAY[0..15] OF BOOL;
END_VAR;
Array with Initial Values
name : ARRAY[0..n] OF TYPE := [val0, val1, ...valn];
VAR
header : ARRAY[0..3] OF BYTE := [0x03, 0x22, 0x45, 0x55];
pattern : ARRAY[0..7] OF BYTE := [0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00];
END_VAR;
Two-Dimensional Array
name : ARRAY[0..rows, 0..cols] OF TYPE := [...];
VAR
canMessages : ARRAY[0..3, 0..7] OF BYTE := [
0x03, 0x22, 0xF1, 0x90, 0x00, 0x00, 0x00, 0x00,
0x03, 0x22, 0xF1, 0x91, 0x00, 0x00, 0x00, 0x00,
0x03, 0x22, 0xF1, 0x92, 0x00, 0x00, 0x00, 0x00,
0x03, 0x22, 0xF1, 0x93, 0x00, 0x00, 0x00, 0x00
];
END_VAR;
Pointer Declaration
name : POINTER TO TYPE;
VAR
buffer : ARRAY[0..7] OF BYTE;
value : BYTE;
pData : POINTER TO BYTE;
pMessage : POINTER TO DINT;
END_VAR;
pData := @buffer[0]; // Point to first element
value := pData^; // Dereference
Declaration with Description (App UI)
You can attach a description string to a variable using square brackets. This description
is displayed in the XBB app. For special UI types like SETTING_TOGGLE, you
can include HTML-like tags to control the label:
name ["description"] : TYPE;
VAR_OUTPUT
DRL ["Daytime Running Lights"] : OUTPUT;
HELLJUS ["<toggle-title>Helljus</toggle-title>"] : SETTING_TOGGLE;
BRIGHTNESS ["<toggle-title>Ljusstyrka</toggle-title>"] : SETTING_SLIDER;
END_VAR;
Function Block Instance
name : FUNCTION_BLOCK_TYPE;
VAR
debounce : TON; // On-delay timer
blinkGen : TON; // Blink generator (use TON for blink patterns)
canReceiver : CAN_RX; // CAN receive block
END_VAR;
Declaration Rules Summary
- Every declaration ends with a semicolon (
;). - Every VAR section ends with
END_VAR;. - Variable names are case-sensitive.
- Multiple
VARsections are allowed; they can appear anywhere in the file. - Only the first
VAR_OUTPUTandVAR_SIGNALsections are shown on the platform. VAR_INPUTis only valid insideFUNCTIONdefinitions, notFUNCTION_BLOCK.VAR_SIGNALvariables are shared across all files (libraries + recipe).- Initial values are assigned once at startup; variables retain their values between scan cycles.
Complete Example
This example shows a realistic recipe that uses most VAR section types together:
// Constants for this recipe
VAR_CONSTANT
CAN_TIMEOUT : TIME := T#2s;
MAX_BRIGHTNESS : INT := 1000;
END_VAR;
// Global signals shared with the CAN library
VAR_SIGNAL
SIGNAL_HELLJUS : BOOL; // High beam from CAN
SIGNAL_HALVLJUS : BOOL; // Low beam from CAN
SIGNAL_TANDNING : BOOL; // Ignition from CAN
SIGNAL_BLINKER_V : BOOL; // Left turn signal
SIGNAL_BLINKER_H : BOOL; // Right turn signal
HW : HARDWARE;
END_VAR;
// Outputs visible in the XBB app
VAR_OUTPUT
HELLJUS ["Helljus"] : OUTPUT;
HALVLJUS ["Halvljus"] : OUTPUT;
DRL ["Daytime Running Lights"] : OUTPUT;
BLINKER_V ["Blinkers vanster"] : OUTPUT;
BLINKER_H ["Blinkers hoger"] : OUTPUT;
END_VAR;
// Local working variables
VAR
canTimer : TON;
canAlive : BOOL := TRUE;
blinkTimer : TON;
blinkState : BOOL;
END_VAR;
// Monitor CAN timeout
canTimer(IN := NOT SIGNAL_TANDNING, PT := CAN_TIMEOUT);
canAlive := NOT canTimer.Q;
// Blink generator for turn signals (500ms period)
blinkTimer(IN := NOT blinkTimer.Q, PT := T#500ms);
blinkState := blinkTimer.Q;
// Control outputs based on signals
IF canAlive AND SIGNAL_TANDNING THEN
HELLJUS(VALUE := BOOL_TO_INT(SIGNAL_HELLJUS) * MAX_BRIGHTNESS, PERIOD := 1000);
HALVLJUS(VALUE := BOOL_TO_INT(SIGNAL_HALVLJUS) * MAX_BRIGHTNESS, PERIOD := 1000);
DRL(VALUE := BOOL_TO_INT(NOT SIGNAL_HELLJUS) * 500, PERIOD := 1000); // DRL at 50% when high beam is off
BLINKER_V(VALUE := BOOL_TO_INT(SIGNAL_BLINKER_V AND blinkState) * MAX_BRIGHTNESS, PERIOD := 1000);
BLINKER_H(VALUE := BOOL_TO_INT(SIGNAL_BLINKER_H AND blinkState) * MAX_BRIGHTNESS, PERIOD := 1000);
ELSE
HELLJUS(VALUE := 0, PERIOD := 1000);
HALVLJUS(VALUE := 0, PERIOD := 1000);
DRL(VALUE := 0, PERIOD := 1000);
BLINKER_V(VALUE := 0, PERIOD := 1000);
BLINKER_H(VALUE := 0, PERIOD := 1000);
END_IF;