NRF24-Autoack



  • Hello,

    as I understand the NRF24-driver relies on auto-ack to deliver a packet on the lowest level before resolving undeliverable packets at the transport layer.

    For some reason my modules don't work very well with auto ack ( https://forum.mysensors.org/topic/1664/which-are-the-best-nrf24l01-modules/276 )

    I have not fully tested the results of disabling auto ack but I wan't to know/get an better insight at what level I might mitigate my issue. To rely on the transport layer seems to be the wrong solution as 3-5 consecutive packet losses results in an expansive parent recovery procedure.

    Is there anybody who had a similar issue or could point out some idea on how to implement an acknowledgement on the RF24 driver level? The main problem would be the receiving node which has to know where to send the ack back.

    It should be no mayor problem to use

    LOCAL bool RF24_sendMessage( uint8_t recipient, const void* buf, uint8_t len ) {
    

    and before returning switch to listen mode and wait for a few millis for an manual acknowledge packet.

    I would be happy about any suggestions on how to resolve this issue.

    PS: Is there any callgraph on the complex mysensors layer or anybody knows a programm to browse trough the code in an call graph fashin? e.g. high level send -> transportlayer send -> rf-layer send .. etc.? To be honest the statemachine is not that easy to understand.

    Greetings Marc



  • As I try to understand what is happening inside the library I got a little diagram. My next goal would be to implement an software autoack on a low level and I would be happy to receive some input.

    0_1469648485549_mysensors.png

    autoack and ack refer the the low level ack-feature of the nrf24

    I am still not sure if a low level software autoack is the feature I want to have but the hardware autoack seems to be blocking in a while loop until the successful ack is received anyway.

    	do {
    		status = RF24_getStatus();
    	} while  (!(status & ( _BV(MAX_RT) | _BV(TX_DS) )) && timeout--);
    

    Waiting for the soft-ack seems to be easy:

    LOCAL bool RF24_sendMessage( uint8_t recipient, const void* buf, uint8_t len ) {
    
            .... sending normal packet here ....
    	RF24_startListening();
    	
    	// <---> WAIT HERE FOR SOFT-ACK ?!
    	
    	// true if message sent and not timeout
    	return (status & _BV(TX_DS) && timeout);
    }
    

    Finding out who send the message in the "RF24_readMessage.h" seems to be the hard part .. although "MyMessage.h" contains some hint:

    	uint8_t last;            	 // 8 bit - Id of last node this message passed
    

    Is it correct to assume that the first byte of the payload is the sending node of the received packet? The node-id reflect the LSB (lowest 8 bit) of the default node-address

    #ifndef MY_RF24_BASE_RADIO_ID
    #define MY_RF24_BASE_RADIO_ID 0x00,0xFC,0xE1,0xA8,0xA8
    #endif
    

    Well .. still waiting on some comments >.>


  • Mod

    @cimba007 the mysensors library already supports requesting a software ack from the target node. A message can be flagged (command_ack_payload) so the target will send the same message back as confirmation it did arrive at the target. A node requesting a software ack should therefore parse incoming messages scanning for the ack response.
    You seem to have a software background, so I guess you can figure out the rest by looking at the code.
    Good luck!



  • @Yveaux Thank you Yveaux. I didn't know that you get whole exact same message as ack so it would be very easy to confirm correct reception. I will try it out! Might be much easier then implementing it on low level.

    		#else
    			// Call incoming message callback if available
    			if (receive) {
    				receive(_msg);
    			}
    		#endif
    

    Got it 😉



  • My current design for utilizing autoack in software.

    This solution does not depend on the receive function but depends on advanced parameters for wait. It relies on receiving the same message in the wait-function (same command and type). In printmsg function of Helper.ino I tried to do some further checks for sender and destination and ackFlag.

    Maybe some1 could create a stripped down version which might be included in the mysensors-examples.

    My "controller" is a "GatewayESP8266MQTTClient" Sketch on a second node.

    wait(200,message.getCommand(),message.type)
    

    MainFile.ino

    #include <Streaming.h>
    #include "Statistic.h"
    
    
    /**
     * The MySensors Arduino library handles the wireless radio link and protocol
     * between your home built sensors/actuators and HA controller of choice.
     * The sensors forms a self healing radio network with optional repeaters. Each
     * repeater and gateway builds a routing tables in EEPROM which keeps track of the
     * network topology allowing messages to be routed to nodes.
     *
     * Created by Henrik Ekblad <henrik.ekblad@mysensors.org>
     * Copyright (C) 2013-2015 Sensnology AB
     * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors
     *
     * Documentation: http://www.mysensors.org
     * Support Forum: http://forum.mysensors.org
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * version 2 as published by the Free Software Foundation.
     *
     *******************************
     *
     * REVISION HISTORY
     * Version 1.0 - Henrik EKblad
     * 
     * DESCRIPTION
     * Example sketch showing how to measue light level using a LM393 photo-resistor 
     * http://www.mysensors.org/build/light
     */
    
    #define MY_NODE_ID 10
    #define MY_BAUD_RATE 57600
    
    // Enable debug prints to serial monitor
    //#define MY_DEBUG 
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    #include <SPI.h>
    #include <MySensors.h>  
    
    #define LIGHT_SENSOR_ANALOG_PIN A3
    #define MICRO_SENSOR_ANALOG_PIN A1
    
    unsigned long SLEEP_TIME = 1000; // Sleep time between reads (in milliseconds)
    
    #define CHILD_ID_LIGHT 0
    #define CHILD_ID_MICRO 0
    #define TRIPPED_THRESHOLD 50
    
    MyMessage msg_light(CHILD_ID_LIGHT, V_LIGHT_LEVEL); // 23
    MyMessage msg_micro(CHILD_ID_MICRO, V_TRIPPED);     // 16
    MyMessage msg_micro_debug(0,V_VAR1);   // 24
    MyMessage msg_obstx_debug(0,V_VAR2);   // 25
    
    unsigned long msg_micro_send;
    
    void before()
    {
      // LightSensor
      pinMode(A3,INPUT_PULLUP);
      pinMode(A2,OUTPUT);
      digitalWrite(A2,LOW);  
    
      // Microwave
      pinMode(5,OUTPUT);        // VCC BISS0001
      digitalWrite(5,HIGH);
      
      pinMode(6,OUTPUT);        // Enabled
      digitalWrite(6,LOW);      // Enable
      
      pinMode(7,OUTPUT);        // GND
      digitalWrite(7,LOW);
      
      pinMode(8,OUTPUT);        // VCC Radar
      digitalWrite(8,HIGH);
    
      pinMode(A1,INPUT);        // PIR 2nd Amplification Stage
    
      // Other
    }
    void setup()
    {
     
    }
    void presentation()  {
      // Send the sketch version information to the gateway and Controller
      sendSketchInfo("Microwave+Light", "1.0");
    
      // Register all sensors to gateway (they will be created as child devices)
      // https://www.mysensors.org/download/serial_api_20#sensor-type
      present(CHILD_ID_LIGHT, S_LIGHT_LEVEL);
      present(CHILD_ID_MICRO, S_MOTION);
      //present(0, S_ARDUINO_NODE);
      
    }
    
    void loop()      
    {     
      // Report VCC
      static long vcc = readVcc();
      static int vccpercent = map(vcc,1800,3280,0,100);
      sendBatteryLevel(max(min(vccpercent,100),0),false);
      Serial << "| vcc: ";
      p(F("%4d"),vcc);
      Serial << " ";
      // Required for ack
      //wait(100);
    
      // Report LightLevel
      analogRead(LIGHT_SENSOR_ANALOG_PIN);
      int lightLevel_raw = analogRead(LIGHT_SENSOR_ANALOG_PIN);
      int lightLevel = (1023-lightLevel_raw)/10.23; // as of 1023 !!
      Serial << "| light_raw: ";
      p(F("%4d"),lightLevel_raw);
      Serial << " ";
      Serial << "| light: ";
      p(F("%3d"),lightLevel);
      Serial << " ";
      send(msg_light.set(lightLevel),false);
    
      // Report WirelessLink Information
      Serial << "| observe_tx: ";
      uint8_t obstx = RF24_readByteRegister(OBSERVE_TX);
      p(F("%X"),obstx);
      Serial << " ";
      send(msg_obstx_debug.set(0x0F&obstx),false);
     
      // Report Microwave
      Statistic mw_s;
      mw_s.clear();
      
      delay(90);
      analogRead(MICRO_SENSOR_ANALOG_PIN);
      delay(10);
      for(int i = 0; i < 1000; i++)
      {
        mw_s.add(analogRead(MICRO_SENSOR_ANALOG_PIN));
        delay(1);
      }
      Serial << "| mw_raw: ";
      int stddev = mw_s.pop_stdev();
      p(F("%4d"),stddev);
      Serial << " ";
      
      Serial << "| mw_min: ";
      int minimum = mw_s.minimum();
      p(F("%4d"),minimum);
      Serial << " ";
    
      Serial << "| mw_max: ";
      int maximum = mw_s.maximum();
      p(F("%4d"),maximum);
      Serial << " ";
      
      Serial << "| mw: " << (stddev > TRIPPED_THRESHOLD ? "1" : "0") << " ";
      send(msg_micro_debug.set(stddev),false);
    
      msg_micro_send = micros();
      send(msg_micro.set(stddev > TRIPPED_THRESHOLD ? "1" : "0"),true);
      
      
      if(isTransportOK()) 
      {
        if(!waitforack(200,msg_micro)) // time, msg
        {
          Serial.print(" | no ack! ");
        }
        else
        {
          Serial.print(" | msg ok! ");
          Serial.print(" | "); Serial.print(micros()-msg_micro_send); Serial.print("uS");
        }
      }
      // waitforack includes time for repair transport
    
      Serial << endl;
    }
    
    bool waitforack(unsigned long ms, MyMessage &message)
    {
      
        bool ok = false;
        uint8_t c = 10;
    
        while(!ok && c)
        {
          // Ack?!
          if(wait(200,message.getCommand(),message.type))
          {
            Serial.print(" | rpd: "); Serial.print(RF24_readByteRegister(RPD)); 
            return true;
          }
          else
          {
            if(isTransportOK()) 
            {
              Serial.print(" | Transport broken! ");
              wait(1000);
            }
            Serial.print(" ~ ");  
            send(message,true);
          } 
          c--;
        }
        if(c == 0)
          return false;
    }
    
    
    // https://forum.mysensors.org/topic/3463/m_ack_variable-or-m_set_variable/2
    void receive(const MyMessage &message) {
      /* Don't do much here, will delay "wait" */
      //Serial.println();
      //printmsg(message,&msg_micro,msg_micro_send);
    }
    

    Helper.ino

    long readVcc() {
      // Read 1.1V reference against AVcc
      // set the reference to Vcc and the measurement to the internal 1.1V reference
      #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
        ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
      #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
        ADMUX = _BV(MUX5) | _BV(MUX0);
      #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
        ADMUX = _BV(MUX3) | _BV(MUX2);
      #else
        ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
      #endif  
    
      delay(2); // Wait for Vref to settle
      ADCSRA |= _BV(ADSC); // Start conversion
      while (bit_is_set(ADCSRA,ADSC)); // measuring
    
      uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
      uint8_t high = ADCH; // unlocks both
    
      long result = (high<<8) | low;
    
      result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
      //result *= 1.0637;
      return result; // Vcc in millivolts
    }
    
    #include <stdarg.h>
    void p(const __FlashStringHelper *fmt, ... ){
      char buf[128]; // resulting string limited to 128 chars
      va_list args;
      va_start (args, fmt);
    #ifdef __AVR__
      vsnprintf_P(buf, sizeof(buf), (const char *)fmt, args); // progmem for AVR
    #else
      vsnprintf(buf, sizeof(buf), (const char *)fmt, args); // for the rest of the world
    #endif
      va_end(args);
      Serial.print(buf);
    }
    
    void mysleep(int SLEEP_TIME)
    {
        if(isTransportOK()){
          Serial << "| wait ";
          wait(25);
          Serial << "| zZz > ";
    
          sleep(SLEEP_TIME);
          Serial << "| < zZz " << endl;
        } 
        else {
          wait(1000);
        }
    }
    
    void printmsg(const MyMessage &message, const MyMessage *reference, unsigned long starttime)
    {
      unsigned long s = micros();
      Serial.print("-------------Message Information:-------------"); Serial.println();
      Serial.print("last:        "); p(F("%8d"),message.last);        Serial.println();
      Serial.print("sender:      "); p(F("%8d"),message.sender);      Serial.println();
      Serial.print("destination: "); p(F("%8d"),message.destination); Serial.println();
      Serial.print("command:     "); 
      switch(message.getCommand())
      {
        case 0: Serial.print("presentation (0)");                     Serial.println(); break;
        case 1: Serial.print("set (1)");                              Serial.println(); break;
        case 2: Serial.print("req (2)");                              Serial.println(); break;
        case 3: Serial.print("internal (3)");                         Serial.println(); break;
        case 4: Serial.print("stream (4)");                           Serial.println(); break;
      }
      Serial.print("isAck:       "); if(message.isAck()) { Serial.print("    true"); } else { Serial.print("   false"); } Serial.println();
      Serial.print("type:        "); p(F("%8d"),message.type);        Serial.println();
      Serial.print("sensor:      "); p(F("%8d"),message.sensor);      Serial.println();
      const char mybuffer[25];
      message.getString(mybuffer);
      Serial.print("payload:     "); Serial.print(mybuffer);          Serial.println();
      if(starttime != 0)
      {
        Serial.print("RoundTripT.  "); p(F("%8ld"),(s-starttime));Serial.println(" uS");
        if(&reference)
        {
          const MyMessage m = *reference; 
          if
          (
            (message.getCommand() == reference->getCommand())
          &&(message.sender       == reference->destination)
          &&(  (message.isAck() == 1) && mGetRequestAck(m)  )
          )
          Serial.print("identical!");                                   Serial.println();
          Serial.print("0x");p(F("%02X"),reference->command_ack_payload);Serial.println();
        }
      }
      Serial.print("----------------------------------------------"); Serial.println();
    }
    
    void printmsg(const MyMessage &message)
    {
      printmsg(message, NULL, 0);
    }
    void printmsg(const MyMessage &message, unsigned long starttime)
    {
      printmsg(message, NULL, starttime);
    }
    

    Example Output:

    // no transmission error
    | vcc: 3280 | light_raw:  729 | light:  28 | observe_tx: 0 | mw_raw:   21 | mw_min:  424 | mw_max:  565 | mw: 0  | rpd: 1 | msg delivered!  | 28200uS
    
    // one retry
    | vcc: 3280 | light_raw:  728 | light:  28 | observe_tx: 0 | mw_raw:   37 | mw_min:  387 | mw_max:  571 | mw: 0  ~  | rpd: 1 | msg delivered!  | 212712uS
    

    "long term"-update:

    0_1469903532785_mysensors_custom_ack.PNG

    You can see that channel 16 got at least loss (channel 16 = software autoack and retry)



  • I stripped down my example and tried to capsulate it in its own wrapper class. This is by no means complete or ready to use .. it is just there to give interested people some kind of input on how to work with the wait/autoack stuff if you can't use your radios autoretry feature like me.

    If you need some comments to understand parts of the code feel free to ask.

    LightSensorSoftwareAckNRF24_class.ino

    #define MY_NODE_ID 10
    #define MY_BAUD_RATE 57600
    
    // Enable debug prints to serial monitor
    //#define MY_DEBUG 
    
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    #include <SPI.h>
    #include <MySensors.h>  
    
    #define LIGHT_SENSOR_ANALOG_PIN A3
    
    #define MINUTES 60000UL
    #define CHILD_ID_LIGHT 0
    
    #include "helper.cpp"
    
    void before()
    {
      // LightSensor
      // http://akizukidenshi.com/download/ds/senba/GL55%20Series%20Photoresistor.pdf
      // GL5549
      pinMode(A3,INPUT_PULLUP); // 20-50 kOhm
      pinMode(A2,OUTPUT);
      digitalWrite(A2,LOW);  
    }
    
    
    MySensorChild photoSensor = MySensorChild(CHILD_ID_LIGHT,S_LIGHT_LEVEL,"Photoresistor GL5549");
    
    void setup()
    {
      
    }
    
    void presentation()  {
      // Send the sketch version information to the gateway and Controller
      sendSketchInfo("LightClass_Test", "0.1");
      photoSensor.do_present();
    }
      
    void loop()      
    {     
      // Set Message
      photoSensor.message()->set(random(0,100));
      // Print Result
      photoSensor.print(photoSensor.waitsend(250));
      delay(1000);
    }
    

    helper.cpp

    #include "core/MySensorsCore.h"
    #include "core/MyTransport.h"
    #include "drivers/RF24/RF24.h"
    #include <stdarg.h>
    
    class MySensorChild
    {  
      public:
        typedef struct {
          uint8_t rpd;
          unsigned long long elapsedtime;
          uint8_t success;
          uint8_t retrycount;
        } return_struct;
    
        void print(return_struct r)
        {
          Serial.print(r.success == 1 ? "success" : "fail   "); Serial.print(" | ");
          Serial.print("time: "); p(F("%8d"),r.elapsedtime);    Serial.print("uS | ");
          Serial.print("retr.: "); p(F("%2d"),r.retrycount);    Serial.print(" | ");
          Serial.print("rpd: "); Serial.print(r.rpd);           Serial.print(" | ");
          Serial.println();
        }
      
      public: 
        MySensorChild(uint8_t childid, uint8_t valuetype, char * description)
        {
          // Base Values that define a Message
          this->description = description;
          this->childid = childid;
          this->valuetype = valuetype;
          this->message_internal = MyMessage(childid, valuetype);
        }
    
        MyMessage * MySensorChild::message()
        {
          return &this->message_internal;
        }
    
        void do_present()
        {
          present(this->childid, this->valuetype, this->description);  
        }
        
    
        return_struct MySensorChild::waitsend(unsigned long ms)
        { 
            bool ok = false;
            const uint8_t retrycountlimit = 10;
            unsigned long long starttime = micros();
            return_struct r;
            
            send(message_internal,true);
            
            uint8_t c = retrycountlimit;
            while(!ok && c)
            {
              // Ack?!
              if(wait(ms,message_internal.getCommand(),message_internal.type))
              {
                r.rpd = RF24_readByteRegister(RPD);
                r.elapsedtime = micros()-starttime;
                r.success = true;
                r.retrycount = retrycountlimit-c;
                return r;
                //Serial.print(" | rpd: "); Serial.print(RF24_readByteRegister(RPD)); 
                // Return!
                //return true;
              }
              else
              {
                if(!isTransportOK()) 
                {
                  Serial.print(" | Transport broken! ");
                  wait(100);
                }
                //Serial.print("~");  
                send(message_internal,true);
              } 
              c--;
            }
            if(c == 0)
            {
              r.elapsedtime = micros()-starttime;
              r.success = false;
              r.retrycount = retrycountlimit-c;
              return r;
              //return false;
            }
        }
    
      private:
        // Base Values that define a Message
        MyMessage message_internal;
        char * description;
        uint8_t childid;
        uint8_t valuetype;
        
        unsigned long lastSend;
    
        void p(const __FlashStringHelper *fmt, ... ){
          char buf[128]; // resulting string limited to 128 chars
          va_list args;
          va_start (args, fmt);
        #ifdef __AVR__
          vsnprintf_P(buf, sizeof(buf), (const char *)fmt, args); // progmem for AVR
        #else
          vsnprintf(buf, sizeof(buf), (const char *)fmt, args); // for the rest of the world
        #endif
          va_end(args);
          Serial.print(buf);
        };
    };
    

    Example output (mydebug enabled @ gateway)

    success | time:   16mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   17mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   13mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   10mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   12mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   11mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   12mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   12mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   10mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   13mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   12mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   10mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   12mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   11mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   12mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:   12mS | retr.:  0 | rpd: 1 | <\r><\n>
    fail    | time: 2504mS | retr.: 10 | rpd: 0 | <\r><\n>
    fail    | time: 2505mS | retr.: 10 | rpd: 0 | <\r><\n>
    

    Always a good idea to disable debugging if you are happy with the performance. Debugging output on serial on the mqtt-gateway (esp8266) takes quite a while.

    success | time:    4mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:    2mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:    4mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:    4mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:    4mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:    2mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:    4mS | retr.:  0 | rpd: 1 | <\r><\n>
    success | time:    5mS | retr.:  0 | rpd: 1 | <\r><\n>
    

 

347
Online

7.9k
Users

8.7k
Topics

93.6k
Posts