Wifi lora32 v2 with Cubecell ab02 packet lost

I have a setup of multiple Cubecell transmitters and 1 WIFI lora32v2 receiver.

I am sending 1 packet per second of size 14 bytes.
With following configuration on the Cubecell Transmitter side:

  • -Tx output power 2dBm
  • -125 kHz
  • -SF7
  • -Preamble length = 8
  • -coding rate 1 (4/5)
  • -fix length payload disabled
  • -freq hop disabled
  • -timeout 100 ms
    On Wifi Lora32 Receiver I have:
  • -Tx output power 2dBm
  • -SF7
  • disabled CRC
  • sync word of 0x12

My problem is that I cannot get an acceptable packet loss.
When I am on 1 tx - 1 rx setup, I get 4-16 packet loss every 5 minutes
When I am on 4 tx - 1 rx setup, I get 45-411 packet loss every 5 minutes

*packet loss is counted with the number of packets received vs number of packets sent

Sample data:

Time Received Sent Difference Message missed every 5 minutes
5 279 291 12 12
10 566 583 17 5
15 849 875 26 9
20 1132 1167 35 9
25 1420 1460 40 5
30 1703 1752 49 9
35 1986 2044 58 9
40 2271 2336 65 7
45 2561 2630 69 4
50 2846 2922 76 7
55 3122 3214 92 16
60 3399 3506 107 15
65 3682 3799 117 10

my program is very long, because of the many features I have added but when all other features are disabled, I only have below in a loop:

void loop()
{
    if(ReceiveLora())
    {
           DisplayValue();
    }
}

where

bool ReceiveLora()
{
          int packetSize = LoRa.parsePacket
         int packetSize = LoRa.parsePacket();
        //Check if packet received
        if (packetSize > PACKET_LEN) {
            //some parsing done on 14 byte data,  (takes about < 1ms)
            return true;
        }
        else
           return false;
}

and

void DisplayValue()
{
        //display on oled the parsed value
        //this only runs if new value is different from old value to reduce processing
}

My requirement is actually 4 samples per second with multiple transmitters but even on 1 sample per second I am still getting huge packet loss.

Is this setup possible? How can I prevent if there is collision on the Lora packets sent?

1 packet per second might be the issue as its frequent and might try 1 packet every 5 - 10 sec and compare the packet loss.
Also if multiple transmitters are sending data at same frequency at the same time there will be packet loss so try to vary their transmission time this might help.

Maybe the output power too low.

Frequent packet sending (1-4 packets per second) is actually one of my requirements. I did try varying the transmission times but at some point the RX will stop receiving from all of the transmitters, it will last for several seconds to 2 mins, then receive again.

I was thinking of frequency hopping but I’m not sure how to use it.
On TX (Cubecell), I set it to enable, then the period to 30, by 30 does it mean does it mean in seconds?
On Rx, do I need to change anything?

Most of the resources I got is on using a Lora gateway but in our case, we don’t need to go for large scale network, nor set up a private network. The setup of our system is actually like a star topology. One central receiver. TX and RX will operate in simplex (either transmitter all the time, or receiver all the time). This is to reduce power consumption.

I will try with the 5-10 seconds interval of sending, then let you know.

Yes I was using the lowest but when we check on our range (2KM) requirement, it is working fine with current TX configuration.

On my testing now, I set it up to be less 1 foot apart actually, is there minimum distance between RX and TX?

Make sure its not going to sleep mode.
I’m not familiar with frequency hooping either.
I haven’t used LoRa for TX and RX. I’m using LoRaWAN network instead where all devices acts as transmitters with one central receiver.

I found a bit of solution. I realize my lorawan is enabled by default, so i turn it off as i should be using only normal mode. I also set duty cycle to 10ms. I did this using the cubecell configurator. However i still didnt completely solve my problem. I found the issue could be on the wifi lora 32 receiver side.

Running longer tests, i found the receiver stops receiving at random period, sometimes after 1 hour, for 10 mins. Sometimes earlier, sometimes it stops receiving for 1 hour. Then it will work again.

Hopefully they will check this existing issue.

If you use 4 nodes to send and one node to receive, channel congestion may occur and cause packet loss. Can you show the code for the sending and receiving part?

#include "LoRaWan_APP.h"
#include "Arduino.h"

/*
 * set LoraWan_RGB to 1,the RGB active in loraWan
 * RGB red means sending;
 * RGB green means received done;
 */
#ifndef LoraWan_RGB
#define LoraWan_RGB 0
#endif
#define CHIP_ID_LEN             8
#define RF_FREQUENCY                                868000000 // Hz
#define TX_OUTPUT_POWER                             2        // dBm
#define LORA_BANDWIDTH                              0         // [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 BUFFER_SIZE                                 255 // Define the payload size here
#define LORA_TX_TIMEOUT                             100   //estimated based on TX power/spreading factor
#define LORA_MAX_MESSAGE_SIZE                       18 
#define LORA_MAX_PACKET_SIZE                        200 


static RadioEvents_t RadioEvents;
int timetowake = 947;
int timetosleep = 53;
TimerEvent_t sleepTimer;
bool sleepTimerExpired=false;
long lastTXDone = 0;
bool isTimeout = false;
union floatToBytes  
  {
        float valueInF;
        unsigned char valueInB[4];
  };
union longToBytes  
  {
        uint64_t valueInL;
        uint8_t valueInB[8];
  };

longToBytes DeviceChipId;
int GeneratePacket(uint8_t type, float value,bool isBatteryValue, uint8_t *chipId,  uint8_t *buffer);
static void wakeUp();
static void lowPowerSleep(uint32_t sleeptime);


static void wakeUp()
{
  sleepTimerExpired=true;
}

//so when it is sleep, do not disturb
static void lowPowerSleep(uint32_t sleeptime)
{
  sleepTimerExpired=false;
  TimerInit( &sleepTimer, &wakeUp );
  TimerSetValue( &sleepTimer, sleeptime );
  TimerStart( &sleepTimer );
  
  //Low power handler also gets interrupted by other timers
  //So wait until our timer had expired
  while (!sleepTimerExpired) lowPowerHandler();
  TimerStop( &sleepTimer );
}

void setup() {
    boardInitMcu( );
    delay(100);
    Serial.begin(9600);
    delay(1000);
    Serial.print("Started.");

    DeviceChipId.valueInL = getID();

    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 ); 
   }


bool IsSending = false;
void loop()
{
      if(!(IsSending))
      {
        
          RadioSleep(); //can also be down after sending packet
          turnOffRGB();
          lowPowerSleep(timetowake); //do not sleep if it is still not done sending
      }
      long tDurBeforeSleep = millis();
      Send();
      if(IsSending)
      {
         while(!RadioIRQProcess()){
          delay(1);
          if((millis()-tDurBeforeSleep) > (timetosleep*2)) //about 110 ms
          { 

            RadioSleep();
            delay(1);
            RadioInit();
            break;
          }
        }
        IsSending = false;
      }

}
 void Send()
  {
       IsSending = true;
       char message[LORA_MAX_MESSAGE_SIZE];
       int len = GeneratePacket(0, (float) analogRead(ADC2) , false , (uint8_t *) DeviceChipId.valueInB, (uint8_t *)  message);
       RadioSend((uint8_t *)message, len);
          
  }
        int GeneratePacket(uint8_t type, float value,bool isBatteryValue, uint8_t *chipId,  uint8_t *buffer){
              buffer[0] = type;
              floatToBytes sInB;
              sInB.valueInF = value;
              int startByte = 2;
              if(isBatteryValue)
              {
                buffer[1] = 1;
              }
              else
              {
                buffer[1] = 0;
              }
              int i=0;
              for(i=0; i< CHIP_ID_LEN; i++)
              {
                buffer[i+startByte] = chipId[i];
              }
              startByte += CHIP_ID_LEN;
              for(i=0; i<4; i++)
              {
                buffer[i+startByte] = sInB.valueInB[i];
              }
              return  startByte + 4;
          }


    void RadioSleep()
    {
        Radio.Sleep();
    }

    void RadioInit()
    {
      isTimeout = false;
       
        RadioEvents.TxDone = RadioOnTxDone;
        RadioEvents.TxTimeout = RadioOnTxTimeout;
        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, LORA_TX_TIMEOUT );                  
    }

    void RadioSend(uint8_t * message, int len)
    {
      lastTXDone = 0;
      isTimeout = false;
      //sentMs= millis();
      Radio.Send((uint8_t*)message, len);
    }

    void RadioOnTxDone( void )
    {
      lastTXDone = millis();
      RadioSleep();
    }

    void RadioOnTxTimeout( void )
    {
      isTimeout = true;
    }

    bool RadioIRQProcess(void){
        Radio.IrqProcess();
        return lastTXDone || isTimeout; //if it is time out, count as it is sent and missed
    }

For the receiver, this is the simplified version:

#include "heltec.h"
#define CHIP_ID_LEN                                 8
#define TX_OUTPUT_POWER                             2        // dBm
#define BAND    868E6  //you can set band here directly,e.g. 868E6,915E6
#define LORA_SPREADING_FACTOR                       7         // [SF7..SF12]
#define LORA_CODINGRATE                             1         // [1: 4/5,
                                                              //  2: 4/6,
                                                              //  3: 4/7,
                                                              //  4: 4/8]

union floatToBytes  
{
        float valueInF;
        unsigned char valueInB[4];
};
//lora_packet
struct LoraPacket_s{
    uint8_t type=0x00;
    bool isBatteryValue = false;
    bool isRunningTime = false;
    floatToBytes value;
    uint8_t chipID[CHIP_ID_LEN];
};
void setup() {
    //WIFI Kit series V1 not support Vext control
        Heltec.begin(true /*DisplayEnable Enable*/, true /*Heltec.LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);
        LoRa.setTxPower(TX_OUTPUT_POWER,RF_PACONFIG_PASELECT_PABOOST);
        LoRa.setSpreadingFactor(LORA_SPREADING_FACTOR);
        // this is needed for communicating with cubecell
        #if defined(TRANSMITTER_CUBECELL)
            //if(DEBUG_LOGS_ENABLED) Serial.println("Transmitter is cubecell");
            LoRa.disableCrc();
            LoRa.setSyncWord(0x12);         
        #endif
        LoRa.receive();
}

void loop() {
    LoraPacket_s packet;
   if(ReceiveLora(&packet))
   {
    //display value 
   }
   delay(1);
}

bool ReceiveLora(LoraPacket_s * pkt){

        int packetSize = LoRa.parsePacket();
        //Check if packet received
        if (packetSize > CHIP_ID_LEN + 1) { 
            uint8_t msg[packetSize];
            Serial.print("New message ");
            for (int i=0; i < packetSize; i++) 
            {
                uint8_t r = (uint8_t) LoRa.read();
                Serial.print(r);
                //if(r=='\0')
                    //break;
                msg[i] =  r;
                if(i==0 && r != 0x00) //stop processing if other device
                    break;
            }
            
            if(msg[0] != 0x00)
                return false;

            pkt->type = (uint8_t)msg[0];    
            pkt->isBatteryValue =  msg[1] == 0x01;
            pkt->isRunningTime =  msg[1] == 0x02;
            
            for (int i = 0; i < CHIP_ID_LEN; i++)
            {
                pkt->chipID[i] = msg[i+2];
            }
            for (int i = 0; i < 4; i++)
            {
                pkt->value.valueInB[i] = msg[i+2+CHIP_ID_LEN];
            }
            Serial.println(" ");
            return true;
        }
        else
        {
          return false;
        }
        
    }

Aside from above settings I also have this settings.

cubecellsettings

[Update] I ran again my tests where I started each TX at some interval (with different duty cycle, and ADR setting ) and I found that the RX seems to be receiving messages continuously. But comparing the messages I receive with the sent, there is still huge packets lost.

You can try to use the receiving part in this example as the receiving code of LoRa32.
https://github.com/HelTecAutomation/ESP32_LoRaWAN/blob/master/examples/pingpong/pingpong.ino
The configuration in Tools only works when LoRaWAN is used.

On TX Cubecell,

What does it mean by when LoRaWAN is used? I read somewhere that it is enabled by default, so I used the WASN configurator then AT+LORAWAN=0 command to disable it, is this correct?

Yes, you can do this. The configuration in Tools will only take effect when LoRaWAN related routines are used.

I redid my test for 1:1 tx and rx, making sure no other tx device is running, I got better results.
I’m not sure my AT commands are really working but I sent
AT+LORAWAN=0
AT+DutyCycle=0
For 3 hour test, I had 44 packets lost out of 8501 which is being sent every second. Every 5 minutes, I am checking, and I have 0-4 packets lost only. And lost is distributed.

Now, for 4TX : 1Rx test, even with the pingpong.ino for the RX wifi lora32, I am STILL getting huge packet lost randomly. Like at beginning, I am losing only 0-10 packet loss then suddenly, about 200 packets (means continuously for some time I am not receiving from a TX).

Things I already tried:

  1. regarding TX configuration:
  • Sending AT commands to disable LORAWAN, make DutyCycle=0
  • Changing between 915 and 868MHz region (maybe somewhere in the code it is putting some restrictions, but I also check the library I am using, I didn’t see any)
  • Adaptive data rate, I notice with the ADR on it is little bit better results
  • Increase the TX power, didn’t make any difference.
  • Increase the bandwidth, I got little bit better results.
  1. regaring RX program:
  • Tried different example of receiving, still some packet lost.
  1. regarding TX program:
  • tried different example of sending, still some packet lost.
  • Add random sleep delay, so that there will be less chances of multiple tx overlapping when sending - I got much linear result. Means packet lost are now more distributed, only in total, the packet lost are still the same.
  1. Hardware
  • I check with battery, not the issue.
  • I tried changing the antenna, not the issue.

What I dont understand is the packet lost itself. Because If I am sending 14 byte message, where it’s triggering “TX done” after 53 ms, then I am putting my TX to deep sleep after that, then how come I am getting “congestion” for long period?

I will run my tests again, now with TX and RX farther, and check the difference.

Have you tried using CubeCell to send, CubeCell to receive, or LoRa32 to send and LoRa32 to receive, because the RF circuits of CubeCell and LoRa32 are different, which may affect communication.

I did try Cubecell to send, and Cubecell to receive, still I am getting the packet loss on multiple TX.
With Tx and Rx farther, still same I am getting packet lost.

Now I am checking with this method on the cubecell TX side before sending
bool ( *IsChannelFree )( RadioModems_t modem, uint32_t freq, int16_t rssiThresh, uint32_t maxCarrierSenseTime );

1 Like

[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);

[Update]

4 Tx with 1 Rx seems really far fetch now. But I did get good results with 2 TX compared to before.

I modified my implementation using “free slot” idea.

Instead of waiting only for the channel is free before starting to send, I made every TX observe first for about 2 seconds the channel. It waits for the longest interval where the channel is free and starts sending mid time of that interval. On setup, I added this function

bool waitForFreePeriod()
{
    bool busyDetected = false;
    TimerTime_t busyStart, checkStart=0;
    int timeout = 2000; //cycle of every TX is 1000 but made it 2000 for allowance
    
    TimerTime_t lastBusy = TimerGetCurrentTime();
    long freeTimeMax = 0;    //max time the channel is free
    TimerTime_t lastTimeFree = TimerGetCurrentTime();
    bool freeStart =false;   //no other tx is around
     checkStart = TimerGetCurrentTime();
     while(Lora.IsChannelFree())
      {
        if( TimerGetElapsedTime( checkStart ) >timeout)
        {
          Serial.println("TX can start since no other tx is around");
          freeStart = true;  
          break;
        }
      }
    if(freeStart)
      return true;
    checkStart = TimerGetCurrentTime();  //restart the timer
    lastBusy = TimerGetCurrentTime();
    TimerTime_t firstTimeFree = TimerGetCurrentTime();
    while( TimerGetElapsedTime( checkStart ) < timeout)
    {
        busyStart  = TimerGetCurrentTime();
        if(!Lora.IsChannelFree())
        {
          busyDetected = true;               //yep the channel started getting busy
          while(!Lora.IsChannelFree()) {}    //measure the time it is not free
        }
       long period =  TimerGetElapsedTime( busyStart ) ;
       if(busyDetected)                     
        {
           
          Serial.print(period);
          Serial.print( ";");
          Serial.print((int) TimerGetElapsedTime( lastBusy ));
          Serial.print( ";");
          
          int freeTime =TimerGetElapsedTime(lastBusy)-timeOnAir; 
          if(freeTimeMax < freeTime)
          {
            freeTimeMax = freeTime;
            lastTimeFree = TimerGetCurrentTime();
          }
           Serial.println();
           lastBusy = TimerGetCurrentTime();
           //Serial.print((int)  lastBusy );
        }
        busyDetected = false;
    }



    Serial.print( "Max time channel is free ");
    Serial.println(freeTimeMax);
    int distanceFromLastBusy =(freeTimeMax/2) - (timeOnAir/2);
    int cycleLength = ((int)lastBusy) - ((int)checkStart);
    int nextCycle =cycleLength + distanceFromLastBusy;
    Serial.print(" wait out ");
    Serial.println(nextCycle);
    delay(nextCycle);
    Serial.print("Ready to start sending");

    return true;
}

This is so that every TX should not have same time slot.

Then after since I intend to send per second data. On the loop, I adjusted the sleep time so that every cycle (every second), it will send on the exact time. i.e. if my TX starts sending at 5010, then it will send again at 6010, 7010 etc… this is to make sure that it will not overlap with the timing of the other TX.

int tStamp = (int) TimerGetElapsedTime(tDurationOfSending);
sleepTime = 1000-tStamp; 
int nextCycle = ((int) TimerGetCurrentTime() + sleepTime; //expected start of sending
int adjustment= (nextCycle%1000) - tStartMs; //where tStartMs is the time%1000 it started sending 
lowPowerSleep(sleepTime -adjustment);

With this setup I got better results, and the issue will now fall on the RX (to always receive the packets set).