[Update] I got help from Navi of Heltec.
I didn’t complete solve my problem but there is some improvement.
What I did is:
- reduce my packet size from 14 bytes ( which consists of 2 bytes config setting + 8 bytes Chip ID + 4 bytes sensor data) to 7 bytes (consisting of 1 byte config + 2 bytes manually set ID + 4 bytes data).
- used 250KHz (though I still need to check performance in 3km range)
- used the IsChannelFree
So I modified the pingpong.ino to this:
#include "LoRaWan_APP.h"
#include "Arduino.h"
#ifndef LoraWan_RGB
#define LoraWan_RGB 0
#endif
#define RF_FREQUENCY 868000000 // Hz
#define TX_OUTPUT_POWER 5 // dBm
#define LORA_BANDWIDTH 1 // [0: 125 kHz,
// 1: 250 kHz,
// 2: 500 kHz,
// 3: Reserved]
#define LORA_SPREADING_FACTOR 7 // [SF7..SF12]
#define LORA_CODINGRATE 1 // [1: 4/5,
// 2: 4/6,
// 3: 4/7,
// 4: 4/8]
#define LORA_PREAMBLE_LENGTH 8 // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT 0 // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define RX_TIMEOUT_VALUE 1000
#define BUFFER_SIZE 30 // Define the payload size here
uint8_t txpacket[BUFFER_SIZE];
char rxpacket[BUFFER_SIZE];
static RadioEvents_t RadioEvents;
void OnTxDone( void );
void OnTxTimeout( void );
void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );
int addDelay = 0;
typedef enum
{
LOWPOWER,
RX,
TX
}States_t;
union floatToBytes
{
float valueInF;
unsigned char valueInB[4];
};
int16_t txNumber;
States_t state;
bool sleepMode = false;
int16_t Rssi,rxSize;
long lastSent = 0;
long tWait = millis();
void setup() {
Serial.begin(115200);
txNumber=0;
Rssi=0;
RadioEvents.TxDone = OnTxDone;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxDone = OnRxDone;
Radio.Init( &RadioEvents );
Radio.SetChannel( RF_FREQUENCY );
Radio.SetTxConfig( MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
LORA_SPREADING_FACTOR, LORA_CODINGRATE,
LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
true, 0, 0, LORA_IQ_INVERSION_ON, 3000 );
Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, true );
state=TX;
tWait = millis();
//calibrating
while(!Radio.IsChannelFree(MODEM_LORA, RF_FREQUENCY, -70, 2) && millis() - tWait < 2000)
{
Serial.println("Channel busy");
delay(5);
}
}
void loop()
{
switch(state)
{
case TX:
delay(947+addDelay); //low power equivalent in my program
addDelay = 0;
txNumber++;
floatToBytes val;
val.valueInF = (float)txNumber;
txpacket[0] = 0x00; //config
txpacket[1] = 0x00; //id
txpacket[2] = 0x01; //id
for(int o=0; o<4; o++)
{
txpacket[o+3] = ((uint8_t)val.valueInB[o]);
}
if(!Radio.IsChannelFree(MODEM_LORA, RF_FREQUENCY, -70, 2))
{
Serial.println("channel is busy");
//channel is busy so adjust
addDelay=Radio.TimeOnAir(MODEM_LORA, 7+12); //12 bytes from preamble 8 bytes + 4 added by hardware
}
Radio.Send( (uint8_t *)txpacket, 7 );
if(millis() - lastSent > (5*1000))
{
Serial.println("TX not sending for 5 sec");
}
state=TX;
break;
case RX:
Serial.println("into RX mode");
Radio.Rx( 0 );
state=TX;
break;
case LOWPOWER:
lowPowerHandler();
break;
default:
break;
}
Radio.IrqProcess( );
}
void OnTxDone( void )
{
//Serial.println("TX done......");
lastSent = millis();
//turnOnRGB(0,0);
state=TX;
}
void OnTxTimeout( void )
{
Radio.Sleep( );
Serial.println("TX Timeout......");
state=TX;
}
void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
Rssi=rssi;
rxSize=size;
memcpy(rxpacket, payload, size );
rxpacket[size]='\0';
turnOnRGB(COLOR_RECEIVED,0);
Radio.Sleep( );
Serial.printf("\r\nreceived packet \"%s\" with Rssi %d , length %d\r\n",rxpacket,Rssi,rxSize);
Serial.println("wait to send next packet");
state=TX;
}
So that before the TX starts the loop of sending and sleeping, it will first check when the channel is available in the void setup()
. I noticed starting at a free time slot, it performs well for about 1 hour, before congestion happens.
Before every sending, it is checking if the channel is free for 2 milliseconds, if it is not free, it will adjust the timing for the next cycle by adjusting the sleep time delay(947+addDelay); //low power equivalent in my program
. I am still testing on the performance of this algorithm. I am expecting it is not perfect, so I am open for suggestions on how to smartly avoid the congestion.
I did try a random delay, but instead of having long congestion (period when Rx is not receiving), I got instead short congestions but numerous (means, it’s not a reliable solution).
I did try letting it wait for the channel to be free for some time (i.e. on 200ms wait timeout, I got 62 packets lost only after 1 hour 15 mins for 1 tx and 47 for another on a 1RX - 4TX setup ), and it offered better result as it will only send when channel is free, but then again the cost is the battery.
I am still not closing the idea that there is something wrong with the LoRa hardware or library (I am using pingpong on my tests for now, so it shouldn’t be my program issue) because if I am having several TX starting to send at different times, considering a fix cycle, of ~26 ms time on air, why do I get congestion that last for random periods ( 3 mins, to 10 mins, at one time 1 hour).
But I guess I have no choice but to make my own workaround for this right now.
Tools for reference :
There is this tool SX1261 Lora Calculator where you can find the Time on Air, depending on your TX configuration. There is also a method in the LoRa library that gives close value:
Radio.TimeOnAir(MODEM_LORA, packetLen);