Electronic – RC LED “Bathroom occupied” indicator

ledremote controlwireless

I want to build a little LED indicator for my bathroom door. The basic idea is that when a person sits or stands next to the toilet, a little transmitter signals it wirelessly to the LED diode which is mounted on the front side of the door. The LED turns on so the next person who wants to come in knows that the toiled is occupied. When the guest comes out of the bathroom, the transmitter yet again signals that the LED should turn off.
I came up with a plan to connect the LED to a little battery. The circuit has to work as a On/Off switch. Next to the toiled will be a circuit with a photoresistor (?) which can help in detecting someone next to the toilet. The change in its current/voltage triggers the little transmitter which sends a simple pulse (or another simple signal), which the LED circuit will be able to receive, and then changes its state.

But I have a few questions. Is using the photoresistor a good plan? Maybe there is a better solution of which I have no idea? What kind of signal should the transmitter produce? What would be best to use as a switch circuit on the LED side?

I would be happy to read some suggestions that may help me with this project.

Best Answer

I built something along these lines. It's a wireless door sensor with a magnetic sensor on one side and an LED on the receiver. Conveniently, both the door sensor and the receiver are identical in schematic, board, and firmware, save for a single jumper which denotes which role the receiver/sensor will play.

This design uses a reed sensor, which is a magnetic sensor that detects the presence of a magnet within a couple inches. I attach a magnet to the bottom of the door and attach the sensor board to the bottom of the door frame. This works on toilet stalls or the bathroom door itself.

In order to save power, the receiver turns on for 1/10th of a second every second. That means the receiver will have a 1 second delay, but it consumes 1/10th of the power. You can easily adjust these numbers based on how little power you want the receiver to use and how much of a delay you are willing to tolerate.

The remote uses hardware pin change interrupts, so it only uses power when broadcasting. That means you can run it off a single coin cell battery for months.

Here's the schematic:

Schematic

This schematic allows for both a CR2032 coin cell battery or a 2-pin JST battery connector (used for AA battery packs and LiPo's). It uses the nRF24L01 wireless IC, which is easily available.

The source code and EAGLE files are available at https://github.com/samuelclay/doormonitor Here's the firmware source code. Libraries and dependencies are linked above, but this is the main routine.

#include <avr/sleep.h> 
#include <avr/interrupt.h> 
#include <avr/wdt.h>

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <pinchange.h>

void sleepNow(void);
void wakeup();
void setupWatchdog(uint8_t prescalar);

#if defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny85__)
    RF24 radio(3,7);
    const int role_pin = 10;
    const int sensor_pin = 2;
    const int led_pin = 1;
    // const int sensor_pin = 8;
    // const int led_pin = 9;
#else
    RF24 radio(9, 10);
    const int role_pin = 6;
    const int sensor_pin = 2;
    const int led_pin = 4;
#endif 

const uint64_t pipe = 0xA8E8F0F0F1LL;

typedef enum { wdt_16ms = 0, wdt_32ms, wdt_64ms, wdt_128ms, wdt_250ms, 
               wdt_500ms, wdt_1s, wdt_2s, wdt_4s, wdt_8s } wdt_prescalar_e;

typedef enum { 
    role_remote = 1, 
    role_led = 2
} role_e;
role_e role;
const char* role_friendly_name[] = { "invalid", "Remote", "LED Board"};

uint8_t led_state = 0;
uint8_t sensor_state = 0;
volatile int awakems = 0;
int send_tries = 0;
bool send_ok = false;

void setup(void) {
    // set up the role pin
    pinMode(role_pin, INPUT);
    digitalWrite(role_pin, HIGH);
    delay(20); // Just to get a solid reading on the role pin

    // read the address pin, establish our role
    role = digitalRead(role_pin) ? role_remote : role_led;
    digitalWrite(role_pin, LOW);

    Serial.begin(9600);

    radio.begin();
    radio.setChannel(38);
    radio.setDataRate(RF24_250KBPS);
    radio.setAutoAck(pipe, true);
    radio.setRetries(15, 15);

    if (role == role_remote) {
        radio.openWritingPipe(pipe);
        radio.stopListening();
    } else {
        radio.openReadingPipe(1,pipe);
        radio.startListening();
    }

//    radio.printDetails();

    if (role == role_remote) {
        pinMode(sensor_pin,INPUT);
        digitalWrite(sensor_pin,HIGH);
    }

    if (role == role_led) {
        setupWatchdog(wdt_2s);
    }

    pinMode(led_pin,OUTPUT);
    led_state = LOW;
    digitalWrite(led_pin, led_state);
    int i = role == role_led ? 4 : 2;
    int pause = role == role_led ? 100 : 300;
    while (i--) {
        delay(pause);
        digitalWrite(led_pin, HIGH);
        delay(pause);
        digitalWrite(led_pin, LOW);
    }
}

void loop(void) {
    bool different = false;

    if (role == role_remote) {
        // Get the current state of buttons, and
        // Test if the current state is different from the last state we sent
        uint8_t state = !digitalRead(sensor_pin);
        Serial.write("Sensor state: ");
        Serial.write(state ? "ON" : "off");
        Serial.write('\n');
        if (state != sensor_state) {
            different = true;
            send_tries = 1000;
            sensor_state = state;
            led_state = sensor_state;
        }

        // Send the state of the buttons to the LED board
        if (send_tries && (different || !send_ok)) {
            Serial.write("Now sending...");
            digitalWrite(led_pin, led_state);
            radio.powerUp();
            delay(10);
            send_ok = radio.write( &sensor_state, sizeof(uint8_t) );
            if (send_ok) {
                Serial.write("ok\n\r");
            } else {
                char tries_left_char[1];
                send_tries--;
                itoa(send_tries, tries_left_char, 10);
                Serial.write("failed (");
                Serial.write((char *)tries_left_char);
                Serial.write(" tries left)\n\r");
                digitalWrite(led_pin, LOW);
                delay(25);        
                digitalWrite(led_pin, led_state);            
            }
            radio.powerDown();
            awakems = 0;
        }

        awakems += 1;
        if (awakems > 10) {
            sleepNow();
            awakems = 0;
        }
    }

    if (role == role_led) {
         // digitalWrite(led_pin, HIGH);

        if (radio.available()) {
            // Dump the payloads until we've gotten everything
            bool done = false;
            awakems = 0;
            while (!done) {
                done = radio.read( &sensor_state, sizeof(uint8_t) );
            }
            Serial.write("Got buttons: ");
            Serial.write(sensor_state ? "ON\n\r" : "off\n\r");
            led_state = sensor_state;
            digitalWrite(led_pin, led_state);
            awakems = -10000;
        }

        uint8_t incomingByte;
        // send data only when you receive data:
        if (Serial.available() > 0) {
            // read the incoming byte:
            incomingByte = Serial.read();

            // say what you got with both the ASCII and decimal representations
            Serial.print("I received: ");
            Serial.write(incomingByte);
            Serial.print(" : ");
            Serial.println(incomingByte, DEC);

            awakems = 0;
        }

        awakems += 1;
        if (awakems > 4000) {
            // digitalWrite(led_pin, LOW);
            sleepNow();
            awakems = 0;
        }
    }
}
#define BODS 7                   //BOD Sleep bit in MCUCR
#define BODSE 2                  //BOD Sleep enable bit in MCUCR
uint8_t mcucr1, mcucr2;

void setupWatchdog(uint8_t prescalar) {
  prescalar = min(9,prescalar);
  uint8_t wdtcsr = prescalar & 7;
  if ( prescalar & 8 )
    wdtcsr |= _BV(WDP3);

  MCUSR &= ~_BV(WDRF);
  WDTCSR = _BV(WDCE) | _BV(WDE);
  WDTCSR = _BV(WDCE) | wdtcsr | _BV(WDIE);
}

void sleepNow(void) {
    Serial.write("Sleeping...\n\r");
    if (role == role_led) {
        radio.stopListening();
    }
    radio.powerDown();

    if (role == role_remote) {
        attachPcInterrupt(sensor_pin, wakeup, CHANGE);
    }
    ACSR |= _BV(ACD);                         //disable the analog comparator
    ADCSRA &= ~_BV(ADEN);                     //disable ADC
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();

    //turn off the brown-out detector.
    //must have an ATtiny45 or ATtiny85 rev C or later for software to be able to disable the BOD.
    //current while sleeping will be <0.5uA if BOD is disabled, <25uA if not.
    cli();
    mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE);  //turn off the brown-out detector
    mcucr2 = mcucr1 & ~_BV(BODSE);
    MCUCR = mcucr1;
    MCUCR = mcucr2;
    sei();                         //ensure interrupts enabled so we can wake up again
    sleep_cpu();                   //go to sleep
    cli();                         //wake up here, disable interrupts
    if (role == role_remote) {
        detachPcInterrupt(sensor_pin);
    }
    sleep_disable();               
    sei();                         //enable interrupts again (but INT0 is disabled from above)
    Serial.print("Wakeup...\n\r");
    if (role == role_led) {
      radio.startListening();
    }
    delay(5);
}

void wakeup() {
    awakems = 0;
}

ISR(WDT_vect) {
    awakems = 0;
}