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.
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 >.>
-
@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:
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>