🔍
v1.3.8

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
Note: All VAR sections end with 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);
Important: 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.

Platform display rule: Only the first 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;
Tip: Comments placed after variables in 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.
Display rule: Like 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:

Tip

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;
Note

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

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;