How to properly write delay()

I am writing a program that controls a door lock with a key pad. My issue is i need a part of the code to lock the keypad out if the incorrect password is input 3 times in a row. Right now i have void functions set up for each process depending on the code entered into the keypad. In my void function for the incorrect password i have a delay set up for 30 seconds but the particle will disconnect if the delay is too long.

void incorrectpass(){
    if (x == 3){
        lockstatus = "Keypad Locked for 30 s";
        myTimerEvent();//Updates blynk app
        code = "";//resets keypad code
        delay(30000);
        x = 0;
        lockstatus = "Ready...";
        myTimerEvent();
    }
    else{
        lockstatus = "Try Again";
        code = "";
    }
}

I looked up other methods using millis() but that also disconnected from the cloud. Any suggestions would be greatly appreciated.

Welcome to the community.

There are many ways to do this.
I commonly just add this before setup() and then use softDelay(x) verses delay(x) elsewhere:

//  create a softDelay, safer than delay()
inline void softDelay(uint32_t t) {
  for (uint32_t ms = millis(); millis() - ms < t; Particle.process());
}

void setup() {
// normal stuff
}

void loop() {
// normal stuff
  softDelay(2000);        // a safe delay for 2 seconds, allows the Cloud Connection in the background.
}

I just tried that, but i dont have my functions in the void loop so i put the softDelay in my void incorrectpass() before the void loop and it still disconnects the photon. Maybe i should clarify, i am using blynk and the blynk app tells me the photon is no longer connected. So i am assuming that means the photon is disconnecting. Thank you for the help!

I just tried to rearrange my code so the softDelay executes inside the loop but it also does the same thing here is the full code:

#include <blynk.h>
#include "Blynk/BlynkTimer.h"
#include <Keypad_Particle.h>

//required to link photon to Blynk
char auth[] = "<redacted>";

BlynkTimer timer;
String lockstatus = "null";
String code;

//required blynk code
int x(0);
void myTimerEvent()
{
    Blynk.virtualWrite(V5, lockstatus); // writes to a virtual pin the lock status
    Blynk.virtualWrite(V1, code); //writes to a virtual pin the current code being entered (for debugging)
    Blynk.virtualWrite(V2, x); //writes to a virtual pin the current code being entered (for debugging)
}

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = { //Assigns a value to each key on the keypad
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'*','0','#'}
};
byte rowPins[ROWS] = {6, 5, 4, 3}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {2, 1, 0}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

inline void softDelay(uint32_t t) {
  for (uint32_t ms = millis(); millis() - ms < t; Particle.process());
}

void setup(){
    Serial.begin(9600);
    delay(5000);
    Blynk.begin(auth); //required to link blynk to photon
    timer.setInterval(1000L, myTimerEvent);
}

void unlocking(){
        code = "";
        lockstatus = "Unlocking...";
        myTimerEvent();
        pinMode(DAC1, OUTPUT);//Turns pin on to output
        digitalWrite(DAC1, LOW);
        delay(12000); //12 second ground pulse
        
        pinMode(DAC1, INPUT);//Turns pin off, otherwise pin always low
        lockstatus = "Unlocked";
        x = 0;
}

void locking(){
        lockstatus = "Locking...";
        myTimerEvent();
        code = "";
        pinMode(D7, OUTPUT);//Turns pin on to output
        digitalWrite(D7, LOW);
        delay(12000); //12 second ground pulse
        
        pinMode(D7, INPUT);//Turns pin off, otherwise pin always low
        lockstatus = "Locked";
        x = 0;
}

void incorrectpass(){
    if (x == 3){
        lockstatus = "Keypad Locked for 5 s";
        myTimerEvent();
        code = "";
        softDelay(30000);
        x = 0;
        lockstatus = "Ready...";
        myTimerEvent();
    }
    else{
        lockstatus = "Try Again";
        code = "";
    }
}

void delayunlocking(){
    x = 0;
    lockstatus = "Ready...";
    myTimerEvent();
}



String PASSWORD = "1111";
void loop(){
    Blynk.run();
    timer.run();
    char key = keypad.getKey();
    
    // builds code string with entered keys
    if((key == '1') || (key == '2') || (key == '3') || (key == '4') || (key == '5') || (key == '6') || (key == '7') || (key == '8') || (key == '9') || (key == '0')){
    code = code + key;
    }
    
    int n(0);//for case number
    if((key == '#') && (code == PASSWORD)) //When # pressed, checks to see if the code string is equal to the password
        n = 1;
    if((key == '#') && (code != PASSWORD)){ // If password incorrect the code resets to blank
        n = 2;
        x++;
    }
    if((key == '*') && (code != PASSWORD)) //Updates lock status to locked
        n = 3;
    
    switch (n)
    {
    case 1:{
        if (x == 3){
            lockstatus = "Keypad Locked for 5 s";
            myTimerEvent();
            softDelay(30000);
            delayunlocking();   
        }
        else{
            unlocking();
        }
    break;
    }
    case 2:
        incorrectpass();
        break;  
    case 3:
        locking();
        break;
    }
}

That won't matter, you can put softDelay() wherever you want (apart from ISRs of course).

There must be another reason than softDelay() since Particle.process() is executed inside that tight loop and this will keep the cloud connection open.

There should also be no need for a BlynkTimer. Software Timers should be the way to go in most cases.
For your "timeout" feature these would be my preferred solution too.

Neither delay() nor softDelay() are a good approach for what you are doing as both will block the rest of your code from executing. FSM or an entirely asynchronous approach would provide a much more appealing user experience.

BTW, what have you got connected to your DAC1/A6 pin?
What's the current draw on that pin?

You seem to be using D7 and DAC1/A6 alternately for locking and unlocking. Would it then not be enough to only have one function that takes a bool parameter to indicate what action you want?


Some other coding tips:
Instead of testing for each individual digit with a dedicated equality check you could use a range check like this

  if ('0' <= key && key <= '9') // test if key is BETWEEN '0' and '9'
    code += key;

Alternatively you could use a switch statement with fall-through

  switch(key) {
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      code += key;
      break;
    case '#':
      // handle hash symbol
      break;
    case '*':
      // handle asterisk
      break:
    default:
  }

But since key can only ever be one of the asigned characters, you can even transfer all the digit cases into the default branch like this

  switch(key) {
    case '#':
      // handle hash symbol
      break;
    case '*':
      // handle asterisk
      break:
    default:
      code += key;
  }

Even if there should happen to be some other key it wouldn't matter as it would just not satisfy the equality check against your desired PASSWORD (which should rather not be hard coded :wink: )

I'd also recommend some autoreset of code when there was no key input for more than 10 seconds or so. Otherwise your first attempt may often fail just because someone "funny" typed in some other digits before.
For real world security reasons I'd also have an auto-lock timer to relock about 30 sec after having unlocked (depending on your actual need of course :wink: )

Finally, I always recommend avoiding String and rather go with standard C strings (aka character arrays).

Also avoid non-descriptive global variables like x and n. For n I'd also suggest using an enum instead of non-descriptive numbers 1, 2 & 3.
And rather use the pin labels D0 .. D6 instead of anonymous pin numbers.

5 Likes

Thank you for the detailed response. I am fairly new to the particle platform and will try to reply to the best of my ability. I have not been intorduced to Software Timers so i will have to look into how to do that. The DAC1 and A6 pins are hooked into a reversing polarity relay setup with 2 relays so the current draw should be pretty minimal. Thank you for the other tips when i get some time i will look into those suggestions. I have the code hard coded for the moment for ease of testing as i had not gotten that far to look into how to not hardcode the password. Do you have any suggestions on how i should do that or where i can look for some tips?

You could use a Particle.function() or a Blynk alternative to send an PWD update and EEPROM to store the new password.

What type relays are these?

Sounds good i will look into thank you.

relay model number: G6RL-14-ASI-DC3 from here

Wired up like this
image

Ignore the 12v and stuff about alarm, this diagram was pulled from a remote start website but has the same function.

Each relay pulls 73.7 mA so i should be okay pulling from the output pins.

Nope, that's not OK.
The Photon can only source/sink up to 25mA per GPIO.
When directly driving a relay coil you'll also need a flyback diode to protect the GPIO against the kick-back from the collapsing magnetic field when depowering it.

This circuit will drive a 5V relay reliably - I have 30+ in production for more than 2 years

RELAY_DRIVE is the photon IO pin
S2 is a test switch/manual override
D1 is a simple schottky diode
the LEDS are optional
Q1 is a 2N3904 - or any basic NPN similar to this spec …

2 Likes

Thank you for the suggestions i have not gotten around to picking up transistors yet so i am not using the motor but i believe i figured out the delay issue with blocking but righ tnow i am trying to blink an led and for some reason it is again causing my cloud connection to drop.

inline void softDelay(uint32_t t) {
  for (uint32_t ms = millis(); millis() - ms < t; Particle.process());
}

//Function to be called when the led needs to be flashed, takes in the rbgColor, and numb of blinks
void blinkLED(int rgbColor, int numblinks){
    for (numblinks; numblinks > 0; numblinks--){ // numblinks, blinks led numblinks times, with 1 second total on off time.
        strip.setPixelColor(0, rgbColor);//turns LED on green for duration of unlocking
        strip.show(); 
        softDelay(500);//led on .5 seconds
        strip.setPixelColor(0, OFF); 
        strip.show();
        softDelay(500);//led off .5 seconds
    }
    
}

Any idea why this is blocking? the for loop class the particle process every time it loops through

Do you have SYSTEM_THREAD(ENABLED) ?

Can you post a complete sampel that can be taken as is to reproduce the problem?
I'd not see the issue in the shown part of the program, but there might be other sections that actually pose the problem.

Is LED 0 the only one connected, or have you got more (and what colour/brightness are they set at the time)? How are they connected?

But are you still using the relays?
Can you show a complete schema of your current test setup?

@shanevanj, while SYSTEM_THREAD(ENABLED) could help, the shown code keeps checking in Particle.process() fast enough so that it shouldn't be needed.

1 Like

I can’t believe this hasn’t been suggested, but you need a finite state machine! There’s some examples in the forum if you search, or look them up on the web, but FSM’s are used for this kind of problem all the time.

You don't need to believe it, since it's not the case :wink:

1 Like

lol that’s what I get for skimming :slight_smile: so I’ll amend my comment here and say, DO WHAT SCRUFF SAYS!

1 Like

Yes here you go, i have not had much time to make all the changes you have suggested but other than the blocking that happens when the blinkLED function runs, the program works pretty good.
There is a Pixel RGB connected to A0, i do not have either the relays or the motors hooked up as i need transistors to run them. So its just a keypad, led, and blynk app. Im not sure of a good way to draw up a diagram other than by hand but. The pixel uses a 220 ohm resistor to connect the data in to A0 and 7 pins from the keypad are used with digital pins D0 - D6. D7 and DAC1 are used to output high or low when i need to.

#include <neopixel.h>
#include <blynk.h>
#include "Blynk/BlynkTimer.h"
#include <Keypad_Particle.h>

//required to link photon to Blynk
char auth[] = "";

//pixel stuff
#define PIXEL_PIN A0
#define PIXEL_COUNT 1
#define PIXEL_TYPE WS2811
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);

    //Colors for rgb
    int PixelColorYellow = strip.Color(90, 40, 0);        
    int PixelColorRed = strip.Color (50 , 0 , 0);
    int PixelColorGreen = strip.Color(0, 50 , 0); 
    int OFF = strip.Color(0,0,0); 
    int rgbColor;
    
    //setups keypad
    const byte ROWS = 4; //four rows
    const byte COLS = 3; //three columns
    char keys[ROWS][COLS] = { //Assigns a value to each key on the keypad
    {'1','2','3'},
    {'4','5','6'},
    {'7','8','9'},
    {'*','0','#'}
    };
    byte rowPins[ROWS] = {6, 5, 4, 3}; //connect to the row pinouts of the keypad
    byte colPins[COLS] = {2, 1, 0}; //connect to the column pinouts of the keypad

    Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );


BlynkTimer timer;
String lockstatus = "Ready...";
String code;

char key;//key from keypad as char
int keypadlockout(0);//Variable so the keypad can be locked out
int notcorrect(0);//Counts how many times incorrect password is enterd
int casenumber(0);//for case number in switch statement
int numblinks(0);//Arguments for led function to tell how many times to blink
int blinkrate(0);//messing around with progresive blink rates when blinkLED funtion is called
bool repeatactionunlock(false);//Keeps track if action has already been done
bool repeatactionlock(false);//Keeps track if action has already been done

void myTimerEvent()
{
    Blynk.virtualWrite(V5, lockstatus); // writes to a virtual pin the lock status
    Blynk.virtualWrite(V1, code); //writes to a virtual pin the current code being entered (for debugging)
    Blynk.virtualWrite(V2, notcorrect);//debugging for incorrect code count
}

bool pinValue;
BLYNK_WRITE(V0){
    pinValue = param.asInt();
}

//timer function, can be called and set the delay. softDelay(10000) = delay of 10 seconds
inline void softDelay(uint32_t t) {
  for (uint32_t ms = millis(); millis() - ms < t; Particle.process());
}


void setup(){
    Serial.begin(9600);
    delay(5000);
    Blynk.begin(auth); //required to link blynk to photon
    timer.setInterval(1000L, myTimerEvent);
    strip.begin(); 
}
//Function to be called when the led needs to be flashed, takes in the rbgColor, and numb of blinks
double i = 1.00;
void blinkLED(int rgbColor, int numblinks, int blinkrate){
    for (numblinks; numblinks > 0; numblinks--){ // numblinks, blinks led numblinks times, with 1 second total on off time.
        i = i + .01;
        blinkrate = blinkrate/(i);
        
        strip.setPixelColor(0, rgbColor);//turns LED on green for duration of unlocking
        strip.show(); 
        softDelay(blinkrate);//led on for whatever blink rate is set to in ms
        strip.setPixelColor(0, OFF); 
        strip.show();
        softDelay(blinkrate);//led off for whatever blink rate is set to in ms
        Serial.print(casenumber);
    }
i = 1.00;
    
}
//Function when led needs to be solid, takes in rbgColor and is set at 2 seconds of solid led
void solidLED(int rgbColor){
    strip.setPixelColor(0, rgbColor);//turns LED on green for duration of unlocking
    strip.show(); 
    softDelay(2000);
    strip.setPixelColor(0, OFF); 
    strip.show();
}

void unlocking(){
    lockstatus = "Unlocking...";
    myTimerEvent();
    Particle.publish("Unlock", "Door Unlocked");
    rgbColor = PixelColorGreen;//sets color of led
    numblinks = 25;//Sets the number of times to blink led, 15 blinks will result in 15 second delay
    blinkrate = 500;
        
    pinMode(DAC1, OUTPUT);//Turns pin on to output
    digitalWrite(DAC1, LOW);
        
    blinkLED(rgbColor,numblinks,blinkrate);//runs blink led while motor running
    
    pinMode(DAC1, INPUT);//Turns pin off, otherwise pin always low
    lockstatus = "Unlocked";
    solidLED(rgbColor);//Ends with solid green led for 2 seconds
    resetvars();
    repeatactionlock = false;//clears true indicator from lock if lock was pressed more than once
}

void incorrectpass(){
    lockstatus = "Try Again";
    Particle.publish("Incorrect", "Incorrect Passcode Entered");
    
    rgbColor = PixelColorRed;
    numblinks = 4;
    blinkrate = 500;
    blinkLED(rgbColor,numblinks,blinkrate);
    
    code = "";
    resetvars();
    }

void locking(){
        lockstatus = "Locking...";
        myTimerEvent();
        Particle.publish("Lock", "Door Locked");
        pinMode(D7, OUTPUT);//Turns pin on to output
        digitalWrite(D7,LOW);
        
        rgbColor = PixelColorGreen;
        numblinks = 25;
        blinkrate = 500; //calulate time of the blinking 25(blinks) * 50ms = 1250ms = 1.250s
        blinkLED(rgbColor,numblinks,blinkrate);


        
        pinMode(D7, INPUT);//Turns pin off, otherwise pin always low
        lockstatus = "Locked";
        solidLED(rgbColor);
        resetvars();
        repeatactionunlock = false;//clears true indicator from unlock if unlock was pressed more than once
}

//resets number of incorrect tries, allows the keypad to be used, reests code to blank
void resetvars(){
    if (notcorrect == 0 || notcorrect == 3){
        notcorrect = 0;
        keypadlockout = 0;
        code = "";
        key = '$';
        myTimerEvent();
        casenumber = 0;
    }
    //doesnt let incorrect pass count reset to zero when there is 1 or 2 attempts at the password
    else{
        keypadlockout = 0;
        code = "";
        key = '$';
        myTimerEvent();
        casenumber = 0;
    }
    
}

String PASSWORD = "1111";
void loop(){
    Blynk.run();
    timer.run();
    key = keypad.getKey();
    
    // builds code string with entered keys
    if (keypadlockout == 0){
        if((key == '1') || (key == '2') || (key == '3') || (key == '4') || (key == '5') || (key == '6') || (key == '7') || (key == '8') || (key == '9') || (key == '0')){
        code = code + key;
        strip.setPixelColor(0, PixelColorGreen);//turns LED on green for duration of unlocking
        strip.show(); 
        softDelay(100);
        strip.setPixelColor(0, OFF); 
        strip.show();
        }
    }
    
    
    if((key == '#') && (code == PASSWORD)){ //When # pressed, checks to see if the code string is equal to the password
        if (repeatactionunlock == false){
            casenumber = 1;
            repeatactionunlock = true;
        }
        else if (repeatactionunlock == true){
            rgbColor = PixelColorYellow;
            numblinks = 4;
            blinkrate = 500;
            blinkLED(rgbColor,numblinks,blinkrate);
            resetvars();
        }
    }
    
    if((key == '#') && (code != PASSWORD)){ // If password incorrect the code resets to blank
        casenumber = 2;
        notcorrect++;//increments the notcorrect variable when user types in worng password
    }
    
    if(key == '*'){ //Updates lock status to locked
        if (repeatactionlock == false){
            casenumber = 3;
            repeatactionlock = true;
        }
        else if (repeatactionlock == true){
            rgbColor = PixelColorYellow;
            numblinks = 4;
            blinkrate = 500;
            blinkLED(rgbColor,numblinks,blinkrate);
            resetvars();
        }
    }
    
    if (pinValue = 0){//For blynk button lock/unlock
        //casenumber = 1;
    }
    
    if (pinValue = 1){//For blynk button  lock/unlock
        //casenumber = 2;
    }
    
    switch (casenumber)
    {
        case 1:{
            unlocking();
            break;
        }
        case 2:{
            if (notcorrect == 3){// Looks to see if incorrect password has been entered 3 times
                keypadlockout = 1; //locks out keypad input
                lockstatus = "Keypad Locked for 10 s";
                myTimerEvent();//updates blynk lock status
                rgbColor = PixelColorRed;
                numblinks = 10;
                blinkrate = 500;
                blinkLED(rgbColor,numblinks,blinkrate);
                resetvars();
                repeatactionunlock = false;
                break;
            }
            if (notcorrect != 3){// whenever the number of attempts is below 3, program allows user to try again
                incorrectpass();
                repeatactionlock = false;
                break;
            }
            }
        case 3:{
            locking();
            break;
        }
        
    }
}

By hand is fine.
But you could also use fritzing which may others use for quick breadboard projects.

So this is all i have right now except for the keypad being hooked up to pins D0-D6.
Pretty straight forward. Not exactly sure how my circuit would efffect the particle from connecting to the cloud.

I’d have to have a test setup.

But I’m not sure about the RGB LED you got there

  • one pin floating
  • one tied direclty to ground
  • one tied to Vcc
  • one tied to A0

What colour is this LED supposed to show and why an RGB LED when you only use one colour?