🔍
v1.3.8

Hardware Interface

The HARDWARE function block provides direct access to the physical hardware on the XBB dongle or PP-CAN unit. Through this single interface, your recipe can read the gyroscope, monitor supply voltage, detect button presses, check Bluetooth connectivity, and identify which hardware platform it is running on. The hardware block is the bridge between your recipe logic and the real world.

HARDWARE Function Block

Declare a single HARDWARE instance in VAR_SIGNAL and call it every scan cycle. The call updates all hardware readings to their current values:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

HW();  // Call each cycle to update all hardware values
Warning: You must call HW() every scan cycle for the values to stay current. If you skip the call, all parameters will retain their previous values and button events may be lost entirely.

Parameters

After calling HW(), the following parameters are available for reading:

Parameter Type Description
GYRO_X DINT Gyroscope X axis reading in milliG (mG)
GYRO_Y DINT Gyroscope Y axis reading in milliG (mG)
GYRO_Z DINT Gyroscope Z axis reading in milliG (mG) - may be 0 on some units
ANALOG_IN0 DINT Analog input voltage in millivolts (mV) - PP-CAN ignition pin
BUTTON DINT Smart Button state (see button constants)
HARDWARE_TYPE BYTE Hardware type identifier (see hardware types)
HOST_CONNECTED BYTE TRUE when a phone/app is connected via Bluetooth
SUPPLY_VOLTAGE DINT Battery/supply voltage in millivolts (mV)

Hardware Type Constants

The HARDWARE_TYPE parameter identifies which XBB hardware the recipe is running on. Use these constants to write hardware-adaptive recipes:

Constant Value Hardware
XBB_DONGLE 0 Original Dongle (v1)
XBB_DONGLE_2 1 Dongle v2 (with analog input)
XBB_PPCAN 2 PP-CAN (v1)
XBB_PPCAN_2 3 PP-CAN v2

Writing Hardware-Specific Code

Different hardware platforms have different capabilities. The original Dongle lacks an analog input, so ignition detection must use CAN signals. Dongle v2 and PP-CAN units have a physical analog input on pin 1:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;
VAR
    ignitionOn : BOOL;
    SIGNAL_TANDNING : BOOL;
END_VAR;

HW();

IF HW.HARDWARE_TYPE = XBB_DONGLE_2 OR HW.HARDWARE_TYPE = XBB_PPCAN THEN
    // Hardware has analog input - use voltage threshold for ignition
    ignitionOn := HW.ANALOG_IN0 > 6000;   // > 6V = ignition on
ELSIF HW.HARDWARE_TYPE = XBB_DONGLE THEN
    // Original dongle - fall back to CAN-based ignition detection
    ignitionOn := SIGNAL_TANDNING;
END_IF;
Tip: When writing recipes that should run on multiple hardware platforms, always check HARDWARE_TYPE and provide fallback logic. This makes your recipe portable across the entire XBB product line.

Button Constants

The Smart Button on the XBB dongle generates three distinct press types. The HW.BUTTON parameter reports the detected press type:

Constant Value Description
BUTTON_NOCLICK 0 No press detected (idle state)
BUTTON_CLICK 1 Single short press
BUTTON_DOUBLECLICK 2 Double press (two quick presses)
BUTTON_LONGCLICK 3 Long press (press and hold)
Warning: HW.BUTTON is edge-triggered - the button value is only present for one single program cycle, then it returns to BUTTON_NOCLICK (0). You do not need R_TRIG to detect button presses. Simply check the value each cycle with a direct comparison:
VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

VAR
    featureEnabled : BOOL := FALSE;
END_VAR;

HW();
// Correct: direct comparison is sufficient
IF HW.BUTTON = BUTTON_CLICK THEN
    // This runs exactly once per button press
    featureEnabled := NOT featureEnabled;
END_IF;

// WRONG: do NOT use R_TRIG with HW.BUTTON
// The value is already edge-triggered!

Complete Button Example

Use all three button events for different actions:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

VAR_OUTPUT
    LED_OUT ["LED"] : OUTPUT;
END_VAR;
VAR
    currentMode : INT := 0;     // 0=off, 1=low, 2=high
    resetFlag : BOOL := FALSE;
END_VAR;

HW();

// Single click: cycle through modes
IF HW.BUTTON = BUTTON_CLICK THEN
    currentMode := currentMode + 1;
    IF currentMode > 2 THEN
        currentMode := 0;
    END_IF;
END_IF;

// Double click: jump to high mode
IF HW.BUTTON = BUTTON_DOUBLECLICK THEN
    currentMode := 2;
END_IF;

// Long press: reset to off
IF HW.BUTTON = BUTTON_LONGCLICK THEN
    currentMode := 0;
    resetFlag := TRUE;
END_IF;

// Apply mode to output
CASE currentMode OF
    0: LED_OUT(VALUE := 0, PERIOD := 1000);
    1: LED_OUT(VALUE := 300, PERIOD := 1000);    // 30%
    2: LED_OUT(VALUE := 1000, PERIOD := 1000);   // 100%
END_CASE;

Supply Voltage

HW.SUPPLY_VOLTAGE returns the current battery or supply voltage in millivolts (mV). A reading of 12400 means 12.4V, and 14300 means 14.3V. This is useful for low-battery detection and voltage monitoring:

Low Battery Detection

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;
VAR
    lowBattery : BOOL;
    criticalBattery : BOOL;
END_VAR;

VAR_OUTPUT
    BATT_STATUS ["Batteri"] : INFO_ONOFF;
    BATT_VOLTAGE ["Spänning (mV)"] : INFO_VALUE;
END_VAR;

HW();

// Voltage thresholds
lowBattery := HW.SUPPLY_VOLTAGE < 11500;       // Below 11.5V
criticalBattery := HW.SUPPLY_VOLTAGE < 10500;   // Below 10.5V

// Display in app
BATT_STATUS(VALUE := (NOT lowBattery) * 1000, PERIOD := 1000);
BATT_VOLTAGE(VALUE := TRUNC(HW.SUPPLY_VOLTAGE, INT), PERIOD := 1000);

Voltage Display with SETTING_VALUE

Since INFO_VALUE is limited to 0–255, use SETTING_VALUE to display the full millivolt reading (see Settings & App UI):

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;
VAR_SIGNAL
    DISPLAY_VOLTAGE ["Spänning (mV)"] : SETTING_VALUE;
END_VAR;

HW();
DISPLAY_VOLTAGE(MIN := 0, MAX := 30000, STORE := FALSE);
DISPLAY_VOLTAGE.VALUE := HW.SUPPLY_VOLTAGE;
// Shows e.g. 13800 in the app (= 13.8V)
Note: Vehicle battery voltage typically ranges from 11.5V (engine off, partially discharged) to 14.5V (engine running, alternator charging). Readings below 10V indicate a nearly dead battery or power supply issues.

Gyroscope

The built-in gyroscope measures acceleration on three axes in milliG (mG). The primary use case is movement detection - for example, to wake up the dongle when the vehicle starts moving, or to detect impacts:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;
VAR
    movement : BOOL;
    gyroMagnitude : DINT;
END_VAR;

HW();

// Calculate total movement magnitude (simplified)
gyroMagnitude := ABS(HW.GYRO_X) + ABS(HW.GYRO_Y);

// Detect significant movement (threshold in mG)
movement := gyroMagnitude > 500;

Z-Axis Workaround

Some hardware units report incorrect values on the Z axis. If you experience erratic GYRO_Z readings, apply the standard workaround by zeroing the value immediately after the HW() call:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

VAR
    vehicleMoving : BOOL;
END_VAR;
HW();
HW.GYRO_Z := 0;  // Workaround: some units report faulty Z values

// Now use GYRO_X and GYRO_Y only for movement detection
IF ABS(HW.GYRO_X) > 300 OR ABS(HW.GYRO_Y) > 300 THEN
    vehicleMoving := TRUE;
END_IF;
Warning: The Z-axis issue affects some production units. If your recipe uses gyroscope data, always test on the target hardware. When in doubt, ignore GYRO_Z and rely on X and Y axes only.

Analog Input

HW.ANALOG_IN0 reads the voltage on analog input pin 1 in millivolts (mV). This input is available on Dongle v2 and PP-CAN units. The original Dongle (v1) does not have an analog input.

The most common use case is detecting an external ignition signal via a wire connected to the vehicle’s ignition circuit:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;
VAR
    ignitionVoltage : DINT;
    ignitionOn : BOOL;
END_VAR;

HW();

ignitionVoltage := HW.ANALOG_IN0;

// Typical threshold: > 6000 mV (6V) means ignition is on
ignitionOn := ignitionVoltage > 6000;

Analog Input with Hysteresis

To prevent flickering at the threshold, use hysteresis (different on/off thresholds):

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;
VAR
    ignitionOn : BOOL := FALSE;
END_VAR;

HW();

IF NOT ignitionOn AND HW.ANALOG_IN0 > 7000 THEN
    ignitionOn := TRUE;     // Turn on at 7V
ELSIF ignitionOn AND HW.ANALOG_IN0 < 5000 THEN
    ignitionOn := FALSE;    // Turn off at 5V (2V hysteresis)
END_IF;
Note: On the PP-CAN, ANALOG_IN0 is connected to the ignition detection pin. The voltage reflects the vehicle’s ignition circuit state. On Dongle v2, it is a general-purpose analog input on pin 1.

Bluetooth Connection

HW.HOST_CONNECTED is TRUE when the XBB mobile app is connected to the dongle via Bluetooth. This is useful for enabling extra data output only when someone is actually looking at the app:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

VAR
    enableDiagnostics : BOOL;
END_VAR;

HW();
IF HW.HOST_CONNECTED THEN
    // App is connected - enable extra diagnostic outputs
    enableDiagnostics := TRUE;
ELSE
    // No app connected - save power and CAN bandwidth
    enableDiagnostics := FALSE;
END_IF;

Optimizing CAN Reads with HOST_CONNECTED

Combine HOST_CONNECTED with OUTPUT.USED to only read diagnostic DIDs when someone can actually see the values. This reduces CAN bus load when the dongle operates standalone:

VAR_OUTPUT
    ENGINE_RPM ["Varvtal"] : INFO_VALUE;
    COOLANT_TEMP ["Kylvätska"] : INFO_VALUE;
END_VAR;

// Only request diagnostic data when needed
IF ENGINE_RPM.USED OR HW.HOST_CONNECTED THEN
    // Read RPM from vehicle CAN
    didReadRPM(enable := TRUE);
END_IF;

IF COOLANT_TEMP.USED OR HW.HOST_CONNECTED THEN
    // Read coolant temperature from vehicle CAN
    didReadTemp(enable := TRUE);
END_IF;
Note: This example assumes HW : HARDWARE is declared in VAR_SIGNAL and that didReadRPM and didReadTemp are user-defined functions. It cannot compile standalone.
Tip: This optimization is especially important on vehicles with busy CAN buses. By gating diagnostic reads behind HOST_CONNECTED and USED, you minimize the recipe’s CAN footprint when running in the background.

Complete Hardware Example

Here is a complete recipe fragment that uses all major hardware features:

VAR_SIGNAL
    HW : HARDWARE;
END_VAR;

VAR_OUTPUT
    SYSTEM_STATUS ["System"] : INFO_ONOFF;
    BATT_VOLTAGE ["Batteri"] : INFO_VALUE;
    MOVEMENT ["Rörelse"] : INFO_ONOFF;
    IGNITION ["Tändning"] : INFO_ONOFF;
END_VAR;

VAR
    ignitionOn : BOOL;
    vehicleMoving : BOOL;
    currentMode : INT := 0;
    SIGNAL_TANDNING : BOOL;
END_VAR;

// Update hardware readings
HW();
HW.GYRO_Z := 0;   // Z-axis workaround

// System always active
SYSTEM_STATUS(VALUE := 1000, PERIOD := 1000);

// Battery voltage display
BATT_VOLTAGE(VALUE := HW.SUPPLY_VOLTAGE, PERIOD := 1000);

// Movement detection (X + Y axes)
vehicleMoving := (ABS(HW.GYRO_X) + ABS(HW.GYRO_Y)) > 500;
MOVEMENT(VALUE := vehicleMoving * 1000, PERIOD := 1000);

// Ignition detection (hardware-adaptive)
IF HW.HARDWARE_TYPE = XBB_DONGLE_2 OR HW.HARDWARE_TYPE = XBB_PPCAN THEN
    ignitionOn := HW.ANALOG_IN0 > 6000;
ELSE
    ignitionOn := SIGNAL_TANDNING;   // CAN-based fallback
END_IF;
IGNITION(VALUE := ignitionOn * 1000, PERIOD := 1000);

// Smart Button: cycle modes
IF HW.BUTTON = BUTTON_CLICK THEN
    currentMode := currentMode + 1;
    IF currentMode > 2 THEN
        currentMode := 0;
    END_IF;
END_IF;