Automating Linear Actuator with multipl PIRs

This is my first project and I am new to Arduino, so I'd welcome your feedback accordingly! I am trying to build an automated dog flap, so that if something passes close to the front or inside of the door(either one of the 2 PIRs activated), the dog flap opens, provided the door itself is not open (tested by Proximity sensor - don't have a better sensor at this point in time)

What I EXPECT: I am expecting that the actuator will push open the flap when either one of the PIR's is activated and the door is closed.
WHAT IS Happening:- The process seems to work erratically opening and closing with no movement detected and then defaulting to Relay2 activated. I have set the PIR time setting to about 10 seconds, which I reckon is sufficient to open the flap, and set in H mode, multiple triggering.

I have attached a hand drawn schematic and the code below. The code originated from RoboJax YouTube tutorial to activate a Linear Actuator with push button switches, but when I tried to modify it for PIR, it became troublesome.

I have used an IF statement to test that the door is closed and then a While statement to check the PIR's. Obviously not correctly!
Appreciate any assistance.

const int relay1 = 2;
const int relay2 = 3;
const int pIR1 = 5;
const int pIR2 = 6;
const int doorSensor = 7;
void openFlap();
void closeFlap();

//void turnOFF();

void setup() {
  pinMode(relay1, OUTPUT);// set pin as output for relay 1
  pinMode(relay2, OUTPUT);// set pin as output for relay 2
  pinMode(pIR1, INPUT);
  pinMode(pIR2, INPUT);
  pinMode(doorSensor, INPUT);
  // keep the actuator off by keeping both relays HIGH
  digitalWrite(relay1, HIGH);
  digitalWrite(relay2, HIGH);
  Serial.begin(9600);// initialize serial monitor with 9600 baud
}

void loop() {
  if (digitalRead(doorSensor) == LOW)
  {
    while ((digitalRead(pIR1) || digitalRead(pIR2) == HIGH))

    {
      delay(1000);
      openFlap();
    }

    while ((digitalRead(pIR2) == LOW && digitalRead(pIR1) == LOW))
    {
      delay(1000);
      closeFlap();
    }
  }
}

/*
   when the actuator is pushed, it closes the flap
*/
void openFlap()
{
  digitalWrite(relay1, LOW);// turn relay 1 ON
  digitalWrite(relay2, HIGH);// turn relay 2 OFF

}//openFlap()
void closeFlap()
{
  digitalWrite(relay1, HIGH);// turn relay 1 OFF
  digitalWrite(relay2, LOW);// turn relay 2 ON
  delay(10000);//wait for actuator to close
  digitalWrite(relay2, HIGH);// turn relay 2 OFF

}//closeFlap()

void turnOFF()
{
  digitalWrite(relay1, HIGH);// turn relay 1 OFF
  digitalWrite(relay2, HIGH);// turn relay 2 OFF

}//turnOFF()

One issue I see with the code is that while both PIR sensors are LOW closeFlap() will get executed continuously. Also, use of delay() will almost always cause you trouble.

I would suggest using a state machine to control the door. It would be much easier to understand the code and debug it. If it were me I would debug using switches and LEDs and then hook up the hardware. Here are a couple of tutorials that might help you:

State Machine

BlinkWithoutDelay

Arduino Multiple Things

Several Things at a Time

Here are some states I quickly mapped out with descriptions. The other nice thing about state machines is it simplifies fault handling. For example, if you command the door closed and your prox sensor doesn’t detect it closed you can indicate a fault (via an LED or something) and return to the idle state or an error state.

IDLE (wait for motion)
CMD_OPEN_FLAP (execute open flap)
VERIFY_OPEN (wait for prox sensor to show open or timeout)
MONITOR_OPEN (monitor PIRs to keep flap open and timeout when no motion detected)
CMD_CLOSE_FLAP (execute close flap)
VERIFY_CLOSE (wait for prox sensor to show close or timeout)

Thank you very much. I will review the tutorials about State Machine and attempt to implement. I will let you know how that goes!

joatmon13:
Thank you very much. I will review the tutorials about State Machine and attempt to implement. I will let you know how that goes!

The following might actually work if you don't have any hardware issues. I haven't tested but it compiles. Either way it is a start for you:

const int relay1 = 2;
const int relay2 = 3;
const int pIR1 = 5;
const int pIR2 = 6;
const int doorSensor = 7;
void openFlap();
void closeFlap();
enum _state_enum
{
  IDLE,
  CMD_OPEN_FLAP,
  WAIT_OPEN,
  MONITOR_OPEN,
  CMD_CLOSE_FLAP,
  WAIT_CLOSE
};
_state_enum systemState = IDLE;
byte lastPIR1Status;
byte lastPIR2Status;
byte lastDoorSensorStatus;
//void turnOFF();
void setup() {
  pinMode(relay1, OUTPUT);// set pin as output for relay 1
  pinMode(relay2, OUTPUT);// set pin as output for relay 2
  pinMode(pIR1, INPUT);
  pinMode(pIR2, INPUT);
  pinMode(doorSensor, INPUT);
  turnOFF();
  lastPIR1Status = digitalRead(pIR1);
  lastPIR2Status = digitalRead(pIR2);
  lastDoorSensorStatus = digitalRead(doorSensor);
  Serial.begin(9600);// initialize serial monitor with 9600 baud
}
void loop() {
  
  byte pIR1Status = digitalRead(pIR1);
  byte pIR2Status = digitalRead(pIR2);
  byte doorSensorStatus = digitalRead(doorSensor);

  switch (systemState)
  {
    // Wait for motion
    case IDLE:
      // If either PIR sensor went from LOW to HIGH then open the flap
      if ((pIR1Status != lastPIR1Status && pIR1Status == HIGH)
      ||  (pIR2Status != lastPIR2Status && pIR2Status == HIGH))
      {
        systemState = CMD_OPEN_FLAP;
      }
      break;
      
    case CMD_OPEN_FLAP:
      openFlap();
      systemState = WAIT_OPEN;
      break;
      
    case WAIT_OPEN:
      if (doorSensorStatus != lastDoorSensorStatus && doorSensorStatus == HIGH)
      {
        systemState = MONITOR_OPEN;
      }
      // TBD: Probably need a millis() timeout to detect an actuator or sensor fault
      break;
      
    case MONITOR_OPEN:
      if (pIR1Status == LOW && pIR2Status == LOW)
      {
        systemState = CMD_CLOSE_FLAP;
      }
      // TBD: Probably need a millis() timer to keep the door open for a minimal amount of time after
      // motion stops
      break;
      
    case CMD_CLOSE_FLAP:
      closeFlap();
      systemState = WAIT_CLOSE;
      break;
      
    case WAIT_CLOSE:
      if (doorSensorStatus != lastDoorSensorStatus && doorSensorStatus == LOW)
      {
        digitalWrite(relay2, HIGH);// turn relay 2 OFF
        systemState = IDLE;
      }
      // TBD: Probably need a millis() timeout to detect an actuator or sensor fault
      break;
  }
  lastPIR1Status = pIR1Status;
  lastPIR2Status = pIR2Status;
  lastDoorSensorStatus = doorSensorStatus;
}
//
//   when the actuator is pushed, it closes the flap
//
void openFlap()
{
  digitalWrite(relay1, LOW);// turn relay 1 ON
  digitalWrite(relay2, HIGH);// turn relay 2 OFF
}//openFlap()
void closeFlap()
{
  digitalWrite(relay1, HIGH);// turn relay 1 OFF
  digitalWrite(relay2, LOW);// turn relay 2 ON
}//closeFlap()
void turnOFF()
{
  digitalWrite(relay1, HIGH);// turn relay 1 OFF
  digitalWrite(relay2, HIGH);// turn relay 2 OFF
}//turnOFF()

Are there external pull-ups on your inputs? It doesn't look like you turn on the internal ones. That can cause wild and exiting new behaviors.

pinMode(pIR1, INPUT);
pinMode(pIR2, INPUT);
pinMode(doorSensor, INPUT);

-jim lee

ToddL1962:
The following might actually work if you don't have any hardware issues. I haven't tested but it compiles. Either way it is a start for you:

const int relay1 = 2;

const int relay2 = 3;
const int pIR1 = 5;
const int pIR2 = 6;
const int doorSensor = 7;
void openFlap();
void closeFlap();
enum _state_enum
{
 IDLE,
 CMD_OPEN_FLAP,
 WAIT_OPEN,
 MONITOR_OPEN,
 CMD_CLOSE_FLAP,
 WAIT_CLOSE
};
_state_enum systemState = IDLE;
byte lastPIR1Status;
byte lastPIR2Status;
byte lastDoorSensorStatus;
//void turnOFF();
void setup() {
 pinMode(relay1, OUTPUT);// set pin as output for relay 1
 pinMode(relay2, OUTPUT);// set pin as output for relay 2
 pinMode(pIR1, INPUT);
 pinMode(pIR2, INPUT);
 pinMode(doorSensor, INPUT);
 turnOFF();
 lastPIR1Status = digitalRead(pIR1);
 lastPIR2Status = digitalRead(pIR2);
 lastDoorSensorStatus = digitalRead(doorSensor);
 Serial.begin(9600);// initialize serial monitor with 9600 baud
}
void loop() {
 
 byte pIR1Status = digitalRead(pIR1);
 byte pIR2Status = digitalRead(pIR2);
 byte doorSensorStatus = digitalRead(doorSensor);

switch (systemState)
 {
   // Wait for motion
   case IDLE:
     // If either PIR sensor went from LOW to HIGH then open the flap
     if ((pIR1Status != lastPIR1Status && pIR1Status == HIGH)
     ||  (pIR2Status != lastPIR2Status && pIR2Status == HIGH))
     {
       systemState = CMD_OPEN_FLAP;
     }
     break;
     
   case CMD_OPEN_FLAP:
     openFlap();
     systemState = WAIT_OPEN;
     break;
     
   case WAIT_OPEN:
     if (doorSensorStatus != lastDoorSensorStatus && doorSensorStatus == HIGH)
     {
       systemState = MONITOR_OPEN;
     }
     // TBD: Probably need a millis() timeout to detect an actuator or sensor fault
     break;
     
   case MONITOR_OPEN:
     if (pIR1Status == LOW && pIR2Status == LOW)
     {
       systemState = CMD_CLOSE_FLAP;
     }
     // TBD: Probably need a millis() timer to keep the door open for a minimal amount of time after
     // motion stops
     break;
     
   case CMD_CLOSE_FLAP:
     closeFlap();
     systemState = WAIT_CLOSE;
     break;
     
   case WAIT_CLOSE:
     if (doorSensorStatus != lastDoorSensorStatus && doorSensorStatus == LOW)
     {
       digitalWrite(relay2, HIGH);// turn relay 2 OFF
       systemState = IDLE;
     }
     // TBD: Probably need a millis() timeout to detect an actuator or sensor fault
     break;
 }
 lastPIR1Status = pIR1Status;
 lastPIR2Status = pIR2Status;
 lastDoorSensorStatus = doorSensorStatus;
}
//
//   when the actuator is pushed, it closes the flap
//
void openFlap()
{
 digitalWrite(relay1, LOW);// turn relay 1 ON
 digitalWrite(relay2, HIGH);// turn relay 2 OFF
}//openFlap()
void closeFlap()
{
 digitalWrite(relay1, HIGH);// turn relay 1 OFF
 digitalWrite(relay2, LOW);// turn relay 2 ON
}//closeFlap()
void turnOFF()
{
 digitalWrite(relay1, HIGH);// turn relay 1 OFF
 digitalWrite(relay2, HIGH);// turn relay 2 OFF
}//turnOFF()



Thank you indeed for taking the time to create this sketch. I have downloaded it to my project and while it opens the flap once a PIR is activated, the flap stays open irrespective of what amount of time has passed or whether PIRs are again actuated. 

A point of clarification: you reference doorSensorStatus and lastDoorSensor Status - are you referring to the actual door status or the flap status, which is controlled by the linear actuator. In my project, I wanted to make sure that the door itself was closed before the flap would operate.

I have been reading about millis() and while I think I understand the concept, it's out of my depth to to implement it in your code. I will continue reading and experimenting and see what happens! Thanks again.

jimLee:
Are there external pull-ups on your inputs? It doesn't look like you turn on the internal ones. That can cause wild and exiting new behaviors.

pinMode(pIR1, INPUT);

pinMode(pIR2, INPUT);
pinMode(doorSensor, INPUT);




-jim lee

Hi Jim, Thank you for your feedback. I have tried adding the INPUT_PULLUP on the input pins in both sketches, but it doesn't seem to have made any improvement.

My bad. I thought door status was used to confirm whether the flap was opened or closed. In that case you can skip the WAIT_OPEN and WAIT_CLOSE states.

OK - I have revised the code and removed the redundant states. Also added millis() function. Now the system runs by switching on Relay 1 and cycling through 30 seconds (set by millis() timer) to momentarily switch on relay2 but immediately goes back to switching on relay1. So the flap remains open all the time. This seems to be whether movement is detected or not.

Any ideas what is going on (wrong) here? In particular I don't understand why these variables are different in setup v's loop:-

lastPIR1Status = digitalRead(pIR1);
  lastPIR2Status = digitalRead(pIR2);
  lastDoorSensorStatus = digitalRead(doorSensor);
  Serial.begin(9600);// initialize serial monitor with 9600 baud
}
void loop() {

  byte pIR1Status = digitalRead(pIR1); 
  byte pIR2Status = digitalRead(pIR2);

Here is the full sketch:

[code]
const int relay1 = 2; //drives linear actuator to open flap when HIGH
const int relay2 = 3; //drives linear actuator to close flap when LOW
const int pIR1 = 5; //outputs HIGH signal when motion detected
const int pIR2 = 6;  //outputs HIGH signal when motion detected
const int doorSensor = 7;
const unsigned long doorOpenTime = 30000UL;
unsigned long previousTime = 0;
void openFlap();
void closeFlap();
enum _state_enum
{
  IDLE,
  CMD_OPEN_FLAP,
  MONITOR_OPEN,
  CMD_CLOSE_FLAP,
};
_state_enum systemState = IDLE;
byte lastPIR1Status;
byte lastPIR2Status;
byte lastDoorSensorStatus;
void turnOFF();
void setup() {
  pinMode(relay1, OUTPUT);// set pin as output for relay 1
  pinMode(relay2, OUTPUT);// set pin as output for relay 2
  pinMode(pIR1, INPUT_PULLUP);
  pinMode(pIR2, INPUT_PULLUP);
  pinMode(doorSensor, INPUT);
  // turnOFF();
  lastPIR1Status = digitalRead(pIR1);
  lastPIR2Status = digitalRead(pIR2);
  lastDoorSensorStatus = digitalRead(doorSensor);
  Serial.begin(9600);// initialize serial monitor with 9600 baud
}
void loop() {

  byte pIR1Status = digitalRead(pIR1);
  byte pIR2Status = digitalRead(pIR2);
  byte doorSensorStatus = digitalRead(doorSensor);
  unsigned long currentTime = millis();

  switch (systemState)
  {
    // Wait for motion
    case IDLE:
      // If either PIR sensor went from LOW to HIGH then open the flap
      if ((pIR1Status != lastPIR1Status && pIR1Status == HIGH)
          ||  (pIR2Status != lastPIR2Status && pIR2Status == HIGH))
      {
        systemState = CMD_OPEN_FLAP;
        previousTime = currentTime;
      }
      break;

    case CMD_OPEN_FLAP:
      openFlap();
      systemState = MONITOR_OPEN;
      break;


    case MONITOR_OPEN:
      if ((pIR1Status != HIGH && pIR2Status != HIGH) && (currentTime - previousTime >= doorOpenTime))
      {
        systemState = CMD_CLOSE_FLAP;

      }

      break;

    case CMD_CLOSE_FLAP:
      closeFlap();
      systemState = IDLE;
      break;


  }
  lastPIR1Status = pIR1Status;
  lastPIR2Status = pIR2Status;
  lastDoorSensorStatus = doorSensorStatus;
}
//
//   when the actuator is pushed, it closes the flap
//
void openFlap()
{
  digitalWrite(relay1, LOW);// turn relay 1 ON
  digitalWrite(relay2, HIGH);// turn relay 2 OFF
}//openFlap()
void closeFlap()
{
  digitalWrite(relay1, HIGH);// turn relay 1 OFF
  digitalWrite(relay2, LOW);// turn relay 2 ON
}//closeFlap()
/*void turnOFF()
  {
  digitalWrite(relay1, HIGH);// turn relay 1 OFF
  digitalWrite(relay2, HIGH);// turn relay 2 OFF
  }//turnOFF()*/

[/code]

UPDATE - I have managed to get the system running and I think much of the weird outcomes was due to the erratic behaviour of the PIR modules which need to stabilize before they output correctly and also too wide a sensing angle, which I solved by simply using steel tubing in front of the PIR's.

Here is the final code. Thank you to all the contributors for your support.

[code]
const int relay1 = 2; //drives linear actuator to open flap
const int relay2 = 3; //drives linear actuator to close flap
const int pIR1 = 5; //outputs HIGH signal when motion detected
const int pIR2 = 6;  //outputs HIGH signal when motion detected
const int doorSensor = 4; //outputs HIGH is closed - reed switch
const unsigned long flapOpenTime = 30000; //in addition to timing of piR pot
unsigned long previousTime = 0;
void openFlap();
void closeFlap();
enum _state_enum
{
  IDLE,
  MONITOR_DOOR_CLOSED,
  CMD_OPEN_FLAP,
  MONITOR_OPEN_FLAP,
  CMD_CLOSE_FLAP,
};
_state_enum systemState = IDLE;
int lastPIR1Status;
int lastPIR2Status;
byte doorSensorStatus;
void turnOFF();


void setup() {
  delay (30000); // wait for PIR modules to stabalize
  pinMode(relay1, OUTPUT);// set pin as output for relay 1
  pinMode(relay2, OUTPUT);// set pin as output for relay 2
  pinMode(pIR1, INPUT);
  pinMode(pIR2, INPUT);
  pinMode(doorSensor, INPUT_PULLUP);
  //turnOFF();
  lastPIR1Status = digitalRead(pIR1);
  lastPIR2Status = digitalRead(pIR2);
  doorSensorStatus = digitalRead(doorSensor);
  closeFlap();
  Serial.begin(9600);// initialize serial monitor with 115200 baud
}


void loop() {

  byte pIR1Status = digitalRead(pIR1);
  byte pIR2Status = digitalRead(pIR2);
  int doorSensorStatus = digitalRead(doorSensor);
  unsigned long currentTime = millis();


  switch (systemState)
  {
    // Wait for motion
    case IDLE:
      Serial.println ("IDLE");
      Serial.print ("pIR1Status (last/Current)= ");
      Serial.print (lastPIR1Status);
      Serial.print ("/");
      Serial.println(pIR1Status);
      Serial.print ("pIR2Status (last/Current)= ");
      Serial.print (lastPIR2Status);
      Serial.print ("/");
      Serial.println(pIR2Status);
      Serial.print ("doorSensorStatus = ");
      Serial.println(doorSensorStatus);
      Serial.println ("***********************");

      // If either PIR sensor went from LOW to HIGH then open the flap
      if ((((pIR1Status != lastPIR1Status) && pIR1Status == 1 && (doorSensorStatus) != 1)
           ||  ((pIR2Status != lastPIR2Status) && pIR2Status == 1 && (doorSensorStatus) != 1)))

      {
        systemState = CMD_OPEN_FLAP;
        previousTime = currentTime;
      }

      break;

    case MONITOR_DOOR_CLOSED:
      Serial.println ("MONITOR_DOOR_CLOSED");
      if (doorSensorStatus == 0)
      {
        systemState = CMD_CLOSE_FLAP;
      }
      break;

    case CMD_OPEN_FLAP:
      Serial.println ("CMD_OPEN_FLAP");
      openFlap();
      systemState = MONITOR_OPEN_FLAP;
      break;

    case MONITOR_OPEN_FLAP:
      Serial.println ("MONITOR_OPEN_FLAP");
      Serial.print ("elapsed time = ");
      Serial.println (currentTime - previousTime);
      Serial.print (lastPIR1Status);
      Serial.println(pIR1Status);
      Serial.print (lastPIR2Status);
      Serial.println(pIR2Status);
      Serial.print ("doorSensorStatus = ");
      Serial.println(doorSensorStatus);
      Serial.println ("***********************");
      if ((pIR1Status == 0 && pIR2Status == 0) && (currentTime - previousTime >= flapOpenTime))
      {
        systemState = CMD_CLOSE_FLAP;
      }
      break;

    case CMD_CLOSE_FLAP:
      Serial.println ("CMD_CLOSE_FLAP");
      closeFlap();
      systemState = IDLE;

      break;

  }
  lastPIR1Status = pIR1Status;
  lastPIR2Status = pIR2Status;

}
//
//   when the actuator is pushed, it closes the flap
//
void openFlap()
{
  digitalWrite(relay1, LOW);// turn relay 1 ON
  digitalWrite(relay2, HIGH);// turn relay 2 OFF
}//openFlap()
void closeFlap()
{
  digitalWrite(relay1, HIGH);// turn relay 1 OFF
  digitalWrite(relay2, LOW);// turn relay 2 ON
}//closeFlap()

// void turnOFF()
//  {
//  digitalWrite(relay1, HIGH);// turn relay 1 OFF
//  digitalWrite(relay2, HIGH);// turn relay 2 OFF
//  }//turnOFF()

[/code]