Calculate and display LoRaWAN packet error rate (PER)

Overview

LoRaWAN frames are sent with a frame counter. If frames are lost, e.g. due to collisions or poor radio channel quality, this can be detected from missing frame numbers. The packet error rate (PER) indicates what percentage of expected packets did not arrive within a given window. In LoRaWAN network monitoring, PER is an established metric for assessing uplink quality and is often used together with RSSI and SNR to monitor link quality, detect interference or collisions, and support network planning.

This best practice describes how to calculate PER from the _raw data point in a device template and then display it as a gauge with thresholds (e.g. light green → orange → red). Two data points are used: one collects frame counters and computes PER for the last 5, 10, and 25 packets; a second takes the PER value (e.g. for the last 25 packets) and is shown as a gauge.

Prerequisites

  • A Virtual Device or device template with a LoRaWAN data source that provides the _raw data point including frame_counter.
  • Access to data point configuration (e.g. in the device template or in the Virtual Device).

Configuration

Create data point for PER calculation

A new data point stores the frame counters of the last 25 received frames and calculates PER for the last 5, 10, and 25 packets.

Setting Value
Title _LoRaPacketLoss
Source Virtual Device Aggregation – _raw
Display as Hidden

JavaScript transformer:

module.exports = (data, lastData, meta) => {
    let returnValue = {
        "savedFrameCounters": [],
        "PERLast5": null,
        "PERLast10": null,
        "PERLast25": null
    };
    if (lastData) {
        returnValue.savedFrameCounters = lastData.value.savedFrameCounters;
    }
    returnValue.savedFrameCounters.unshift(data._raw.frame_counter);
    returnValue.PERLast5 = calculateRecentPacketLoss(returnValue.savedFrameCounters, 5);
    returnValue.PERLast10 = calculateRecentPacketLoss(returnValue.savedFrameCounters, 10);
    returnValue.PERLast25 = calculateRecentPacketLoss(returnValue.savedFrameCounters, 25);
    returnValue.savedFrameCounters = returnValue.savedFrameCounters.slice(0, 25);
    return returnValue;
};

function calculateRecentPacketLoss(frameCounters, interval) {
    if (!Array.isArray(frameCounters) || frameCounters.length < 2 || interval < 1) {
        return null;
    }
    const expected = [];
    const received = new Set(frameCounters);
    let count = 0;
    let current = frameCounters[0];
    expected.push(current);
    count++;
    for (let i = 1; i < frameCounters.length && count < interval; i++) {
        const next = frameCounters[i];
        let step;
        if (current > next) {
            step = current - next;
            for (let j = 1; j <= step && count < interval; j++) {
                expected.push(current - j);
                count++;
            }
        } else if (current < next) {
            for (let j = current - 1; j >= 0 && count < interval; j--) {
                expected.push(j);
                count++;
            }
            if (count < interval) {
                expected.push(next);
                count++;
            }
        }
        current = next;
    }
    if (expected.length < interval) {
        return null;
    }
    const trimmedExpected = expected.slice(0, interval);
    const missingFrames = trimmedExpected.filter(f => !received.has(f));
    const per = (missingFrames.length / interval) * 100;
    return per;
}

This data point remains hidden and is only used as the source for the PER display. Device rejoins (frame counter reset) are handled in the logic.

Create data point for PER display (gauge)

A second data point reads the PER value from the first data point and is displayed as a gauge with thresholds.

Setting Value
Title per
Icon wifi
Source Virtual Device Aggregation – _LoRaPacketLoss
Type number
Display as Gauge
Minimum value 0
Maximum value 100
Thresholds 33, 66
Decimal places 0
Colour mapping light green → orange → red

JavaScript transformer:

module.exports = (data) => {
  return data._lorapacketloss.PERLast25;
};

This shows the PER for the last 25 packets in percent (0–100) on the gauge. The thresholds 33 and 66 split the range into green (0–33), orange (33–66), and red (66–100) for a quick assessment of link quality.

  • Framecounter – Read the frame counter and detect packet loss via the difference to the last value.