A new item to automate... the mailbox!

I need some clarification if you can. In your example you listed something that I think translates incorrectly… code wise, not language wise :slight_smile: You put the following:

If I read that correct, when this compiles, won’t it say “millis minus millis”? I ask because I have seen this a few different ways. The first (compressed for ease of reading) is like this:

//At the top of the sketch
unsigned long something_here = 0; 
.... 
// In the void_loop area
if (millis() - something_here > my_interval){   //In the void_loop section
something_here = millis();

OR:

//At the top of the sketch
unsigned long something_here = 0; 
.... 
// In the void_loop area
unsigned long time_now = millis()
if (time_now - something_here > my_interval){
something_here = time_now;

Example one simply declares millis() (current time) minus the previously declared “0” and checks it against a set interval. Example two just renames millis() and then proceeds like example one. If you start out in the code at zero with your activity timer at 0, it can check against valid numbers. ALL the timers should work through the first round without an issue because they start at zero and end at your interval number. The next time through though, the current millis number could be WAAAY over your interval and the timer wouldn’t work the second time through. If you don’t update the second value (something_here) with a current millis() time right as you invoke the timer again, it appears that you get exactly what I have been struggling with… a millis() time that will rapidly exceed the timer interval making the timer worthless. In my understanding of this, there must be a way to reset something_here at the moment you come into the case state so the math done between current time and activity counter is correct. No? If your example does compile to current millis minus current millis, then they would essentially be the same number

I tried out my hypothesis…

  unsigned long something_here = millis() 
  if (millis() - something_here >= interval){
  }

the time between the declaration of something_here and the actual equation performed below it is mere milliseconds and the LED blinks so fast it almost isn’t blinking. I moved the declaration to the previous state and it blinks only slightly slower, since it is only different by a millisecond or two from the previous version.

I even tried using the same exact timer for each state. That way, at the end of it using that timer, it would update it to current time at that point. I realize the math would be off and the timers might be skewed a bit but it reacts EXACTLY the same as if it has totally separate timers for each operation like it did before.

So the real question is WTF is going on with my script?

@Guru_Of_Nothing I have attached a small blink example using a state machine to hopefully better explain how this kind of timer works. As usual - there are 1000 ways to skin a cat - and I liked to show my proposal below. It’s not tested and needs to be tweaked a little to go into a sketch.

For this type of timers it’s important to set and save the timer variable (timer_activity) only once at the beginning of the intended time period to the actual time (millis()). This variable needs to be unchanged until the intended time period has expired. During the next cycles of the state machine the saved timer_activity is subtracted from the current time. As soon as this calculated difference is equal to or greater than the the intended time intervall the state machine advances to the next step.

Hope this helps ?

#define OFF_TIME      500		// 500 ms off
#define ON_TIME	      1000	        // 1000 ms on

#define IS_OFF		1
#defineIS_ON		2

int current_state; 
unsigned long timer_activity = 0;  


	digitalWrite(ledG, LOW); 	// turn off
	timer_activity = millis();	// starting time for fist state
	current_state = IS_OFF;		// next state


	while (true) {		// state machine runs forever
	
	switch(current_state){
      
      case IS_OFF:
        if (millis() - timer_activity >= OFF_TIME) { // wait for OFF_TIME to expire
			// this code executes only once after OFF_TIME expired
			digitalWrite(ledG, HIGH); 	// turn on
			timer_activity = millis();	// prepare for next state
			current_state = IS_ON;		// next state
        }
        
        break;
      

      case IS_ON:
        if (millis() - timer_activity >= ON_TIME) {// wait for ON_TIME to expire
			// this code executes only once after ON_TIME expired
			digitalWrite(ledG, LOW); 	// turn off
			timer_activity = millis();	// prepare for next state
			current_state = IS_OFF;		// next state
        }
        
        break;
	}
	}

Well, after wanting to stab myself in the face repeatedly with a fork, I FINALLY GOT IT WORKING!!!
Don’t ask me what I did because I honestly don’t know. What I do know is that I have tried so many permutations on the timers that I had every possible one in my code and that was probably why I was having an issue. First of all, the LED blink piece only seems to work with millis() i.e.

if (millis() - insertcraphere > some_timer)  //Only for blinking LED's

And the rest have to use the exact same timer or it will not work between states. You can’t call them different names for different timers like I did. And for some reason it will not work if you use millis() alone. You have to give it an alternate name. That doesn’t make sense to me since it is still millis() no matter what. I would love to hear the reason why that is if anyone knows.

unsigned long junk = millis();
if (junk - onlyonetimer > some_timer_interval)

I also had used some >= in there mixed in with some additional qualifiers like

if ((junk - onlyonetimer >= some_timer_interval) && (some_timer_interval  > 0)

As well as a compound operator

onlyonetimer += junk;

And that ALL was because I was using a number of different code examples that had varying purposes such as mine. HOWEVER… most of it was crap and I had no idea why it didn’t work. I just went back to basics and started with what worked, going case by case until each one worked properly. I kept plugging in delays and println() to see if I could tell where stuff was hanging or not processing.

Now that the basic states work fine, it’s time to start plugging one additional element in at a time (like the panic protocol and the MQTT items. Then I can chop it up one state at a time and create the subroutines from the working code blocks. WHEEEEEEEEWWW! I am so relieved!

Thank you @moinmoin and @JacobJohnson for your assistance along the way.

The mostly working code if anyone cares…

/*
  Package Vault Controller for ESP32
  Copyright 2019 theguruofnothing
  Board: ESP32 Devkit V1 (DoIt)
  This controller is the brain for "The Package Vault Project", an open source high(er) security package
  reception device. It was designed with the sole purpose of curtailing package theft. I want others to take
  this design and make it their own. However, I would hope that you would honor my efforts for the greater
  good and not use it for unbridaled commercial gain. Custom designed controller units are available at
  www.superhouse.tv and I hope you come by and check out the community on the Discourse channel there.
  Questions? You can reach me at TheGuruOfNothing@yahoo.com
*/

#include <WiFi.h>
#include <PubSubClient.h>

WiFiClient Vault1;
PubSubClient client(Vault1);

//Timer Intervals
#define BLINK_INTERVAL        1000     // 800MS BLINK (RED OR BLUE LED)
#define BLINK_INTERVAL_TOGGLE 400     // EMERGENCY INDICATOR (R,G,B)
#define LID_OPEN_TIME_OUT     120000  // 2 MINUTES BEFORE LID_OPEN MESSAGE TO MQTT
#define LOCKDOWN_TIMER        15000   // 15 SECOND LOCKDOWN TIMER
#define RELAY_TIMER           6000    // TIME ACTUATOR RELAY SHOULD BE ACTIVE IN ANY DIRECTION
#define DEBOUNCE_DELAY        400     // MAG SWITCH DEBOUNCE TIMER

//States
#define STATE_STARTUP       0
#define STATE_READY         1
#define STATE_LID_OPEN      2
#define STATE_TIMER_RUNNING 3
#define STATE_LOCKING_DOWN  4
#define STATE_SECURED       5

//Pin Definitions
#define mag_sw       5  //Lid switch
#define relay_1      18 //Unlock 
#define relay_2      19 //Lock
#define int_lights   21 //Interior light strip
#define pir          2  //Passive infrared sensor, internal to box (Panic/Safety)
#define panic_button 4  //Lit button, internal to box (Panic/Safety)
#define ledG         12 //Green LED
#define ledB         13 //Blue LED
#define ledR         14 //Red LED

bool lid_state = 0;
bool last_lid_state  = 0;
int ledBstate = LOW;
int ledRstate = LOW;
int ledGstate = LOW;

unsigned long time_now = 0;
unsigned long last_debounce_time = 0;
unsigned long one = 0;
unsigned long two = 0;
unsigned long blink_time = 0;
unsigned long ajar = 0;


//Your Wifi and MQTT info here


int current_state = STATE_STARTUP;



void setup() {
  //Removed for brevity

void read_lid()
{
  lid_state = digitalRead(mag_sw);
  if (millis() - last_debounce_time > DEBOUNCE_DELAY) {   //Debounce Timer
    last_debounce_time = millis();
  }

  if (lid_state != last_lid_state) {   //check if the   button is pressed or released
    last_lid_state = lid_state;
  }
}

/*void blink_led_toggle()
  {
  unsigned long blink_interval_activity = millis();
  //Blink the RED LED at once per second...until the reset request is received
  if (led_toggle - blink_interval_activity > BLINK_INTERVAL_TOGGLE){
      blink_interval_activity = led_toggle;// Update the time for this activity:
  digitalWrite(ledR, !digitalRead(ledR));
  digitalWrite(ledG, !digitalRead(ledG));
  digitalWrite(ledB, !digitalRead(ledB));
  }
  }*/

void panic_check() { //This is a kill switch for the box. Forces an unlock and then locks the processor in an infinite loop
  // to prevent any possibility of resetting and relocking
  int PANIC1 = digitalRead(pir);
  int PANIC2 = digitalRead(panic_button);

  if (PANIC1 || PANIC2 == LOW) // Something has gone very wrong and box needs to be unlocked NOW!!!
  {
    //  unlock(); Needs function built once debug is complete...*********************************************************************************


    Serial.println("Emergency Protocol Activated");
    Serial.println("A failure has occurred and vault has been disabled. Power must be cycled to reset");
    client.publish("vault/state", "Panic");

    while (true) {} //I want to kill the process so there is no way to restart the  main loop unless power cycled
    //so I created an infinite while() loop to do that. Probably a more elegant way to do it
    //but let's face it, if this protocol has to be enacted, shit HAS hit the fan...
  }
}

void loop() {
  
  time_now = millis();
  read_lid();     // Update the lid status
  Serial.println (lid_state);
  
  client.loop();   //Loops connection with MQTT subscriptions

  switch (current_state) {

    case STATE_STARTUP:
      {
     
        Serial.println("Startup In Progress...");
        Serial.println("______________________");
        digitalWrite(relay_2, LOW);  //Starts actuator power
        // start up the relay run timer
        if (time_now - one >= RELAY_TIMER) {
          one = time_now;
          digitalWrite(relay_2, HIGH);  //Stops actuator power
          digitalWrite(ledG, HIGH);
          current_state = STATE_READY;
        }
      }

      break;


    case STATE_READY:
      {
        Serial.println("Ready For Packages...");
        Serial.println("______________________");
        // If lid is opened
        if (lid_state == 1) {
          current_state = STATE_LID_OPEN;
        }
      }
      break;


    case STATE_LID_OPEN:
      {
   
        Serial.println("The Lid Has Been Opened...");
        Serial.println("______________________");
        digitalWrite(ledG, LOW);
        if (lid_state == 1)
        {
          //Blink the BLUE LED at once per second...until the reset request is received
         if(millis() - blink_time > BLINK_INTERVAL){
         blink_time = millis();// Update the time for this activity:
          digitalWrite(ledB, !digitalRead(ledB));
  }
         if (millis() - ajar > LID_OPEN_TIME_OUT) {
            ajar = millis();// Update the time for this activity:
            client.publish("vault/lid_ajar", "TRUE");
          }
        }
        else
        {
          digitalWrite(ledB, LOW);
          digitalWrite (int_lights, LOW);
          digitalWrite(ledR, HIGH);
          current_state = STATE_TIMER_RUNNING;
          
        }
      }
      break;


    case STATE_TIMER_RUNNING:  
      {
        Serial.println ("Timer Operation Has Begun...");
        Serial.println ("______________________");
        if ((time_now - one) >= LOCKDOWN_TIMER && LOCKDOWN_TIMER >0)
        {
          one = time_now;
          Serial.print("Timer has timed out now...");
          //delay(6000); ///Only so I can stop the message scroll long enough to see what it is doing...
          current_state = STATE_LOCKING_DOWN;
        }
      }
      break;

    case STATE_LOCKING_DOWN:
      {
        Serial.println("Locking The Box Now...");
        Serial.println("______________________");
        digitalWrite (int_lights, HIGH);
        digitalWrite (relay_1, LOW);  //Starts actuator power
        // start up the relay run timer
        //delay(5000);
        if (time_now - one >= RELAY_TIMER)
        {
          one = time_now;
          digitalWrite (relay_1, HIGH);  //Stops actuator power
          current_state = STATE_SECURED;
        }
      }
      break;


    case STATE_SECURED:
      {
        Serial.println("The captain has extinguished the NO SMOKING sign...");
        Serial.println("You may now roam freely about the cabin...");
        Serial.println("Vault Secured...");
        Serial.println("______________________");
        //Blink the RED LED at once per second...until the reset request is received
         if(millis() - blink_time > BLINK_INTERVAL){
         blink_time = millis();// Update the time for this activity:
          digitalWrite(ledR, !digitalRead(ledR));
        }
        panic_check();  //Check if the panic indicators have been tripped after the lockdown has occurred
      }
      break;
  }
}

It’s me again. I tested the code that I posted earlier on an ESP and found a bug. Here is the updated functional code (complete Arduno sketch using the builtin led)

#define OFF_TIME    500   // 500 ms off
#define ON_TIME     1000  // 1000 ms on

#define IS_OFF    1
#define IS_ON     2

int current_state; 
unsigned long timer_activity = 0;  


void setup() {
  // put your setup code here, to run once:
  pinMode(BUILTIN_LED, OUTPUT);     // Initialize the BUILTIN_LED pin as an output
  Serial.begin(115200);
  Serial.println("setup ...");


}

void loop() {
  // put your main code here, to run repeatedly:

  digitalWrite(BUILTIN_LED, LOW);  // turn off
  timer_activity = millis();  // starting time for fist state
  current_state = IS_OFF;   // next state


  while (true) {    // state machine runs forever
    delay (1);   // this is required to prevent crashing
    switch(current_state){
        
        case IS_OFF:
            if ((millis() - timer_activity) >= OFF_TIME) { // wait for OFF_TIME to expire
              // this code executes only once after OFF_TIME expired
              digitalWrite(BUILTIN_LED, LOW);   // turn on
              timer_activity = millis();  // prepare for next state
              current_state = IS_ON;    // next state
            }
            
          break;
        
  
        case IS_ON:
          if ((millis() - timer_activity) >= ON_TIME) {// wait for ON_TIME to expire
            // this code executes only once after ON_TIME expired
            digitalWrite(BUILTIN_LED, HIGH);  // turn off
            timer_activity = millis();  // prepare for next state
            current_state = IS_OFF;   // next state
          }
          
          break;
    }
  }
}

The delay(1); line after starting the while loop (once per cycle of the state machine) is required to prevent the ESP from crashing. I don’t understand why. May be is just my harware (D1 mini)

@moinmoin I assume the >= operator is used instead of just > for timer accuracy? If you only use > the timer has to go past your interval number to be TRUE where as >= allows it to be true exactly at your interval number?

@Guru_Of_Nothing Yes, you are right. >= increases the accuracy slightly.
Happy to hear that you got it working. Haven’t looked at you new code so far, but will do …

@#%$^% I spoke too soon. When I tested it early this morning, I banged quickly through the states. It went through all of them as it should (unlike it ever has). ALL the LED blinking works properly and it hits every state message in order. But that was because I banged through it quickly. If I hang out in a state for awhile, each successive state gets shorter and the timers time out immediately again. It is the timer defined as “one”… the same timer used for all but the LED blinks. Going to continue fiddling with that specific timer and maybe, state by state give it new timers? I know I have been down this path before but I need consistency so now that I know what syntax works, let’s see if I can’t get proper initialization this time around. Will get back to you…

I have tried a few things but they are basically the same exact things I tried in previous iterations. Nothing has worked. I decided to break everything back into functions and clean up the states hoping that this clarity would expose the issue at hand. uh, nope. What it DID do was to return me to where I was… the states work fine until you get to the lockdown timer and it just runs and never increments even though it’s the same timer codeing as the startup timer which works. The CURRENT as of now code is below:

/*
  Package Vault Controller for ESP32
  Copyright 2019 theguruofnothing
  Board: ESP32 Devkit V1 (DoIt)
  This controller is the brain for "The Package Vault Project", an open source high(er) security package
  reception device. It was designed with the sole purpose of curtailing package theft. I want others to take
  this design and make it their own. However, I would hope that you would honor my efforts for the greater
  good and not use it for unbridaled commercial gain. 
  Questions? You can reach me at TheGuruOfNothing@yahoo.com
*/

#include <WiFi.h>
#include <PubSubClient.h>

WiFiClient Vault1;
PubSubClient client(Vault1);

//Timer Intervals
#define BLINK_INTERVAL        1000     // 800MS BLINK (RED OR BLUE LED)
#define BLINK_INTERVAL_TOGGLE 400     // EMERGENCY INDICATOR (R,G,B)
#define LID_OPEN_TIME_OUT     120000  // 2 MINUTES BEFORE LID_OPEN MESSAGE TO MQTT
#define LOCKDOWN_TIMER        15000   // 15 SECOND LOCKDOWN TIMER
#define RELAY_TIMER           6000    // TIME ACTUATOR RELAY SHOULD BE ACTIVE IN ANY DIRECTION
#define DEBOUNCE_DELAY        400     // MAG SWITCH DEBOUNCE TIMER

//States
#define STATE_STARTUP       0
#define STATE_READY         1
#define STATE_LID_OPEN      2
#define STATE_TIMER_RUNNING 3
#define STATE_LOCKING_DOWN  4
#define STATE_SECURED       5

//Pin Definitions
#define mag_sw       5  //Lid switch
#define relay_1      18 //Lock 
#define relay_2      19 //Unlock
#define int_lights   21 //Interior light strip
#define pir          2  //Passive infrared sensor, internal to box (Panic/Safety)
#define panic_button 4  //Lit button, internal to box (Panic/Safety)
#define ledG         12 //Green LED
#define ledB         13 //Blue LED
#define ledR         14 //Red LED

bool lid_state = 0;
bool last_lid_state  = 0;

unsigned long time_now = 0;
unsigned long last_debounce_time = 0;
unsigned long one = 0;
unsigned long two = 0;
unsigned long three = 0;
unsigned long blink_time = 0;
unsigned long ajar = 0;

int current_state = STATE_STARTUP;

// **********Wifi and MQTT connect info removed from here for brevity*******

void setup() {
  Serial.begin(115200); //Change this if using Arduino board

  // We set up the pinmode() and starting conditions
  pinMode(relay_1, OUTPUT);
  pinMode(relay_2, OUTPUT);
  pinMode(mag_sw, INPUT_PULLUP);
  pinMode(pir, INPUT_PULLUP);
  pinMode(panic_button, INPUT_PULLUP);
  pinMode(int_lights, OUTPUT);
  pinMode(ledG, OUTPUT);
  pinMode(ledB, OUTPUT);
  pinMode(ledR, OUTPUT);

  //Set the relay's to off to begin with or they float partially active...
  digitalWrite(relay_1, HIGH);    //Relay board requires these HIGH to be off
  digitalWrite(relay_2, HIGH);    //Relay board requires these HIGH to be off
  digitalWrite(int_lights, HIGH); //Relay board requires these HIGH to be off

//************Wifi and MQTT setup stuff removed from here for brevity... ********************

   void read_lid()
{
  lid_state = digitalRead(mag_sw);
  if (millis() - last_debounce_time > DEBOUNCE_DELAY) {   //Debounce Timer
    last_debounce_time += millis();
  }

  if (lid_state != last_lid_state) {   //check if the   button is pressed or released
    last_lid_state = lid_state;
  }
}

void panic_check() { //This is a kill switch for the box. Forces an unlock and then locks the processor in an infinite loop
  // to prevent any possibility of resetting and relocking
  int PANIC1 = digitalRead(pir);
  int PANIC2 = digitalRead(panic_button);

  if (PANIC1 || PANIC2 == LOW) // Something has gone very wrong and box needs to be unlocked NOW!!!
  {
    unlock();
    Serial.println("Emergency Protocol Activated");
    Serial.println("A failure has occurred and vault has been disabled. Power must be cycled to reset");
    client.publish("vault/state", "Panic");

  while (true) {} //I want to kill the process so there is no way to restart the  main loop unless power cycled
    //so I created an infinite while() loop to do that. Probably a more elegant way to do it
    //but let's face it, if this protocol has to be enacted, shit HAS hit the fan...
  }
}

void unlock()
{
  time_now = millis();
  digitalWrite(relay_2, LOW);  //Starts actuator power
  // start up the relay run timer
  if ((time_now - one >= RELAY_TIMER) && (RELAY_TIMER >0)) 
   {
   one = time_now;
   Serial.println(one);
   digitalWrite(relay_2, HIGH);  //Stops actuator power
   digitalWrite(ledG, HIGH);
   }
}

void lock()
{
  time_now = millis();
  digitalWrite (int_lights, HIGH);
  digitalWrite (relay_1, LOW);  //Starts actuator power
  // start up the relay run timer
   if ((time_now - two > RELAY_TIMER) && (RELAY_TIMER >0))
   {
   two = time_now;
   digitalWrite (relay_1, HIGH);  //Stops actuator power 
   }
}

void lockout_timer()
{
  time_now = millis();
  if ((time_now - three > LOCKDOWN_TIMER) && (LOCKDOWN_TIMER >0))
   {
   three = time_now;
   Serial.print("Timer has timed out now...");  
   }
}

void blink_blue()
{
   //Blink the BLUE LED at once per second...until the reset request is received
   if(millis() - blink_time > BLINK_INTERVAL)
   {
   blink_time = millis();// Update the time for this activity:
   digitalWrite(ledB, !digitalRead(ledB));
   }
}

void blink_red()
{
   //Blink the RED LED at once per second...until the reset request is received
   if(millis() - blink_time > BLINK_INTERVAL)
   {
   blink_time = millis();// Update the time for this activity:
   digitalWrite(ledR, !digitalRead(ledR));
   }
}

/*void blink_led_toggle()  //currently unused in this sketch
  {
  unsigned long blink_interval_activity = millis();
  //Blink the RED LED at once per second...until the reset request is received
  if (led_toggle - blink_interval_activity > BLINK_INTERVAL_TOGGLE) 
  {
  blink_interval_activity = led_toggle;// Update the time for this activity:
  digitalWrite(ledR, !digitalRead(ledR));
  digitalWrite(ledG, !digitalRead(ledG));
  digitalWrite(ledB, !digitalRead(ledB));
  }
  }*/ 
  
void lid_ajar()
{
  if (millis() - ajar > LID_OPEN_TIME_OUT) {
  ajar = millis();// Update the time for this activity:
  client.publish("vault/lid_ajar", "TRUE");
  }
} 
void loop() {
  
  time_now = millis();
  read_lid();     // Update the lid status
  Serial.println (lid_state);
  client.loop();   //Loops connection with MQTT subscriptions

  switch (current_state) {
    case STATE_STARTUP:
        Serial.println("Startup In Progress...");
        Serial.println("______________________");
        unlock();
        if (one > RELAY_TIMER){
        current_state = STATE_READY;
        }
      break;
    case STATE_READY:
        Serial.println("Ready For Packages...");
        Serial.println("______________________");
        if (lid_state == 1) 
        {
        current_state = STATE_LID_OPEN;
        }
      break;
    case STATE_LID_OPEN:
        Serial.println("The Lid Has Been Opened...");
        Serial.println("______________________");
        digitalWrite(ledG, LOW);
        if (lid_state == 1)
        {
         blink_blue();
         lid_ajar();
        }
        else
        {
        digitalWrite(ledB, LOW);
        digitalWrite (int_lights, LOW);
        digitalWrite(ledR, HIGH);
        current_state = STATE_TIMER_RUNNING;
        }
      break;
    case STATE_TIMER_RUNNING:  
        Serial.println ("Timer Operation Has Begun...");
        Serial.println ("______________________");
        lockout_timer();
        if (three > LOCKDOWN_TIMER){
        current_state = STATE_LOCKING_DOWN;
        }
      break;
    case STATE_LOCKING_DOWN:
        Serial.println("Locking The Box Now...");
        Serial.println("______________________");
        lock();
        if (two > RELAY_TIMER){
        current_state = STATE_SECURED;
        }
      break;
    case STATE_SECURED:
        Serial.println("The captain has extinguished the NO SMOKING sign...");
        Serial.println("You may now roam freely about the cabin...");
        Serial.println("Vault Secured...");
        Serial.println("______________________");
        blink_red();
        panic_check();  //Check if the panic indicators have been tripped after the lockdown has occurred
      break;
  }
}

Out of curiousity, I dumped some code into the lockdown timer to see what it was doing internally… like if it was counting or not. Weirdly enough, it doesn’t look like it is doing ANYTHING. I put the following code in there:

void lockout_timer()
{
  time_now = millis();
  if (time_now - three > LOCKDOWN_TIMER)
   {
   three = time_now;
   if (three > 6000){
    Serial.print("Timer has timed out now...");
   }
   else{
    Serial.println("counting still");
   }
   //current_state = STATE_LOCKING_DOWN;  
    
   }
}

What I got was a continuous- “Timer Operation Has Begun…” It never printed "Timer has timed out now OR “counting still”. SO that tells me what? That the lockout_timer function isn’t being called in that state? It should output one or the other but outputs neither. The state I was calling it from is this one:

case STATE_TIMER_RUNNING:  
        Serial.println ("Timer Operation Has Begun...");
        Serial.println ("______________________");
        lockout_timer();
      break;

Looks like your else clause is not placed as intended. I think you want the else condition related to the first if like this :

void lockout_timer()
{
    time_now = millis();
    if (time_now - three > LOCKDOWN_TIMER)
     {
        three = time_now;
        if (three > 6000)
            {
            Serial.print("Timer has timed out now...");
            }
      }
     else
      {
      Serial.println("counting still");
      }
   //current_state = STATE_LOCKING_DOWN;  
    
 }

please see the additional curly bracket before the else

@Guru_Of_Nothing Sorry to say it again, but I’m still missing the initialization of your timer variables (two, three) before your timer starts. As I said before, the timer variable has to be initialized with the current time (millis()) imediately before the timer starts. This is not an issue with the blink timers because they are set after every cycle, so only the first cycle is wrong, but for one-time timers (lock / unlock) it’s essential.

Cleanly formatted code is must for debugging! You can set the Arduino IDE to use tabs instead of spaces. See a guide found in this post. And set the indentation size.

This is just my preference so that the indentation is quite visible:
(use tabs and set equal to 4 spaces in size)

editor.tabs.expand=false
editor.tabs.size=4

I’ve formatted the code as such. And used ‘snake_case’ for variables and ‘camelCase’ for functions so that the two are easily differentiated.

I’ve refactored what I initially posted. Couldn’t sleep and was thinking over how to simplify the code. As you can only be in one state at a time, I’ve created a shared timer variable that resets between states.

Wasn’t sure when you wanted the interior lights on/off so I left that out. Panic doesn’t lock up but resets to unlocking state - presumably waits another 15secs and relocks.

// Timer Intervals
#define BLINK_INTERVAL		1000
#define LID_OPEN_INTERVAL	120000
#define LOCKDOWN_INTERVAL	15000
#define RELAY_INTERVAL		6000
#define DEBOUNCE_INTERVAL	400


// States
#define STATE_UNLOCKING		0
#define	STATE_LOCKING		1
#define STATE_OPENED		2 
#define STATE_CLOSED		3
#define	STATE_LOCKED		4


// I/O
#define MAG					5
#define REPLAY1				18
#define REPLAY2				19
#define LIGHTS				21
#define PIR					2
#define PANIC				4
#define LEDG				12
#define LEDB				13
#define LEDR				14


unsigned long timer = 0;
unsigned long debounce_timer = 0;
unsigned long blink_timer = 0;

int current_state = STATE_UNLOCKING;
bool package = false;



void setup()
{
	Serial.begin(115200);

	pinMode(REPLAY1, OUTPUT);
	pinMode(REPLAY2, OUTPUT);
	pinMode(MAG, INPUT_PULLUP);
	pinMode(PIR, INPUT_PULLUP);
	pinMode(PANIC, INPUT_PULLUP);
	pinMode(LIGHTS, OUTPUT);
	pinMode(LEDG, OUTPUT);
	pinMode(LEDB, OUTPUT);
	pinMode(LEDR, OUTPUT);

	// Set the relays to off to begin with or they float partially active
	digitalWrite(REPLAY1, HIGH); // HIGH to be off
	digitalWrite(REPLAY2, HIGH); // HIGH to be off
	digitalWrite(LIGHTS, HIGH); // HIGH to be off

	/*** Wifi and MQTT setup stuff removed from here for brevity ***/
}



void loop()
{
	switch (current_state)
	{
		case STATE_UNLOCKING:
			if (getTimer(timer, RELAY_INTERVAL))
			{
				// Stops actuator power
				digitalWrite(REPLAY2, HIGH);
				current_state = STATE_CLOSED;
			}
			else
			{
				// Starts actuator power
				digitalWrite(REPLAY2, LOW);
				Serial.println("Unlocking");
			}

			// Flash while unlocking
			if (getTimer(blink_timer, BLINK_INTERVAL))
			{
				digitalWrite(LEDG, !digitalRead(LEDG));
			}
			break;

		case STATE_LOCKING:
			if (getTimer(timer, RELAY_INTERVAL))
			{
				// Stops actuator power
				digitalWrite(REPLAY2, HIGH);
				current_state = STATE_LOCKED;
			}
			else
			{
				// Starts actuator power
				digitalWrite (REPLAY1, LOW);
				Serial.println("Locking");
			}

			// Flash while locking
			if (getTimer(blink_timer, BLINK_INTERVAL))
			{
				digitalWrite(LEDG, !digitalRead(LEDG));
			}
			break;

		case STATE_CLOSED:
			// Just opened and debounced
			if (digitalRead(MAG) && getTimer(debounce_timer, DEBOUNCE_INTERVAL))
			{
				current_state = STATE_OPENED;
				timer = 0;
				debounce_timer = 0;
				Serial.println("Opened!");
			}
			// Package arrived and lockout timer expired
			else if (package == true && getTimer(timer, LOCKDOWN_INTERVAL))
			{
				current_state = STATE_LOCKING;
			}

			// Indicate ready
			digitalWrite(LEDG, HIGH);
			break;

		case STATE_OPENED:
			// Just closed and debounced
			if (!digitalRead(MAG) && getTimer(debounce_timer, DEBOUNCE_INTERVAL))
			{
				current_state = STATE_CLOSED;
				timer = 0;
				debounce_timer = 0;
				Serial.println("Closed!");
			}
			// Lid open for too long
			else if (getTimer(timer, LID_OPEN_INTERVAL))
			{
				Serial.println("AJAR");
				//client.publish("vault/lid_ajar", "TRUE");
			}

			// Flash while open
			if (getTimer(blink_timer, BLINK_INTERVAL))
			{
				digitalWrite(LEDB, !digitalRead(LEDB));
			}

			// Assume package arrived
			package = true;
			digitalWrite(LEDG, LOW);
			break;

		case STATE_LOCKED:
			// Unlock incase of panic tripped
			if (digitalRead(PIR) == HIGH || digitalRead(PANIC) == LOW)
			{
				current_state = STATE_UNLOCKING;
				timer = 0;
				debounce_timer = 0;
				Serial.println("Panic!");
			}

			// Flash while locked
			if (getTimer(blink_timer, BLINK_INTERVAL))
			{
				digitalWrite(LEDR, !digitalRead(LEDR));
			}
			
			Serial.println("Locked!");
			break;
	}
}



/**
 * Determine if time has reached given interval
 * 
 * @param unisnged long time The current timer
 * @param int interval Length of time to run timer
 * @return bool True when timer is complete
 * @return bool False when timer is counting
 */
bool getTimer(unsigned long &timer, int interval)
{
	if (timer < 1)
	{
		timer = millis();
	}

	if (millis() - timer >= interval)
	{
		timer = 0;
		return true;
	}

	return false;
}
1 Like

Well… thank you. I mean SERIOUSLY… thank you. I would normally compare what I had (that didn’t work) to what I have now (that works great!) and see what I did wrong. However, you have made enough changes that I think I will just study the new version so I can do a better job at coding in the future… because let’s face it, what i had was crap compared to your revisions. Outside of horrible formatting, any specific ideas as to what the culprit was as to why the timer went AWOL mid sketch?

This was probably a bit big of a project to use as a starter project but I have learned a ton (not nearly enough) so far. I was referencing the myriads of example code that is out there for specific pieces and everything is so different from each other… like there is no real software engineers building code examples. So I took what I thought looked cleaner and easier to track through.

As far as the resetting the vault goes, I had removed that code because it was part of the MQTT block that I cut out for simplicity. I have no idea if it will work when I plug it back in but I figured I could cross that bridge once I got a functioning FSM working. SO I guess that is next.

I have a question for you, why is it that the code I had would not allow me to put the functions below the void loop()? If they were not above, it would not compile. Your code compiled just fine with the functions below.

1 Like

I think @moinmoin was on the right track that the timers couldn’t start at zero else the conditions would always fail and not “count”. Needs a once-off trigger per counter/state change to equal the current mills() to then compare against.

The trigger being if (timer < 1) { timer = millis(); } which then later gets reset to be re-triggered during the next time I call for a timer.

I’m unsure why a function below loop() wouldn’t compile. Obviously variables need to be defined before they can be used elsewhere. So perhaps a var wasn’t defined before the loop/your functions?

Hope it all helps! :grinning:

1 Like

I guess you could use mqtt callback to set:

current_state = STATE_UNLOCKING;
timer = 0;
debounce_timer = 0;
package = false;

Twice, once to fetch your package and then again after you’ve closed the lid to “reset” the system to prevent it triggering lockdown.

I dropped the code blocks I removed from the original sketch below. At the bottom of the callback I had put in a reset message dealy that works in my current vault sketch. I know it does not work as built in this application though. Frankly, that part is over my head still. Figured I would sort that out when I got there. I guess I am there. :face_with_raised_eyebrow:

I have said it to Jon and I say it to all who have this kind of skill… I bow at the feet of the masters! I can fabricate and fix with the best of them but this is a totally new world for me. I would be embarassed at the state of the work on my vault except it was merely a proof of concept. Vault 2.0 will be super clean now that I know what works well hardware wise.

Anywhoo… Now I can sleep better :smile: I have been trying to fix this in my dreams

//Your Wifi and MQTT info here
const char* ssid = "";
const char* password =  "";
const char* mqttServer = "";
const int mqttPort = 1883;
const char* mqttUser = "";
const char* mqttPassword = "";

  
//In void setup()******************
  //Start up Wifi connection
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println("Connecting to WiFi..");
  }

  Serial.println("Connected to the WiFi network");


  //Connecting to MQTT server and set callback
  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);

  while (!client.connected()) {
    Serial.println("Connecting to MQTT...");

    if (client.connect("Package_Vault", mqttUser, mqttPassword )) {

      Serial.println("connected");

    } else {

      Serial.print("failed with state ");
      Serial.print(client.state());
      delay(2000);

    }
  }

}

// Defines what info we want (and expect) in our MQTT communications
void callback(char* topic, byte* payload, unsigned int length) {

  Serial.print("State Changing...Message Published on ");
  Serial.println(topic);

  Serial.print("Message Delivered is:");
  String messageTemp;  //Sets string var

  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
    messageTemp += (char)payload[i];
  }

  Serial.println();
  Serial.println("-----------------------");

  //Subscribe to appropriate topics on MQTT
  client.subscribe("vault/reset");

  if (String(topic) == "vault/reset") {  //Unlocks the box
    if (messageTemp == "1") {
      current_state = STATE_STARTUP;
    }
  }
}

I’m a web programmer by day. Physical wiring/fabricating/etc is what I’m very-slowing learning!

You need to subscribe to the topic outside of your callback function.
This:

// Subscribe to appropriate topics on MQTT
client.subscribe("vault/reset");

Would go in place where you have:

if (client.connect("Package_Vault", mqttUser, mqttPassword )) {
	Serial.println("connected");
	// Should go here:
	client.subscribe("vault/reset");
}

Also if the WIFI connection is flaky, you may want to look at a wifi mqtt example I wrote that reconnects with a random generated mqtt client name so that when it reconnects it doesn’t have conflict issues.

1 Like

Thank you. Will make the changes when I get back home from work tonight. I have upgraded all my wifi to Ubiquiti and the AP is 8 feet away from the vault. I was going to look at ethernet for the project but nobody is going to have that where the box would go so wireless only made the most sense.

1 Like