PIR node with interrupts



  • Hi guys!

    Hopefully someone can help me out...

    I'm trying to build a node that uses a PIR to detect motion and report it back to Domoticz.
    But I want this node to consume as little power as possible, so I jumped into the world of Arduino interrupts to put my node to sleep between PIR state changes. I cobbled together the code and when I run it it seems to work. The node initializes, registers itself with the gateway and goes to sleep, waiting for a state change from the PIR. When I trigger the PIR, the debug output shows that the node wakes up, determines what state the PIR is in and sends a message to the gateway. The message seems to be received fine by the gateway, since the status of the send is 1. See a debug grab below:

    wakeup from sleep
    Awake-1
    Motion detected!
    TSP:MSG:SEND 100-100-0-0 s=0,c=1,t=16,pt=2,l=2,sg=0,ft=0,st=ok:1
    wakeup from sleep
    Sleeping-0
    Motion ended!
    TSP:MSG:SEND 100-100-0-0 s=0,c=1,t=16,pt=2,l=2,sg=0,ft=0,st=ok:0
    

    But I don't see any updates in my controller! Domoticz doesn't register the PIR states at all... When I try a sketch without interrupts, everything works fine and Domoticz registers every PIR state change...

    Here is the code that uses interrupts, what am I doing wrong?

    // PIR motion detector sensor with Arduino nano in ultimate power saving mode 
    //(reducing power consumption almost by 70% (4.7mA) of normal operation consumption (13mA)) 
    // Current code by Electromania 24th Feb 2016 //https://youtu.be/n-Oiz76aVYs
    // Thanks to:- http://playground.arduino.cc/Learning/ArduinoSleepCode  for information about sleep.h library and modes
    // http://www.kevindarrah.com/download/arduino_code/LowPowerVideo.ino 
    // for useful explanation of  "Low Power Arduino! Deep Sleep Tutorial" for bare arduino uno chip
    // http://www.atmel.com/images/atmel-8271-8-bit-avr-microcontroller-atmega48a-48pa-88a-88pa-168a-168pa-328-328p_datasheet_complete.pdf  // datasheet
    
    #include <MyConfig.h>
    
    #define MY_DEBUG
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    //#define MY_REPEATER_FEATURE
    #define MY_NODE_ID 100
    #include <MySensors.h>
    
    #define SN "PIRnode"
    #define SV "0.1"
    #define PRIMARY_CHILD_ID 0
    
    #include <avr/interrupt.h>        // Library to use interrupt
    #include <avr/sleep.h>            // Library for putting our arduino into sleep modes
    
    const int PIRsensorInterrupt = 0; //interrupt 0 at arduino nano pin D2
    const int LedPin = 13;            // external LED or relay connected to pin 13
    
    int inputPin = 2;               // choose the input pin (for PIR sensor)
    int val = 0;                    // variable for reading the pin status
    long lastUpdate = 0;
    MyMessage PIRMsg(PRIMARY_CHILD_ID, V_TRIPPED);
    
    volatile int lastPIRsensorState = 1;  // previous sensor state
    volatile int PIRsensorState = 0;   // current state of the button
    
    void wakeUpNow(){                  // Interrupt service routine or ISR  
    	PIRsensorState = !lastPIRsensorState;    // we negate previous state and assign to current state
    }
    
    void setup() {
    	pinMode(LedPin, OUTPUT);    // initialize pin 13 as an output pin for LED or relay etc.
    	Serial.begin(115200);     // initialize serial communication only for debugging purpose
    	//Serial.println("Warming up... wait for a min...");
    
    	//// delay execution of sketch for a min, to allow PIR sensor get stabilized
    	//for (int i = 1; i <= 120; i++){  // LED at pin 13 blinks until PIR sensor is stabilized
    	//	digitalWrite(LedPin, HIGH);
    	//	delay(100);
    	//	digitalWrite(LedPin, LOW);
    	//	delay(100);
    	//}
    
    	Serial.println("Ready");     // enable only for debugging purpose
    
    	pinMode(PIRsensorInterrupt, INPUT);        // define interrupt pin D2 as input to read interrupt received by PIR sensor
    }
    
    void presentation()
    {
    	// Register sensor to sensor_node (they will be created as child devices)
    	// You can use S_DOOR, S_MOTION or S_LIGHT here depending on your usage. 
    	// If S_LIGHT is used, remember to update variable type you send in. See "msg" above.
    	present(PRIMARY_CHILD_ID, S_MOTION);
    	sendSketchInfo(SN, SV);
    }
    
    void Hibernate()         // here arduino is put to sleep/hibernation
    {
    	set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // lowest power consumption mode 
    	//"The Power-down mode saves the register contents but freezes the Oscillator, disabling all other chip functions 
    	// until the next interrupt or hardware reset."  text from ATMEGA328P datasheet
    
    	ADCSRA &= ~(1 << 7);   // Disable ADC - don't forget to flip back after waking up if you need ADC in your application ADCSRA |= (1 << 7);  (From Kevin's sketch)
    
    	sleep_enable();                       // enable the sleep mode function
    	sleep_bod_disable();                  //to disable the Brown Out Detector (BOD) before going to sleep. 
    
    	attachInterrupt(PIRsensorInterrupt, wakeUpNow, CHANGE);   // Attach interrupt at pin D2  (int 0 is at pin D2  for nano, UNO)
    	// here since PIR sensor has inbuilt timer to swtich its state from OFF to ON, we are detecting its CHANGE IN STATE to control our LED/relay at pin 13. 
    	// therefore, we will not need to use arduino delay timer to Set "ON time" of our LED/relay, it can be adjusted physically using potentiometer provided on PIR sensor board.
    	// This further helps in using SLEEP_MODE_PWR_DOWN which is ultimate lowest power consumption mode for ATMEGA8328P chip  
    	//(please note - because of onboard power regulators of arduino boards, power consumption cannot be reduced to predicted few microAmps level of bare chips. 
    	//To achieve further reduction in current consumption, we will need bare ATMEGA328P chip)
    
    	for (int i = 0; i < 20; i++) {
    		if (i != 13)//  because the LED/Relay is connected to digital pin 13
    			pinMode(i, INPUT);
    	}
    
    	sleep_mode();                // calls function to put arduino in sleep mode
    	Serial.println("wakeup from sleep");
    	sleep_disable();            // when interrupt is received, sleep mode is disabled and program execution resumes from here
    	detachInterrupt(PIRsensorInterrupt);   // we detach interrupt from pin D2, to avoid further interrupts until our ISR is finished
    }
    
    
    void loop() {
    	interrupts();    // enable interrupts for Due and Nano V3
    
    	if (PIRsensorState != lastPIRsensorState){
    
    		if (PIRsensorState == 0) {
    			digitalWrite(LedPin, LOW);
    			Serial.print("Sleeping-");            // enable for debugging
    			Serial.println(PIRsensorState);   // read status of interrupt pin
    			Serial.println("Motion ended!");
    			send(PIRMsg.set(0));
    		}
    
    		else {
    			digitalWrite(LedPin, HIGH);
    			Serial.print("Awake-");    // enable for debugging
    			Serial.println(PIRsensorState);  // read status of interrupt pin   enable for debugging
    			Serial.println("Motion detected!");
    			send(PIRMsg.set(1));
    			delay(50);
    		}
    	}
    
    	lastPIRsensorState = PIRsensorState;    // reset lastinterrupt state
    	delay(50);
    	Hibernate();   // go to sleep - calling sleeping function
    }
    

  • Hardware Contributor

    @Creaky - It would be good to see the gw log here if the message is received or not. Maybe the radio isnt powered quick enough after the interrupt and somethings is going wrong there... but as I said, when you know what happens at the gw its easier do say.



  • Is there a way to grab that log from Domoticz?
    Otherwise I have to grab it directly from the serial interface of my PI, but I have no clue how to do that under Linux...

    But apart from that, if the radio isn't powered fast enough, I wouldn't get a st=ok message right? I sort of assume you only get a st=ok when the node has received an ack from the gateway, or am I assuming wrong?


  • Hardware Contributor

    Hello, you should do yourself a favor and use the sleep function from MySensors 😉 Put the sleep command at the end of the loop, and read the status of the pir at the beginning. It's better to use pin 3 as interrupt pin as pin 2 is supposed to be used as radio interrupt.
    Also, it is not a good idea to try to use the led on pin 13 as that pin is used for the SPI communication with the radio. Use pins from 4 to 8 for that, or one of the analog pins up to A5 (A6/A7 cannot be used as digital pins).

    In Domoticz you should see a text to tell you the message has arrived, it doesn't have a value but at least you will be sure the message arrived. What type of switch did you select in Domoticz ?
    2017-02-14 22:15:09.909 (MySensors SerialGW) General/Voltage (White Round Sensor 2)


  • Contest Winner

    @Creaky couple of remarks.

    • use wait i.s.o delay (delay is a blocked call so no MySensor messaging)
    • give the MySensors functions some time after the trigger to process the messages so wait for 200 or 300 milliseconds i.s.o of 50
    • and as stated by @Nca78 beter use the sleep function from MySensors to wait for a interrupt


  • Thanks for all your feedback guys! I really appreciate it!
    I now use the following code based on a MySensors example, it does the job but I have the feeling it still uses quite some power. Is this the best we can do code wise to reduce power? My Chinese Nano V3 clone still uses 17.2mA with this code and I have already disabled the power LED...

    *******************************
    *
    * DESCRIPTION
    *
    * Interrupt driven binary switch example with dual interrupts
    * Author: Patrick 'Anticimex' Fallberg
    * Connect one button or door/window reed switch between
    * digitial I/O pin 3 (BUTTON_PIN below) and GND and the other
    * one in similar fashion on digital I/O pin 2.
    * This example is designed to fit Arduino Nano/Pro Mini
    *
    */
    
    #include <MyConfig.h>
    
    #define MY_DEBUG
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    
    //#define MY_REPEATER_FEATURE
    #define MY_NODE_ID 100
    
    #include <MySensors.h>
    
    #define SN "PIRnode"
    #define SV "0.1"
    
    #define PRIMARY_CHILD_ID 0
    
    #define PRIMARY_BUTTON_PIN 2   // Arduino Digital I/O pin for button/reed switch
    
    #if (PRIMARY_BUTTON_PIN < 2 || PRIMARY_BUTTON_PIN > 3)
    #error PRIMARY_BUTTON_PIN must be either 2 or 3 for interrupts to work
    #endif
    #if (PRIMARY_BUTTON_PIN == SECONDARY_BUTTON_PIN)
    #error PRIMARY_BUTTON_PIN and BUTTON_PIN2 cannot be the same
    #endif
    
    
    // Change to V_LIGHT if you use S_LIGHT in presentation below
    MyMessage msg(PRIMARY_CHILD_ID, V_TRIPPED);
    
    
    void setup()
    {
    	// Setup the buttons
    	pinMode(PRIMARY_BUTTON_PIN, INPUT);
    
    	// Activate internal pull-ups
    	//digitalWrite(PRIMARY_BUTTON_PIN, HIGH);
    }
    
    void presentation()
    {
    	// Send the sketch version information to the gateway and Controller
    	sendSketchInfo(SN, SV);
    
    	// Register binary input sensor to sensor_node (they will be created as child devices)
    	// You can use S_DOOR, S_MOTION or S_LIGHT here depending on your usage.
    	// If S_LIGHT is used, remember to update variable type you send in. See "msg" above.
    	present(PRIMARY_CHILD_ID, S_MOTION);
    }
    
    // Loop will iterate on changes on the BUTTON_PINs
    void loop()
    {
    	uint8_t value;
    	static uint8_t sentValue = 2;
    	static uint8_t sentValue2 = 2;
    
    	// Short delay to allow buttons to properly settle
    	sleep(5);
    
    	value = digitalRead(PRIMARY_BUTTON_PIN);
    
    	if (value != sentValue) {
    		// Value has changed from last transmission, send the updated value
    		send(msg.set(value == HIGH));
    		sentValue = value;
    	}
    
    	// Sleep until something happens with the sensor
    	sleep(PRIMARY_BUTTON_PIN - 2, CHANGE, 0);
    }
    

  • Hardware Contributor

    @Creaky you have a problem in your loop. You cannot define the variables at the beginning of the loop else they are reinitialized at each loop.
    You should have :

    	uint8_t value;
    	static uint8_t sentValue = 2;
    	static uint8_t sentValue2 = 2;`
    
    // Loop will iterate on changes on the BUTTON_PINs
    void loop()
    {
    

    Also, I would use wait(50) instead of sleep(5). And it's only necessary if you're using a physical contact that can bounce a bit between on and off position (door sensor for example), but it's not necessary for a PIR.

    For the power consumption it seems high, but if you want low power you should not use a nano but a pro mini. Even in sleep mode on a nano the usb to serial chip is powered.



  • @Nca78 Good one! So much for using an example without thouroughly checking the code 🙂


  • Hero Member

    @Creaky In order to really reduce power consumption you should go for a "less complete" arduino. A nano includes a lot of power hungry circuitry (at least for battery power). The voltage regulator is an AMS1117 in most cases which eats > 5mA on its own. Add to that the FTDI or CH340 (12 mA when active) usb-serial converter and you know where the energy is going.

    There are many examples around the forum based on pro-mini (with regulator and power led removed) which let you reduce power by a factor 1000!.

    A nano is great for breadboarding, experimenting and non-battery powered devices. 😉



  • @AWI I realize that now and already purchased a Pro mini 3.3v.


Log in to reply
 

Suggested Topics

13
Online

11.4k
Users

11.1k
Topics

112.7k
Posts