🔍
v1.3.8

Getting Started

This chapter walks you through the anatomy of a TSharkRex program, introduces comments and reserved keywords, and explains how the execution model works. By the end, you will understand enough to read and modify simple recipes.

Program Anatomy

Every TSharkRex program (recipe) is composed of two parts:

  1. Variable declarations - one or more VAR blocks that define all data the program uses.
  2. Executable code - the logic that runs on every scan cycle.

Here is a complete minimal recipe:

VAR
    A : BOOL;
    B : BOOL;
END_VAR;

VAR_OUTPUT
    MY_OUTPUT : OUTPUT;
END_VAR;

// Output always called every cycle - use (A OR B) directly as value
MY_OUTPUT(VALUE := (A OR B) * 1000, PERIOD := 1000);

Let’s break this down section by section.

The VAR Block

VAR
    A : BOOL;
    B : BOOL;
END_VAR;

The VAR ... END_VAR block declares local variables. These are private to your program and retain their values between scan cycles (they are not re-initialized on each cycle).

Each variable declaration follows the pattern:

name : TYPE;
name : TYPE := initial_value;

If no initial value is given, the variable is initialized to zero (for numeric types) or FALSE (for BOOL).

The VAR_OUTPUT Block

VAR_OUTPUT
    MY_OUTPUT : OUTPUT;
END_VAR;

The VAR_OUTPUT block declares output channels. Each output corresponds to a physical output on the dongle or PowerUnit. The OUTPUT type is a special built-in type that provides methods for controlling brightness, flash patterns, and timing.

Outputs declared here appear in the XBB mobile app, where the user can see their status in real time.

The Executable Code

VAR
    A : BOOL;
    B : BOOL;
END_VAR;

VAR_OUTPUT
    MY_OUTPUT : OUTPUT;
END_VAR;

// Boolean expression directly as value: TRUE (1) * 1000 = on, FALSE (0) * 1000 = off
MY_OUTPUT(VALUE := (A OR B) * 1000, PERIOD := 1000);

Everything after the variable declarations is executable code that runs on every scan cycle. In this example:

The VALUE parameter uses a scale of 0–1000, where 1000 = 100.0% and 500 = 50.0%. This gives you 0.1% resolution for PWM dimming. The PERIOD parameter is in milliseconds.

Variable Declaration Blocks

TSharkRex provides several types of declaration blocks, each serving a different purpose:

Block Purpose
VAR Local variables (general-purpose storage)
VAR_SIGNAL CAN bus signal inputs (mapped to vehicle data)
VAR_OUTPUT Physical output channels (lights, relays)
VAR_INPUT Input parameters for function blocks
VAR_CONSTANT Compile-time constants

A more realistic recipe uses multiple block types:

VAR_SIGNAL
    SIGNAL_HELLJUS : BOOL;       // High-beam from vehicle CAN
    SIGNAL_HASTIGHET : INT;      // Speed in km/h
END_VAR;

VAR_OUTPUT
    EXTRALJUS : OUTPUT;          // Auxiliary lights
    VARNINGSLJUS : OUTPUT;       // Warning indicator
END_VAR;

VAR
    aktiv : BOOL := FALSE;       // Local state variable
END_VAR;

VAR_CONSTANT
    MAX_HASTIGHET : INT := 120;  // Speed threshold
END_VAR;

// Determine if aux lights should be active
aktiv := SIGNAL_HELLJUS AND (SIGNAL_HASTIGHET < MAX_HASTIGHET);

// Outputs always called every cycle
EXTRALJUS(VALUE := aktiv * 1000, PERIOD := 1000);
VARNINGSLJUS(VALUE := (SIGNAL_HASTIGHET > MAX_HASTIGHET) * 1000, PERIOD := 1000);

Comments

TSharkRex supports two styles of comments:

Line Comments

Use // for single-line comments. Everything after // on that line is ignored by the compiler:

// This entire line is a comment

VAR
    hastighet : INT;  // This comment explains the variable
END_VAR;

Block Comments

Use (* ... *) for multi-line block comments:

(*
   This is a block comment.
   It can span multiple lines.
   Useful for longer explanations or temporarily
   disabling sections of code.
*)

VAR
    (* You can also use block comments inline *)
    temp : INT;
END_VAR;

Comments in VAR_SIGNAL

Comments placed on signal declarations have a special behavior: they are displayed in the TSharkRex Platform UI (xbb-code.com) as the signal’s description. This makes your code easier to understand when browsing libraries:

VAR_SIGNAL
    SIGNAL_HELLJUS : BOOL;        // Helljus (aktiv/inaktiv)
    SIGNAL_BLINKERS_V : BOOL;     // Vänster blinkers
    SIGNAL_BLINKERS_H : BOOL;     // Höger blinkers
    SIGNAL_HASTIGHET : INT;       // Fordonets hastighet (km/h)
END_VAR;
Always add descriptive comments to your VAR_SIGNAL declarations. End users see these comments in the app when viewing signal values, and they make your recipe much easier to understand.

Case Sensitivity

TSharkRex is case-sensitive, consistent with the IEC 61131-3 standard. This applies to:

// CORRECT:
IF aktiv THEN
    EXTRALJUS(VALUE := 1000);
END_IF;

// WRONG - will NOT compile:
if aktiv then          // 'if' and 'then' must be uppercase
    EXTRALJUS(value := 1000);  // 'value' must be uppercase (VALUE)
end_if;                // 'end_if' must be uppercase
Using the wrong case for a keyword is one of the most common errors for new TSharkRex programmers. If the compiler reports an unexpected token, check your casing first.

Reserved Keywords

The following words are reserved by the TSharkRex language and cannot be used as variable names, function names, or other identifiers:

Control Flow

IF THEN ELSE ELSIF END_IF
CASE OF END_CASE
WHILE DO END_WHILE
FOR TO END_FOR
EXIT RETURN

Declarations

VAR VAR_SIGNAL VAR_INPUT VAR_OUTPUT
VAR_CONSTANT CONSTANT GLOBAL END_VAR

Functions, Function Blocks, and Types

FUNCTION END_FUNCTION FUNCTION_BLOCK END_FUNCTION_BLOCK
TYPE END_TYPE STRUCT END_STRUCT

Operators and Literals

TRUE FALSE NOT AND OR
MOD BAND BOR BNOT XOR
SHL SHR

Types and Data

ARRAY POINTER NULL nullptr

CAN Mode Constants

CAN_MODE_CONFIG CAN_MODE_NORMAL CAN_MODE_SLEEP
CAN_MODE_DEEP_SLEEP CAN_MODE_SILENT
Attempting to use a reserved keyword as an identifier will cause a compilation error. If you need a variable name that resembles a keyword, add a prefix or suffix (e.g., is_array instead of ARRAY, or exit_flag instead of EXIT).

Program Execution Model

Understanding the execution model is fundamental to writing correct TSharkRex programs. Here are the key rules:

The Scan Cycle

Your code runs in a continuous loop. On each iteration (called a scan):

  1. Inputs are read - all VAR_SIGNAL values are updated with the latest data from the CAN bus.
  2. Your code executes - from the first line after the declarations to the last line, top to bottom.
  3. Outputs are written - all VAR_OUTPUT values are sent to the physical output hardware.
  4. Repeat - the cycle starts over immediately.

Initialization

Variable declarations (VAR blocks) are processed once when the program starts. Initial values are set at this point. After initialization, the declarations are not re-executed - only the executable code section runs on each scan.

VAR
    raknare : INT := 0;   // Set to 0 once at startup
END_VAR;

// This runs every scan cycle
raknare := raknare + 1;  // Increments on every cycle

State Persistence

Local variables in VAR blocks retain their values between scans. This is different from many programming languages where local variables are re-created each time a function is called. In TSharkRex, think of VAR variables as persistent state:

VAR
    har_sett_helljus : BOOL := FALSE;  // Remembers across scans
END_VAR;

VAR_SIGNAL
    SIGNAL_HELLJUS : BOOL;
END_VAR;

// Once high-beam has been seen, this stays TRUE forever
// (until the device is reset)
IF SIGNAL_HELLJUS THEN
    har_sett_helljus := TRUE;
END_IF;

No Main Function

There is no main(), no entry point function, and no explicit loop. You write the body of the scan cycle directly. The runtime provides the loop structure for you.

A Complete Example

Putting it all together, here is a recipe that reads vehicle high-beam and speed signals, then controls auxiliary lights with speed-dependent behavior:

(*
   Recipe: Extraljus med hastighetsspärr
   Controls auxiliary lights based on high-beam,
   with automatic disable above 160 km/h.
*)

VAR_SIGNAL
    SIGNAL_HELLJUS : BOOL;       // High beam active
    SIGNAL_HASTIGHET : INT;      // Speed in km/h
END_VAR;

VAR
    extraljus_varde : INT;       // Intermediate: calculated output value
END_VAR;

VAR_OUTPUT
    EXTRALJUS : OUTPUT;          // Auxiliary light output
END_VAR;

VAR_CONSTANT
    HASTIGHETSGRANS : INT := 160;  // Max speed for aux lights
END_VAR;

// Calculate the output value based on conditions
IF SIGNAL_HELLJUS AND (SIGNAL_HASTIGHET < HASTIGHETSGRANS) THEN
    extraljus_varde := 1000;     // Full brightness
ELSE
    extraljus_varde := 0;        // Off (no high beam, or speed too high)
END_IF;

// Output is ALWAYS called every scan cycle - never inside IF!
EXTRALJUS(VALUE := extraljus_varde, PERIOD := 1000);
This pattern - read signals, apply logic, drive outputs - is the foundation of virtually every TSharkRex recipe. The complexity comes from the signal sources (CAN frames, UDS responses) and the control logic, not from the program structure itself.

A Note on Semicolons

TSharkRex uses semicolons (;) as statement terminators. Every variable declaration and every executable statement ends with a semicolon. The END_VAR keyword also requires a trailing semicolon:

VAR
    x : INT;        // Semicolon after declaration
END_VAR;            // Semicolon after END_VAR

x := x + 1;        // Semicolon after assignment
IF x > 10 THEN     // No semicolon after THEN
    x := 0;        // Semicolon after assignment
END_IF;             // Semicolon after END_IF
Keywords that start a block (IF ... THEN, WHILE ... DO, FOR ... TO ... DO) do not take a semicolon. Keywords that end a block (END_IF, END_WHILE, END_FOR, END_VAR) do.