buffering and packet retry

Started by jbeale, October 16, 2013, 12:58:31 PM

jbeale

Starting to play around with my pair of Moteinos, I added a simple packet retry with exponential backoff delay to the "Send" demo sketch (based on how I vaguely recall Ethernet collision algorithms work). My application is measuring pulse width on an input pin, and sending the measurement out via RF packet. It was simple if we only send once and assume it works. Once I've allowed for possible packet retries, I may need to deal with more than one thing happening at once, if several retries may be needed meanwhile data is still coming in. So I need to put the "not yet ACK'd" packets into a buffer and send them out according to a schedule, while maintaining CPU availability to acquire the input data.

This is probably a common scenario and maybe someone has already published an example? (Just checking in case I can save some programming time.)

Felix

The examples I provided are simplistic, no complex buffering scheme. You can certainly do that, but from my perspective, most packets should go through on the first or perhaps second retry, which takes a dozen or two milliseconds, maybe more if it's a longer packet + roundtrip. After that point, queuing means the data is most likely stale at that point. Think of reading some sensor or doing some action that needs to be executed NOW, not 1 second later (which might be too late).

The receiving end is interrupt based, so you could send your packet, and then periodically check for the ACK (which is just a packet with an extra bit to indicate the ACK). But keep in mind any extra received packets will overwrite the current received packet in the RX buffer. Anyway, this way you can do other things while checking for an expected ACK.

jbeale

Yes, my slightly modified versions of your Send and Receive sketch report that a 1-byte packet is about 10 msec (including the return ACK) and an 88-byte packet is 33 msec.  In my application, I prefer to get the data through even if it is several seconds old, so I'm willing to live with several retries and pauses.  In case of interest, my current test code is below

// Simple RFM12B sender program, with ACK 
// [email protected], mods JBeale Oct. 15 2013

#include <RFM12B.h>
#include <avr/sleep.h>

// You will need to initialize the radio by telling it what ID it has and what network it's on
// The NodeID takes values from 1-127, 0 is reserved for sending broadcast messages (send to all nodes)
// The Network ID takes values from 0-255
// By default the SPI-SS line used is D10 on Atmega328. You can change it by calling .SetCS(pin) where pin can be {8,9,10}
#define NODEID        2  //network ID used for this unit
#define NETWORKID    99  //the network ID we are on
#define GATEWAYID     1  //the node ID we're sending to
#define ACK_TIME     50  // # of ms to wait for an ack
#define RETRY_LIMIT  8   // # of times to retry if no ACK. Must be < 16
#define SLOT_TIME    33  // pretend we have a fixed packet transmission time for CDMA collision algorithm
#define MAX_SEND     25  // maximum packet size
#define SERIAL_BAUD  115200

//encryption is OPTIONAL
//to enable encryption you will need to:
// - provide a 16-byte encryption KEY (same on all nodes that talk encrypted)
// - to call .Encrypt(KEY) to start encrypting
// - to stop encrypting call .Encrypt(NULL)
uint8_t KEY[] = "ABCDABCDABCDABCD";

int interPacketDelay = 1000; //wait this many ms between sending packets
char input = 0;
unsigned char LED = 9;  // LED is D9 on Motetino

// Need an instance of the Radio Module
RFM12B radio;
byte sendSize=0;
char payload[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*(){}[]`|<>?+=:;,.";
bool requestACK=false;
long totalRetries = 0;
long totalPackets = 0;
unsigned short retryHist[MAX_SEND];

void setup()
{
  pinMode(LED, OUTPUT);
  Serial.begin(SERIAL_BAUD);
  radio.Initialize(NODEID, RF12_915MHZ, NETWORKID);
  radio.Encrypt(KEY);
  radio.Sleep(); //sleep right away to save power
  Serial.println("JPB Test Transmitting...\n\n");
  for (int i=0;i<MAX_SEND;i++) {
    retryHist[i]=0;  // initialize retries histogram to 0
  }
}

void loop()
{
long ackTime;  // time between TX and RX of ACK

  digitalWrite(LED,HIGH);
  Serial.print("Sending[");
  Serial.print(sendSize+1);
  Serial.print("]:");
  
  requestACK = true;    // Pay attention. Everything I say is important :-)
  unsigned short retries = 0;  

  radio.Wakeup();
  long sendTime = millis();
  unsigned short slots = 1;
  radio.Send(GATEWAYID, payload, sendSize+1, requestACK);
  totalPackets++;
  if (requestACK)
  {
    Serial.print(" - wait "); 
    unsigned char timeout = 12 + (sendSize+1)/4;  // Actual: 1 char = 10 msec, 88 characters = 33 msec
    Serial.print(timeout);
    while (!waitForAck(timeout) && (retries < RETRY_LIMIT)) {
      long dtime = SLOT_TIME * random(slots); // see: wikipedia "exponential backoff"
      delay(dtime);
      radio.Send(GATEWAYID, payload, sendSize+1, requestACK);
      retries++;
      slots = slots *2;
    }
    ackTime = millis() - sendTime;
    if (retries < RETRY_LIMIT) {
      Serial.print(" On try ");
      Serial.print(retries+1);
      Serial.print(" ACK after ");
    } else {
      Serial.print(" No ACK after ");
    }
  }
  radio.Sleep();
  digitalWrite(LED,LOW);
  totalRetries += retries;
  retryHist[sendSize] += retries;  // record retry count in histogram
  Serial.print(ackTime);   Serial.print(" msec. Packets: ");
  Serial.print(totalPackets);   Serial.print(" Retries: ");
  Serial.print(totalRetries);
  
  sendSize = (sendSize + 1) % MAX_SEND;
  Serial.println();
  if (!(totalPackets % 50)) {
      for (int i=0;i<MAX_SEND;i++) {
        Serial.print(i+1);
        Serial.print(",");
        Serial.println(retryHist[i]);  // print how many total retries for each packet length
      }
  }
  delay(interPacketDelay);   // low power sleep mode?
}

// wait a few milliseconds for proper ACK, return true if received
static bool waitForAck(unsigned char timeout) {
  long now = millis();
  while (millis() - now <= timeout)
    if (radio.ACKReceived(GATEWAYID))
      return true;
  return false;
}

Felix

Thanks for sharing and formatting the code with the code tags :)
I'm wondering though .. were you able to send an 88 byte packet? Or was it truncated at 63 bytes even if you dumped 88 bytes in the buffer?
The reason I ask is because the chip has a 66 byte buffer of which a few bytes of overhead from the library.

jbeale

My as-modified code example only goes to 25 data payload bytes, but previously with your original code, it did print out
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*(){}[]`|<>?+=:;,." and say CRC OK, so I assume it was successfully receiving them all.

Felix

Oh wait, I thought it was RFM69, never mind!

The RFM12B lib can send up to 128 bytes messages :)