Toilet occupation sensor

Hi all,

I’m currently working on a toilet occupation sensor. Which has a lot of benefits, so far.

The sensor is a Panasonic ultra-low power PIR-sensor (which is incredible, if you ask me).
To send the data I use the NRF24L01.

But, I’m still facing issues to be precise; 1 problem on the Rx side, 1 problem at the Tx side.

The problem on the Tx side;
The code sends a message when a person is detected, when it sent the message it should go back to sleep directly. See in code comments

Problem on the Rx side: Reading a string
So, when the message is sent I’m able to receive and print the variable “text”. But when checking in code if(text == "Occupied"); it won’t work, it also is mentioned in code in comments

Rx

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <LowPower.h>
#include <avr/sleep.h>
#include <avr/power.h>

RF24 radio(9, 10); // CE, CSN

const byte ledPin_gents = 5;
const byte ledPin_ladies = 6;

char text[32] = {0};


const byte address[6] = "00001";

void wake(){
  // cancel sleep as a precaution
  sleep_disable();
  // precautionary while we do other stuff
  detachInterrupt (0);
}  // end of wake

void setup() {
  Serial.begin(9600);

  pinMode(ledPin_gents, OUTPUT);
  pinMode(ledPin_ladies, OUTPUT); 

  for (int i = 0; i < 20; i++) {
  if(i != 2)//just because the button is hooked up to digital pin 2
  pinMode(i, OUTPUT);
   }

   clock_prescale_set(clock_div_256);

  //Disable ADC - don't forget to flip back after waking up if using ADC in your application ADCSRA |= (1 << 7);
  ADCSRA &= ~(1 << 7);

  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
  
  //DISABLE BOD
  MCUCR |= (3 << 5); //set both BODS and BODSE at the same time
  MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); //then set the BODS bit and clear the BODSE bit at the same time

  //SET CLOCK PROCESSOR
  CLKPR = 0x80;
  CLKPR = 0x01;
  
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  digitalWrite(ledPin_gents, HIGH);
  delay (1000);
  digitalWrite(ledPin_ladies, HIGH);
  digitalWrite(ledPin_gents, LOW);
  delay (1000);
  digitalWrite(ledPin_ladies, LOW); 
 
}

void loop() {

  if (radio.available()) {
    char text[32] = {0};
    radio.read(&text, sizeof(text));
    Serial.println(text);

    if(text == "Occupied"){     // However the program actually reads/receives this text, it won't do the actions behind it
      Serial.println("Light sequence activated"); 
      digitalWrite(ledPin_gents, HIGH);
      delay(250); 
      digitalWrite(ledPin_gents, LOW); 
      delay(250); 
      digitalWrite(ledPin_ladies, HIGH); 
      delay(250);
      digitalWrite(ledPin_ladies, LOW); 
      delay(250); 
    }
  }
}

Tx

#include <LowPower.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(9, 10); // CE, CSN

const byte address[6] = "00001";

const byte ledPin = 8; //debug only
const byte interrupt_pin = 2;

unsigned long interval = 15000;

volatile byte state = LOW;

void wake(){
  // cancel sleep as a precaution
  sleep_disable();
  // precautionary while we do other stuff
  detachInterrupt (0);
}  // end of wake

void setup() {
  Serial.begin(9600); //temp 
  pinMode(ledPin, OUTPUT); 
  pinMode(interrupt_pin, INPUT);

  for (int i = 0; i < 20; i++) {
  if(i != 2)//just because the button is hooked up to digital pin 2
  pinMode(i, OUTPUT);
    }

   clock_prescale_set(clock_div_256);

  //Disable ADC - don't forget to flip back after waking up if using ADC in your application ADCSRA |= (1 << 7);
  ADCSRA &= ~(1 << 7);

  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
  
  //DISABLE BOD
  MCUCR |= (3 << 5); //set both BODS and BODSE at the same time
  MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); //then set the BODS bit and clear the BODSE bit at the same time

  //SET CLOCK PROCESSOR
  CLKPR = 0x80;
  CLKPR = 0x01;
  
  radio.begin();
  radio.openWritingPipe(address);
  radio.setRetries(5, 5);
  radio.setPALevel(RF24_PA_MAX);
  radio.stopListening();

  digitalWrite(ledPin, HIGH);
  delay (2000);
  digitalWrite(ledPin, LOW);
}

void loop() {
    
  ADCSRA = 0;
  
  // the interrupt must be attached each loop
  attachInterrupt(digitalPinToInterrupt(interrupt_pin),interrupt_routine,RISING);
  LowPower.powerDown(SLEEP_FOREVER,ADC_OFF,BOD_OFF); // sleep until interrupt
  detachInterrupt(digitalPinToInterrupt(interrupt_pin)); // remove interrupt
  
  // the usual wake routine that turns on the LED
  if (state==HIGH){
    const char text[] = "Occupied");
    digitalWrite(ledPin,HIGH);
    radio.write(&text, sizeof(text));
    Serial.println(text);
    delay(250);
  }
  
  if (state==HIGH){
    state = LOW;
    digitalWrite(ledPin,LOW);
    attachInterrupt(digitalPinToInterrupt(interrupt_pin),interrupt_routine,RISING);   //SO i'm trying to put the atMega328p back into sleep again before the interval. To save battery. Somehow I do have a drop in power consumption to 1,5mA. If the atMega sleeps, it consumes 43uA which I prefer... 
    delay(interval); // after this delay the sensor can check again if someone is still using the toilet. It is allowed to interrupt again
  }
}

void interrupt_routine(){
  state = HIGH;
}

strcmp is your friend.
Also, check the scope of “text”

And don’t put semicolons at the end of "if"s.

Hi TheMemberFormerlyKnownAsAWOL,

Thanks, so I managed indeed along with the tutorial from here https://www.arduino.cc/en/Tutorial/StringComparisonOperators how to fix that communication part.

See code below for adjustments.
Would you mind to also take a look at my Tx side problem?
The thing is; the setup consumes around 45uA when nothing happens.

If there is a detection, it sends it’s message which consumes 6mA. after that, I put it in delay, which consumes 1.5mA, I would prefer to put it directly back to sleep, and then give it a delay(interval);
In that case it consumes directly 45uA for 30s, after 30s it will check the status of the toilet again.

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <LowPower.h>
#include <avr/sleep.h>
#include <avr/power.h>

RF24 radio(9, 10); // CE, CSN

const byte ledPin_gents = 5;
const byte ledPin_ladies = 6;


String text;
String toiletStatus;

const byte address[6] = "00001";

void wake(){
  // cancel sleep as a precaution
  sleep_disable();
  // precautionary while we do other stuff
  detachInterrupt (0);
}  // end of wake

void setup() {
  Serial.begin(9600);

  pinMode(ledPin_gents, OUTPUT);
  pinMode(ledPin_ladies, OUTPUT); 

  for (int i = 0; i < 20; i++) {
  if(i != 2)//just because the button is hooked up to digital pin 2
  pinMode(i, OUTPUT);
   }

   clock_prescale_set(clock_div_256);

  //Disable ADC - don't forget to flip back after waking up if using ADC in your application ADCSRA |= (1 << 7);
  ADCSRA &= ~(1 << 7);

  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
  
  //DISABLE BOD
  MCUCR |= (3 << 5); //set both BODS and BODSE at the same time
  MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); //then set the BODS bit and clear the BODSE bit at the same time

  //SET CLOCK PROCESSOR
  CLKPR = 0x80;
  CLKPR = 0x01;
  
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  toiletStatus = String("Occupied");

  digitalWrite(ledPin_gents, HIGH);
  delay (1000);
  digitalWrite(ledPin_ladies, HIGH);
  digitalWrite(ledPin_gents, LOW);
  delay (1000);
  digitalWrite(ledPin_ladies, LOW); 
 
}

void loop() {

  if (radio.available()) {
    char text[32] = {0};
    radio.read(&text, sizeof(text));
    Serial.println(text);

    if(toiletStatus == "Occupied"){
      Serial.println("Light sequence activated"); 
      digitalWrite(ledPin_gents, HIGH);
      delay(250); 
      digitalWrite(ledPin_gents, LOW); 
      delay(250); 
      digitalWrite(ledPin_ladies, HIGH); 
      delay(250);
      digitalWrite(ledPin_ladies, LOW); 
      delay(250); 
    }
  }
}

Why have you changed it to use Strings?

TheMemberFormerlyKnownAsAWOL:
Why have you changed it to use Strings?

I used the example from the link: https://www.arduino.cc/en/Tutorial/StringComparisonOperators

since they've used Strings, I did the same.
It works good though, but I guess you have an other suggestion.

But you didn’t take my hint about the scope of the variables called “text”

TheMemberFormerlyKnownAsAWOL:
But you didn't take my hint about the scope of the variables called "text"

hmmm, no had to think, but I'm missing the hint.. Sorry, can you help me a bit more maybe? :slight_smile:

How many variables called “text” do you have?
(Hint: it’s more than one)

TheMemberFormerlyKnownAsAWOL:
How many variables called “text” do you have?
(Hint: it’s more than one)

Currently two I guess, one is

 String text;

And the other

    char text[32] = {0};

I should delete the String text;
the other one is necessary.
Code is currently broken as well, so I’m still wrong.

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <LowPower.h>
#include <avr/sleep.h>
#include <avr/power.h>

RF24 radio(9, 10); // CE, CSN

const byte ledPin_gents = 5;
const byte ledPin_ladies = 6;


//String text;
String occupied;

const byte address[6] = "00001";

void wake(){
  // cancel sleep as a precaution
  sleep_disable();
  // precautionary while we do other stuff
  detachInterrupt (0);
}  // end of wake

void setup() {
  Serial.begin(9600);

  pinMode(ledPin_gents, OUTPUT);
  pinMode(ledPin_ladies, OUTPUT); 

  for (int i = 0; i < 20; i++) {
  if(i != 2)//just because the button is hooked up to digital pin 2
  pinMode(i, OUTPUT);
   }

   clock_prescale_set(clock_div_256);

  //Disable ADC - don't forget to flip back after waking up if using ADC in your application ADCSRA |= (1 << 7);
  ADCSRA &= ~(1 << 7);

  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
  
  //DISABLE BOD
  MCUCR |= (3 << 5); //set both BODS and BODSE at the same time
  MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); //then set the BODS bit and clear the BODSE bit at the same time

  //SET CLOCK PROCESSOR
  CLKPR = 0x80;
  CLKPR = 0x01;
  
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();

  occupied = String();

  digitalWrite(ledPin_gents, HIGH);
  delay (1000);
  digitalWrite(ledPin_ladies, HIGH);
  digitalWrite(ledPin_gents, LOW);
  delay (1000);
  digitalWrite(ledPin_ladies, LOW); 
 
}

void loop() {

  if (radio.available()) {
    char text[32] = {0};
    radio.read(&text, sizeof(text));
    Serial.println(text);

    if(occupied == "Occupied"){
      Serial.println("Light sequence activated"); 
      digitalWrite(ledPin_gents, HIGH);
      delay(250); 
      digitalWrite(ledPin_gents, LOW); 
      delay(250); 
      digitalWrite(ledPin_ladies, HIGH); 
      delay(250);
      digitalWrite(ledPin_ladies, LOW); 
      delay(250); 
    }
  }
}

What is the String ‘occupied’ ?
(Apart from empty? (twice))

TheMemberFormerlyKnownAsAWOL:
What is the String 'occupied' ?
(Apart from empty? (twice))

Nothing in fact, what I've tried to do;

The Tx sends a variable text with the string "Occupied"
(since motion is detected, the toilet is occupied)

The Rx needs to capture the string and then recognize to take further action.
At first I had
if (text == "Occupied"){
action
}

since that didn't work I've tried to change the variable string to toiletStatus without any luck.

So
if (toiletStatus == "Occupied"){
action
}

It's super stupid, since I feel like I'm close, but somehow can't manage to find the issue.

Don’t you think you should be comparing the string you receive to the string “occupied”?
Maybe using strcmp, as suggested earlier.

TheMemberFormerlyKnownAsAWOL:
Don’t you think you should be comparing the string you receive to the string “occupied”?
Maybe using strcmp, as suggested earlier.

Sorry for my late response, I’ve spent some hours on trying to fix the issue, with partly the help from you.
So I’ve managed to get things going, first of all I’m hoping you like this approach.

Rx

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <LowPower.h>
#include <avr/sleep.h>
#include <avr/power.h>

RF24 radio(9, 10); // CE, CSN

const byte ledPin_gents = 5;
const byte ledPin_ladies = 6;

const byte address[6] = "00001";

boolean sensorState = 0; 

int ledState = LOW;             // ledState used to set the LED
unsigned long previousMillis = 0;        // will store last time LED was updated
long OnTime = 250;           // milliseconds of on-time
long OffTime = 750;          // milliseconds of off-time

void wake(){
  // cancel sleep as a precaution
  sleep_disable();
  // precautionary while we do other stuff
  detachInterrupt (0);
}  // end of wake

void setup() {
  Serial.begin(9600);

  pinMode(ledPin_gents, OUTPUT);
  pinMode(ledPin_ladies, OUTPUT); 

  for (int i = 0; i < 20; i++) {
  if(i != 2)//just because the button is hooked up to digital pin 2
  pinMode(i, OUTPUT);
   }

   clock_prescale_set(clock_div_256);

  //Disable ADC - don't forget to flip back after waking up if using ADC in your application ADCSRA |= (1 << 7);
  ADCSRA &= ~(1 << 7);

  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
  
  //DISABLE BOD
  MCUCR |= (3 << 5); //set both BODS and BODSE at the same time
  MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); //then set the BODS bit and clear the BODSE bit at the same time

  //SET CLOCK PROCESSOR
  CLKPR = 0x80;
  CLKPR = 0x01;
  
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MAX);
  radio.startListening();
  
  digitalWrite(ledPin_gents, HIGH);
  delay (1000);
  digitalWrite(ledPin_ladies, HIGH);
  digitalWrite(ledPin_gents, LOW);
  delay (1000);
  digitalWrite(ledPin_ladies, LOW); 
 
}

void loop() {
  if (radio.available()) {
   // char text[32] = "";
    radio.read(&sensorState, sizeof(sensorState));
    
      if(sensorState == HIGH){
        Serial.println("Received message: toilet is occcupied");
        digitalWrite(ledPin_gents, HIGH); 
        delay(250); 
        occupied(); 
        digitalWrite(ledPin_gents, HIGH); 

    }
  } 
}
void occupied(){ 
      
    unsigned long currentMillis = millis();
    Serial.println("I've been triggered, counting down... from 10"); 
    
  if((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
  {
    ledState = LOW;  // Turn it off
    previousMillis = currentMillis;  // Remember the time
    digitalWrite(ledPin_gents, ledState);  // Update the actual LED
  }
  else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
  {
    ledState = HIGH;  // turn it on
    previousMillis = currentMillis;   // Remember the time
    digitalWrite(ledPin_gents, ledState);    // Update the actual LED
  }
}

Tx

//v4 belongs to Rx v2(!) 

#include <LowPower.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(9, 10); // CE, CSN

const byte address[6] = "00001";

const byte ledPin = 8; //debug only
const byte interrupt_pin = 2;

unsigned long interval = 15000;

volatile byte state = LOW;
boolean sensorState = 0;

void wake(){
  // cancel sleep as a precaution
  sleep_disable();
  // precautionary while we do other stuff
  detachInterrupt (0);
}  // end of wake

void setup() {
  Serial.begin(9600); //temp 
  pinMode(ledPin, OUTPUT); 
  pinMode(interrupt_pin, INPUT);

  for (int i = 0; i < 20; i++) {
  if(i != 2)//just because the button is hooked up to digital pin 2
  pinMode(i, OUTPUT);
    }

   clock_prescale_set(clock_div_256);

  //Disable ADC - don't forget to flip back after waking up if using ADC in your application ADCSRA |= (1 << 7);
  ADCSRA &= ~(1 << 7);

  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
  
  //DISABLE BOD
  MCUCR |= (3 << 5); //set both BODS and BODSE at the same time
  MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); //then set the BODS bit and clear the BODSE bit at the same time

  //SET CLOCK PROCESSOR
  CLKPR = 0x80;
  CLKPR = 0x01;
  
  radio.begin();
  radio.openWritingPipe(address);
  radio.setRetries(3, 5);
  radio.setPALevel(RF24_PA_MAX);
  radio.stopListening();

  digitalWrite(ledPin, HIGH);
  delay (2000);
  digitalWrite(ledPin, LOW);
}

void loop() {

  ADCSRA = 0;
  
  // the interrupt must be attached each loop
  attachInterrupt(digitalPinToInterrupt(interrupt_pin),interrupt_routine,RISING);
  LowPower.powerDown(SLEEP_FOREVER,ADC_OFF,BOD_OFF); // sleep until interrupt
  detachInterrupt(digitalPinToInterrupt(interrupt_pin)); // remove interrupt
  
  // the usual wake routine that turns on the LED
  if (state==HIGH){
    send();
    delay(250);
  }
  
  if (state==HIGH){
    state = LOW;
    digitalWrite(ledPin,LOW);
    attachInterrupt(digitalPinToInterrupt(interrupt_pin),interrupt_routine,RISING);
    delay(interval);
  }
}

void interrupt_routine(){
  state = HIGH;
}

void send(){ 

  sensorState = digitalRead(interrupt_pin);
  
    //const char text[] = "Occupied";
    digitalWrite(ledPin,HIGH);
    radio.write(&sensorState, sizeof(sensorState));
    Serial.println("Motion is detected, toilet is occupied");
  
}

Do know, I’m currently playing in the Rx code, since I have a new problem. When turning the LED on, on the Rx side, I want to put it on for 30s, but I also want to keep listening for new rf-data, since there are multiple toilets, thats why the millis() function is there, which doesnt work yet

Still playing with this, to see if I can fix it