Skip to content

adr: plugin_skeleton: Use the result in the comment #721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from

Conversation

ekaitz-zarraga
Copy link
Contributor

The comment of the plugin_skeleton.js does not happen to be what the algorithm returns for the input example. This might confuse users.

This commit fixes that.

@brocaar
Copy link
Contributor

brocaar commented Aug 6, 2025

Hi @ekaitz-zarraga, I do not think this is is an issue, the comment just contains an example of the data-structure. In your case it might have returned DR 5, but this depends on the uplink DR of your device, this has nothing to do with the skeleton. The skeleton just returns the same value as the input.

@ekaitz-zarraga
Copy link
Contributor Author

Oh yes, you are right, but the default adr does return that.
I added a better comment, so people can understand what's going on by default.

@brocaar
Copy link
Contributor

brocaar commented Aug 6, 2025

image

But that is still not true. For one it might return dr: 1, for an other dr: 2, ... The input depends per user / device and so does the output. As the documentation says, it is an example. The example defines the structure, not the exact values that are returned.

@ekaitz-zarraga
Copy link
Contributor Author

But it would return that for the message of the example.
The ADR plugin is a pure function.

We could say "for that input, the default ADR would return..."

@brocaar
Copy link
Contributor

brocaar commented Aug 6, 2025

The input example contains dr: 1. In that case, the txPowerIndex should be updated too.

@ekaitz-zarraga
Copy link
Contributor Author

The input example contains dr: 1. In that case, the txPowerIndex should be updated too.

what do you mean?

I made this. I took the default algorithm, and executed it independently:

export function name() {
  return "JS example for default ADR algorithm";
}

export function id() {
  return "js_example_default";
}

export function handle(req) {
  let resp = {
    dr: req.dr,
    txPowerIndex: req.txPowerIndex,
    nbTrans: req.nbTrans,
  };

  if (!req.adr) {
    return resp;
  }

  if (req.dr > req.maxDr) {
    resp.dr = req.maxDr;
  }

  // Set the new Nb Trans.
  resp.nbTrans = getNbTrans(req.nbTrans, getPacketLossPercentage(req));

  // Calculate the number of steps.
  let snrMax = getMaxSnr(req);
  let snrMargin = snrMax - req.requiredSnrForDr - req.installationMargin;
  let nStep = Math.floor(snrMargin / 3);

  // In case of negative steps the ADR algorithm will increase the TxPower
  // if possible. To avoid up / down / up / down TxPower changes, wait until
  // we have at least the required number of uplink history elements.
  if (nStep < 0 && getHistoryCount(req) != requiredHistoryCount()) {
    return resp;
  }

  let [desiredTxPowerIndex, desiredDr] = getIdealTxPowerIndexAndDr(
    nStep,
    resp.txPowerIndex,
    resp.dr,
    req.maxTxPowerIndex,
    req.maxDr,
  );

  resp.dr = desiredDr;
  resp.txPowerIndex = desiredTxPowerIndex;

  return resp;
}

function getIdealTxPowerIndexAndDr(nbStep, txPowerIndex, dr, maxTxPowerIndex, maxDr) {
  while (nbStep !== 0) {
    if (nbStep > 0) {
      if (dr < maxDr) {
        // Increase the DR.
        dr++;
      } else if (txPowerIndex < maxTxPowerIndex) {
        // Decrease the Tx Power.
        txPowerIndex++;
      }
      nbStep--;
    } else {
      // Incease the TxPower.
      if (txPowerIndex > 0) {
        txPowerIndex--;
      }
      nbStep++;
    }
  }

  return [txPowerIndex, dr];
}

function requiredHistoryCount() {
  return 20;
}

function getHistoryCount(req) {
  let count = 0;
  for (let uh of req.uplinkHistory) {
    if (uh.txPowerIndex === req.txPowerIndex) {
      count++;
    }
  }

  return count;
}

function getMaxSnr(req) {
  let maxSnr = -999.0;

  for (let uh of req.uplinkHistory) {
    if (uh.maxSnr > maxSnr) {
      maxSnr = uh.maxSnr;
    }
  }

  return maxSnr;
}

function getNbTrans(currentNbTrans, pktLossRate) {
  const pktLossTable = [
    [1, 1, 2],
    [1, 2, 3],
    [2, 3, 3],
    [3, 3, 3],
  ];

  if (currentNbTrans < 1) {
    currentNbTrans = 1;
  }
  if (currentNbTrans > 3) {
    currentNbTrans = 3;
  }

  const nbTransIndex = currentNbTrans - 1;

  if (pktLossRate < 5.0) {
    return pktLossTable[0][nbTransIndex];
  } else if (pktLossRate < 10.0) {
    return pktLossTable[1][nbTransIndex];
  } else if (pktLossRate < 30.0) {
    return pktLossTable[2][nbTransIndex];
  }

  return pktLossTable[3][nbTransIndex];
}

function getPacketLossPercentage(req) {
  if (req.uplinkHistory.length < requiredHistoryCount()) {
    return 0.0;
  }

  let lostPackets = 0;
  let previousFCnt = req.uplinkHistory[0].fCnt;

  for (let uh of req.uplinkHistory.slice(1)) {
    lostPackets += uh.fCnt - previousFCnt - 1;
    previousFCnt = uh.fCnt;
  }

  return lostPackets / req.uplinkHistory.length * 100.0;
}


console.log(JSON.stringify(handle(
{
 regionConfigId: "eu868",
 regionCommonName: "EU868",
 devEui: "0102030405060708",
 macVersion: "1.0.3",
 regParamsRevision: "A",
 adr: true,
 dr: 1,
 txPowerIndex: 0,
 nbTrans: 1,
 maxTxPowerIndex: 15,
 requiredSnrForDr: -17.5,
 installationMargin: 10,
 minDr: 0,
 maxDr: 5,
 skipFCntCheck: false,
 deviceVariables: {
   "varA": "value1",
   "varB": "value2",
 },
 uplinkHistory: [
   {
     "fCnt": 10,
     "maxSnr": 7.5,
     "maxRssi": -110,
     "txPowerIndex": 0,
     "gatewayCount": 3
   }
 ]
})))

That prints:

{"dr":5,"txPowerIndex":1,"nbTrans":1}

@brocaar
Copy link
Contributor

brocaar commented Aug 6, 2025

This is the input example:
https://github.com/chirpstack/chirpstack/blob/master/examples/adr_plugins/plugin_skeleton.js#L21

// Input object example:
// {
//  regionConfigId: "eu868",
//  regionCommonName: "EU868",
//  devEui: "0102030405060708",
//  macVersion: "1.0.3",
//  regParamsRevision: "A",
//  adr: true,
//  dr: 1,
//  txPowerIndex: 0,
//  nbTrans: 1,
//  maxTxPowerIndex: 15,
//  requiredSnrForDr: -17.5,
//  installationMargin: 10,
//  minDr: 0,
//  maxDr: 5,
//  skipFCntCheck: false,
//  deviceVariables: {
//    "varA": "value1",
//    "varB": "value2",
//  },
//  uplinkHistory: [
//    {
//      "fCnt": 10,
//      "maxSnr": 7.5,
//      "maxRssi": -110,
//      "txPowerIndex": 0,
//      "gatewayCount": 3
//    }
//  ]
// }

The function is:

export function handle(req) {
  return {
    dr: req.dr,
    txPowerIndex: req.txPowerIndex,
    nbTrans: req.nbTrans
  };
}

Thus output:

  • dr == input dr (= 1 in the input example)
  • txPowerIndex = input txPowerIndex (= 0 in the input example)
  • nbTrans == input nbTrans (=1 in the input example)

The scope of the comments is the plugin skeleton, it does not apply to the behavior of the ADR plugin you have implemented.

I'm going to close this PR as I do not think the examples are wrong. They are there such that you know which fields will be available in the object and to get a sense of what data they will hold. It does not define the behavior of the codec function. That is up to how you implement the ADR algorithm. For some it might be dr: 0, for others dr: 5 or anything in-between.

@brocaar brocaar closed this Aug 6, 2025
@ekaitz-zarraga ekaitz-zarraga deleted the adr_example branch August 6, 2025 12:50
@ekaitz-zarraga
Copy link
Contributor Author

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants