Arduino-Based Bathroom Occupancy Detector

Tony DiPasquale

At the thoughtbot Boston office, when nature calls, there is an arduous, uphill both ways and often snow covered trek to the bathroom area. The bathrooms are also out of sight from anywhere in the office. Many times someone will turn the corner to the bathrooms and see that they are all occupied. That person now must either wait around until a bathroom opens up or go back to their desk and try again later. We wanted an indicator, visible throughout our Boston office, that informs whether a bathroom is available. We used the power of hardware to hack together a solution.

Arduino-Based Bathroom Occupancy Detector

Overview

We decided to use two microcontrollers to monitor and report the bathroom status. One microcontroller would sit near the bathrooms and use door sensors to detect if they were closed or open. The other microcontroller would sit in an area visible to everyone in the office and use an LED to indicate the status of the bathroom doors. They will have to communicate wirelessly since their distance apart could be long. The bathroom door microcontroller has to run from batteries since there is not a nearby outlet. We also decided that besides using an LED to show if there was an available door, we would also post the info to a remote server so other applications could digest it.

Hardware

We’ll be using Arduinos for the microcontrollers because there is a ton of support and information about how to use them available online. For similar reasons, we will use XBee V1 radios for the communication between the Arduinos. The door sensing Arduino will be an Arduino Fio because it comes with a LiPo battery charging circuit and an easy way to plug in an XBee radio. The reporting Arduino will be an Arduino Yún because of its WiFi capabilities. The door sensors are generic reed switches with magnets. We’ll also use a couple of solderless breadboards to prototype extra circuitry needed to tie things together.

Door Sensors

These sensors are a combination of a magnet and a reed switch. The magnet is placed on the door and the reed switch is on the frame. When the door closes, the magnet closes the reed switch, closing the circuit. We connect one side of the switch to power and the other side goes into a GPIO port on the Arduino. We must also connect a pull down resistor from the GPIO port to ground so when the switch is open, the port reads a low signal. Now when the door is shut, the switch is closed and there is a high signal on the port. When the door is open, the switch is open and there is a low signal on the port.

Door Sensor

Arduino Fio (sensor module)

This module is responsible for sensing the bathroom door’s state and sending that state to the reporter. First, we wire up the two door sensors to the Fio. We’ll use digital pins 2 and 3 on the Fio so we can use the interrupts later for power savings. Then we connect a common 10K resistor as pull down to ground from each pin. The only thing left to do is plug the XBee into the dedicated connector on the Fio. Now that everything is connected, we need to write the code that reads the sensors and sends their state to the XBee.

Arduino Fio

void setup() {
  pinMode(2, INPUT);
  pinMode(3, INPUT);
  Serial1.begin(9600);
}

Here we initialize digital pins 2 and 3 as inputs and create a serial interface for communication with the XBee. The XBee is programmed at 9600 baud by default so we need to create the serial connection to match. Now in the main program loop, lets check the door sensors and send their state to the XBee.

void loop() {
  int leftDoor = digitalRead(2);
  int rightDoor = digitalRead(3);

  Serial1.write(0xF0);
  Serial1.write(leftDoor);
  Serial1.write(rightDoor);
  Serial1.write(0xF1);

  delay(1000);
}

Here we read the status of the doors and then send it to the XBee. Lets add a delay of 1000ms or 1 second so we are not constantly sending data, but also update often enough so the data isn’t stale. We also add a prefix and postfix to our transmission so it is easier to receive on the other end.

This implementation isn’t very efficient because there could be long periods of time where neither door has changed. Lets save the state of the doors and transmit only if one has changed.

int leftDoorState = 0;
int rightDoorState = 0;
boolean hasChanged = false;

void loop() {
  int leftDoor = digitalRead(2);
  int rightDoor = digitalRead(3);

  if (leftDoor != leftDoorState) {
    leftDoorState = leftDoor;
    hasChanged = true;
  }

  if (rightDoor != rightDoorState) {
    rightDoorState = rightDoor;
    hasChanged = true;
  }

  if (hasChanged) transmit();

  delay(1000);
}

void transmit() {
  Serial1.write(0xF0);
  Serial1.write(leftDoorState);
  Serial1.write(rightDoorState);
  Serial1.write(0xF1);
  hasChanged = false;
}

Here we create two global variables to hold the current door’s state. When we read the doors’ states, we check if it has changed since the last read. If either of the doors has changed state, we transmit the data with the XBee. This will save us from transmitting every second to maybe only 20 to 50 transmits per day depending on bathroom use. The XBee module is using the most power when transmitting so reducing the number of times it has to transmit will reduce the power consumption.

We can go one step further and use interrupts to notify the Arduino that a door state has changed.

void setup() {
  pinMode(2, INPUT);
  attachInterrupt(0, transmitDoorState, CHANGE);

  pinMode(3, INPUT);
  attachInterrupt(1, transmitDoorState, CHANGE);

  Serial1.begin(9600);
}

void transmitDoorState() {
  int leftDoor = digitalRead(2);
  int rightDoor = digitalRead(3);

  Serial1.write(0xF0);
  Serial1.write(leftDoorState);
  Serial1.write(rightDoorState);
  Serial1.write(0xF1);
}

void loop() {

}

We attach a change interrupt to each of our door sensors. Any time a door state changes, the interrupt will execute the function transmitDoorState. Since our interrupt is doing all the work now, we can remove the global variables and all functionality from the main program loop.

The sensor module is now waiting for a change on the interrupt pins before it does anything. While its waiting, the CPU is active and processing no-op commands. This is wasting power because we’re keeping the CPU on when we’re not doing anything. To save power lets sleep the CPU when we don’t need it.

#include <avr/sleep.h>

void setup() {
  pinMode(2, INPUT);
  attachInterrupt(0, transmitDoorState, CHANGE);

  pinMode(3, INPUT);
  attachInterrupt(1, transmitDoorState, CHANGE);

  Serial1.begin(9600);
}

void transmitDoorState() {
  int leftDoor = digitalRead(2);
  int rightDoor = digitalRead(3);

  Serial1.write(0xF0);
  Serial1.write(leftDoorState);
  Serial1.write(rightDoorState);
  Serial1.write(0xF1);
  delay(100);
}

void loop() {
  set_sleep_mode(SLEEP_MODE_IDLE);
  sleep_enable();
  sleep_mode();
}

This will put the Arduino into an idle sleep while waiting for a change on the interrupts. When a change occurs, the Arduino will wake up, transmit the state, then go back to sleep. We need to add a delay after we send data to the XBee to ensure that all the data has been sent before we try to sleep the processor again.

Further Improvements

The transmitDoorState function is an Interrupt Service Routine or ISR. An ISR should be quick because you want to be out of it before another interrupt occurs. The transmitDoorState function is not very quick because it has to send data serially and delay for 100ms. We probably won’t run into any issues since the time between interrupts (door opening and closing) will most likely be greater than the time it takes to transmit, but to be safe we could move this code into the program loop and execute it after the sleep function. We could also reduce the power consumption further by using the sleep mode SLEEP_MODE_PWR_DOWN. This sleep mode affords us the most power savings but doesn’t allow us to use the CHANGE interrupt. Instead we would have to use level interrupts at LOW or HIGH and manage which one to interrupt on depending on the state of the door.

Arduino Yún (reporter module)

This module is responsible for receiving the door state data and reporting that state to the office. First, we need to wire up the XBee module. It would be easy to get a XBee shield for the Arduino and use that but we have an XBee explorer instead so we need to manually wire up the XBee. Connect XBee explorer power and ground to the Arduino, then the RX and TX to digital pins 8 and 9 respectively (pins chosen at random). We also need to add a 10K pull up resistor from the RX and TX lines to power. Now add an LED in series with a 330 ohm resistor (or similar value) to digital pin 4 (also chosen at random). The LED & resistor combo should be connected low side and we’ll drive it high from the Arduino to turn it on. Time to code!

Arduino Yún

#include <Bridge.h>
#include <Process.h>
#include <SoftwareSerial.h>

SoftwareSerial xbee(8, 9); // RX, TX

void setup()  {
  Bridge.begin();
  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
  xbee.begin( 9600 );
}

Here we set up the Bridge to the Linux processor on the Yún, set the mode of our LED pin to an output, start the LED off, and initialize the software serial connection for the XBee. Next, we need to receive data from the XBee and report the door status.

int leftDoor, rightDoor;

enum state {
  waiting_for_prefix,
  get_left_door,
  get_right_door,
  waiting_for_postfix
};

state currentState = waiting_for_prefix;

void loop()  {
  if (xbee.available()) {
    int data = xbee.read();

    switch (currentState) {
      case waiting_for_prefix:
        if (data == 0xF0)
          currentState = get_left_door;
        break;

      case get_left_door:
        leftDoor = data;
        currentState = get_right_door;
        break;

      case get_right_door:
        rightDoor = data;
        currentState = waiting_for_postfix;
        break;

      case waiting_for_postfix:
        if (data == 0xF0) {
          currentState = waiting_for_prefix;
          reportState();
        }
        break;
    }
  }
}

We’ll use a state machine to receive the data from the XBee. There are four states: one for each the prefix and postfix to see the start and end of our data and one for each door. The door states will record the data from the XBee and the prefix and postfix states are used for flow control. When we see the postfix data we report the door states with reportState(). This function should turn on an LED if both doors are closed.

void reportState() {
  digitalWrite(4, leftDoor & rightDoor);
}

When the doors are closed their state is HIGH or 1 so when they are both HIGH the bitwise & operator will evaluate that to HIGH and turn on the LED.

Lets also report the door status online. Connect the Arduino Yún to your WiFi network. The Arduino processor can send shell commands to the Linux processor on the board. We will use the curl command to post the door states to an external API.

void reportState() {
  digitalWrite(4, leftDoor & rightDoor);

  Process curl;
  curl.begin("curl");
  curl.addParameter("--data");

  String data = "leftDoor=";
  data += leftDoor;
  data += "&rightDoor=";
  data += rightDoor;

  curl.addParameter(data);
  curl.addParameter(YOUR_EXTERNAL_API_SERVICE);
  curl.run();
}

That’s all! Now, you’ll be able to see if there is an available bathroom via an LED in the room or with a cool application that digests the API web service.

Areas For Improvement

During the project we realized that the power consumption of the XBee module was very high and had no way of going into a low power state. We measured the current consumption from the Fio board by placing a 0.5 ohm resistor in series with the power from the battery. The voltage drop across the resistor divided by the resistance gives us the current which was around 50mA to 60mA. The battery we are using is a 400mAh battery, so we would get about 8 hours of use. Charging the battery everyday is not an option so more research needs to be done into a lower power wireless communication solution. Also, this solution was expensive and more research can be done to find a lower cost implementation. thoughtbot Boston has two other bathrooms also located in remote places. We eventually want door sensors on these as well and a low cost setup for the door sensor would make this easier.