@AWI Thanks for this great tool. It helped me very much and I tried to implement some extended features.
Here is my current result: It has now a menu interface with two buttons. You can change the pa level and the send repeat tries. Further you can measure the current of the radio module and reset the store. Configuration parameters are stored in EEPROM. See following code for more details.
/*
PROJECT: MySensors / Quality of radio transmission
PROGRAMMER: AWI (MySensors libraries), modified by Heizelmann
DATE: 20160529/ last update: 20171012
FILE: AWI_Send.ino
LICENSE: Public domain
Hardware: ATMega328p board (e.g. Arduino pro nano) w/ NRF24l01
and MySensors 2.1.1
Special:
Summary:
Sends a radio message with counter each x time to determine fault ratio with receiver
Remarks:
Fixed node-id & communication channel to other fixed node
Change log:
20160530 - added moving average on fail/ miss count, update to 2.0
20171012 - extensions by @Heizelmann
. added radio current measurement
. added pa level switching
. added send repeat option
. added LCD Display with parallel interface
. added two button to change pa level, send repeat count, reset store and display radio current
. store config params in EEPROM
. connect timeout with MY_TRANSPORT_WAIT_READY_MS
@see https://forum.mysensors.org/topic/3984/nrf24l01-connection-quality-meter
*/
//**** MySensors *****
// Enable debug prints to serial monitor
//#define MY_DEBUG
#define MY_RADIO_NRF24 // Enable and select radio type attached
//#define MY_RF24_CHANNEL 80 // radio channel, default = 76
// Set LOW transmit power level if you have an amplified NRF-module
// if power your radio separately with a good regulator you can turn up PA level.
// MIN, LOW, HIGH, MAX
#define MY_RF24_PA_LEVEL RF24_PA_MAX //default = MAX on nodes, LOW on gateway and repeater
#define MY_NODE_ID 250
#define NODE_TXT "Quality counter Q 250"
// Init timeout for gateway not reachable
#define MY_TRANSPORT_WAIT_READY_MS 20000 //ms; supported since MySensors V2.1 beta
//#define MY_PARENT_NODE_ID 32 // fixed parent to controller when 0 (else comment out = AUTO)
//#define MY_PARENT_NODE_IS_STATIC
// #define MY_RF24_CE_PIN 7 // Ceech board, 3.3v (7,8) (pin default 9,10)
// #define MY_RF24_CS_PIN 8
#define DESTINATION_NODE 0 // receiving fixed node id (default 0 = gateway)
#define MY_BAUD_RATE 115200
#include <SPI.h>
#include <MySensors.h>
//#include <LiquidCrystal_I2C.h> // LCD display with I2C interface
#include <LiquidCrystal.h> // LCD display with parallel interface
#include <OneButton.h> //from https://github.com/mathertel/OneButton/blob/master/examples/TwoButtons/TwoButtons.ino
// helpers
#define LOCAL_DEBUG
#ifdef LOCAL_DEBUG
#define Sprint(a) (Serial.print(a)) // macro as substitute for print, enable if no print wanted
#define Sprintln(a) (Serial.println(a)) // macro as substitute for println
#else
#define Sprint(a) // enable if no print wanted -or-
#define Sprintln(a) // enable if no print wanted
#endif
// MySensors sensor
#define COUNTER_CHILD 0
// send constants and variables
int messageCounter = 0 ;
const int messageCounterMax = 100 ; // maximum message counter value
const unsigned counterUpdateDelay = 500 ; // send every x ms and sleep in between
// receive constants and variables
boolean failStore[messageCounterMax] ; // moving average stores & pointers
int failStorePointer = 0 ;
boolean missedStore[messageCounterMax] ;
int missedStorePointer = 0 ;
int newMessage = 0 ;
int lastMessage = -1 ;
int missedMessageCounter = 0 ; // total number of messages in range (messageCounterMax)
int failMessageCounter = 0 ; // total number of messages in range (messageCounterMax)
uint8_t parent = 0 ; // parent node-id
// Loop delays
const unsigned long displayInterval = 1000UL ; // display update in ms
unsigned long lastDisplayUpdate = 0 ; // last update for loop timers
// standard messages
MyMessage counterMsg(COUNTER_CHILD, V_PERCENTAGE); // Send value
// ***** LCD
#define LCD_COLS 16
#define LCD_ROWS 2
//LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address
//LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the LCD I2C address
LiquidCrystal lcd(8, 7, 6, 5, 4, 3); // LCD with paralell interface
#define CURRENT_PIN A5
#define BUTTON1_PIN A1
#define BUTTON2_PIN A2
OneButton button1(BUTTON1_PIN, true); //PullUp, Activelow
OneButton button2(BUTTON2_PIN, true); //PullUp, Activelow
// Options
#define EEPROM_FLAG 0
#define EEPROM_PA_LEVEL 1
#define EEPROM_SEND_REPEATS 2
#define LEVEL_ITEMS 4
const char *paLevelNames[LEVEL_ITEMS] = { "MIN", "LOW", "HIGH", "MAX" };
uint8_t rf24palevel = MY_RF24_PA_LEVEL;
#define MIN_REPEAT_DELAY 50
#define MAX_REPEAT_DELAY 200
uint8_t sendRepeats = 3;
// Current measurement
#define CORRECTION 9.285
int currentMa;
// Menu
#define MENU_TIMEOUT 10000
enum mode { STATE_RUN, STATE_RUN2, STATE_PALEVEL, STATE_RESEND};
mode opState = STATE_RUN;
unsigned long lastClickedMillis;
boolean dspRefresh = true;
void before() {
pinMode(CURRENT_PIN, INPUT);
analogReference(INTERNAL);
// Wire.begin(); // I2C
// ** LCD display **
lcd.begin(LCD_COLS, LCD_ROWS);
//lcd.setBacklight(HIGH);
lcd.home();
lcd.setCursor(0, 0);
lcd.print("AWIQuality nRF24");
lcd.setCursor(0, 1);
lcd.print("Connect...");
initStore();
delay(1000);
button1.attachClick(onButton1Pressed);
button1.attachLongPressStart(onButton1LongPressed);
button2.attachClick(onButton2Pressed);
if (loadState(EEPROM_FLAG) == 0xFF) {
rf24palevel = loadState(EEPROM_PA_LEVEL);
sendRepeats = loadState(EEPROM_SEND_REPEATS);
} else {
saveState(EEPROM_FLAG, 0xFF);
saveState(EEPROM_PA_LEVEL, rf24palevel);
saveState(EEPROM_SEND_REPEATS, sendRepeats);
}
}
void presentation() {
present(COUNTER_CHILD, S_DIMMER, NODE_TXT) ; // counter uses percentage from dimmer value
}
void loop() {
button1.tick();
button2.tick();
if (opState != STATE_RUN && opState != STATE_RUN2 && millis() - lastClickedMillis > MENU_TIMEOUT) {
Sprintln("Timeout");
setRFLevel(rf24palevel);
saveState(EEPROM_PA_LEVEL, rf24palevel);
saveState(EEPROM_SEND_REPEATS, sendRepeats);
opState = STATE_RUN;
}
if (dspRefresh) LCD_local_display();
if ( opState == STATE_RUN ) {
Sprint("count:") ; Sprintln(messageCounter++) ;
missedStore[failStorePointer] = false ; // set slot to false (ack message needs to set) ;
boolean success = failStore[failStorePointer] = resend(counterMsg.setDestination(DESTINATION_NODE).set(failStorePointer), sendRepeats); // send to destination with ack
currentMa = analogRead(CURRENT_PIN) / CORRECTION;
if (!success) {
failMessageCounter++ ;
Sprint("Fail on message: ") ; Sprint(failStorePointer) ;
Sprint(" # ") ; Sprintln(failMessageCounter);
}
failStorePointer++ ;
if (failStorePointer >= messageCounterMax) {
failStorePointer = 0 ; // wrap counter
}
parent = getParentNodeId(); // get the parent node (0 = gateway)
wait(counterUpdateDelay) ; // wait for things to settle and ack's to arrive
dspRefresh = true;
}
}
void receive(const MyMessage &message) { // Expect few types of messages from controller
newMessage = message.getInt(); // get received value
switch (message.type) {
case V_PERCENTAGE:
missedStore[newMessage] = true ; // set corresponding flag to received.
if (newMessage > lastMessage) { // number of messages missed from lastMessage (kind of, faulty at wrap)
Sprint("Missed messages: ") ; Sprintln( newMessage - lastMessage - 1) ;
missedMessageCounter += newMessage - lastMessage - 1 ;
}
lastMessage = newMessage ;
break ;
default: break ;
}
}
// calculate number of false values in array
// takes a lot of time, but who cares...
int getCount(boolean countArray[], int size) {
int falseCount = 0 ;
for (int i = 0 ; i < size ; i++) {
falseCount += countArray[i] ? 0 : 1 ;
}
return falseCount ;
}
void initStore() {
for (int i = 0 ; i < messageCounterMax ; i++) { // init stores for moving averages
failStore[i] = true ;
missedStore[i] = true ;
}
missedMessageCounter = failMessageCounter = 0;
}
void setRFLevel(uint8_t rfLevel) {
Sprint("Set RF Level to "); Sprintln(rf24palevel);
uint8_t rfsetup = (((MY_RF24_DATARATE) & 0b10 ) << 4) | (((MY_RF24_DATARATE) & 0b01 ) << 3) | (((rfLevel << 1))) + 1; // +1 for Si24R1;
RF24_setRFSetup(rfsetup);
}
boolean resend(MyMessage &msg, int repeats) {
int repeat = 0;
int repeatdelay = 0;
boolean sendOK = false;
while ((sendOK == false) and (repeat < repeats)) {
if (send(msg, true)) { //send
sendOK = true;
} else {
sendOK = false;
repeatdelay += random(MIN_REPEAT_DELAY, MAX_REPEAT_DELAY);
}
repeat++;
delay(repeatdelay);
}
return sendOK;
}
void onButton1Pressed() {
dspRefresh = true;
lcd.clear(); lcd.home();
switch (opState) {
case STATE_RUN:
opState = STATE_PALEVEL;
break;
case STATE_RUN2:
opState = STATE_RUN;
break;
case STATE_PALEVEL:
opState = STATE_RESEND;
break;
case STATE_RESEND:
setRFLevel(rf24palevel);
saveState(EEPROM_PA_LEVEL, rf24palevel);
saveState(EEPROM_SEND_REPEATS, sendRepeats);
opState = STATE_RUN;
break;
}
lastClickedMillis = millis();
}
void onButton2Pressed() {
dspRefresh = true;
lcd.clear(); lcd.home();
switch (opState) {
case STATE_RUN:
opState = STATE_RUN2;
break;
case STATE_RUN2:
opState = STATE_RUN;
break;
case STATE_PALEVEL:
rf24palevel++; if (rf24palevel > 3) rf24palevel = 0;
break;
case STATE_RESEND:
sendRepeats++; if (sendRepeats > 3) sendRepeats = 1;
break;
}
lastClickedMillis = millis();
}
void onButton1LongPressed() {
initStore();
}
void LCD_local_display(void) {
dspRefresh = false;
char buf[LCD_COLS + 1]; // buffer for max 16 char display
switch (opState) {
case STATE_RUN:
lcd.setCursor(0, 0);
snprintf(buf, sizeof buf, "P%-3dFail%4d%3d%%", parent, failMessageCounter, getCount(failStore, messageCounterMax));
lcd.print(buf);
lcd.setCursor(0, 1);
snprintf(buf, sizeof buf, "D%-3dMiss%4d%3d%%", DESTINATION_NODE , missedMessageCounter, getCount(missedStore, messageCounterMax));
lcd.print(buf);
break;
case STATE_RUN2:
lcd.setCursor(0, 0);
snprintf(buf, sizeof buf, "PA Level = %s", paLevelNames[rf24palevel]);
lcd.print(buf);
lcd.setCursor(0, 1);
snprintf(buf, sizeof buf, "Current = %dmA", currentMa);
lcd.print(buf);
break;
case STATE_PALEVEL:
lcd.setCursor(0, 0);
snprintf(buf, sizeof buf, "PA Level = %s", paLevelNames[rf24palevel]);
lcd.print(buf);
break;
case STATE_RESEND:
lcd.setCursor(0, 0);
snprintf(buf, sizeof buf, "Send repeats = %d", sendRepeats);
lcd.print(buf);
break;
}
}