adafruit / Adafruit_CircuitPython_RFM69

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

question: receive function #31

Closed Pythonaire closed 4 years ago

Pythonaire commented 4 years ago

I use python on raspberry and c++ on a mcu. The mcu use the RHReliableDatagram library. Each time, I send a packet from the raspberry to the mcu, the python receive function returns (via log) a packet with 12 byte 0x00. Is that the "ACK part" of the the reliable library?

jerryneedell commented 4 years ago

No - the ACK packet is 5 bytes 4 byte RadioHead Header with the Identifier feld set with the received Identifier and the Flags Field set with the the most significant bit (0x80) set -- the 5th byte, the "payload" is a single byte containing "!". I do not know what the 12 bits of 0x00 you are seeing is from.

If you can post the code you are running on both the MCU and the Rasperry Pi, I can try to reproduce the situation. What MCU and RFM69 board are you using?

jerryneedell commented 4 years ago

here is more information on the RadioHead "ACK" packet https://www.airspayce.com/mikem/arduino/RadioHead/classRHReliableDatagram.html

An ack consists of a message with:

TO set to the from address of the original message
FROM set to this node address
ID set to the ID of the original message
FLAGS with the RH_FLAGS_ACK bit set
1 octet of payload containing ASCII '!' (since some drivers cannot handle 0 length payloads)
Pythonaire commented 4 years ago

No - the ACK packet is 5 bytes 4 byte RadioHead Header with the Identifier feld set with the received Identifier and the Flags Field set with the the most significant bit (0x80) set -- the 5th byte, the "payload" is a single byte containing "!". I do not know what the 12 bits of 0x00 you are seeing is from.

If you can post the code you are running on both the MCU and the Rasperry Pi, I can try to reproduce the situation. What MCU and RFM69 board are you using?

@jerryneedell thx for you help. I think, I need to have more tests, before I engage you in this. Now I see more then 12 bytes ... weird. I hoped, the byte sequence I saw, was I simple RH69 internal ACK, so I could make my program flow a bit easier. By the way, on raspberry I use the adafruit breakout board and the mcu too is a adafruit feather m0 rfm69. The communication visversa works fine, but these 0x00 byte flow was a bit strange. If you are interesting in the mcu code for your own testing:

// Messageing client with the RH_RF69 class. Using RHDatagram for addressing. // Packet header contains to, from, if and flags.

include

//#include

include

include

include

// Feather M0 w/Radio

define RF69_FREQ 433.0

define RFM69_CS 8

define RFM69_INT 3

define RFM69_RST 4

define CLIENT_ADDRESS 12 // RHDatagram

define SERVER_ADDRESS 2 // RHDatagram

RH_RF69 rf69(RFM69_CS, RFM69_INT); RHDatagram manager(rf69, CLIENT_ADDRESS);

void setup() { Serial.begin(115200); while (!Serial) { delay(1);} pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); // LED off // manual reset the radio pinMode(RFM69_RST, OUTPUT); digitalWrite(RFM69_RST, HIGH); delay(10); digitalWrite(RFM69_RST, LOW); delay(10); if (!manager.init()) { Serial.println("init failed"); while (1);} Serial.println("RFM69 radio init OK!"); // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, // +13dbM (for low power module) and No encryption if (!rf69.setFrequency(RF69_FREQ)) { Serial.println("setFrequency failed"); while (1);} Serial.println("RFM69 radio frequency OK!"); // If you are using a high power RF69 eg RFM69HW, you must set a Tx power with the // ishighpowermodule flag set like this: rf69.setTxPower(17, true); // range from 14-20 for power, 2nd arg must be true for 69HCW // The encryption key has to be the same as the one in the server uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; rf69.setEncryptionKey(key); }; void set_state(uint8_t buffer){ if (buffer == 1) {digitalWrite(LED_BUILTIN, HIGH);} else { digitalWrite(LED_BUILTIN, LOW); } } uint8_t data[] = "{'message': 'hello'}"; uint8_t buf[RH_RF69_MAX_MESSAGE_LEN];

void loop() { char rpacket[60]; // max packet length RFM69 60 Byte if (manager.available()) { // Wait for a message addressed to us from the client uint8_t len = sizeof(buf); uint8_t from = SERVER_ADDRESS; if (manager.recvfrom(buf, &len, &from)) { Serial.print("got request from : 0x"); Serial.print(from, HEX); Serial.print(": "); Serial.println((char)buf); int ack; sscanf((char)buf, "%d", &ack); delay(500); //Send a reply back int n = sprintf(rpacket, "{'ACK':%d}", ack); Serial.print("packet length: "); Serial.print(n); Serial.print(", data: "); Serial.println(rpacket); uint8_t radiopacket[n]; // reduce to the needed packet size 'n' memcpy(radiopacket, (const char*)rpacket, sizeof(rpacket)); manager.sendto(radiopacket, sizeof(radiopacket), SERVER_ADDRESS); } } //rf69.sleep(); };

jerryneedell commented 4 years ago

I'm a bit confused by your example above. It is not using the "reliable datagram" mode. You would use the recvfromAck and sendtoWait functions for "reliable datagram". The recvfromAck function automatically generates the "ACK" packet.

On the Raspberry Pi (python) side you would use rfm69.receive(with_ack=True) and send_with_ack()

Pythonaire commented 4 years ago

I'm a bit confused by your example above. It is not using the "reliable datagram" mode. You would use the recvfromAck and sendtoWait functions for "reliable datagram". The recvfromAck function automatically generates the "ACK" packet.

On the Raspberry Pi (python) side you would use rfm69.receive(with_ack=True) and send_with_ack()

Yes, sorry ... just a quick copy/paste from the last test => datagram. I tried both version datagram and reliable datagram, to see what happened, if I change the mcu library. In the pasted code "#include RHReliableDatagram" should be useless, because of calling RHDatagram "sendto". Though, the raspberry receive spontaneous 0x00 in different length. It's not really a problem, but weird.

Pythonaire commented 4 years ago

@jerryneedell this is the working test code between raspberry and mcu. I have to explain: I use this to transfer data between mcu and Apple HomeKit. The raspberry work as a bridge. In this example, I like to control a power switch (adafruit relay hooked up a Feather M0 RFM69). I need to by sure, that the action I trigger from the Homekit is exactly done on the mcu. So, a "simple" ACK byte (with "!" payload) as a confirmation by receiving a command is not enough for me. Because of that, I use RHDatagram and let the mcu confirm the command and actually switch state by resending a payload with "ACK" and the real switch state. ('FF' is a state request,'0' and '1' for state changing). After some testings: On raspberry side I set a time delay of 0.5 seconds between receive function call and send function call. On the mcu side, I wait 300 ms before reply with the "ACK:state" message. This seems to be a good balance. The blank byte sequences I have seen, seems to be from devices in the neighborhood, its not a problem, because I use the header information to suppress unwanted packets.

// Messageing client with the RH_RF69 class. Using RHDatagram for addressing.
// Packet header contains to, from, if and flags.
#include <Arduino.h>
#include <SPI.h>
#include <RH_RF69.h>
#include <RHDatagram.h>

// Feather M0 w/Radio
#define RF69_FREQ 433.0
#define RFM69_CS      8
#define RFM69_INT     3
#define RFM69_RST     4

#define CLIENT_ADDRESS 12 // RHDatagram
#define SERVER_ADDRESS 2 // RHDatagram

RH_RF69 rf69(RFM69_CS, RFM69_INT);
RHDatagram manager(rf69, CLIENT_ADDRESS);
uint8_t buf[RH_RF69_MAX_MESSAGE_LEN];

void setup() {
  Serial.begin(115200);
  while (!Serial) { delay(1);}
  pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, LOW); // LED off
  // manual reset the radio
  pinMode(RFM69_RST, OUTPUT);
  digitalWrite(RFM69_RST, HIGH); delay(10);
  digitalWrite(RFM69_RST, LOW); delay(10);
  if (!manager.init()) {
    Serial.println("init failed"); 
    while (1);}
    Serial.println("RFM69 radio init OK!");
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250,
  //  +13dbM (for low power module) and No encryption
  if (!rf69.setFrequency(RF69_FREQ)) 
  { 
    Serial.println("setFrequency failed"); 
    while (1);}
    Serial.println("RFM69 radio frequency OK!");
  // If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the
  // ishighpowermodule flag set like this:
  rf69.setTxPower(17, true);  // range from 14-20 for power, 2nd arg must be true for 69HCW
  // The encryption key has to be the same as the one in the server
  uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 
                    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
  rf69.setEncryptionKey(key);
};

void state(String s){
  int return_state = 0;
  if (s == "FF") {
    if (digitalRead(LED_BUILTIN)) {return_state =1;}
    else {return_state = 0;}
  }
  if (s == "1") {
      return_state = 1;
      digitalWrite(LED_BUILTIN, HIGH);}
  if (s == "0"){
      return_state = 0;
      digitalWrite(LED_BUILTIN, LOW);}
    char rpacket[60];
    int n = sprintf(rpacket, "{'ACK':%d}", return_state);
    uint8_t radiopacket[n]; // reduce to the needed packet size 'n'
    Serial.print("reply with data: ");
    Serial.println(rpacket);
    memcpy(radiopacket, (const char*)rpacket, sizeof(rpacket));
    delay(300);
    manager.sendto(radiopacket, sizeof(radiopacket), SERVER_ADDRESS);
};

String convertToString(char * a, uint8_t size)
{ int i;
String x = "";
for (i=0;i< size;i++)
{x = x +a[i];}
return x;
};

void loop() {
   if (manager.available())
  {
    // Wait for a message addressed to us from the client
    uint8_t len = sizeof(buf);
    uint8_t from = SERVER_ADDRESS;
    if (manager.recvfrom(buf, &len, &from))
    {
      Serial.print("got request from : 0x");
      Serial.print(from, HEX);
      String s = convertToString((char*)buf,len);
      Serial.print(" , string s is: ");
      Serial.print(s);
      Serial.print(", with length: ");
      Serial.println(len);
      state(s);
    }
  }
  //rf69.sleep();
};
jerryneedell commented 4 years ago

@Pythonaire That looks like good way to implement your application. It's nice that you have fond enough flexibility in the implementation to make it work for you thank you for sharing your example. I have found it very difficult to get good results with the Reliable Datagram (automatic ACKs) from an MCU running Arduino, and a Raspberry Pi because the MCU sends the ACK too quickly and there is no way to delay it. I added a delay capability in the CircuitPython implementation for that reason.

Pythonaire commented 4 years ago

@jerryneedell you are right, I messing around a lot of time with that, either the mcu was to quick or the raspberry to slow. In my case, if the "Rasperry Homekit bridge" comes up, all Homekit clients seems to floating the bridge to get the status of my mcu. All that requests are running in parallel threads. Because I using interrupt (GPIO event_detection) and let the mcu confirm each action, most requests leading into runtime error. At least the solution was simple: try/except for set GPIO event detection. I measured the "data flow" (the Homekit request a accessory state --> send "FF" to the mcu --> switch to GPIO event_detection --> receive the payload "ACK: value" --> forward to the Homekit) by time.monotonic and got values around 0.5 seconds.

Pythonaire commented 4 years ago

@jerryneedell

At least, testing over night, puting the mcu in the garden, through wall, some obstacle, distance 7 meter, I got rssi 55. The communication between raspberry and mcu seems to be stable. The delay between receive and send on the mcu is set to 200 ms. No delays (time.sleeps) on raspberry.