What's up with ack?

For CircuitPython issues, ask in the Adafruit CircuitPython forum.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
Rcayot
 
Posts: 321
Joined: Sat Feb 08, 2020 6:48 pm

What's up with ack?

Post by Rcayot »

sorry about the title (not sorry!)

I am trying to use an rfm69HC to send packet data to another radio (Pibonnet with oled).

The sending unit is a feather RP2040, with an rf69hc breakout, and a BME280 breakout boards.

While trying things like the example simple test etc, there were to me, a few more missed packets recieved by the Pi than optimum. I want to try error correcting etc. No I am using cirvuitpython, and I understand it is a 'best effort'.

My question is why do the examples nrf69_node1_ack.py have both send.with_ack and recieve.with_ack?

What is the purpose of haveing both?

What I want to happen is the radio with the sensor to send the data, with an 'ack' and what happens? I would guess that the recieving board sends an 'ack' acknowledgement back, but do I need a separate recieve.with_ack to actually recieve it or is it part of the send.with_ack to listen for the ack?

Here is my send code:

Code: Select all

# SPDX-FileCopyrightText: 2020 Jerry Needell for Adafruit Industries
# SPDX-License-Identifier: MIT
#modified by Roger Ayotte 11/12/2021
# Example to send a packet periodically between addressed nodes with ACK

import time
import board
import busio
import digitalio
import adafruit_bme280
import adafruit_rfm69

# Create library object using our Bus I2C port
i2c = busio.I2C(board.SCL, board.SDA)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
bme280.sea_level_pressure = 1013.25
print("\nTemperature: %0.1f C" % bme280.temperature)
print("Humidity: %0.1f %%" % bme280.relative_humidity)
print("Pressure: %0.1f hPa" % bme280.pressure)
print("Altitude = %0.2f meters" % bme280.altitude)

# set the time interval (seconds) for sending packets
transmit_interval = 10

# Define radio parameters.
RADIO_FREQ_MHZ = 915.0  # Frequency of the radio in Mhz. Must match your
# module! Can be a value like 915.0, 433.0, etc.

# Define pins connected to the chip.
# set GPIO pins as necessary -- this example is for Raspberry Pi
CS = digitalio.DigitalInOut(board.D9)
RESET = digitalio.DigitalInOut(board.D10)

# Initialize SPI bus.
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
# Initialze RFM radio
rfm69 = adafruit_rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)

# Optionally set an encryption key (16 byte AES key). MUST match both
# on the transmitter and receiver (or be set to None to disable/the default).
rfm69.encryption_key = (
    b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
)

# set delay before sending ACK
rfm69.ack_delay = 0.1
# set node addresses
rfm69.node = 1
rfm69.destination = 2
# initialize counter
counter = 0
ack_failed_counter = 0
# send startup message from my_node
rfm69.send_with_ack(bytes("startup message from node {}".format(rfm69.node), "UTF-8"))

# Wait to receive packets.
print("Waiting for packets...")
# initialize flag and timer
time_now = time.monotonic()
while True:
    temperature = str(bme280.temperature)
    print(temperature)
    rfm69.send_with_ack(temperature.encode("utf-8"))
    
    # Look for a new packet: only accept if addresses to my_node
    # packet = rfm69.receive(with_ack=True, with_header=True)
    # If no packet was received during the timeout then None is returned.
    # if packet is not None:
        # Received a packet!
        # Print out the raw bytes of the packet:
        print("Received (raw header):", [hex(x) for x in packet[0:4]])
        print("Received (raw payload): {0}".format(packet[4:]))
        print("RSSI: {0}".format(rfm69.last_rssi))
        # send reading after any packet received
    if time.monotonic() - time_now > transmit_interval:
        # reset timeer
        time_now = time.monotonic()
        counter += 1
        # send a  mesage to destination_node from my_node
        if not rfm69.send_with_ack(
            bytes("message from node {} {}".format(rfm69.node, counter), "UTF-8")
        ):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter)

and here is the sending serial output:

Code: Select all

Temperature: 21.7 C
Humidity: 51.3 %
Pressure: 987.9 hPa
Altitude = 213.18 meters
Waiting for packets...
21.743
21.6812
21.6711
21.6812
21.6865
 No Ack:  2 1
21.6711
21.6711
21.6711
21.6711
21.6812
21.6711
21.6762
21.6658
 No Ack:  6 2
The recieving code is:

Code: Select all

# SPDX-FileCopyrightText: 2020 Jerry Needell for Adafruit Industries
# SPDX-License-Identifier: MIT

# Example to receive addressed packed with ACK and send a response

import time
import board
import busio
import digitalio
import adafruit_rfm69

# Define radio parameters.
RADIO_FREQ_MHZ = 915.0  # Frequency of the radio in Mhz. Must match your
# module! Can be a value like 915.0, 433.0, etc.

# Define pins connected to the chip.
# set GPIO pins as necessary - this example is for Raspberry Pi
CS = digitalio.DigitalInOut(board.D9)
RESET = digitalio.DigitalInOut(board.D10)

# Initialize SPI bus.
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
# Initialze RFM radio
rfm69 = adafruit_rfm69.RFM69(spi, CS, RESET, RADIO_FREQ_MHZ)

# Optionally set an encryption key (16 byte AES key). MUST match both
# on the transmitter and receiver (or be set to None to disable/the default).
rfm69.encryption_key = (
    b"\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08"
)

# set delay before transmitting ACK (seconds)
rfm69.ack_delay = 0.1
# set node addresses
rfm69.node = 2
rfm69.destination = 1
# initialize counter
counter = 0
ack_failed_counter = 0

# Wait to receive packets.
print("Waiting for packets...")
while True:
    # Look for a new packet: only accept if addresses to my_node
    packet = rfm69.receive(with_ack=True, with_header=True)
    # If no packet was received during the timeout then None is returned.
    if packet is not None:
        # Received a packet!
        # Print out the raw bytes of the packet:
        print("Received (raw header):", [hex(x) for x in packet[0:4]])
        print("Received (raw payload): {0}".format(packet[4:]))
        print("RSSI: {0}".format(rfm69.last_rssi))
        # send response 2 sec after any packet received
        time.sleep(2)
        counter += 1
        # send a  mesage to destination_node from my_node
        if not rfm69.send_with_ack(
            bytes("response from node {} {}".format(rfm69.node, counter), "UTF-8")
        ):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter)


User avatar
jerryn
 
Posts: 1868
Joined: Sat Sep 14, 2013 9:05 am

Re: What's up with ack?

Post by jerryn »

What I want to happen is the radio with the sensor to send the data, with an 'ack' and what happens? I would guess that the recieving board sends an 'ack' acknowledgement back, but do I need a separate recieve.with_ack to actually recieve it or is it part of the send.with_ack to listen for the ack?
I'n not sure if I understand you question completely, but I'll try to respond.

Let me review what "send_with_ack" and "receive(with_ack=True)" does.
When you use "send_with_ack" the sending board will expect and wait for a special ACK packet in response to every packet sent.
If it does not receive the ACK packet, it will resend the packet up to the retry limit number of times (typically 5, but adjustable). If no ACK packet is received, the sending program will return an error.
On the receiving board, when you use "receive(with_ack=True)" then receiving board will automatically send the special ACK packet when a packet is received. You do not have to execute a "send_with_ack" from the receiving board.

Note that the special ACK packet is just a predefined packet with a packet header and a payload consisting of a singe character ("!").

So the short answer is that your receiving program does not ever have to send anything. If it is just listening for packets, it can just use "receive(with_ack=True")

In the examples, the receiving program also generates a "response packet" just to be able to demonstrate this. The "response packet" is NOT the ACK packet, it is just a message sent in response to the incoming packet and is totally optional depending on your program needs.

In your code example, you are sending the temperature packet very rapidly and every 10 seconds you are seeing an additional packet with the massage counter. Your sending code is not looking for any received packets (that code is commented out) so it won't see the "response packets" from the receiving board anyway. It will receive the ACK packets. I suggest you take out the response packet generation for your receiving code and just listen for packets.
You may be trying to send packets too quickly. If you still have occasional ACK failures try putting a delay between packets on the sending side. How fast do you need to send the temperature? Also remove the time.sleep(2) from receiving code. Just keep listening for incoming packets.
the receiving code could just be

Code: Select all

while True:
    # Look for a new packet: only accept if addresses to my_node
    packet = rfm69.receive(with_ack=True, with_header=True)
    # If no packet was received during the timeout then None is returned.
    if packet is not None:
        # Received a packet!
        # Print out the raw bytes of the packet:
        print("Received (raw header):", [hex(x) for x in packet[0:4]])
        print("Received (raw payload): {0}".format(packet[4:]))
        print("RSSI: {0}".format(rfm69.last_rssi))
Good luck -- I hope this helps.

Note: I do not represent Adafruit. Just trying to help.

User avatar
Rcayot
 
Posts: 321
Joined: Sat Feb 08, 2020 6:48 pm

Re: What's up with ack?

Post by Rcayot »

Jerryn,

Thank you for that explanation. I thought it was as you suggested, but I have been unable to find an explicit description like that.

Now I have to figure out how to format the recieved payload to send to Adafruit IO.

ROGER

User avatar
Rcayot
 
Posts: 321
Joined: Sat Feb 08, 2020 6:48 pm

Re: What's up with ack?

Post by Rcayot »

I hope this gets some attention...

This is the send part of my code, see original post:

Code: Select all

while True:
    if time.monotonic() - time_now > transmit_interval:
        temperature = str(bme280.temperature)
        print(temperature)
        rfm69.send_with_ack(temperature.encode("utf-8"))
        if not rfm69.send_with_ack(
            bytes("message from node {} {}".format(rfm69.node, counter), "UTF-8")
        ):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter) 
        # reset timeer
        time_now = time.monotonic()
        counter += 1
So, in my serial output, I get teh temperature printed, and nothing else.

The important part of teh recieving code is:

Code: Select all

while True:
    # Look for a new packet: only accept if addresses to my_node
    packet = rfm69.receive(with_ack=True, with_header=True)
    # If no packet was received during the timeout then None is returned.
    if packet is not None:
        # Received a packet!
        # Print out the raw bytes of the packet:
        # print("Received (raw header):", [hex(x) for x in packet[0:4]])
        print("Received (raw payload): {0}".format(packet[4:]))
        print("RSSI: {0}".format(rfm69.last_rssi))
        # send response 2 sec after any packet received
        time.sleep(1)
        counter += 1
        # send a  mesage to destination_node from my_node
        if not rfm69.send_with_ack(
            bytes("response from node {} {}".format(rfm69.node, counter), "UTF-8")
        ):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter)
Now when a packet is recieved, the following is printed:

Received (raw payload): bytearray(b'21.041')

Which is what I expect. However, every time it recieves a packet, it also prints:

Received (raw payload): bytearray(b'message from node 1 300')

My understanding is that in the sending code the lines:

Code: Select all

if not rfm69.send_with_ack(
            bytes("message from node {} {}".format(rfm69.node, counter), "UTF-8")
        ):
should return rfm69.send_with_ack as True, and thus go directly to resetting the timer.

Why is it sending the text "message from node 1 300"?

I just want the sending program to send teh data, and resend if the ack is not recieved.

User avatar
jerryn
 
Posts: 1868
Joined: Sat Sep 14, 2013 9:05 am

Re: What's up with ack?

Post by jerryn »

This code"

Code: Select all

rfm69.send_with_ack(temperature.encode("utf-8"))
        if not rfm69.send_with_ack(
            bytes("message from node {} {}".format(rfm69.node, counter), "UTF-8")
        ):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter) 
will always send two packets. First the temperature then the "message from ...." packet

Code: Select all

rfm69.send_with_ack(
            bytes("message from node {} {}".format(rfm69.node, counter), "UTF-8")
will always send the packet -- it will return "True" if it see an ACK. and False if it does not , then it will print the "No Ack" message.
Since you are not seeing that, it appears to be getting the ACK.
You probably don't want to be sending both packets -- would something like this be better:

Code: Select all

while True:
    if time.monotonic() - time_now > transmit_interval:
        temperature = str(bme280.temperature)
        print(temperature)
        if not rfm69.send_with_ack(rfm69.send_with_ack(temperature.encode("utf-8"))):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter)
# reset timeer
        time_now = time.monotonic()
        counter += 1 

User avatar
Rcayot
 
Posts: 321
Joined: Sat Feb 08, 2020 6:48 pm

Re: What's up with ack?

Post by Rcayot »

sounded good but this is what I got:

Code: Select all

Temperature: 21.0 C
Humidity: 48.0 %
Pressure: 987.8 hPa
Altitude = 214.11 meters
21.0551
Traceback (most recent call last):
  File "<stdin>", line 64, in <module>
  File "adafruit_rfm69.py", line 795, in send_with_ack
  File "adafruit_rfm69.py", line 739, in send
TypeError: object of type 'bool' has no len()

User avatar
jerryn
 
Posts: 1868
Joined: Sat Sep 14, 2013 9:05 am

Re: What's up with ack?

Post by jerryn »

oops -- cut/paste error

Code: Select all

if not rfm69.send_with_ack(rfm69.send_with_ack(temperature.encode("utf-8"))):
should be

Code: Select all

if not rfm69.send_with_ack(temperature.encode("utf-8")):

User avatar
Rcayot
 
Posts: 321
Joined: Sat Feb 08, 2020 6:48 pm

Re: What's up with ack?

Post by Rcayot »

Jerryn,

Ok, this seems to work. I just do not understand why this works. I would really like to know that the ack was recieved, but since I get no "no ack" I gues it works?

Code: Select all

while True:
    if time.monotonic() - time_now > transmit_interval:
        temperature = str(bme280.temperature)
        print(temperature)
        if not rfm69.send_with_ack(temperature.encode("utf-8")):
        #if not rfm69.send_with_ack(bytes("message from node {} {}".format(rfm69.node, counter), "UTF-8")):
            ack_failed_counter += 1
            print(" No Ack: ", counter, ack_failed_counter) 
        # reset timeer
        time_now = time.monotonic()
        counter += 1
        # send a  mesage to destination_node from my_node

User avatar
jerryn
 
Posts: 1868
Joined: Sat Sep 14, 2013 9:05 am

Re: What's up with ack?

Post by jerryn »

the Ack was received if send_with_ack() returns True.
So yes if if does not return False and trigger the error report, it was received.

User avatar
Rcayot
 
Posts: 321
Joined: Sat Feb 08, 2020 6:48 pm

Re: What's up with ack?

Post by Rcayot »

Jerryn,

It just seems weird that the boolean 'if not' placed before the actual send command determines the logic of the next line. Seems like the boolean would be tested before the send command was run.

It appears to work. Though it does NOT appear that there is any error checking, so a packet was sent and recieved, but you do not know if it was corrupted or not. Not necessarily a big deal, just interesting challenge on how to trap corrupted data on teh recieving end.

Want to send data to Adafruit_IO, but want any corrupted data not forwarded. I will work on that later.

Thanks for your help, I think I have it working well enough to proceed with the transmitting of the pressure and humidity data as well.


Roger

User avatar
jerryn
 
Posts: 1868
Joined: Sat Sep 14, 2013 9:05 am

Re: What's up with ack?

Post by jerryn »

It just seems weird that the boolean 'if not' placed before the actual send command determines the logic of the next line. Seems like the boolean would be tested before the send command was run.
The "if not" cannot be evaluated until after the send returns a value. What would to be testing? When you withe "if not x" the result is True if x is False and False if x is True. But you have to know what x is to make the determination
It appears to work. Though it does NOT appear that there is any error checking, so a packet was sent and recieved, but you do not know if it was corrupted or not. Not necessarily a big deal, just interesting challenge on how to trap corrupted data on teh recieving end.
According to the datasheet https://cdn-shop.adafruit.com/product-f ... W-V1.1.pdf on Page 72, the CRC for each packet is enabled by default. If the CRC fails, the packet will be ignored. This may not be ideal, but should help avoid corruption.
Looking ad the library code, it looks like there could be some better control and access to the crc status added. I have created an "issue" in the library repository to track this https://github.com/adafruit/Adafruit_Ci ... /issues/38

I'm glad you have it working enough to move ahead with your project Good luck with it.

Locked
Please be positive and constructive with your questions and comments.

Return to “Wireless: WiFi and Bluetooth”