2014-10-17 17:32:03 +01:00
// Sample RFM69 sender/node sketch for the MotionMote
2016-08-26 20:18:29 +01:00
// http://lowpowerlab.com/motionmote
2013-11-10 05:26:34 +00:00
// PIR motion sensor connected to D3 (INT1)
// When RISE happens on D3, the sketch transmits a "MOTION" msg to receiver Moteino and goes back to sleep
2016-08-26 20:18:29 +01:00
// In sleep mode, Moteino + PIR motion sensor use about ~60uA
// IMPORTANT: adjust the settings in the configuration section below !!!
2014-10-17 17:32:03 +01:00
// **********************************************************************************
2016-08-26 20:18:29 +01:00
// Copyright Felix Rusu of LowPowerLab.com, 2016
2015-11-11 03:57:38 +00:00
// RFM69 library and sample code by Felix Rusu - lowpowerlab.com/contact
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
// Public License as published by the Free Software
// Foundation; either version 3 of the License, or
// (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.
//
// 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
// **********************************************************************************
2014-03-05 13:53:15 +00:00
# include <RFM69.h> //get it here: https://www.github.com/lowpowerlab/rfm69
2015-11-11 03:57:38 +00:00
# include <RFM69_ATC.h>//get it here: https://www.github.com/lowpowerlab/rfm69
# include <SPI.h> //comes with Arduino IDE (www.arduino.cc)
2014-10-17 17:32:03 +01:00
# 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/
2016-08-26 20:18:29 +01:00
# include <SPIFlash.h> //get it here: https://www.github.com/lowpowerlab/spiflash
2016-11-02 17:00:16 +00:00
# include <SparkFunBME280.h> //get it here: https://github.com/sparkfun/SparkFun_BME280_Breakout_Board/tree/master/Libraries/Arduino/src
2016-08-26 20:18:29 +01:00
# include <Wire.h> //comes with Arduino
2013-11-10 05:26:34 +00:00
2014-10-17 17:32:03 +01:00
//*********************************************************************************************
2015-11-11 03:57:38 +00:00
//************ IMPORTANT SETTINGS - YOU MUST CHANGE/CONFIGURE TO FIT YOUR HARDWARE *************
2014-10-17 17:32:03 +01:00
//*********************************************************************************************
# define NODEID 88 //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
# define GATEWAYID 1
//Match frequency to the hardware version of the radio on your Moteino (uncomment one):
//#define FREQUENCY RF69_433MHZ
//#define FREQUENCY RF69_868MHZ
# define FREQUENCY RF69_915MHZ
2016-04-13 14:52:44 +01:00
# define IS_RFM69HW //uncomment only for RFM69HW! Remove/comment if you have RFM69W!
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
2016-04-13 14:52:44 +01:00
# define ATC_RSSI -75
2016-08-26 20:18:29 +01:00
//#define ENABLE_BME280 //uncomment to allow reading the BME280 (if present)
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
2014-10-17 17:32:03 +01:00
# define ONBOARDLED 9 // Moteinos have LEDs on D9
2015-11-11 03:57:38 +00:00
# define LED 5 // MotionOLEDMote has an external LED on D5
# 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 BATT_MONITOR A7 // Sense VBAT_COND signal (when powered externally should read ~3.25v/3.3v (1000-1023), when external power is cutoff it should start reading around 2.85v/3.3v * 1023 ~= 883 (ratio given by 10k+4.7K divider from VBAT_COND = 1.47 multiplier)
2016-04-13 14:52:44 +01:00
# define BATT_FORMULA(reading) reading * 0.00322 * 1.49 // >>> fine tune this parameter to match your voltage when fully charged
2015-11-11 03:57:38 +00:00
// details on how this works: https://lowpowerlab.com/forum/index.php/topic,1206.0.html
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)
//#define SERIAL_EN //comment this out when deploying to an installed Mote 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
# define FLASH_SS 8 // and FLASH SS on D8 on regular Moteinos (D23 on MoteinoMEGA)
SPIFlash flash ( FLASH_SS , 0xEF30 ) ; //EF30 for 4mbit Windbond chip (W25X40CL)
# ifdef ENABLE_BME280
BME280 bme280 ;
# endif
2013-11-10 05:26:34 +00:00
volatile boolean motionDetected = false ;
2014-10-17 17:32:03 +01:00
float batteryVolts = 5 ;
2016-01-12 04:10:02 +00:00
char BATstr [ 10 ] ; //longest battery voltage reading message = 9chars
2014-10-17 17:32:03 +01:00
char sendBuf [ 32 ] ;
byte sendLen ;
2016-08-26 20:18:29 +01:00
# ifdef ENABLE_BME280
float temperature = 0 ;
char Fstr [ 10 ] ;
# endif
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 ) ;
# ifdef IS_RFM69HW
radio . setHighPower ( ) ; //uncomment only for RFM69HW!
# endif
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
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
pinMode ( LED , OUTPUT ) ;
radio . sendWithRetry ( GATEWAYID , " START " , 5 ) ;
# ifdef ENABLE_ATC
DEBUGln ( " RFM69_ATC Enabled (Auto Transmission Control) \n " ) ;
# endif
2016-08-26 20:18:29 +01:00
if ( flash . initialize ( ) ) flash . sleep ( ) ; //if Moteino has FLASH-MEM, make sure it sleeps
# ifdef ENABLE_BME280
bme280 . settings . commInterface = I2C_MODE ;
bme280 . settings . I2CAddress = 0x77 ;
bme280 . settings . runMode = 3 ; //Normal mode
bme280 . settings . tStandby = 0 ;
bme280 . settings . filter = 0 ;
bme280 . settings . tempOverSample = 1 ;
bme280 . settings . pressOverSample = 1 ;
bme280 . settings . humidOverSample = 1 ;
# endif
2013-11-10 05:26:34 +00:00
}
void motionIRQ ( )
{
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
{
2015-11-11 03:57:38 +00:00
digitalWrite ( LED , HIGH ) ;
2016-08-26 20:18:29 +01:00
MLO = time ; //save timestamp of event
# ifdef ENABLE_BME280
//read BME sensor
bme280 . begin ( ) ;
dtostrf ( bme280 . readTempF ( ) , 3 , 2 , Fstr ) ;
bme280 . writeRegister ( BME280_CTRL_MEAS_REG , 0x00 ) ; //sleep the BME280
sprintf ( sendBuf , " MOTION BAT:%sv F:%s " , BATstr , Fstr ) ;
# else
2014-10-17 17:32:03 +01:00
sprintf ( sendBuf , " MOTION BAT:%sv " , BATstr ) ;
2016-08-26 20:18:29 +01:00
# endif
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
{
2014-10-17 17:32:03 +01:00
DEBUG ( " MOTION ACK:OK! RSSI: " ) ;
DEBUG ( radio . RSSI ) ;
2014-11-03 17:02:11 +00:00
batteryReportCycles = 0 ;
2013-11-10 05:26:34 +00:00
}
2014-10-17 17:32:03 +01:00
else DEBUG ( " MOTION ACK:NOK... " ) ;
DEBUG ( " VIN: " ) ;
DEBUGln ( BATstr ) ;
radio . sleep ( ) ;
2015-11-11 03:57:38 +00:00
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
{
2016-08-26 20:18:29 +01:00
# ifdef ENABLE_BME280
//read BME sensor
bme280 . begin ( ) ;
dtostrf ( bme280 . readTempF ( ) , 3 , 2 , Fstr ) ;
bme280 . writeRegister ( BME280_CTRL_MEAS_REG , 0x00 ) ; //sleep the BME280
sprintf ( sendBuf , " BAT:%sv F:%s " , BATstr , Fstr ) ;
# else
2014-11-03 17:02:11 +00:00
sprintf ( sendBuf , " BAT:%sv " , BATstr ) ;
2016-08-26 20:18:29 +01:00
# endif
2014-11-03 17:02:11 +00:00
sendLen = strlen ( sendBuf ) ;
2016-08-26 20:18:29 +01:00
BLO = time ;
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)
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 ;
radio . sleep ( ) ;
LowPower . powerDown ( SLEEP_250MS , ADC_OFF , BOD_OFF ) ;
DEBUGln ( " WAKEUP250ms " ) ;
}
else
{
time = time + 8000 + millis ( ) - now ;
radio . sleep ( ) ;
LowPower . powerDown ( SLEEP_8S , ADC_OFF , BOD_OFF ) ;
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
{
unsigned int readings = 0 ;
2016-08-26 20:18:29 +01:00
BLR = time ;
2014-11-03 17:02:11 +00:00
for ( byte i = 0 ; i < 10 ; i + + ) //take 10 samples, and average
readings + = analogRead ( BATT_MONITOR ) ;
2015-11-11 03:57:38 +00:00
batteryVolts = BATT_FORMULA ( readings / 10.0 ) ;
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
}
2014-10-17 17:32:03 +01:00
}