sandeepmistry / arduino-LoRa

An Arduino library for sending and receiving data using LoRa radios.
MIT License
1.63k stars 626 forks source link

Add Channel Activity Detection ability. #334

Closed w-ockham closed 1 year ago

w-ockham commented 4 years ago

This modification adds the following functions. LoRa.onCadDone(callback) : Register the callback function for channel activity detection. LoRa.CAD(void) : Activate channel activity detection mode.

IoTThinks commented 4 years ago

Hi, I believe CAD is to detect if any TX activity on the channel by other LoRa nodes to avoid interference.

In the example, I see we do onCad and if there is signal we do onReceive.

What is the advantage if we dont use onCad and use onReceive right away? Less power consumption or something else?

Thanks a lot.

w-ockham commented 4 years ago

That's right. The example is intended to provide a minimum description of CAD. In the latter half of the CAD cycle, power consumption is roughly half of the full receiver mode. You can also implement collision avoidance with onCADdone as follows,

onCADdone(boolean detected) {
   if (detected) {
       channelBusy = true;
       LoRa.receive();
   } else {
   ...
   }
} 

void loop() {
  if (txReady) {
     if (channelBusy) {
      // Backoff for a while.
     } else {
       LoRa.beginPacket();
   ....
}
IoTThinks commented 4 years ago

Yes, now I understand. Thanks a lot for your response.

artemen commented 4 years ago

One thing, as far as I was able to understand there are two interrupts possible with CAD one is triggered on any preamble and the other on your specific one. The later one i believe should be mapped to a different dio pin, would it be possible to add that one as well. Thanks!

morganrallen commented 4 years ago

As I pointed out in the inline comment.. https://github.com/sandeepmistry/arduino-LoRa/pull/334#discussion_r376877207

One, CADDone, is triggered when the CAD is complete, on DIO0 & DIO2 The other, CADDetected is triggered only if a signal is detected, on DIO1

artemen commented 4 years ago

ideally, would it be possible to add another interrupt on DIO1? or its sufficient to short them and use single pin on MCU as long as onReceive is not enabled at the same time. but we would still need to check the relevant register on the radio.

As I pointed out in the inline comment.. #334 (comment)

One, CADDone, is triggered when the CAD is complete, on DIO0 & DIO2 The other, CADDetected is triggered only if a signal is detected, on DIO1

morganrallen commented 4 years ago

I don't think you'd want to just short them, but yes, we should add that, I believe there's an open issue outlining it. But as you suggested, the same functionality can be achieved with just DIO0. When CadDone is raised on this pin, in software you can check the IRQ flag for CadDetected.

ricaun commented 4 years ago

I create this Branch a long time, and use cad to detect receiving data with sf7, sf8, sf9, sf10, sf11 or sf12. I used the dio0 and dio1 on the same interrupt pin with some diodes. The ideia was to create some single gateway or something.

https://github.com/ricaun/arduino-LoRa/tree/CAD

IoTThinks commented 4 years ago

@w-ockham : I'm going to use your CAD code for my deep-sleep node. By right, if I do CAD then make my ESP32 go to deep sleep (and wake up via DIO1) Will my ESP32 wake up and able to receive the current coming message?

Is this possible? I see from here https://learn.circuit.rocks/esp32-lora-gateway-battery-optimized He needs to init LoRa correctly after wakeup otherwise the current coming message is gone. Thanks millions for your effort.

 // register the channel activity dectection callback
  LoRa.onCadDone(onCadDone);
  // register the receive callback
  LoRa.onReceive(onReceive);
  // put the radio into CAD mode
  LoRa.channelActivityDetection();

 // Then ESP32 go deep sleep at DIO1
 // Setup deep sleep with wakeup by external source
 esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_LORA_DIO_1, RISING);
 // Finally set ESP32 into sleep
 esp_deep_sleep_start();
IoTThinks commented 4 years ago

@ricaun Will your CAD code wake up a deep sleep MCU and receive the current incoming message? Thanks millions.

IoTThinks commented 4 years ago

@w-ockham : Would you mind explain to me what is B111 in isTransmitting()? Even if it is a hex, then it should be 0xb111. Thanks a lot for your effort.

image

ricaun commented 4 years ago

@ricaun Will your CAD code wake up a deep sleep MCU and receive the current incoming message? Thanks millions.

I believe so, but I design the code to find the spreading factor when the node receiving a message, It's not perfect but It's possible to receive a message from sf7 to sf12 (with some loss). In the end, I don't know if how much consumption the Lora module has on the CAD state, and if it makes sense to make the Lora module to wake up the MCU with some interruption when the CAD trigger.

Is this possible? I see from here https://learn.circuit.rocks/esp32-lora-gateway-battery-optimized He needs to init LoRa correctly after wakeup otherwise the current coming message is gone.

Interesting, the SX126x has this SetRxDutyCycle function to make the LoRa module switch to receive and sleep. I don't know if the SX127x has some similar functions. 😞

IoTThinks commented 4 years ago

The current CAD code syncs CadDone to dio0. Then check flag for cadDetected. The check for cadDetected flag is not possible as the MCU sleeps. If wakeup on Dio0 then it is pointless as I need CadDetected only for incoming messages.

I think I will add new code to sync CadDetected to Dio1. Then use dio1 to wakeup MCU.

Thanks a lot for your CAD code. :D

ricaun commented 4 years ago

The current CAD code syncs CadDone to dio0. Then check flag for cadDetected. The check for cadDetected flag is not possible as the MCU sleeps. If wakeup on Dio0 then it is pointless as I need CadDetected only for incoming messages.

I think I will add new code to sync CadDetected to Dio1. Then use dio1 to wakeup MCU.

Thanks a lot for your CAD code. :D

On my code, I used the MAP_DIO0_LORA_CADDONE and MAP_DIO1_LORA_CADDETECT and if I remembered ready when the module goes to CAD mode, the CARDONE trigger after some time and put the module on the Standby, and if the module detects something on this window the CADDETECT triggers.

I guess the SX127x Lora module is not designed to wake up the MCU with CAD. 😞

IoTThinks commented 4 years ago

@ricaun Most of the LoRa boards don't have DIO1 connected. e.g: TTGO, Heltec or Ready-made LoRa PCB. After doing the CAD, it will issue a CADDone. But not all CADDone events lead to CadDetected. Hence, we can not base on DIO0 to know when to wakeup MCU.

We need DIO1 for CadDetected. BTW, it may be for another PR :D Let me try for CadDetected.

image

IoTThinks commented 4 years ago

@w-ockham : Would you mind explain to me what is B111 in isTransmitting()? Even if it is a hex, then it should be 0xb111. Thanks a lot for your effort.

image

@w-ockham Could you advise me on the B111? So curious. Thanks a lot :(y)

IoTThinks commented 4 years ago

B111 is binary 111 or 0x07. It should be MODE_CAD instead.

define MODE_CAD 0x07

bool LoRaClass::isTransmitting()
{
  if ((readRegister(REG_OP_MODE) & B111) == MODE_TX) {
    return true;
  }
...
IoTThinks commented 4 years ago

This example from @w-ockham has something "not right" too me. https://github.com/w-ockham/arduino-LoRa/blob/master/examples/LoRaCADCallback/LoRaCADCallback.ino

void onCadDone(boolean signalDetected) {
  // detect preamble
  if (signalDetected) {
    Serial.println("Signal detected");
    // put the radio into continuous receive mode
    LoRa.receive();  <======= This line is likely to clear the current message and only receive the NEXT message.
  } else {
    // try next activity dectection
    LoRa.channelActivityDetection();
  }
}

When a CadDetected is detected, LoRa.receive() seems to clear the incoming data? And only receive the next incoming message using Continuous RX. Hence, the receiving node on the right only received HALF of the messages.

image

By right, after CADDetected and set to Single RX or RX Continous Mode, we should receive the current incoming message. If I use SingleRX by parsePacket, the size will be 0. image

cobbm commented 2 years ago

I have been trying to get this code to work for the last few hours, and I was running into the same problem as IOTThinks where only every other packet was being received. I think this is caused by the LoRa module not switching back to CAD mode properly after entering the receive state (after the call to receive() in onCadDone()).

After trying several other things which didn't work, I added a call to idle() at the start of channelActivityDetection():

void LoRaClass::channelActivityDetection(void)
{
  idle(); // <-- set state to idle first 
  writeRegister(REG_DIO_MAPPING_1, 0x80);// DIO0 => CADDONE
  writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_CAD);
}

This seems to have fixed the problem and CAD is working as expected and I'm now receiving all packets! 😄

morganrallen commented 1 year ago

@Cangjie103 there are still fixes that need to be addressed on this, please look at the inline comments and fix those before merging.

morganrallen commented 1 year ago

This PR has been modified, tested and merged and currently is available in the 'master' branch. Please test and check for side effects.

Additionally while reading some of the details on CAD Flow in the SX1276 datasheet, Pages 43 and specifically 44 I realized there is a point that probably needs clarification. CAD seems to only work to detect a packet preamble. From the datasheet page 44...

During a CAD the following operations take place:

This last line in particular seems to indicate the purpose is to detect if you are able to receive, not if you're able to transmit. Maybe further testing will indicate if both are the case.