Momentary switch to change state

Hi - I'm new to Arduino and C++, so forgive me if this is dumb.

What I'd like to do:

I would like to use a momentary switch (it's an NO key turn switch that only engages as long as you're turning it) to arm / disarm my alarm project.

I would like it to set an ARMED state if you turn the key for 2 seconds - only then would the motion detector start reading and all subsequent alerts after it goes HIGH.

To put it in a DISARMED state, you simply turn the key again for 2 seconds. In this state, it just sits there with the LCD displaying a message and waits to be armed. It works like a toggle switch.

The issue is that I'm not sure if I would make a function for each state (ARMED & DISARMED) and stuff the codes in there below the loop, then just have a conditional in the loop that listens on the key switch and sets the states. I also don't know how to make this momentary switch only engage a state after 2 seconds of continued signal state HIGH (and toggle back to disarmed after 2 more seconds engaged).

I was reading some things about edge detection, but before I start going crazy, I figured I'd ask better programmers what the best strategy would be.

Background on project (in case it helps):

I made a silent alarm system thing. It's an Arduino Nano that has an LCD display (2line/16pin), a motion sensor (hcsr501), SIM900, and an LED connected.

When it detects motion, the LCD displays that it's in an alarm state, an LED goes on, and the SIM900 sends me a text. Right now, the alarm state is only set to function for a couple seconds for testing. It has a serial readout too but I haven't done much with it yet.

Here's the code so far:

#include <LiquidCrystal.h>
#include <SoftwareSerial.h>

SoftwareSerial mySerial(7,8);

char msg;

/* LCD params */
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

int led = 9;                // the pin that the LED is atteched to
int sensor = 6;              // the pin that the sensor is atteched to
int state = LOW;             // by default, no motion detected
int val = 0;                 // variable to store the sensor status (value)


void setup() {
  pinMode(led, OUTPUT);      // initalize LED as an output
  pinMode(sensor, INPUT);    // initialize sensor as an input
  Serial.begin(9600);        // initialize serial
mySerial.begin(19200);  // initialize SIM900

  /* LCD */
  lcd.begin(16, 2);
  lcd.print("Hello!");
  lcd.setCursor(0, 1);
  lcd.print("Unarmed");
  
}


void loop(){
  val = digitalRead(sensor);   // read sensor value
  if (val == HIGH) {           // check if the sensor is HIGH
    digitalWrite(led, HIGH);   // turn LED ON
    delay(100);                // delay 100 milliseconds 
    
    if (state == LOW) {
      Serial.println("Motion detected!"); 
      state = HIGH;       // update variable state to HIGH
  lcd.begin(16, 2);
  lcd.print("Intrusion");
  lcd.setCursor(0, 1);
  lcd.print("Detected");
  SendMessage();
    }
  } 
  else {
      digitalWrite(led, LOW); // turn LED OFF
      delay(200);             // delay 200 milliseconds 
      
      if (state == HIGH){
        Serial.println("Motion stopped!");
        state = LOW;       // update variable state to LOW
  lcd.begin(16, 2);
  lcd.print("Armed");
  lcd.setCursor(0, 1);
  lcd.print("Clear");
    }
  }
 if (mySerial.available()>0)
 Serial.write(mySerial.read());
}



void SendMessage()
{
  mySerial.println("AT+CMGF=1");    //Sets the GSM Module in Text Mode
  delay(1000);  // Delay of 1000 milli seconds or 1 second
  mySerial.println("AT+CMGS=\"+529995117743\"\r"); // Replace x with mobile number
  delay(1000);
  mySerial.println("Intrusion detected!");// The SMS text you want to send
  delay(100);
   mySerial.println((char)26);// ASCII code of CTRL+Z
  delay(1000);
}

If I think of your switch as a push button, then you want to verify if the button stays pushed for 2 seconds (or close to this duration) to arm or unarm your alarm system. Is this correct?

To do this, you need to monitor the state of the button regularly in the loop. When you detect it is pushed, you begin to measure the time and when it is released you verify if the 2 seconds (or so) have passed (not too much less, not too much more, say for example: abs(measured_duration - 2000) < 250 (milliseconds)). If yes, you just need to change the value of a boolean variable (from true to false or false to true : alarm_state = !alarm_state ; )

Then depending on the value of this state, you do what is requested

lesept:
To do this, you need to monitor the state of the button regularly in the loop. When you detect it is pushed, you begin to measure the time and when it is released you verify if the 2 seconds (or so) have passed (not too much less, not too much more, say for example: abs(measured_duration - 2000) < 250 (milliseconds)). If yes, you just need to change the value of a boolean variable (from true to false or false to true : alarm_state = !alarm_state ; )

Then depending on the value of this state, you do what is requested

OK, I'm not familiar with this abs(measured_duration) business, but I'm going to look it up now. I made a test code on a separate arduino to make sure that the analog signal with the key is reliable, and it seems to be good (I have the signal running to ground after the Digital Input signal, so the value is only between 1 and 3 when engaged:

int sig = A0;
int key = A1;
int keyVal = 0;

void setup() {
  Serial.begin(9600);
  pinMode(key, INPUT);
  pinMode(sig, OUTPUT);
  digitalWrite(sig, HIGH);
  
}

void loop() {
  keyVal = analogRead(key);
  delay(2000);

if (keyVal >= 1) {
  Serial.println("KEY ENGAGED");
  } else {
    Serial.println(keyVal);
    }

}

Anyway, I'm gonna google that abs function now, thanks. I still have to figure out how to nest the functions within separate states (arm/disarm), but I might be able to figure it out. If you or anyone else has any suggestions there, or happens to know whether or not i should be nesting all that stuff or what, I'd be happy to hear about it! Thanks!

Edit: I should mention that I also plan on changing the ARM/DISARM states with a text message, dunno if that makes too much difference in the way i should be approaching this

abs is for absolute value : it checks that the measured duration stays between 2000 - 250 and 2000 + 250 ms (in this example). This is because I'll pay you a beer if you can repeatedly push your button for exactly 2 seconds (wherever you live :wink: )

for the alarm_state, you just need to create and call 2 functions like this :

if (alarm_state == HIGH) function_when_alarm_is_on();
else function_when_alarm_is_off();

and do whatever you need in those 2 functions.

Here is a good beginner's tutorial on the use of the millis() function for timing.

When the switch changes state, record the value of millis() in a variable (say, startTime). Then each time through loop() check the value of startTime against the current value of millis(). When the difference is equal or grater than your "delay time" then take whatever action. If the switch changes state again within the "delay time", set the start time to the current time so the timer will not time out.

OK! I was able to combine input from the both of you and make a working alarm state setting with a momentary key switch. I'm going to leave the code here in case anyone else has the same need:

(the armed state just has a blinking LED in it for now)

unsigned long periodStartMillis;
unsigned long currentMillis;
const unsigned long period = 5000;  //period during which button input is valid
const byte buttonPin1 = A1;    //button on pin A1
byte currentButtonState;
byte previousButtonState;
int count = 0;
boolean printFinalMessage = true;
unsigned long debounceStartMillis;
unsigned long debouncePeriod = 2000;
boolean debouncing = false;

int armState = false;

int led = 2;

void setup()
{
  Serial.begin(9600);
  pinMode(buttonPin1, INPUT_PULLUP);
  Serial.println("Hello! Hold key for 2s to arm / disarm.");
  periodStartMillis = millis();
  pinMode(led,OUTPUT);
  digitalWrite(led,LOW);
}

void loop()
{
  currentMillis = millis();

    previousButtonState = currentButtonState;    //save the previous button state
    currentButtonState = digitalRead(buttonPin1);  //read the current state of the input
    if (currentButtonState != previousButtonState) //if the button state has changed
    {
      debounceStartMillis = currentMillis;  //save the time that the state change occured
      debouncing = true;  //flag that debouncing in progress
    }    //end state change check
    
    if (currentMillis - debounceStartMillis >= debouncePeriod)  //if the debounce period has elapsed
    {
      if (debouncing == true)    //debouncing taking place
      {
        if (currentButtonState == LOW)  //if the button is currently pressed
        {
          if (armState == false) {
          debouncing = false;
          Serial.println("ARMED");
          armState = true;
          // digitalWrite(led,HIGH);
            } else {
          debouncing = false;
          Serial.println("DISARMED");
          armState = false;
          // digitalWrite(led,LOW);
            }
        }    // ARM/DISARM STATE
      }  //end debouncing in progress check
    }    //end debounce time elapsed check

    if (armState == true) {
      digitalWrite(led,HIGH);
      delay(50);
      digitalWrite(led,LOW);
      delay(50);
      digitalWrite(led,HIGH);
      delay(50);
      digitalWrite(led,LOW);
      delay(2000);
      } else {
        
        } // do nothing if alarmState is disarmed
        
  }  // end loop

Thanks! Sorry the code is pretty ugly.

Now I'm just gonna have to figure out how to get the thing to arm disarm with text messages