Video How To: Battery Powered Chair Occupancy (Contact) Sensor

  • Admin

    I finally got around to making my first battery powered sensor (after 3+ years 😬 ) and I thought I'd make a video on how I did it. I'm amazed how easy it was and I'm not sure why I didn't do this sooner… I am using the sensor to monitor my dining room chairs so I can better automate the lights in that room (check out the video for more details).

    If my calculations are correct (and it's entirely possible they aren't) this sensor should run for about 3 years on 2 AA Alkaline batteries. Here is my math if anyone is interested (or wants to correct it). NOTE: I had an issue with one of my radios where it was using a couple if mA when sleeping so make sure you check your setup before finalizing the device so your batteries don't drain in a couple of weeks. You should be getting about 6uA when your Pro Mini is sleeping.

    Usage when sleeping =

    • 5.8uA (0.0058mA) when contact sensor is open/disconnected
    • 10uA when contact sensor is closed/connected with a 1M external pullup resistor
    • 100uA when contact sensor is closed/connected with internal pullup resistor

    Usage when transmitting =

    • 16mA with the NRF radio set to LOW

    I'm estimating a total of 10 transmissions per meal (5 times sitting/getting up). Just for good measure I'll say there will be 4 meals a day which would equal 40 transmissions per day. The sensor is also sending a battery level every 4 hours which would give an additional 6 transmissions. So that would give a total of 46 transmissions per day. So, 40/24=1.6667 transmissions per hour with it sleeping the rest of the time.

    Here is the equation to get the average mA:
    Iavg = (mA x time awake) + (mA x time asleep) / ( 1 hour)
    Iavg = (16mA x (1.67 x 2 seconds per transmission) + (.006mA x (1 hour - awake time)) / (7200sec)
    Iavg = (16x3.34) + (.006x7196.66) / 7200 = 53.446 mA per hour
    Iavg = 53.446 mA per hour

    Battery Life = Battery Capacity in Milli amps per hour / Load Current in Mill amps * 0.70

    Alkaline batteries should get around 985 days (32 months). AA alkaline batteries typically have a capacity rating of over 2,500 mAh (each).

    Ok, enough of that. Let's get on with the building…

    Build Video
    $4 Battery Powered Chair Occupancy (Contact) Sensor using Arduino and MySensors – 17:53
    — Pete B

    Wiring Diagram
    0_1493422745392_Fritzing Chair Occupancy Sensor.png

    Parts Needed (BOM)
    3.3v Arduino Pro Mini
    NRF24L01+ Radio
    1M Ohm Resistor
    Battery holder (eBay) or Battery holder (3D print)
    Contact Sensor:

    Arduino_VCC Library link -
    Gert Sanders Bootloader's -
    MySensors Battery Build Page -

    Minimum MySensors Library build of 2.2 is recommended for this sensor

       The MySensors Arduino library handles the wireless radio link and protocol
       between your home built sensors/actuators and HA controller of choice.
       The sensors forms a self healing radio network with optional repeaters. Each
       repeater and gateway builds a routing tables in EEPROM which keeps track of the
       network topology allowing messages to be routed to nodes.
       Created by Henrik Ekblad <>
       Copyright (C) 2013-2015 Sensnology AB
       Full contributor list:
       Support Forum:
       This program is free software; you can redistribute it and/or
       modify it under the terms of the GNU General Public License
       version 2 as published by the Free Software Foundation.
       Please use MySensors version 2.2.0 or higher to avoid issues with interrupts and sleep
       Low power/battery operated contact switch
       - Send heartbeat (battery & sensor status) every 4 hours
       - Send contact switch status after performing 3 checks to make sure it's not someone shifting in the chair
       - Uses approximately 5.8uA when sleeping
       To watch a video on how to make this sensor go here:
    #define SKETCH_NAME "Chair Sensor"
    #define SKETCH_VERSION "1.0"
    // Enable debug prints to serial monitor
    //#define MY_DEBUG
    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    //#define MY_RADIO_RFM69
    #define MY_RF24_PA_LEVEL RF24_PA_LOW //Options: RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH, RF24_PA_MAX
    #define MY_RF24_CHANNEL  76
    #define MY_NODE_ID 1  //Manually set the node ID here. Comment out to auto assign
    #define MY_TRANSPORT_WAIT_READY_MS 3000 //This will allow the device to function without draining the battery if it can't find the gateway when it first starts up
    #define MY_BAUD_RATE 9600 //Serial monitor will need to be set to 9600 Baud
    #include <MySensors.h>
    #include <Vcc.h>
    #define CONTACT_CHILD_ID 0
    #define CONTACT_PIN  3  // Arduino Digital I/O pin for button/reed switch
    #define CONTACT_CHILD_NAME "Chair Sensor 1" //The name of this specific child device will be sent to the controller
    #define DEBOUNCE_DELAY 1200 //DO NOT SET BELOW 1000! Amount of time to sleep between reading the contact sensor (used for debouncing)
    #define BATTERY_DELAY 14400000 //(4 hours) Amount of time in milliseconds to sleep between battery messages (as long as no interrupts occur)
    //#define BATTERY_DELAY 60000
    uint8_t oldContactVal = 2; //Used to track last contact value sent.  Starting out of bounds value to force an update when the sensor is first powered on
    uint8_t contactVal[2]; //Used for storing contact debounce values
    uint8_t contactTracker = 0; //Used as a sort of debounce to stop frequent updates when shifting in chair
    int8_t wakeVal = 0; //Used to determine if wake was from timer or interrupt
    const float VccMin   = 1.9;           // Minimum expected Vcc level, in Volts. (NRF can only go to 1.9V)
    const float VccMax   = 3.3;           // Maximum expected Vcc level, in Volts.
    const float VccCorrection = 1.0 / 1.0; // Measured Vcc by multimeter divided by reported Vcc
    Vcc vcc(VccCorrection);
    MyMessage msgContact(CONTACT_CHILD_ID, V_TRIPPED);
    void presentation() {
      // Present sketch name & version
      sendSketchInfo(SKETCH_NAME, SKETCH_VERSION);
      // Register binary input sensor to gw (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.
    void setup()
      //Set unused pins low to save a little power (
      for (uint8_t i = 4; i <= 8; i++)
        pinMode (i, INPUT);
        digitalWrite (i, LOW);
      // Setup the button and activate internal pullup resistor
      //pinMode(CONTACT_PIN, INPUT_PULLUP); //(Uses 100uA in sleep)
      pinMode(CONTACT_PIN, INPUT);  //Use this with an external pullup resistor (uses 10uA in sleep)
      float p = vcc.Read_Perc(VccMin, VccMax);
    #ifdef MY_DEBUG
      Serial.print("Batt Level: ");
      sendBatteryLevel((uint8_t)p); //Send battery level to gateway
    void loop()
      //Read the value returned by sleep.  If it was a timer send battery info otherwise
      if (wakeVal < 0) {
        //Woke by timer, send battery level and current state of contact sensor
        float p = vcc.Read_Perc(VccMin, VccMax);
    #ifdef MY_DEBUG
        float v = vcc.Read_Volts(); //Only using this for debugging so we don't need it taking up resources normally
        Serial.println("Woke by timer.");
        Serial.print("VCC = ");
        Serial.println(" Volts");
        Serial.print("VCC = ");
        Serial.println(" %");
        sendBatteryLevel((uint8_t)p); //Send battery level to gateway
        send(msgContact.set(oldContactVal ? 0 : 1)); //Send current sensor value
        wakeVal = sleep(digitalPinToInterrupt(CONTACT_PIN), CHANGE, BATTERY_DELAY);  //Go back to sleep
      else {
        //Woke by interrupt, send contact value
        if (contactTracker < 2) {
          contactVal[contactTracker] = digitalRead(CONTACT_PIN);
    #ifdef MY_DEBUG
          Serial.print("contactVal: ");
          Serial.print("contactTracker: ");
          contactTracker++; //increment contact tracker
          sleep(DEBOUNCE_DELAY); //sleep until next read
        else {
          contactTracker = 0; //Reset contact tracker
          uint8_t readValue = digitalRead(CONTACT_PIN);
    #ifdef MY_DEBUG
          Serial.print("contactVal: ");
          if (readValue == contactVal[0] && readValue == contactVal[1]) {
            //All values are the same, send an update
    #ifdef MY_DEBUG
            Serial.println("Values the same");
            if (oldContactVal != readValue) {
    #ifdef MY_DEBUG
              Serial.println("Contact changed. Sending to gateway");
              send(msgContact.set(readValue == LOW ? 1 : 0));
              oldContactVal = readValue;
          wakeVal = sleep(digitalPinToInterrupt(CONTACT_PIN), CHANGE, BATTERY_DELAY);  //Go back to sleep

  • Hardware Contributor

    Awesome work dude, ts the video that get me with your builds, not many people even bother to document things but you go to the extra length of a video to help others. Thank you!

    I'm definitely going to work on my battery sensing for my battery nodes, you seem to have got it working pretty nicely here...

  • Admin

    @Samuel235 thanks 🙂
    I love MySensors and have so much help over the years from everyone. I try to help when/where I can. I may not be the best at coding but I do know how to make videos (well, better than I code at least 🙂 ).

  • Hardware Contributor

    Well, i for one appreciate the help that you're injecting back into the community. I've had a lot of help from people like you around here and its my aim to provide help back too. I try to get everything documented that people help me on just so there is information written down for people to see and get help from themselves. If one person has struggled on something, i'm pretty sure others are too and this is why I attempt to document and share everything that others help me on.

  • Pete, Thank you for taking the time to create, post and explain this. I've been struggling with an optimized battery powered sensor and this nailed it. Personally, I broke new ground on multiple fronts with this one. First foray with 2.2 and first custom boot loaders.


    • My objective is to create a standardized set of sketches for battery powered motion and door/window switches. I'm looking for that instant gratification of a light turning on the moment presence is detected. Yet, battery optimization is still important.

    • How do you recommend I remove some of your bouncing goodness in this sketch for my purposes?

    • I experimented with the following, but my knowledge of 2.2 and interrupt challenges is limited. My attempt was to reduce the sensor state checks from 3 to 1, but I failed at that. I assume it might be better to change the approach, but didn't want to start hacking your code goodness with my lack of 2.2 knowledge.

    #define DEBOUNCE_DELAY 1001 // reduced to 1001. Appear to be no issues, but the impact is minimal

    uint8_t oldContactVal = 1; //changed from 2 to 1. Caused failure, but I changed this combo of 3 variables at the same time. I'm not sure which one or if all together cause the failure. I'm still slow on arduino / mysensors logic.

    uint8_t contactVal[1]; //changed from 2 to 1

    uint8_t contactTracker = 0; //did not change. I left this at zero

    int8_t wakeVal = 0; //did not change. I left this at zero

    else {
    //Woke by interrupt, send contact value
    if (contactTracker < 1) {
    // I changed this for <2 to <1

    Any insights from the community are appreciated.

  • Hardware Contributor

    Thank a lot for the idea and for the video explaining the implementation.

    Did you check the power consumption when you don't send battery life at regular intervals ? If you set the wake up only on interrupt then current in sleep mode goes from 5-6uA to 2 when contact is opened. It's so low that you don't really need regular reporting of battery life. I do that and report battery life only on opening/closing of doors/windows and my battery life is great even on chinese CR2032 cells (still over 85% on my entrance door).
    I guess it would drop to 5-6µA when sensor is connected ?

  • Admin

    @pgregg88 said in Video How To: Battery Powered Chair Occupancy (Contact) Sensor:

    I experimented with the following, but my knowledge of 2.2 and interrupt challenges is limited. My attempt was to reduce the sensor state checks from 3 to 1, but I failed at that. I assume it might be better to change the approach, but didn't want to start hacking your code goodness with my lack of 2.2 knowledge.

    If you're using this for contact or motion sensors I would remove most of my code as it's not needed. For the motion sensor code it can be really simple as you don't even need a debounce. Check out the motion sensor sketch in the MySensors library. Really all you need is a digitalRead then send that value.
    For the contact sensors I would just use a wait(20); and then read the sensor again to make sure it's still the same value (or you could use the debounce library). You can also check out the code in the binary sleep sensor and pull from that

  • Admin

    @Nca78 Wow, cool. I never got readings down to 2uA. Maybe my multimeter isn't very good or something else...? The main reason I regularly report the battery is to provide a heartbeat as well as reset the sensor if for some reason it could not communicate with the gateway the first time.

    Edit: I just re-read your post. I now see that you were saying I could save those additional uA by not using the sleep. That is a good idea. I'm still a little torn on the idea because I like the assurance of the heartbeat. I'll have to think about it. Thanks for sharing though!

  • That doesn't work to me! 😞 I am using an arduino clone. I don't Know if that is the problem.

    When I use your sketch (an others from Mysensors) my Arduino usage is 30mA, seems like the sleep method doesn't work rigth.

    I test with a liltle example from rocketscream/Low-Power and then my Arduino usage is 16uA. Anyone know where the problem may be?


  • Hardware Contributor

    @Jic did you remove the led and voltage regulator?

  • @sundberg84

    Yes, I have removed both

  • Mod

    @Jic 30mA is way too high even for an unmodified Arduino that is fully awake. There must be something wrong with the Arduino or the measurement.

  • Hardware Contributor

    @Jic - you should see in the serial debug if the node is sleeping or not. I have heard there might be a problem with sleep in som recent version. Maybe you investigate this and if nessecaru upgrade to development branch.

  • Admin

    @Jic In addition to the great suggestions above also try to disconnect all the sensors and measure again. I had two bad radios that were using way too much power. Bad clones I guess.

  • Thanks for sharing this awesome project...

    I want to install all my windows with reed sensors powered by 2AA alcaline batteries and would like they last more than 5 years... and I think your project fits perfectly
    How long do those used batteries last?

    Did you implement what @Nca78 suggested?

    I have already cut LED and Power Regulator from Arduino Pro Mini 3.3v and will change bootloader.
    Have you choosen the best (lowest power consumption) bootloader to use?

    I would try the MYSBootloader_1MHz.hex from MySensors GitHub.
    Would the optiboot_atmega328_01M_009600_NOLED from GertSanders better for low power?

    Thanks for the help...

  • Hardware Contributor

    @OliverDog if you are using 2xAA then 6uA in sleep mode is fine for a few decades.
    You can estimate battery life there, if you don't spend your time jumping on your chair it should last the 5 years easily 😉

    For the bootloaders battery life won't be different, any bootloader using internal oscillator at 1MHz is fine.

  • Great!!! Got it working, but can't measure the consumption...
    My multimeter shows always 000.
    The sensors is working fine and reporting properly while connected to multimeter, but always 0 current...
    Tried 200u, 2000u, 20m and 200m, always reporting zero... Is is that low?

    Did I connect something wrong??? (multimeter in series with positive battery pole and circuit line in (Radio+APM+1M ResistorPin3)
    Or is my aliexpress multimeter that bad???


  • Hardware Contributor

    Clean desk you have here @OliverDog this would make my wife dream 😄

    For the multimeter, no idea. At 200u it should see something, maybe your red wire is in the wrong plug of the multimeter ? Usually it's a different plug for voltage and for current but you can have a different plug for high current (A) and low current (mA) too...

  • I had just cleaned it!!! My wife gave up caring about this desk...😏

    I found my multimeter fuse was burned... so I wired its base poles and now I can measure:

    Sleeping + Reed Disconnected - most time 6 uA
    Sleeping + Reed Connected - most time 9 uA
    When sending new status to gateway - 25 - 230 uA (around 140 uA most times)
    Sending time was less than 1 second.

    2 AA will give me 29 years (battery certainly will die first)
    2 AAA - 12 years.
    1 CR2032 - 2,5 years.

    Thanks for the help...

  • Hardware Contributor

    @OliverDog when sending to gateway you cannot trust your multimeter. It's probably around 15mA but only during a fraction of a second, and what you read is some kind of average between that and the very low power consumption in sleep mode.

Log in to reply

Suggested Topics

  • 8
  • 1
  • 2
  • 2
  • 35
  • 5