c++arduinosensorsinfrared

Problem with counting number of visitors in my smart room


Mealy DiagramI'm building a visitor counter in my room using Arduino, IR transmitter, and two SHARP IR sensors. The sensors count the number of people in my room - one person turns up the light, more do nothing, and zero turns the light off. The IR transmitter works great (it turns the light on/off) so as the detection, but I have a problem and a question.

My question is, is there an option to make a function and call it twice instead of repeating myself inside the loop? if so, how? they are mostly symmetric, but still have some differences like the flags and num_people manipulating.

My main problem right now is counting num_people and I found out that this line is the problem: num_people = (num_people > 0) ? num_people-1 : 0; When I put this line in a comment I can count more than one person, but when I'm not it runs over the code and counts 1,0,1,0,1,0 (only when getting inside the room). Need to say that when I go out it decreases num_people as it should and when on zero keeps that number.

The code is here:



/*
    Smart Light V1.0 Software
    ==========================
    Copyright (C) 2020 Yuval Kedar - KD Tech
    The program counts visitors in my room and controls the light using SHARP IR sensors
    Board: Arduino Pro Mini
*/


#include "Arduino.h"
#include <IRremote.h>
// TODO: try a dedicated library for the sensors. Raw readings aren't the best way to do that - need to overcome oversampling, debouncing, hysteresis, etc.

#define IR_TRANSMITTER_PIN  (3)
#define SENS_1_PIN (A1)
#define SENS_2_PIN (A0)

#define SENS_1_MIN  (160)
#define SENS_2_MIN  (160)
#define MAX_TIME    (1000)
#define IR_KEY      (0x68B92)
#define DEBOUNCE_MS (300)

IRsend irsend;

uint8_t num_people = 0;
uint8_t prev_num_people = 0;
bool sens_1_flag = 0;
bool sens_2_flag = 0;
uint32_t timeout;

void setup() {
    Serial.begin(115200);
    pinMode(SENS_1_PIN, INPUT);
    pinMode(SENS_2_PIN, INPUT);
    pinMode(IR_TRANSMITTER_PIN, OUTPUT);
    Serial.println(F(
                "_______________________________\n"
                "\n"
                "      S M A R T    R O O M     \n"
                "_______________________________\n"
                "\n"
                "      Made by KD Technology    \n"
                "\n"));
}

void loop() {
    uint16_t sens_1_val = analogRead(SENS_1_PIN);
    uint16_t sens_2_val = analogRead(SENS_2_PIN);
    /*
    Serial.print("LDR 1: ");
    Serial.print(sens_1_val);
    Serial.print("\t LDR 2: ");
    Serial.println(sens_2_val);
    delay(200);
    */

    //TODO: there is a duplication here. Create a function and call it twice inside the loop.

    //Someone goes in
    if (sens_1_val < SENS_1_MIN && sens_2_val > SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 0) sens_2_flag = 1;
    if (sens_1_val > SENS_1_MIN && sens_2_val < SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 1) {
        // TODO: add timeout to while loop. Otherwise, the program will stuck because of a sensor reading.
        // timeout = millis() + MAX_TIME;
        // while (sens_2_val > SENS_2_MIN && (millis() > timeout));
        num_people = num_people+1;
        if (num_people == 1 && prev_num_people == 0) {
            irsend.sendSony(IR_KEY, 20);
            Serial.println("BLING!");
        }
        sens_1_flag = 0;
        sens_2_flag = 0;
        prev_num_people = num_people;           
        Serial.print("People in the room: ");
        Serial.println(num_people);
        delay(DEBOUNCE_MS);
    }

    //Someone goes out
    if (sens_1_val > SENS_1_MIN && sens_2_val < SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 0) sens_1_flag = 1;
    if (sens_1_val < SENS_1_MIN && sens_2_val > SENS_2_MIN && sens_1_flag == 1 && sens_2_flag == 0) {
        timeout = millis() + MAX_TIME;
        while (sens_1_val > SENS_1_MIN && (millis() > timeout));
        num_people = (num_people > 0) ? num_people-1 : 0;
        if (num_people == 0 && prev_num_people != 0) {
            irsend.sendSony(IR_KEY, 20);
            Serial.println("BLING!");
        }
        sens_1_flag = 0;
        sens_2_flag = 0;
        prev_num_people = num_people;           
        Serial.print("People in the room: ");
        Serial.println(num_people);
        delay(DEBOUNCE_MS);
    }
}

EDIT:


/*
    Smart Light V1.0 Software
    ==========================
    Copyright (C) 2020 Yuval Kedar - KD Tech
    The program counts visitors in my room and controls the light using SHARP IR sensors
    Board: Arduino Pro Mini
*/


#include "Arduino.h"
#include <IRremote.h>
// TODO: try a dedicated library for the sensors. Raw readings aren't the best way to do that - need to overcome oversampling, debouncing, hysteresis, etc.

#define IR_TRANSMITTER_PIN  (3)
#define SENS_1_PIN (A1)
#define SENS_2_PIN (A0)

#define SENS_1_MIN  (160)
#define SENS_2_MIN  (160)
#define MAX_TIME    (1000)
#define IR_KEY      (0x68B92)
#define DEBOUNCE_MS (300)

IRsend irsend;

uint8_t num_people = 0;
uint8_t prev_num_people = 0;
bool sens_1_flag = 0;
bool sens_2_flag = 0;
uint32_t timeout;

void setup() {
    Serial.begin(115200);
    pinMode(SENS_1_PIN, INPUT);
    pinMode(SENS_2_PIN, INPUT);
    pinMode(IR_TRANSMITTER_PIN, OUTPUT);
    Serial.println(F(
                "_______________________________\n"
                "\n"
                "      S M A R T    R O O M     \n"
                "_______________________________\n"
                "\n"
                "      Made by KD Technology    \n"
                "\n"));
}

void loop() {
    uint16_t sens_1_val = analogRead(SENS_1_PIN);
    uint16_t sens_2_val = analogRead(SENS_2_PIN);
    /*
    Serial.print("LDR 1: ");
    Serial.print(sens_1_val);
    Serial.print("\t LDR 2: ");
    Serial.println(sens_2_val);
    delay(200);
    */

    //TODO: there is a duplication here. Create a function and call it twice inside the loop.

    //Someone goes in
    if (sens_1_val < SENS_1_MIN && sens_2_val > SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 0) sens_2_flag = 1;
    if (sens_1_val > SENS_1_MIN && sens_2_val < SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 1) {
        // TODO: add timeout to while loop. Otherwise, the program will stuck because of a sensor reading.
        // timeout = millis() + MAX_TIME;
        // while (sens_2_val > SENS_2_MIN && (millis() > timeout));
        num_people += 1;
        if (num_people == 1 && prev_num_people == 0) {
            irsend.sendSony(IR_KEY, 20);
            Serial.println("BLING!");
        }
        sens_1_flag = 0;
        sens_2_flag = 0;
        prev_num_people = num_people;           
        Serial.print("People in the room: ");
        Serial.println(num_people);
        delay(DEBOUNCE_MS);
    }

    //Someone goes out
    if (sens_1_val > SENS_1_MIN && sens_2_val < SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 0) sens_1_flag = 1;
    if (sens_1_val < SENS_1_MIN && sens_2_val > SENS_2_MIN && sens_1_flag == 1 && sens_2_flag == 0) {
        // timeout = millis() + MAX_TIME;
        // while (sens_1_val > SENS_1_MIN && (millis() > timeout));
        // num_people = (num_people > 0) ? num_people-1 : 0;
        if (num_people > 1) num_people -= 1;
        if (num_people == 1 && prev_num_people != 0) {
            num_people -= 1;
            irsend.sendSony(IR_KEY, 20);
            Serial.println("BLING!");
        }
        sens_1_flag = 0;
        sens_2_flag = 0;
        prev_num_people = num_people;           
        Serial.print("People in the room: ");
        Serial.println(num_people);
        delay(DEBOUNCE_MS);
    }
}

DIFFERENT APPROACH:

I figured out the new machine state. There are 4 states I need to pass: 1. sens_1 detects and sens_2 doesn't 2. both sensors detect 3. sens_2 detect and sens_1 doesn't 4. none of them detects.

Here is my code:


/*
    Smart Light V1.0 Software
    ==========================
    Copyright (C) 2020 Yuval Kedar - KD Tech
    The program counts visitors in my room and controls the light using SHARP IR sensors
    Board: Arduino Pro Mini
*/


#include "Arduino.h"
#include <IRremote.h>
// TODO: try a dedicated library for the sensors. Raw readings aren't the best way to do that - need to overcome oversampling, debouncing, hysteresis, etc.

#define IR_TRANSMITTER_PIN  (3)
#define SENS_1_PIN (A0)
#define SENS_2_PIN (A1)

#define SENS_1_MIN  (115)
#define SENS_2_MIN  (115)
#define IR_KEY      (0x68B92)
#define DEBOUNCE_MS (2000)


IRsend irsend;

int8_t num_people = 0;
uint8_t state = 0;

void setup() {
    Serial.begin(115200);
    pinMode(SENS_1_PIN, INPUT);
    pinMode(SENS_2_PIN, INPUT);
    pinMode(IR_TRANSMITTER_PIN, OUTPUT);
    Serial.println(F(
                "_______________________________\n"
                "\n"
                "      S M A R T    R O O M     \n"
                "_______________________________\n"
                "\n"
                "      Made by KD Technology    \n"
                "\n"));
}

void loop() {
    uint16_t sens_1_val = analogRead(SENS_1_PIN);
    uint16_t sens_2_val = analogRead(SENS_2_PIN);
    switch (state) {
        case 0:
            if ((sens_1_val > SENS_1_MIN) && (sens_2_val < SENS_2_MIN)) {
            // if (!sens_1_val && sens_2_val) {
                state = 4;
                Serial.println("0 to 4");
            }
            if ((sens_2_val > SENS_2_MIN) && (sens_1_val < SENS_1_MIN)) {
            // if (!sens_2_val && sens_1_val) {
                state = 1;
                Serial.println("0 to 1");
            }
            break;
        case 1:
            if ((sens_1_val > SENS_1_MIN) && (sens_2_val > SENS_2_MIN)) {
            // if (!sens_1_val && !sens_2_val) {
                state = 2;
                Serial.println("1 to 2");
            } else state = 0;
            break;
        case 2:
            if ((sens_1_val > SENS_1_MIN) && (sens_2_val < SENS_2_MIN)) {
            // if (!sens_1_val && sens_2_val) {
                state = 3;
                Serial.println("2 to 3");
            } else state = 1;
            break;
        case 3:
            if ((sens_1_val < SENS_1_MIN) && (sens_2_val < SENS_2_MIN)) {   //someone went out
            // if (sens_1_val && sens_2_val) {  //someone went out
                num_people -= 1;
                if (num_people == 0) {
                    irsend.sendSony(IR_KEY, 20);
                    Serial.println("BLING!");
                }
                else if (num_people == -1) num_people = 0;
                Serial.print("People in the room: ");
                Serial.println(num_people);
                // delay(DEBOUNCE_MS);
                state = 0;
            }  else state = 2;
            break;
        case 4:
            if ((sens_1_val > SENS_1_MIN) && (sens_2_val > SENS_2_MIN)) {
            // if (!sens_1_val && !sens_2_val) {
                state = 5;
                Serial.println("4 to 5");
            } else state = 0;
            break;
        case 5:
            if ((sens_2_val > SENS_2_MIN) && (sens_1_val < SENS_1_MIN)) {
            // if (!sens_2_val && sens_1_val) {
                state = 6;
                Serial.println("5 to 6");
            } else state = 4;
            break;
        case 6:
            if ((sens_1_val < SENS_1_MIN) && (sens_2_val < SENS_2_MIN)) {   //someone came in
            // if (sens_1_val && sens_2_val) {  //someone came in
                num_people += 1;
                if (num_people == 1) {
                    irsend.sendSony(IR_KEY, 20);
                    Serial.println("BLING!");
                }
                Serial.print("People in the room: ");
                Serial.println(num_people);
                // delay(DEBOUNCE_MS);
                state = 0;
            } else state = 5;
            break;
    }
    // Serial.print("LDR 1: ");
    // Serial.print(sens_1_val);
    // Serial.print("\t LDR 2: ");
    // Serial.println(sens_2_val);
    // delay(DEBOUNCE_MS);
}

I first checked the code with buttons and digital readings and it worked perfectly! But then, when I went back to analog readings a problem came up: I printed to the serial monitor a string at each state to find the problem and noticed that it passes all the states but when it reaches the last state and suppose to increase\decrease num_people and print it as well it doesn't. Instead it goes back to state 0...


Solution

  • is there an option to make a function and call it twice instead of repeating myself inside the loop? if so, how? they are mostly symmetric, but still have some differences like the flags and num_people manipulating.

    For sure your two parts are very similar, and this is more visible changing the order of the expressions in the 2 ifs.

    For instance in the second part you have

    if (sens_1_val > SENS_1_MIN && sens_2_val < SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 0) sens_1_flag = 1;
    if (sens_1_val < SENS_1_MIN && sens_2_val > SENS_2_MIN && sens_1_flag == 1 && sens_2_flag == 0) {
    

    which is equivalent of that after reordering :

    if (sens_2_val < SENS_2_MIN && sens_1_val > SENS_1_MIN && sens_2_flag == 0 && sens_1_flag == 0) sens_1_flag = 1;
    if (sens_2_val > SENS_2_MIN && sens_1_val < SENS_1_MIN && sens_2_flag == 0 && sens_1_flag == 1) {
    

    so exactly the two tests in the first part when exchanging sens_1_val with sens_2_val and SENS_1_MIN with SENS_2_MIN, and it is the same in the rest of the lines.

    Then :

    void f(int sens1, int sens1_min, int sens2,int sens2_min, int offset, int bling)
    {
        if (sens1 < sens1_min && sens2 > sens2_min && sens_1_flag == 0 && sens_2_flag == 0) sens_2_flag = 1;
        if (sens1 > sens1_min && sens2 < sens2_min && sens_1_flag == 0 && sens_2_flag == 1) {
            timeout = millis() + MAX_TIME;
            while (sens2 > sens2_min && (millis() > timeout));
            if ((num_people += offset) < 0)
              num_people = 0;
            Serial.print("People in the room: ");
            Serial.println(num_people);
            sens_1_flag = 0;
            sens_2_flag = 0;
            delay(DEBOUNCE_MS);
            if (num_people == bling) {
                irsend.sendSony(IR_KEY, 20);
                Serial.println("BLING!");
            }
        }
    }
    

    the first part in your code is done by

     f(sens_1_val, SENS_1_MIN, sens_2_val, SENS_2_MIN, 1, 1);
     sens_1_val = sens_2_val = 0;
    

    and the second by

    f(sens_2_val, SENS_2_MIN, sens_1_val, SENS_1_MIN, -1, 0); 
    sens_1_val = sens_2_val = 0;
    

    I did not wanted to reset the sens_x_val in the function to not have to give a pointer

    For the rest sorry but I do not understand your problem, explain it more


    edit after you added the state machine

    Your state machine is strange, I think you do not need so many states. The machine definition depends on if the light can be put off/on manually by someone inside and/or outside of the room.

    If the light is only commanded by your sensors more a timeout in case nobody went out nor come in after a time :

    enter image description here

    I use here UML notation, in the transition a '[]' indicate a guard/condition to respect else the transition cannot be done, the body after the '/' is executed after the transition is done.

    To manage the case where several persons come out at the same time but the sensors count only one I use a timeout.

    If the light can be also commanded by someone inside the room, and the light is forced on when someone come in, and the light is forced off when the room becomes empty / after the timeout :

    enter image description here


    edit, found initial problem

    In your initial version your problem was in :

       num_people = (num_people > 0) ? num_people-1 : 0;
       ...
       if (num_people == 0) {
           irsend.sendSony(IR_KEY, 20);
           Serial.println("BLING!");
       }
    

    because when you do the test in the if you do not know if your light is on or off, you lost that information in the first line, and you can wrongly decide to change its state.

    edit after your code editing

    For me your new code seems ok, anyway you can do without prev_num_people.

    You set prev_num_people to num_people after each in/out, so in the case "come in" :

       num_people = num_people+1;
       if (num_people == 1 && prev_num_people == 0) {
         irsend.sendSony(IR_KEY, 20);
         Serial.println("BLING!");
       }
    

    before to increment num_people you had prev_num_people == num_people then after having num_people == 1 implies prev_num_people == 0 and it is useless to test it. Just do

    if (++num_people == 1) {
       /* light is off, put it on */
       irsend.sendSony(IR_KEY, 20);
       Serial.println("BLING!");
     }
    

    In the case "went out" rather than to do

       num_people = (num_people > 0) ? num_people-1 : 0;
       if (num_people == 0 && prev_num_people != 0) {
           irsend.sendSony(IR_KEY, 20);
           Serial.println("BLING!");
       }
    

    just do

     if (--num_people == 0) {
        /* light is on, put it off */
        irsend.sendSony(IR_KEY, 20);
        Serial.println("BLING!");
     }
     else if (num_people == -1) {
        /* light is already off but in past several person
           went out at the same time and only one was count */
        num_people = 0;
     }
    

    All of that supposes you detect rightly someone come in / went out, I cannot help you for that, I don't know how your sensors work.

    Note if you want to allow a manual change of the light you need to memorize the state of the light, NumPerson is not anymore sufficient to decide what is the current state of the light.


    edit about your new version containing:

       if (num_people > 1) num_people -= 1;
       if (num_people == 1 && prev_num_people != 0) {
           num_people -= 1;
           irsend.sendSony(IR_KEY, 20);
           Serial.println("BLING!");
       }
    

    this is 'disturbing' because when you reach the second if you only know where you come from thank to prev_num_people (num_people lost the history), and you do a lot of tests for nothing

    I strongly encourage you to use the simplified version I given you before without prev_num_people. The more a code is simple, the more it is easy to read and the more it is robust


    edit to add simulation

    If I simulate your sensors, your code is disabled by "#if 0" :

    #include <stdio.h>
    
    int main()
    {
      int num_people = 0;
      int real_people = 0;
      int n;
    
      while (scanf("%d", &n) == 1) {
        real_people += n;
    
        //Someone goes in
    #if 0
        if (sens_1_val < SENS_1_MIN && sens_2_val > SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 0) sens_2_flag = 1;
        if (sens_1_val > SENS_1_MIN && sens_2_val < SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 1)
    #else
        if (n > 0)
    #endif
        {
          if (++num_people == 1) {
            /* light is off, put it on */
    #if 0
            irsend.sendSony(IR_KEY, 20);
            Serial.println("BLING!");
    #else
            puts("put light on");
    #endif
          }
    #if 0
          sens_1_flag = 0;
          sens_2_flag = 0;
          Serial.print("People in the room: ");
          Serial.println(num_people);
          delay(DEBOUNCE_MS);
    #else
          printf("People supposed the room: %d (real %d)\n", num_people, real_people);
    #endif
        }
    
        //Someone goes out
    #if 0
        if (sens_1_val > SENS_1_MIN && sens_2_val < SENS_2_MIN && sens_1_flag == 0 && sens_2_flag == 0) sens_1_flag = 1;
        if (sens_1_val < SENS_1_MIN && sens_2_val > SENS_2_MIN && sens_1_flag == 1 && sens_2_flag == 0)
    #else
        if (n < 0)
    #endif
        {
          if (--num_people == 0) {
            /* light is on, put it off */
    #if 0
            irsend.sendSony(IR_KEY, 20);
            Serial.println("BLING!");
    #else
            puts("put light off");
    #endif
          }
          else if (num_people == -1) {
            /* light is already off but in past several person
               went out at the same time and only one was count */
            num_people = 0;
          }
    #if 0
          sens_1_flag = 0;
          sens_2_flag = 0;
          prev_num_people = num_people;           
          Serial.print("People in the room: ");
          Serial.println(num_people);
          delay(DEBOUNCE_MS);
    #else
          printf("People supposed the room: %d (real %d)\n", num_people, real_people);
    #endif
        }
      }
      return 0;
    }
    

    to enter a positive number correspond to the "come in" case, a negative number a "want out". I also added real_people corresponding to the real number of people in the room

    Compilation and execution :

    pi@raspberrypi:/tmp $ gcc -Wall light.c 
    pi@raspberrypi:/tmp $ ./a.out
    1
    put light on
    People supposed the room: 1 (real 1)
    -1
    put light off
    People supposed the room: 0 (real 0)
    1
    put light on
    People supposed the room: 1 (real 1)
    1
    People supposed the room: 2 (real 2)
    -1
    People supposed the room: 1 (real 1)
    1
    People supposed the room: 2 (real 2)
    -1
    People supposed the room: 1 (real 1)
    -1
    put light off
    People supposed the room: 0 (real 0)
    2
    put light on
    People supposed the room: 1 (real 2)
    -1
    put light off
    People supposed the room: 0 (real 1)
    -1
    People supposed the room: 0 (real 0)
    1
    put light on
    People supposed the room: 1 (real 1)
    1
    People supposed the room: 2 (real 2)
    -2
    People supposed the room: 1 (real 0)
    ^C
    pi@raspberrypi:/tmp $ 
    

    Without a timeout on no come-in / went-out the light cannot become off from the end except if 2 people come-in at the same time then 1 went-out then again 1 went-out

    Anyway you see the algorithm is ok, so if you have a problem this is in the sensor management (code disabled by #if 0#)