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
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;
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) |
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)
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;
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;
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;
HW : HARDWARE is declared in
VAR_SIGNAL and that didReadRPM and didReadTemp
are user-defined functions. It cannot compile standalone.
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;