Tracking BLE beacons during trips

Extended tracking of BLE beacons that were located by the tracker during the trip

The message counter with aggregate_by_field method is able to automatically process array parameters, making it perfect for tracking BLE beacons during trips. It works in three ways:

  • For arrays of strings (like can.dtc: ["P0068", "P0507"]), it creates a separate aggregation group for each string
  • For arrays of objects (like ble.beacons: [{"id":"7CD9F413834E", ...}]), it automatically uses the "id" field from each object for grouping
  • For all other values it converts them to a string representation and uses result for grouping

To illustrate this ability in practice, let's create a calculator that tracks all BLE beacons encountered during a trip, recording the maximum speed of the tracker in the vicinity of each beacon, their first and last positions.

In flespi each BLE beacon in the vicinity of device is reported into standard message parameter "ble.beacons" and typically looks the following way:

"ble.beacons": [
{"data":"B708EA","id":"7CD9F413834E","rssi":-47},
{"data":"B708A8","id":"7CD9F4101E7A","rssi":-76},
]

As you can see it represents an array of beacons nearby the device with a unique BLE beacon ID reported in the "id" sub-parameter.

Now let's create the calculator with the following configuration:

{
"name": "BLE beacon trip tracker",
"selectors": [
{
"type": "expression",
"expression": "position.speed>0",
"method": "boolean",
"max_inactive": 300,
"max_messages_time_diff": 600
}
],
"counters": [
    {
"type": "message",
"name": "beacons",
"method": "aggregate_by_field",
"fields": [
"ble.beacons",
{
"name": "first_seen",
"expression": "timestamp",
"aggregate": "first"
},
{
"name": "first_lat",
"expression": "position.latitude",
"aggregate": "first"
},
{
"name": "first_lon",
"expression": "position.longitude",
"aggregate": "first"
},
{
"name": "last_seen",
"expression": "timestamp",
"aggregate": "last"
},
{
"name": "last_lat",
"expression": "position.latitude",
"aggregate": "last"
},
{
"name": "last_lon",
"expression": "position.longitude",
"aggregate": "last"
},
{
"name": "max_speed",
"expression": "position.speed",
"aggregate": "maximum"
},
{
"name": "distance",
"expression": "if(variable(\"last_timestamp\") == previous(\"timestamp\"), mileage(), 0)",
"aggregate": "summary"
}
],
"validate_message": "json_array_count(ble.beacons) > 0"
},
    {
      "type": "variable",
      "name": "last_timestamp",
      "expression": "timestamp"
    },
{
"type": "expression",
"name": "trip_distance",
"expression": "mileage()",
"method": "summary"
}
]
}

The selector detects trips when the vehicle is moving (speed > 0), with a 5-minute tolerance for stops (300 seconds) and handling message gaps up to 10 minutes (600 seconds).

The message counter with method: "aggregate_by_field":

  • Automatically processes the ble.beacons array, creating a group for each beacon by its ID
  • Records when and where each beacon was first and last detected
  • Tracks maximum speed while each beacon was in range
  • Calculates distance traveled with each beacon
  • Only processes messages that contain BLE beacons data

The variable counter is a particularly interesting part of this solution:

{
"expression": "timestamp",
"name": "last_timestamp",
"type": "variable"
}

This counter:

  1. Stores the current message timestamp in a variable named "last_timestamp" and updates it with each next message processed withing interval
  2. Doesn't add any field to the interval JSON (since it's type "variable")
  3. Makes this value available to other counters via the variable("last_timestamp") function

This variable is effectively used in the traveled distance calculation expression for the each beacon:

if(variable("last_timestamp") == previous("timestamp"), mileage(), 0)

This expression:

  • Compares the previous message timestamp for this beacon with the current value of variable (overall previous message timestamp)
  • Only adds mileage when processing consecutive messages (no gaps) with this beacon in vicinity
  • Ensures accurate distance calculation for each beacon by preventing distance accumulation during periods when the beacon wasn't detected

In this setup it is important to place "last_timestamp" variable counter in the end of counters array so that its value with previous message timestamp is accessed inside beacons counter expressions. Alternatively it is possible to place it as first counter and initialize with different expression: previous("timestamp")

Each trip interval produced by calculator will contain a beacons array with entries like:

{
"begin": 1740824711,
"end": 1740825981,
"duration": 1270,
"id": 875,
"trip_distance": 6.82635,
"beacons": [
{
"ble.beacons": "7CD9F413834E",
"first_seen": 1740824817,
"first_lat": 54.739207,
"first_lon": 25.275613,
"last_seen": 1740824907,
"last_lat": 54.736013,
"last_lon": 25.278055,
"max_speed": 52,
"distance": 0.42
},
{
"ble.beacons": "7CD9F4101E7A",
"first_seen": 1740825005,
"first_lat": 54.727083,
"first_lon": 25.280103,
"last_seen": 1740825036,
"last_lat": 54.724077,
"last_lon": 25.281297,
"max_speed": 38,
"distance": 0.31
}
]
}

See also
Article explains how to configure a calculator to detect trips between geofences.
How to configure a calculator to detect when a device enters or exits any geofence.