RFM69_LowPowerLab/Examples/MotionMote/MotionMote.ino

257 lines
10 KiB
Arduino
Raw Normal View History

// **********************************************************************************
2020-11-17 20:07:23 +00:00
// Sample RFM69 sender/node sketch for the MotionMote R4 With Panasonic PIR sensors
// http://lowpowerlab.com/motion
// PIR motion sensor connected to D3 (INT1)
// When RISE happens on D3, the sketch transmits a "MOTION" msg to receiver Moteino Gateway and goes back to sleep
// In sleep mode, Moteino + PIR motion sensor use ~2uA
// Get the RFM69 and SPIFlash library at: https://github.com/LowPowerLab/
// Make sure you adjust the settings in the configuration section below !!!
// (C) 2020, Felix Rusu, LowPowerLab.com
// **********************************************************************************
// License
// **********************************************************************************
// This program is free software; you can redistribute it
// and/or modify it under the terms of the GNU General
// Foundation; either version 3 of the License, or
2020-11-17 20:07:23 +00:00
// Public License as published by the Free Software
// (at your option) any later version.
//
// This program is distributed in the hope that it will
// be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A
// PARTICULAR PURPOSE. See the GNU General Public
// License for more details.
2020-11-17 20:07:23 +00:00
//
// You should have received a copy of the GNU General
// Public License along with this program.
// If not, see <http://www.gnu.org/licenses/>.
//
// Licence can be viewed at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Please maintain this license information along with authorship
// and copyright notices in any redistribution of this code
// **********************************************************************************
2020-11-17 20:07:23 +00:00
#include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
#include <RFM69_ATC.h>//get it here: https://www.github.com/lowpowerlab/rfm69
#include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
#include <LowPower.h> //get library from: https://github.com/lowpowerlab/lowpower
//writeup here: http://www.rocketscream.com/blog/2011/07/04/lightweight-low-power-arduino-library/
#include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
//*********************************************************************************************
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE *************
//*********************************************************************************************
2020-11-18 01:21:18 +00:00
#define NODEID 123 //unique for each node on same network
2013-11-10 05:26:34 +00:00
#define NETWORKID 100 //the same on all nodes that talk to each other
2020-11-17 20:07:23 +00:00
#define GATEWAYID 1
2013-11-10 05:26:34 +00:00
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
#define FREQUENCY RF69_915MHZ
#define ENCRYPTKEY "sampleEncryptKey" //exactly the same 16 characters/bytes on all nodes!
#define ENABLE_ATC //comment out this line to disable AUTO TRANSMISSION CONTROL
2020-11-18 01:21:18 +00:00
#define ATC_RSSI -90
//*********************************************************************************************
#define ACK_TIME 30 // max # of ms to wait for an ack
2020-11-18 01:21:18 +00:00
#define ONBOARDLED 9 // same as LED_BUILTIN on Moteinos (D9)
#define PIR_POWER 7 // PIR is powered from D7
#define MOTION_PIN 3 // PIR output
#define MOTION_IRQ 1 // hardware interrupt 1 (D3) - where motion sensors OUTput is connected, this will generate an interrupt every time there is MOTION
2016-08-26 20:18:29 +01:00
#define DUPLICATE_INTERVAL 20000 //avoid duplicates in 55second intervals (ie mailman sometimes spends 30+ seconds at mailbox)
#define BATT_INTERVAL 300000 // read and report battery voltage every this many ms (approx)
2020-11-18 01:21:18 +00:00
const uint16_t INTERNAL_AREF_V = 1100; //measured internal 1.1v bandgap
2020-11-17 20:07:23 +00:00
#define LED_PWR 6
#define LED_GND 5
#define LED_HIGH digitalWrite(LED_PWR, HIGH)
#define LED_LOW digitalWrite(LED_PWR, LOW)
2016-08-26 20:18:29 +01:00
2020-11-17 20:07:23 +00:00
#define SERIAL_EN //comment this out when deploying to an installed SM to save a few KB of sketch size
#define SERIAL_BAUD 115200
#ifdef SERIAL_EN
#define DEBUG(input) {Serial.print(input); delay(1);}
#define DEBUGln(input) {Serial.println(input); delay(1);}
2016-08-26 20:18:29 +01:00
#define DEBUGFlush() { Serial.flush(); }
#else
#define DEBUG(input);
#define DEBUGln(input);
2016-08-26 20:18:29 +01:00
#define DEBUGFlush();
#endif
#ifdef ENABLE_ATC
RFM69_ATC radio;
#else
RFM69 radio;
#endif
2016-08-26 20:18:29 +01:00
2020-11-17 20:07:23 +00:00
SPIFlash flash(SS_FLASHMEM, 0xEF30); //EF30 for 4mbit Windbond chip (W25X40CL)
2016-08-26 20:18:29 +01:00
2013-11-10 05:26:34 +00:00
volatile boolean motionDetected=false;
float batteryVolts = 5;
2020-11-17 20:07:23 +00:00
float temp = 0;
2016-01-12 04:10:02 +00:00
char BATstr[10]; //longest battery voltage reading message = 9chars
2020-11-17 20:07:23 +00:00
char TEMPstr[10];
char sendBuf[32];
byte sendLen;
2013-11-10 05:26:34 +00:00
void motionIRQ(void);
void checkBattery(void);
2013-11-10 05:26:34 +00:00
void setup() {
Serial.begin(SERIAL_BAUD);
radio.initialize(FREQUENCY,NODEID,NETWORKID);
2020-11-17 20:07:23 +00:00
radio.setHighPower(); //for RFM69HCW only!
2013-11-10 05:26:34 +00:00
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
2016-04-13 14:52:44 +01:00
radio.enableAutoPower(ATC_RSSI);
#endif
2020-11-17 20:07:23 +00:00
pinMode(MOTION_PIN, INPUT);
attachInterrupt(MOTION_IRQ, motionIRQ, RISING);
2013-11-10 05:26:34 +00:00
char buff[50];
sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915);
DEBUGln(buff);
pinMode(ONBOARDLED, OUTPUT);
radio.sendWithRetry(GATEWAYID, "START", 5);
2020-11-17 20:07:23 +00:00
#ifdef ENABLE_ATC
DEBUGln("RFM69_ATC Enabled (Auto Transmission Control)\n");
#endif
2016-08-26 20:18:29 +01:00
2020-11-17 20:07:23 +00:00
if (flash.initialize()) flash.sleep();
pinMode(PIR_POWER, OUTPUT);
digitalWrite(PIR_POWER, HIGH);
pinMode(LED_PWR, OUTPUT);
pinMode(LED_GND, OUTPUT);
2013-11-10 05:26:34 +00:00
}
2020-11-17 20:07:23 +00:00
void motionIRQ() {
2013-11-10 05:26:34 +00:00
motionDetected=true;
DEBUGln("IRQ");
2013-11-10 05:26:34 +00:00
}
2015-11-11 13:48:18 +00:00
uint16_t batteryReportCycles=0;
2016-08-26 20:18:29 +01:00
uint32_t time=0, now=0, MLO=0, BLO=0;
byte motionRecentlyCycles=0;
2013-11-10 05:26:34 +00:00
void loop() {
2016-08-26 20:18:29 +01:00
now = millis();
2014-11-03 17:02:11 +00:00
checkBattery();
2016-08-26 20:18:29 +01:00
//DEBUG("Slept: ");DEBUG(now-lastSleepTime);DEBUGln("ms");
if (motionDetected && (time-MLO > DUPLICATE_INTERVAL))
2013-11-10 05:26:34 +00:00
{
2020-11-17 20:07:23 +00:00
LED_HIGH; //digitalWrite(LED, HIGH);
2016-08-26 20:18:29 +01:00
MLO = time; //save timestamp of event
2020-11-17 20:07:23 +00:00
sprintf(sendBuf, "MOTION BAT:%sv F:%s", BATstr, TEMPstr);
DEBUG(sendBuf);
sendLen = strlen(sendBuf);
if (radio.sendWithRetry(GATEWAYID, sendBuf, sendLen))
2013-11-10 05:26:34 +00:00
{
2020-11-17 20:07:23 +00:00
DEBUG("..OK! RSSI:");
DEBUG(radio.RSSI);
2014-11-03 17:02:11 +00:00
batteryReportCycles = 0;
2013-11-10 05:26:34 +00:00
}
2020-11-17 20:07:23 +00:00
else DEBUG("..NOK..");
radio.sleep();
2020-11-17 20:07:23 +00:00
LED_LOW; //digitalWrite(LED, LOW);
2013-11-10 05:26:34 +00:00
}
2016-08-26 20:18:29 +01:00
else if (time-BLO > BATT_INTERVAL)
2014-11-03 17:02:11 +00:00
{
2020-11-17 20:07:23 +00:00
sprintf(sendBuf, "BAT:%sv F:%s", BATstr, TEMPstr);
2014-11-03 17:02:11 +00:00
sendLen = strlen(sendBuf);
2016-08-26 20:18:29 +01:00
BLO = time;
2020-11-17 20:07:23 +00:00
DEBUGln(sendBuf);
radio.sendWithRetry(GATEWAYID, sendBuf, sendLen);
2014-11-03 17:02:11 +00:00
radio.sleep();
batteryReportCycles=0;
}
2016-08-26 20:18:29 +01:00
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)
2020-11-17 20:07:23 +00:00
if (motionDetected ||motionRecentlyCycles>0)
2016-08-26 20:18:29 +01:00
{
if (motionDetected) motionRecentlyCycles=8;
else motionRecentlyCycles--;
motionDetected=false; //do NOT move this after the SLEEP line below or motion will never be detected
2020-11-17 20:07:23 +00:00
time = time + 250 + millis()-now; //correct millis() resonator drift, may need to be tweaked to be accurate
2016-08-26 20:18:29 +01:00
radio.sleep();
LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF);
DEBUGln("WAKEUP250ms");
}
else
{
2020-11-17 20:07:23 +00:00
time = time + 8000 + millis()-now /*+ 480*/; //correct millis() resonator drift, may need to be tweaked to be accurate
2016-08-26 20:18:29 +01:00
radio.sleep();
2020-11-17 20:07:23 +00:00
//LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); //watchdog sleep uses extra ~4uA!
sleep(8000);
2016-08-26 20:18:29 +01:00
DEBUGln("WAKEUP8s");
}
2014-11-03 17:02:11 +00:00
batteryReportCycles++;
2013-11-10 05:26:34 +00:00
}
2016-08-26 20:18:29 +01:00
uint32_t BLR=0;
2014-11-03 17:02:11 +00:00
void checkBattery()
2013-11-10 05:26:34 +00:00
{
2016-08-26 20:18:29 +01:00
if (time-BLR > 30000) //only read battery every 30s or so
2014-11-03 17:02:11 +00:00
{
2016-08-26 20:18:29 +01:00
BLR = time;
2020-11-17 20:07:23 +00:00
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, 3,2, TEMPstr);
2014-11-03 17:02:11 +00:00
dtostrf(batteryVolts, 3,2, BATstr); //update the BATStr which gets sent every BATT_CYCLES or along with the MOTION message
}
2020-11-17 20:07:23 +00:00
}
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();
if (sleepTime%262 && sleepTime > 262*2) {
DEBUG("Sleeping "); DEBUGln(sleepTime-sleepTime%262-262); DEBUGFlush();
listenModeSleep(sleepTime-sleepTime%262-262);
DEBUG("Sleeping "); DEBUGln(sleepTime%262 + 262); DEBUGFlush();
listenModeSleep(sleepTime%262 + 262);
} else {
DEBUG("Sleeping "); 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();
}