From add5e292087a3852d594701ccfc3dfb3a62e3676 Mon Sep 17 00:00:00 2001 From: Felix Rusu Date: Wed, 15 Dec 2021 10:35:38 -0500 Subject: [PATCH] Add RangeTest examples --- .../RangeTest_Gateway/RangeTest_Gateway.ino | 193 +++++++++++++ .../RangeTest_Transmitter.ino | 259 ++++++++++++++++++ 2 files changed, 452 insertions(+) create mode 100644 Examples/RangeTest_Gateway/RangeTest_Gateway.ino create mode 100644 Examples/RangeTest_Transmitter/RangeTest_Transmitter.ino diff --git a/Examples/RangeTest_Gateway/RangeTest_Gateway.ino b/Examples/RangeTest_Gateway/RangeTest_Gateway.ino new file mode 100644 index 0000000..dbbd1a9 --- /dev/null +++ b/Examples/RangeTest_Gateway/RangeTest_Gateway.ino @@ -0,0 +1,193 @@ +//********************************************************************************************* +// RangeTest_Gateway (to be used with an RFGateway or similar Moteino) +// Adapted from Gateway firmware sketch +// Acts as a listener for testing RF performance, in tandem with a RangeTest_Transmitter +//********************************************************************************************* +// Copyright Felix Rusu 2021, http://www.LowPowerLab.com/contact +//********************************************************************************************* +#include //https://www.github.com/lowpowerlab/rfm69 +#include //https://www.github.com/lowpowerlab/rfm69 +#include //https://github.com/olikraus/u8g2/wiki/u8g2reference fonts:https://github.com/olikraus/u8g2/wiki/fntlistall +#include //i2c scanner: https://playground.arduino.cc/Main/I2cScanner +#include //easy string manipulator: http://arduiniana.org/libraries/pstring/ +//********************************************************************************************* +// *********** IMPORTANT SETTINGS - YOU MUST CHANGE/ONFIGURE TO FIT YOUR HARDWARE ************* +//********************************************************************************************* +#define NODEID 1 //unique for each node on same network +#define NETWORKID 100 //the same on all nodes that talk to each other +#define FREQUENCY RF69_915MHZ //others: RF69_433MHZ, RF69_868MHZ, match to radio variant on your Moteino/board +#define ENCRYPTKEY "sampleEncryptKey" +#define IS_RFM69HW_HCW //assumes RFM69 HCW/HW, remove if you have RFM69 W/CW +#define ENABLE_ATC //comment out to disable AUTO TRANSMISSION CONTROL (ie. always transmit at max power) +//********************************************************************************************* +#define SERIAL_BAUD 500000 //this works with 0% error on all LowPowerLab boards, for SAMD it is irrelevant +#define LED LED_BUILTIN +#define BUZZER 6 +//*********************************************************************************************************** +#define OLED_BAUD 1600000 //fast i2c clock +#define OLED_ADDRESS 0x3C //i2c address on most small OLEDs +U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); +//*********************************************************************************************************** +#define LED_HIGH digitalWrite(LED, HIGH) +#define LED_LOW digitalWrite(LED, LOW) + +#ifdef ENABLE_ATC + RFM69_ATC radio; +#else + RFM69 radio; +#endif + +#ifdef SERIAL_BAUD + #define DEBUG(input) Serial.print(input) + #define DEBUGln(input) Serial.println(input) + #define DEBUGHex(input) Serial.print(input, HEX) + #define DEBUGFlush() Serial.flush() +#else + #define DEBUG(input) + #define DEBUGln(input) + #define DEBUGHex(input) + #define DEBUGFlush() +#endif + +#define MSG_MAX_LEN 30 //don't need more for testing purposes +#define HISTORY_LEN 9 //hold this many past messages - to display on OLED at small font size + +byte OLEDfound=false; +char buff[61]; +PString Pbuff(buff, sizeof(buff)); //easy string manipulator +byte ackCount=0; +byte lastMessageIndex = HISTORY_LEN; +byte historyLength = 0; +uint16_t senderID; +int rxRSSI; + +typedef struct { + char msg[MSG_MAX_LEN]; + int rssi; + uint16_t from; + byte pinged; + byte pingedOK; +} Message; +Message * messageHistory = new Message[HISTORY_LEN]; + +void setup() { + pinMode(BUZZER, OUTPUT); + pinMode(LED, OUTPUT); + Serial.begin(SERIAL_BAUD); + radio.initialize(FREQUENCY,NODEID,NETWORKID); +#ifdef IS_RFM69HW_HCW + radio.setHighPower(); +#endif + radio.encrypt(ENCRYPTKEY); + DEBUG(F("Listening at ")); DEBUG(radio.getFrequency()); DEBUGln(F(" Hz..")); + DEBUGln(buff); + + Pbuff = F("Listening at "); + Pbuff.print(radio.getFrequency()); + Pbuff.print(F("Hz...")); + DEBUGln(buff); + + delay(1000); //Wire apparently needs 50ms + Wire.begin(); + Wire.beginTransmission(OLED_ADDRESS); + byte error = Wire.endTransmission(); + if (error == 0) { + DEBUG(F("OLED FOUND at 0x")); DEBUGln(OLED_ADDRESS); + u8g2.begin(); + u8g2.setDisplayRotation(U8G2_R2); //if required (inside/custom mount?) + u8g2.setBusClock(OLED_BAUD); + OLEDfound = true; + } else { + DEBUGln(F("NO OLED found...")); + } + + DEBUGFlush(); + u8g2.clearBuffer(); + u8g2.setFont(u8g2_font_8x13B_tf); + u8g2.setCursor(0,10); u8g2.print("OLED PKT CAPTURE"); + u8g2.sendBuffer(); + delay(1000); +} + +void loop() { + if (radio.receiveDone()) { + LED_HIGH; + senderID = radio.SENDERID; + rxRSSI = radio.RSSI; + + DEBUG('[');DEBUG(senderID);DEBUG(F("] ")); + DEBUG((char*)radio.DATA); + DEBUG(F(" [RX_RSSI:"));DEBUG(rxRSSI);DEBUG(F("]")); + + Pbuff = ""; + Pbuff.print(radio.SENDERID); + Pbuff.print(' '); + Pbuff.print((char*)radio.DATA); + Pbuff.print(' '); + Pbuff.print(radio.RSSI); + + if (radio.ACKRequested()) { + radio.sendACK(); + + if (++ackCount%3==0) { + DEBUG(F(" Pinging [")); + DEBUG(senderID); + DEBUG(F("]:")); + delay(3); //need this when sending right after reception .. ? + //if (radio.sendWithRetry(senderID, "ACK TEST", 8, 0)) { // 0 = only 1 attempt, no retries + if (radio.sendWithRetry(senderID, "ACK TEST", 8)) { + DEBUG(F("OK!")); + Pbuff.print(" OK"); + } else { + DEBUG(F("nothing")); + Pbuff.print(" NOK"); + } + } + } + DEBUGln(); + saveToHistory(buff); + DEBUGln(); + if (OLEDfound) draw(); + DEBUGln(); + LED_LOW; + } + + //LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); +} + +void draw() { + u8g2.clearBuffer(); + u8g2.setFont(u8g2_font_tom_thumb_4x6_tf); + + byte offset=0; + for (byte i=lastMessageIndex+1; i //https://www.github.com/lowpowerlab/rfm69 +#include //https://www.github.com/lowpowerlab/rfm69 +#include //https://github.com/lowpowerlab/lowpower +#include //https://www.github.com/lowpowerlab/spiflash +//********************************************************************************************* +//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE ************ +//********************************************************************************************* +#define NODEID 2 //unique for each node on same network +#define NETWORKID 100 //the same on all nodes that talk to each other +#define GATEWAYID 1 +#define FREQUENCY RF69_915MHZ //others: RF69_433MHZ, RF69_868MHZ, match to radio variant on your Moteino/board +#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes! +#define IS_RFM69HW_HCW //assumes RFM69 HCW/HW, remove if you have RFM69 W/CW +#define ENABLE_ATC //comment out to disable AUTO TRANSMISSION CONTROL (ie. always transmit at max power) +#define ATC_RSSI -90 //noise floor is at -100dB, keep above by a margin of 10db +//********************************************************************************************* +//************ GPIO & MISC ******************************************************************** +//********************************************************************************************* +#define ACK_TIME 30 // max # of ms to wait for an ack +#define ONBOARDLED 9 // MotionMote onboard LED on D9 +#define PIR_POWER 7 //PIR is powered from D7 +#define RFM_RST A0 //used to reset the radio module at power up to ensure a clean start +#define MOTION_PIN 3 // D3 +#define MOTION_IRQ 1 // hardware interrupt 1 (D3) - where motion sensors OUTput is connected, this will generate an interrupt every time there is MOTION +#define LED_PWR 6 // separate MotionMote LED powered from D6 +#define LED_GND 5 // separate MotionMote LED ground on D5 +#define LED_HIGH digitalWrite(LED_PWR, HIGH) +#define LED_LOW digitalWrite(LED_PWR, LOW) +#define DUPLICATE_INTERVAL 10000 //avoid duplicates in 55second intervals (ie mailman sometimes spends 30+ seconds at mailbox) +#define BATT_INTERVAL 4000 // read and report battery voltage every this many ms (approx) +#define INTERNAL_AREF_V 1100 //=1.1v internal bandgap. can be adjusted to more or less to be more accurate +//********************************************************************************************* +#define DEBUG_EN //comment this out when deploying to an installed SM to save a few KB of sketch size +#ifdef DEBUG_EN + #define SERIAL_BAUD 500000 + #define DEBUG(input) Serial.print(input) + #define DEBUGln(input) Serial.println(input) + #define DEBUGHEX(input, param) Serial.print(input, param) + #define DEBUGFlush() Serial.flush() +#else + #define DEBUG(input) + #define DEBUGln(input) + #define DEBUGHEX(input, param) + #define DEBUGFlush(); +#endif +//********************************************************************************************* +#ifdef ENABLE_ATC + RFM69_ATC radio; +#else + RFM69 radio; +#endif +SPIFlash flash(SS_FLASHMEM, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL) + +volatile boolean motionDetected=false; +float batteryVolts = 5; +float temp = 0; +char BATstr[10]; //longest battery voltage reading message = 9chars +char TEMPstr[10]; +char sendBuf[32]; +void motionIRQ(void); +void checkBattery(void); + +void setup() { +#ifdef DEBUG_EN + Serial.begin(SERIAL_BAUD); +#endif + + if (flash.initialize()) { + DEBUGln(F("SPI Flash Init OK!")); + flash.sleep(); //safe to call because initialize() wakes it up + } + else DEBUGln(F("SPI Flash MEM FAIL!")); + + pinMode(MOTION_PIN, INPUT); + pinMode(ONBOARDLED, OUTPUT); + pinMode(PIR_POWER, OUTPUT); + pinMode(RFM_RST, OUTPUT); + pinMode(LED_PWR, OUTPUT); + pinMode(LED_GND, OUTPUT); + digitalWrite(PIR_POWER, HIGH); + digitalWrite(RFM_RST, LOW); + attachInterrupt(MOTION_IRQ, motionIRQ, RISING); + digitalWrite(RFM_RST, HIGH); delay(100); digitalWrite(RFM_RST, LOW); + + radio.initialize(FREQUENCY,NODEID,NETWORKID); +#ifdef IS_RFM69HW_HCW + radio.setHighPower(); +#endif + radio.encrypt(ENCRYPTKEY); + +//Auto Transmission Control - dials down transmit power to save battery (-100 is the noise floor, -90 is still pretty good) +//For indoor nodes that are pretty static and at pretty stable temperatures (like a MotionMote) -90dBm is quite safe +//For more variable nodes that can expect to move or experience larger temp drifts a lower margin like -70 to -80 would probably be better +//Always test your ATC mote in the edge cases in your own environment to ensure ATC will perform as you expect +#ifdef ENABLE_ATC + radio.enableAutoPower(ATC_RSSI); + DEBUGln("RFM69_ATC Enabled (Auto Transmission Control)\n"); +#endif + + DEBUG("Transmitting at "); DEBUG(radio.getFrequency()); DEBUGln(" Hz.."); + + radio.sendWithRetry(GATEWAYID, "START", 5); +} + +void motionIRQ() { + motionDetected=true; + DEBUGln("IRQ"); +} + +uint32_t time=0, now=0, MLO=0, BLO=0; +byte motionRecentlyCycles=0; +byte ackCount=0; +byte packetCounter=0; +void loop() { + now = millis(); + checkBattery(); + //DEBUG("Slept: ");DEBUG(now-lastSleepTime);DEBUGln("ms"); + + if (motionDetected && (time-MLO > DUPLICATE_INTERVAL)) { + packetCounter++; + LED_HIGH; //digitalWrite(LED, HIGH); + MLO = BLO = time; //save timestamp of event + sprintf(sendBuf, "%d MOTION X:%d", packetCounter, radio.getPowerLevel()); + DEBUG(sendBuf); + + if (radio.sendWithRetry(GATEWAYID, sendBuf, strlen(sendBuf))) { + DEBUG("..OK! RSSI:"); + DEBUG(radio.RSSI); + listen_a_little(); + } else DEBUG("..NOK.."); + + radio.sleep(); + LED_LOW; + } else if (time-BLO > BATT_INTERVAL) { + packetCounter++; + sprintf(sendBuf, "%d V:%s F:%s X:%d", packetCounter, BATstr, TEMPstr, radio.getPowerLevel()); + BLO = time; + + DEBUG(sendBuf); + if (radio.sendWithRetry(GATEWAYID, sendBuf, strlen(sendBuf))) { + DEBUG("..OK!"); + listen_a_little(); + } else DEBUG("..NOK.."); + } + DEBUGln(); DEBUGFlush(); + + //while motion recently happened sleep for small slots of time to better approximate last motion event + //this helps with debouncing a "MOTION" event more accurately for sensors that fire the IRQ very rapidly (ie panasonic sensors) + if (motionDetected || motionRecentlyCycles>0) { + if (motionDetected) motionRecentlyCycles=8; + else motionRecentlyCycles--; + motionDetected=false; //do NOT move this after the SLEEP line below or motion will never be detected + time = time + 250 + millis()-now; //correct millis() resonator drift, may need to be tweaked to be accurate + radio.sleep(); + LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF); + DEBUGln("WAKEUP250ms"); + } else { + time = time + 4000 + millis()-now /*+ 480*/; //correct millis() resonator drift, may need to be tweaked to be accurate + radio.sleep(); + LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF); //watchdog sleep uses extra ~4uA! + //sleep(4000); + DEBUGln("WAKEUP4s"); + } +} + +uint32_t BLR=0; +void checkBattery() +{ + if (time-BLR > 3) //only read battery every 30s or so + { + BLR = time; + long vavg = 0; + temp = 0; + ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // Ref to Vcc. Measure internal 1.1V ref + for (int j = 0; j < 10; j++) + { // Read a few times to get ADC to settle + ADCSRA |= _BV(ADSC); // Start conversion + temp += radio.readTemperature(-1); // Temperature. -1 = user cal factor, adjust for correct ambient + while (bit_is_set(ADCSRA,ADSC)); // measuring + if (j > 4) { // Skip the first 5 Vcc readings, take the next 5 + vavg = vavg + (((INTERNAL_AREF_V * 1024L) / ADC) + 5L); + } + } + batteryVolts = (vavg/5.0)/1000.0; + temp /= 10; + dtostrf(temp, 2,0, TEMPstr); + dtostrf(batteryVolts, 3,1, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message + } +} + + +void sleep(uint32_t sleepTime) { + DEBUGFlush(); + if (sleepTime < 262) { //sleeps just the MCU, using WDT (radio is not touched) + LowPower.longPowerDown(sleepTime); + } else { //sleeps MCU using the radio timer - should not be used if radio needs to be in RX mode! + uint32_t freq = radio.getFrequency(); + uint32_t remainingSleepTime = sleepTime; + + while (remainingSleepTime) { //split into sleep(60s) calls if > 60s sleep + if (remainingSleepTime > 65500) { + sleepTime = 65500; + remainingSleepTime -= 65500; + } else { + sleepTime = remainingSleepTime; + remainingSleepTime = 0; + } + + if (sleepTime%262 && sleepTime > 262*2) { + DEBUG("Sleep "); DEBUGln(sleepTime-sleepTime%262-262); DEBUGFlush(); + listenModeSleep(sleepTime-sleepTime%262-262); + DEBUG("Sleep "); DEBUGln(sleepTime%262 + 262); DEBUGFlush(); + listenModeSleep(sleepTime%262 + 262); + } else { + DEBUG("Sleep "); DEBUGln(sleepTime); DEBUGFlush(); + listenModeSleep(sleepTime); + } + + //WAKEUP happens here (must reinit!) + radio.RFM69::initialize(FREQUENCY,NODEID,NETWORKID); //call base init! + #ifdef ENCRYPTKEY + radio.encrypt(ENCRYPTKEY); + #endif + radio.setFrequency(freq); + } + } +} + +void listenModeSleep(uint16_t millisInterval) { + radio.listenModeSleep(millisInterval); + LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF ); + LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF ); + LowPower.powerDown( SLEEP_FOREVER, ADC_OFF, BOD_OFF ); + radio.endListenModeSleep(); +} + +void listen_a_little() { + radio.receiveDone(); //radio RX! + LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF); + + if (radio.receiveDone()) { + DEBUG(" [");DEBUG(radio.SENDERID);DEBUG("] "); + DEBUG((char*)radio.DATA); + DEBUG("[RX_RSSI:");DEBUG(radio.RSSI);DEBUG("]"); + + if (radio.ACKRequested()) { + radio.sendACK(); + DEBUG("[ACK sent]"); + } + time += 10; + } else time += 60; +}