2014-10-17 17:32:03 +01:00
// **********************************************************************************
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
2014-10-17 17:32:03 +01:00
// **********************************************************************************
// 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
2014-10-17 17:32:03 +01:00
// (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/>.
//
2014-10-17 17:32:03 +01:00
// 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
2015-11-11 03:57:38 +00:00
# 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
2014-10-17 17:32:03 +01:00
//*********************************************************************************************
2015-11-11 03:57:38 +00:00
# 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
2015-11-11 03:57:38 +00:00
# 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
2014-10-17 17:32:03 +01:00
# 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(); }
2014-10-17 17:32:03 +01:00
# else
# define DEBUG(input);
# define DEBUGln(input);
2016-08-26 20:18:29 +01:00
# define DEBUGFlush();
2014-10-17 17:32:03 +01:00
# endif
2015-11-11 03:57:38 +00:00
# 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 ;
2014-10-17 17:32:03 +01:00
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 ] ;
2014-10-17 17:32:03 +01:00
char sendBuf [ 32 ] ;
byte sendLen ;
2013-11-10 05:26:34 +00:00
2015-11-11 03:57:38 +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 ) ;
2015-11-11 03:57:38 +00:00
//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 ) ;
2015-11-11 03:57:38 +00:00
# endif
2020-11-17 20:07:23 +00:00
2015-11-11 03:57:38 +00:00
pinMode ( MOTION_PIN , INPUT ) ;
attachInterrupt ( MOTION_IRQ , motionIRQ , RISING ) ;
2013-11-10 05:26:34 +00:00
char buff [ 50 ] ;
sprintf ( buff , " \n Transmitting at %d Mhz... " , FREQUENCY = = RF69_433MHZ ? 433 : FREQUENCY = = RF69_868MHZ ? 868 : 915 ) ;
2014-10-17 17:32:03 +01:00
DEBUGln ( buff ) ;
pinMode ( ONBOARDLED , OUTPUT ) ;
2015-11-11 03:57:38 +00:00
radio . sendWithRetry ( GATEWAYID , " START " , 5 ) ;
2020-11-17 20:07:23 +00:00
2015-11-11 03:57:38 +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 ;
2015-11-11 03:57:38 +00:00
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 ) ;
2014-10-17 17:32:03 +01:00
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: " ) ;
2014-10-17 17:32:03 +01:00
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.. " ) ;
2014-10-17 17:32:03 +01:00
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 ) ;
2015-11-11 03:57:38 +00:00
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)
2020-11-18 14:54:55 +00:00
longPowerDown ( sleepTime ) ;
2020-11-17 20:07:23 +00:00
} 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 ( ) ;
}
2020-11-18 14:54:55 +00:00
void longPowerDown ( uint32_t sleepTime ) {
do {
if ( sleepTime > 8000 )
{
LowPower . powerDown ( SLEEP_8S , ADC_OFF , BOD_OFF ) ;
sleepTime - = 8000 ;
}
else if ( sleepTime > 4000 )
{
LowPower . powerDown ( SLEEP_4S , ADC_OFF , BOD_OFF ) ;
sleepTime - = 4000 ;
}
else if ( sleepTime > 2000 )
{
LowPower . powerDown ( SLEEP_2S , ADC_OFF , BOD_OFF ) ;
sleepTime - = 2000 ;
}
else if ( sleepTime > 1000 )
{
LowPower . powerDown ( SLEEP_1S , ADC_OFF , BOD_OFF ) ;
sleepTime - = 1000 ;
}
else if ( sleepTime > 512 )
{
LowPower . powerDown ( SLEEP_500MS , ADC_OFF , BOD_OFF ) ;
sleepTime - = 512 ;
}
else if ( sleepTime > 256 )
{
LowPower . powerDown ( SLEEP_250MS , ADC_OFF , BOD_OFF ) ;
sleepTime - = 256 ;
}
else if ( sleepTime > 128 )
{
LowPower . powerDown ( SLEEP_120MS , ADC_OFF , BOD_OFF ) ;
sleepTime - = 128 ;
}
else if ( sleepTime > 64 )
{
LowPower . powerDown ( SLEEP_60MS , ADC_OFF , BOD_OFF ) ;
sleepTime - = 64 ;
}
else if ( sleepTime > 32 )
{
LowPower . powerDown ( SLEEP_30MS , ADC_OFF , BOD_OFF ) ;
sleepTime - = 32 ;
}
else if ( sleepTime > 16 )
{
LowPower . powerDown ( SLEEP_15MS , ADC_OFF , BOD_OFF ) ;
sleepTime - = 16 ;
}
else
{
sleepTime = 0 ;
}
} while ( sleepTime ) ;
}