Multiple input question

Hello everyone,
I am working on automating some lighting in my house and i have a question.

Setup: i am using an Arduino Nano to control an LED using a IR motion sensor, but also i am using the HC-05 Bluetooth module to manually control the light using voice recognition if i decided to.

Hardware:

  • Arduino Nano
  • IR Sensor
  • HC-05 Bluetooth module
  • LED
  • 220 ohm resistor
  • Android device for voice recognition

I was able to successfully make it work independently, meaning either by the motion sensor alone or by the speech commands alone, but i cannot seem to be able to do both at the same time.

I have attached a picture of the setup and the code.

I think the problem with my code is that when the signal from the motion sensor is LOW (no motion detected) then the LED output pin is forced LOW, which is causing it not to be turned on when the speech command is sent to turn it on (as a manual override). How can i go around this problem? and is there a way to override the output on demand?

Thank you so much for the help
Jamil

Pantry_Lights.ino (1.28 KB)

Please post your code.

Please post the code inside code tags.

I am new to this could you please walk me through posting the code, i have attached it in the original post.

Welcome to the Forum. Please read the two posts How to use this forum - please read. and Read this before posting a programming question ...

The use of code tags make the code look

like this

when posting source code files. It makes it easier to read, and can be copied with a single mouse click. Also, if you don't do it, some of the character sequences in the code can be misinterpred by the forum code as italics or funny emoticons.

// This program controls an LED light with both voice
// recognition and an IR motion sensor

#include<SoftwareSerial.h>;

SoftwareSerial BT(2, 3);  // TX, RX
String command; // variable to store command characters
int LED = 11;   // LED pin  
int IR = 12;    //  IR sensor pin

void setup() {
  BT.begin(9600);           // starts BT to Serial connection
  Serial.begin(9600);       // starts serial connection
  pinMode(LED, OUTPUT);     //  LED pin as output
  pinMode(IR, INPUT);       // IR pin as input
}

void loop() {
  while (BT. available()) {
    delay(10);
    char c = BT.read();     // store bluetooth voice command character by character
    if (c == '#') {         // is # is stored, means command is finished
      break;                // stop the loop
    }
    command += c;           // store characters in the command string
  }

  if ((digitalRead(IR) == HIGH) || (command == "*on")) {  // if motion is detected or "on" is sent
    digitalWrite(LED, HIGH);  // turn on LED
    Serial.println(command);  // print received command
  } else if ((digitalRead(IR) == LOW) || (command == "*off")) { // if no motion detected or "off" is sent
    digitalWrite(LED, LOW); // turn off LED
    Serial.println(command);  // print received command
  }
  command = ""; // reset command to blank
}

Your scheme for receiving data is not robust. You are assuming that all of the data will be available even though you just test for a single character.

Have a look at the examples in Serial Input Basics. Note, however that they do not use the String class because it can cause problems in the small memory of the Arduino.

Also I note that each of your IF statements uses a different value from the IR pin. It would be better to read the value once and use the same value in all the tests - just in case it changes between tests.

...R

Robin has good comments. But there's more to it...

  if ((digitalRead(IR) == HIGH) || (command == "*on")) {  // if motion is detected or "on" is sent
    digitalWrite(LED, HIGH);  // turn on LED
    Serial.println(command);  // print received command
  } else if ((digitalRead(IR) == LOW) || (command == "*off")) { // if no motion detected or "off" is sent
    digitalWrite(LED, LOW); // turn off LED
    Serial.println(command);  // print received command
  }
  command = ""; // reset command to blank

Assume your IR digital input is LOW: When you pass through the loop, the first IF statement will be false, but the second will be true because the input is low. This will turn off the output.

Now, you get the "*on" command, while the IR input is still low: The first IF statement will be true, and the LED will turn on, and the second IF statement will be skipped because it's an ELSE clause. But the next time through the loop, the first IF will be false (command was cleared at the bottom of loop) and the second IF statement will be true: the LED will be turned off.

In essence, when you get a "*on" command while the IR is low, it will turn on the LED for a very short time, then turn it off. Same when you get a "*off" command while the IR is high.

The solution is that you will have to track the two types of commands (Bluetooth and IR pin) separately and then combine them. But before you can do that, you need to fully define how things will operate: does the "*off" command simply cancel a previous "*on" command, or can it turn off the light if it's on because of the IR input?

Think of the control scheme as a pair of switches at opposite ends of a hallway. There are three ways they can be connected:

  • Both switches in parallel: turning either switch on will turn on the light, but both must be off to turn the light off.
  • Both switches in series: turning both switches on will turn on the light, and either switch off (or both off) will turn off the light.
  • 3-way switches: the typical hall light scenario where either switch will turn on the light, or either switch can turn off the light -- both switches in the same position turns off the light, switches in opposite positions turns on the light. Option 1 is easy: define a boolean flag to track the Bluetooth command: set it true when the "*on" command is received, and set it false when "*off" is received. Then, turn on the output if IR input is HIGH, OR the flag is true, otherwise turn off the output.

Option 2 is a just as easy: do everything the same as Option 1, but change the last bit of logic to AND the input and Bluetooth flags, instead of using OR.

Option3 is the tricky because flipping one output reverses the sense of the other. You can do it by using the same logic and using EXCLUSIVE OR to combine the IR state and Bluetooth flags, but that's probably not what you really want. If you do this, sending the "*on" command will make the IR sensor turn OFF the LED when the input is HIGH, and turn it ON when the input is LOW - very confusing. I've done that sort of thing where the normal command can be overridden in either state, but it uses three commands and two flags:

  • FORCE_ON command sets the forceOn flag and clears the forceOff flag. The LED is on regardless of IR activity
  • FORCE_OFF command sets the forceOff flag and clears the forceOn flag. The LED is off regardless of IR activity
  • NORMAL command clears both the forceOn and forceOff flags. The LED tracks the IR activity Then, the logic to actually control the output becomes:
if (forceOn)
   digitalWrite(LED, HIGH);
else if (forceOff)
   digitalWrite(LED, LOW);
else
   digitalWrite(LED, digitalRead(IR));

To use your existing command structure, the actual commands could be "*on", "*off", and "*ir" (the last one being the NORMAL command that allows the IR sensor to control the output.)

ShapeShifter,

Thank you so much for the clarification. Using the boolean bits make complete sense. I went a head after testing it and made it neat and created a method for each function.

// This program controls an LED light with both voice
// recognition and an IR motion sensor

#include<SoftwareSerial.h>;

SoftwareSerial BT(2, 3);  // TX, RX
String command; // variable to store command characters
int LED = 11;   // LED pin  
int IR = 12;    //  IR sensor pin
boolean speechEnable;
boolean motionEnable;

void setup() {
  BT.begin(9600);           // starts BT to Serial connection
  Serial.begin(9600);       // starts serial connection
  pinMode(LED, OUTPUT);     //  LED pin as output
  pinMode(IR, INPUT);       // IR pin as input
}

void loop() {
  
  Serial.print("Speech");
  Serial.print(", ");
  Serial.println("Motion");
  
  
  if (motion() || speech()) { // if motion or speech enabled
    digitalWrite(LED, LOW); // turn LED on
    
    Serial.print("  ");
    Serial.print(speech());
    Serial.print("   ,    ");
    Serial.println(motion());
    delay(500);
    
  } else {  // if motion or speech disbled
    digitalWrite(LED, HIGH);  // turn LED off
    
    Serial.print("  ");
    Serial.print(speech());
    Serial.print("   ,    ");
    Serial.println(motion());
    delay(500);
  }
}

// speech detected method
boolean speech() {
  while (BT.available()) {  // while bluetooth available
    delay(10);              // wait 10 millisecond
    char c = BT.read();     // store BT charachters in c
    if (c == '#') {         // if # is received
      break;  // break loop
    }
    command += c;   // add characters to command
  }

  if (command.length() > 0) {       // if a string is detected
    if (command == "*pantry lights on") { // if "on is received
      speechEnable = true;  // speech command enabled
      Serial.println(command);  // print command
    } else if (command == "*pantry lights off") { // if "off" is receievd
      speechEnable = false; // speech command disabled
      Serial.println(command);  // print command
    }
    command = ""; // reset command string to blank
  }
  return speechEnable;
}

// motion detected method
boolean motion() {
  if (digitalRead(IR) == HIGH) {  // if motion detected
    motionEnable = true;  // motion enabled
  } else {  // motion not detected
    motionEnable = false; // motion disabled
  }
  return motionEnable;
}

Thank you so much for your help.

Looks good! I have one suggestion for an improvement:

jimmyjack11: ```   if (command.length() > 0) {  // if a string is detected     Serial.println(command);    // print command

    if (command == "*pantry lights on")        // if "on is received       speechEnable = true;                    // speech command enabled     else if (command == "*pantry lights off")  // if "off" is receievd       speechEnable = false;                    // speech command disabled

    command = "";                              // reset command string to blank   }

Not how I removed the println() call that appeared in two places, and put a single instance of it before the checks. This not only reduces the amount of code, and simplifies the message decoding IF statements (making them easier to understand) but it will cause ALL received commands to be printed, not just the ones that are properly decoded. This can be very helpful for debugging when you send a command and nothing happens - seeing what was actually received might give you an important clue to figuring out the problem.