adafruit / Adafruit_CircuitPython_RFM69

CircuitPython module for the RFM69 series of 433/915 mhz packet radios.
MIT License
31 stars 28 forks source link

Implement "Reliable Datagram" #20

Closed jerryneedell closed 4 years ago

jerryneedell commented 4 years ago

The Radiohead library has a "Reliable Datagram" mode that uses acknowledgement and retries to insure packet delivery.

Is there interest in implementing that here. I'll be happy to take it on, but wanted to see if there was any concern or interest.

brentru commented 4 years ago

@jerryneedell I'd love reliable packet delivery for the RFM9x/69 modules (https://github.com/adafruit/Adafruit_CircuitPython_RFM9x/issues/34). I feel this would be a mode which should be enabled by default.

BiffoBear commented 4 years ago

@jerryneedell reliable datagram in the driver would be awesome. At the moment, I work around this by sending every packet twice and having the server ignore the duplicate if it sees it. I drop about 1 packet in 5000 with this, but it's not elegant. If I can help with testing, I'll be available mid-January with a Raspi and a couple of ItsyBitsy M4s.

Pythonaire commented 4 years ago

@jerryneedell reliable datagram in the driver would be awesome. At the moment, I work around this by sending every packet twice and having the server ignore the duplicate if it sees it. I drop about 1 packet in 5000 with this, but it's not elegant. If I can help with testing, I'll be available mid-January with a Raspi and a couple of ItsyBitsy M4s.

I use the middle way, RH Datagram stack on SAMD21 board on sender side and raspi as a receiver.. It's not the full reliable version included the ACK requirement, but you can set sender functions ex. to wait until the packets is properly sended. My tests: it's a good idea to give the receiver time to handle the packets. In my case, after "RHDatagram.waitPacketSent()" I set a delay of 500ms. Now it works perfect. No bad packets, no ignores.

jerryneedell commented 4 years ago

@jerryneedell reliable datagram in the driver would be awesome. At the moment, I work around this by sending every packet twice and having the server ignore the duplicate if it sees it. I drop about 1 packet in 5000 with this, but it's not elegant. If I can help with testing, I'll be available mid-January with a Raspi and a couple of ItsyBitsy M4s.

I use the middle way, RH Datagram stack on SAMD21 board on sender side and raspi as a receiver.. It's not the full reliable version included the ACK requirement, but you can set sender functions ex. to wait until the packets is properly sended. My tests: it's a good idea to give the receiver time to handle the packets. In my case, after "RHDatagram.waitPacketSent()" I set a delay of 500ms. Now it works perfect. No bad packets, no ignores.

Thank you for this example of how you are using it. I have been working on implementing the Reliable Datagram into the CircuitPython Library and I have also found it useful to allow a small delay before sending the ACK especially when the receiver is a Raspberry Pi. -- I have made it configurable. I am still working out some issues but hope to heave a version ready for others to test soon.

BiffoBear commented 4 years ago

I use the middle way, RH Datagram stack on SAMD21 board on sender side and raspi as a receiver.. It's not the full reliable version included the ACK requirement, but you can set sender functions ex. to wait until the packets is properly sended. My tests: it's a good idea to give the receiver time to handle the packets. In my case, after "RHDatagram.waitPacketSent()" I set a delay of 500ms. Now it works perfect. No bad packets, no ignores.

Thanks for sharing that, it's an interesting solution.

I use CircuitPython on SAMD21 and SAMD51 boards for sending and Python on the Raspberry Pi 3B as the server.

On my Raspi, which receives from multiple stations, I've implemented threading in the Python app that receives data and writes it out to the database. The main thread sleeps until the RFM69 interrupt occurs. Then the interrupt callback function reads the radio data, writes it to a queue and goes back to sleep. A daemon thread sleeps until something is added to the queue, reads the data, processes it, writes it to the database and goes back to sleep.

On average, the program cannot handle more than one packet per the time interval it takes to process the data and write it to the database. However, the Raspi can handle a quick burst of packets arriving from multiple stations without dropping any because the queue is a buffer. The callback function can continue to respond to new packets while queued packets are being processed by the other thread.

In the future, I will be sending commands from the Raspi to the remote stations, and this is where I'll need the reliable datagrams.

jerryneedell commented 4 years ago

This is just to update the status of "reliable datagram". The good news is that the basic function is working but the bad news is that the timing performance with Circuitpython and especially on the Raspberry Pi is making it very difficult to get ti to work reliably with boards using the Arduino/Radiohead library. I am having a lot of trouble getting the Circuitpython library to be able to respond fast enough to receive the "ack" packets. I added a workaround to the Circuitpython side to allow a delay to be specified, but this does not help when talking to Arduino/Radiohead.

I'm still working on this -- trying to find ways to make it respond better. my current work is in my fork https://github.com/jerryneedell/Adafruit_CircuitPython_RFM69/tree/jerryn_ack

Pythonaire commented 4 years ago

This is just to update the status of "reliable datagram". The good news is that the basic function is working but the bad news is that the timing performance with Circuitpython and especially on the Raspberry Pi is making it very difficult to get ti to work reliably with boards using the Arduino/Radiohead library. I am having a lot of trouble getting the Circuitpython library to be able to respond fast enough to receive the "ack" packets. I added a workaround to the Circuitpython side to allow a delay to be specified, but this does not help when talking to Arduino/Radiohead.

I'm still working on this -- trying to find ways to make it respond better. my current work is in my fork https://github.com/jerryneedell/Adafruit_CircuitPython_RFM69/tree/jerryn_ack

maybe it helps - take a look on receive function (if fifo_length < 4 ...). Sometimes I got "bad packets" and the fifo_length was 0. Then the function stop working.

jerryneedell commented 4 years ago

This is just to update the status of "reliable datagram". The good news is that the basic function is working but the bad news is that the timing performance with Circuitpython and especially on the Raspberry Pi is making it very difficult to get ti to work reliably with boards using the Arduino/Radiohead library. I am having a lot of trouble getting the Circuitpython library to be able to respond fast enough to receive the "ack" packets. I added a workaround to the Circuitpython side to allow a delay to be specified, but this does not help when talking to Arduino/Radiohead. I'm still working on this -- trying to find ways to make it respond better. my current work is in my fork https://github.com/jerryneedell/Adafruit_CircuitPython_RFM69/tree/jerryn_ack

maybe it helps - take a look on receive function (if fifo_length < 4 ...). Sometimes I got "bad packets" and the fifo_length was 0. Then the function stop working.

Thank you for that warning. I think that issue will be resolved by some of the changes I have made, especially https://github.com/jerryneedell/Adafruit_CircuitPython_RFM69/blob/jerryn_ack/adafruit_rfm69.py#L848

Pythonaire commented 4 years ago

This is just to update the status of "reliable datagram". The good news is that the basic function is working but the bad news is that the timing performance with Circuitpython and especially on the Raspberry Pi is making it very difficult to get ti to work reliably with boards using the Arduino/Radiohead library. I am having a lot of trouble getting the Circuitpython library to be able to respond fast enough to receive the "ack" packets. I added a workaround to the Circuitpython side to allow a delay to be specified, but this does not help when talking to Arduino/Radiohead. I'm still working on this -- trying to find ways to make it respond better. my current work is in my fork https://github.com/jerryneedell/Adafruit_CircuitPython_RFM69/tree/jerryn_ack

maybe it helps - take a look on receive function (if fifo_length < 4 ...). Sometimes I got "bad packets" and the fifo_length was 0. Then the function stop working.

Thank you for that warning. I think that issue will be resolved by some of the changes I have made, especially https://github.com/jerryneedell/Adafruit_CircuitPython_RFM69/blob/jerryn_ack/adafruit_rfm69.py#L848

you are right, that's better. if the fifo length is between 0 and 4 it seems to be the datagram header, but no payload data - a useless data frame.

Pythonaire commented 4 years ago

I use the middle way, RH Datagram stack on SAMD21 board on sender side and raspi as a receiver.. It's not the full reliable version included the ACK requirement, but you can set sender functions ex. to wait until the packets is properly sended. My tests: it's a good idea to give the receiver time to handle the packets. In my case, after "RHDatagram.waitPacketSent()" I set a delay of 500ms. Now it works perfect. No bad packets, no ignores.

Thanks for sharing that, it's an interesting solution.

I use CircuitPython on SAMD21 and SAMD51 boards for sending and Python on the Raspberry Pi 3B as the server.

On my Raspi, which receives from multiple stations, I've implemented threading in the Python app that receives data and writes it out to the database. The main thread sleeps until the RFM69 interrupt occurs. Then the interrupt callback function reads the radio data, writes it to a queue and goes back to sleep. A daemon thread sleeps until something is added to the queue, reads the data, processes it, writes it to the database and goes back to sleep.

On average, the program cannot handle more than one packet per the time interval it takes to process the data and write it to the database. However, the Raspi can handle a quick burst of packets arriving from multiple stations without dropping any because the queue is a buffer. The callback function can continue to respond to new packets while queued packets are being processed by the other thread.

In the future, I will be sending commands from the Raspi to the remote stations, and this is where I'll need the reliable datagrams.

See the example for RH Datagram : https://www.airspayce.com/mikem/arduino/RadioHead/rf69_client_8pde-example.html. I tested that and it works well. Sending a packet, you can get ACK with values (maybe commands) without the full reliable version. My point is battery consumption. Using the full sequences of reliable version and got some transmission problems, it can drop the battery capacity of an SAMD21 board very fast - because of permanent looping.

Pythonaire commented 4 years ago

This is just to update the status of "reliable datagram". The good news is that the basic function is working but the bad news is that the timing performance with Circuitpython and especially on the Raspberry Pi is making it very difficult to get ti to work reliably with boards using the Arduino/Radiohead library. I am having a lot of trouble getting the Circuitpython library to be able to respond fast enough to receive the "ack" packets. I added a workaround to the Circuitpython side to allow a delay to be specified, but this does not help when talking to Arduino/Radiohead. I'm still working on this -- trying to find ways to make it respond better. my current work is in my fork https://github.com/jerryneedell/Adafruit_CircuitPython_RFM69/tree/jerryn_ack

maybe it helps - take a look on receive function (if fifo_length < 4 ...). Sometimes I got "bad packets" and the fifo_length was 0. Then the function stop working.

Thank you for that warning. I think that issue will be resolved by some of the changes I have made, especially https://github.com/jerryneedell/Adafruit_CircuitPython_RFM69/blob/jerryn_ack/adafruit_rfm69.py#L848

you are right, that's better. if the fifo length is between 0 and 4 it seems to be the datagram header, but no payload data - a useless data frame.

after some new test cycles I saw a surprising fact. My sensor send packets with around 47 byte length. Sometime I got 0 or 10 bytes length. But these are not "bad packets" as I thought. These are packets from other 433 MHz systems in the neighborhood, like remote controls for garage doors. I use the "from" value in the receive function to identify the sender, but that taken place in the program after DIO is triggered and the packet is received.

jerryneedell commented 4 years ago

Sorry for the delay. It will likely be another 2 weeks before I can get back to working on this.

geekguy-wy commented 4 years ago

I tinkered with this a bit. I have two Feather M0 (SAMD21) RFM69 boards, with Circuitpython. I was just using the Adafruit supplied demo scripts. Neither board could keep up with a full-speed exchange between them. There were a lot of message losses. I tried several ways of tweaking the scripts, but could still not get a good exchange stream between them. I stopped tinkering with these at this point because these would not have any chance of keeping up with RadioHead running on an Arduino.

I would try implementing the simplest and less involved protocols in the RadioHead library firs and see where that goes. I just do not think the M0 boards with Circuitpython are fast enough for this kind of thing. The M4 and faster boards should do much better but would require using an RFM69 module.

Pythonaire commented 4 years ago

I tinkered with this a bit. I have two Feather M0 (SAMD21) RFM69 boards, with Circuitpython. I was just using the Adafruit supplied demo scripts. Neither board could keep up with a full-speed exchange between them. There were a lot of message losses. I tried several ways of tweaking the scripts, but could still not get a good exchange stream between them. I stopped tinkering with these at this point because these would not have any chance of keeping up with RadioHead running on an Arduino.

I would try implementing the simplest and less involved protocols in the RadioHead library firs and see where that goes. I just do not think the M0 boards with Circuitpython are fast enough for this kind of thing. The M4 and faster boards should do much better but would require using an RFM69 module.

Yes, that's my experience too. I use the Feather M0 (SAMD21) RFM69 board as client and Raspberry Pi Zero as server. Both units not really high speed devices. I use the Radio Head Datagram, because this version has a minimum of "reliability" - we can wait until the full packet is send (manager.waitPacketSent()) and get the "from, to" header flags, that I need for my use case. Because of battery powered client in my case, it could lead into trouble using the full reliable version if one side (server or client) have any problems - it could drain the battery fast.

I have some different tasks/function running in parallel on the server side to receive and handle the data. Maybe it sounds curious, but using threading helps a bit on the single core raspberry. Now, i got data from the client in 30 min interval and have one "bad packet" per week in average.

Pythonaire commented 4 years ago

The "bad packet" issue seems to be a "signal flank" problem. I set bouncetime = 200 for the GPIO event detection, now I have stable packets.

jerryneedell commented 4 years ago

The "bad packet" issue seems to be a "signal flank" problem. I set bouncetime = 200 for the GPIO event detection, now I have stable packets.

Thank you for the update. That is good to know. I'll try it here as well.

Pythonaire commented 4 years ago

question: could we simple take "pybind11" to bind the Reliabllity functions of the c++ RadioHead library into the python code?

jerryneedell commented 4 years ago

question: could we simple take "pybind11" to bind the Reliabllity functions of the c++ RadioHead library into the python code?

I have my doubts about the "simple" part ;-) Have you tried anything like this? I have no experience with pybind. From my experience, the code is not all that complex. I have the reliability functionality implemented. The issues I have are with the execution speed on the Raspberry Pi and with the lack of interrupts in Circuitpython on the microcontrollers. I am assuming that pybind would only really be applicable to the Raspberry Pi implementation and not really applicable to CircuitPython.

Pythonaire commented 4 years ago

Hm, if im right, CPython is a subset of python functions/modules written in C. If pybind work as I read (there are other like boost.python, Swip ...), we wouldn't need the CPython implementation ( with some lacks of functions). We could use this kind of API and call the c++ function directly from the Python code. I will try that...

jerryneedell commented 4 years ago

Hm, if im right, CPython is a subset of python functions/modules written in C. If pybind work as I read (there are other like boost.python, Swip ...), we wouldn't need the CPython implementation ( with some lacks of functions). We could use this kind of API and call the c++ function directly from the Python code. I will try that...

Good luck - It'll be interesting to hear how it works. This may be a good approach for the Raspberry Pi.

Pythonaire commented 4 years ago

question: could we simple take "pybind11" to bind the Reliabllity functions of the c++ RadioHead library into the python code?

I have my doubts about the "simple" part ;-) Have you tried anything like this? I have no experience with pybind. From my experience, the code is not all that complex. I have the reliability functionality implemented. The issues I have are with the execution speed on the Raspberry Pi and with the lack of interrupts in Circuitpython on the microcontrollers. I am assuming that pybind would only really be applicable to the Raspberry Pi implementation and not really applicable to CircuitPython.

I'm a bit confused, sorry. Maybe a dump question.If we use small low-power mcu (ESP-, SAMD-, AVR-based, whatever), we have C/C++ libraries like RadioHead to work with. What is the idea, to convert these existing libraries into python-like language for these mcu?

jerryneedell commented 4 years ago

question: could we simple take "pybind11" to bind the Reliabllity functions of the c++ RadioHead library into the python code?

I have my doubts about the "simple" part ;-) Have you tried anything like this? I have no experience with pybind. From my experience, the code is not all that complex. I have the reliability functionality implemented. The issues I have are with the execution speed on the Raspberry Pi and with the lack of interrupts in Circuitpython on the microcontrollers. I am assuming that pybind would only really be applicable to the Raspberry Pi implementation and not really applicable to CircuitPython.

I'm a bit confused, sorry. Maybe a dump question.If we use small low-power mcu (ESP-, SAMD-, AVR-based, whatever), we have C/C++ libraries like RadioHead to work with. What is the idea, to convert these existing libraries into python-like language for these mcu?

That is what I have been working on -- creating python code (for use with CircuitPython on the supported MCUs) that implements the same functionality as in the RadioHead library.

jerryneedell commented 4 years ago

implemented by #24