A new item to automate... the mailbox!

I mentioned it at the tail end of the video… the need for a panic button in case someone climbs in the vault and it locks down… and I think I figured out what i am going to do with that. The controller cabinet (waterproof box that houses the electronics) has a nice big surface on the lid that a PIR sensor and back lit push button can be mounted on. I will put in a button that has the (relatively) universal power symbol on it and it will stay lit as long as the box is powered up. The PIR sensor will be polled during the locked-down state and if it sees movement, specifically IR (human or animal), the box will go into a panic mode and unlock, send out a panic message on MQTT, and lock out the programming so the box stays unlocked until reset manually via power cycling the controller. That way, it can’t relock potentially trapping someone.

I think this will be standard equipment for any controller I build. Even if the enclosure is going into a small box, it doesn’t add enough cost to the controller assembly to make it an optional item. Safety first. This could easily be scaled up to a large locker as well so is better safe than sorry.

2 Likes

Would it be possible to do a video of how you went about doing the mechanical part of the project. I’m referring to the whole locking mechanism…
Thanks
Frank

Absolutely. Thank you for asking. I will work on that today! It is a simple setup. I have been working at completing a good solid sketch to make the code all internal and not reliant on external inputs. RIght now, every step it takes is handed to it by MQTT. So if that goes down or the connection drops out, the box goes non-responsive. I am having issues with that and I have not nailed down exactly why since my goal has always been to have the controller be self contained. It works great security wise at this point but I have to keep resetting it.

1 Like

Hot off the dirty pressing room floor… for your entertainment and 5 minutes you can never get back…

The Vault Project - Hardware Tour

If you have any more ideas of what you want to see on this, send them this way…

2 Likes

Thank you much appreciated !!!

@Guru_Of_Nothing have u tried moving your actual operation commands to the vault itself and just use mqtt for status and remote unlock? U could add in a metal momentary contact key switch on the back for an “on location” actuator bypass if the onboard controller were to crash…

Oh yeah. I am lips deep in coding now to do exactly that. I wanted to have proof of concept and I didn’t know enough about coding to make it happen so making a slave was what I could accomplish at the time. I took Jon’s advice and started rebuilding the code from scratch. It’s been one hell of a learning curve but it has turned into an addiction :star_struck::crazy_face: I have what is pretty close to a working sketch but am really stuck on a part of it that I can’t debug. I was offered some assistance in figuring it all out so I have fired off the code and am waiting to see what guidance they have for me. I know it is something stupid like not declaring something important or improper syntax. It’s mostly my ignorance… but I can fix that!!! I don’t want to post broken code to Github but if anyone wants to bore themselves to death, I can drop it on here. I planned to do a video of what is going on when I get off work early in the morning today.

@Guru_Of_Nothing I have a few hours free to debug this weekend if u need any assistance… I also have struggled with syntax but I’m getting much better as the years pass…

1 Like

That is great, if you have the time to spare. I understand the basics but I am just not connecting on this part. It’s like knowing just enough Spanish to get some food but not enough to find a bathroom afterwards :laughing:

So… I was given some great advice on switch…case stuff and an example that is really quite a bit like what I am trying to accomplish as a reference that I have been following. I built my state machine layout and created functions to do the heavy lifting in the code… and couldn’t wade through the timers in 15 different places (functions) to debug properly. I opted to drop most of the functions and integrate that code into my states so I could work state by state and make each one work. Then I can take that working code and create functions to call and clean up my code. So, at the moment, all the work for each state is being done within the walls of each state. I will drop the whole sketch below for your reference… cuz I know it’s vital to have it all in the end… but here is the state that is causing me fits. It is no where near clean enough for me but it is where I last left it at 5AM this morning.

case STATE_TIMER_RUNNING:   //*********************Needs ISR for lid reopen during timer?????
      {
       
        Serial.println("Timer Operation Has Begun...");
        Serial.println("______________________");
        
        while (millis() - lockdown_timer_activity >= LOCKDOWN_TIMER){         
         lockdown_timer_activity = millis(); 
        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;
           }
        if ((lid_state != last_lid_state) && (lockdown_timer_activity < LOCKDOWN_TIMER)) {
          digitalWrite(ledR, LOW);
          digitalWrite(int_lights, LOW);
        current_state = STATE_LID_OPEN;  
        }
        }
      
        break;

How I envisioned this reading in plain English is thus: In the previous state, I turned the box lights on and turned the red LED on as well to indicate state change. We enter the state and it gives me a serial print. It starts the lockdown timer and does nothing UNLESS the lid is reopened, at which time it should reset the timer, probably by going back a state to the lid opened state so that the appropriate lights can be turned on/off.
All of my states up to this point work properly… I think. HOWEVER, once it gets to the above state, it does the serial prints and half the time it goes into the timer and half it immediately prints “Timer has timed out now…”, stops at the delay and then books it right past the lockdown and into the locked out state. I don’t want delay() in my scripts anywhere but had to plug that one in just to stop the scroll in the serial windowI have tried if, if…else, while, do… while if…else if… etc in any combination I can come up with and I can’t get the timer to work properly OR the lid reopening in this state causing it to revert back to the STATE_LID_OPEN state to reset the timer. I have no idea at all on what to do to make it see the lid has been reopened BEFORE the timer has completed it’s cycle and then restart the timer.
I am riding the desk tonight at work and will be checking back here really often if you have questions and once I get home (at 0215), I am going to make a quick unlisted video of the process as it is with the board functioning, the code and the serial print window so it is really visually obvious as to what it isn’t doing right. Thanks for your efforts…

The whole code minus the wifi and mqtt section…

/*
 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        800     // 800MS BLINK (RED OR BLUE LED)
#define BLINK_INTERVAL_TOGGLE 500     // 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        800     // 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 current_state = STATE_STARTUP; 

unsigned long last_debounce_time = 0;  
unsigned long debounce_delay = 200;   
unsigned long blink_interval_activity = 0;
unsigned long lid_open_time_out_activity = 0;
unsigned long lockdown_timer_activity = 0;
unsigned long relay_timer_activity = 0;
unsigned long relay2_timer_activity = 0;
uint16_t last_lid_time = 0;

Wifi and MQTT info here


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
 
 
//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;
            }
  }
}

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 led_toggle = 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();
    blink_led_toggle();
    
    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() {
  
  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 (millis() - relay_timer_activity >= RELAY_TIMER) {
        relay_timer_activity = millis();
        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 lid is closed
        if(millis() - blink_interval_activity > BLINK_INTERVAL){
        blink_interval_activity = millis();// Update the time for this activity:
        digitalWrite(ledB, !digitalRead(ledB));
         }
         if (millis() - lid_open_time_out_activity > LID_OPEN_TIME_OUT){
         lid_open_time_out_activity = 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:   //*********************Needs ISR for lid reopen during timer?????
      {
       
        Serial.println("Timer Operation Has Begun...");
        Serial.println("______________________");
        
        while (millis() - lockdown_timer_activity >= LOCKDOWN_TIMER){         
         lockdown_timer_activity = millis(); 
        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;
           }
        if ((lid_state != last_lid_state) && (lockdown_timer_activity < LOCKDOWN_TIMER)) {
          digitalWrite(ledR, LOW);
        current_state = STATE_LID_OPEN;  
        }
        }
      
        break;
      
      // ****************not working through this point**********************************
      // or gets past this point and blasts to STATE_SECURED



      
      case STATE_LOCKING_DOWN:
     {
        Serial.println("Locking The Box Now...");
        Serial.println("______________________");
         digitalWrite(relay_1, LOW);  //Starts actuator power
        // start up the relay run timer
        if (millis() - relay2_timer_activity >= RELAY_TIMER) {
        relay2_timer_activity = millis();
        digitalWrite(relay_1, HIGH);  //Stops actuator power
        current_state = STATE_SECURED;
        }
     }
        break;
      
      
      case STATE_SECURED:
     {
      uint16_t time_now = millis();
        Serial.println("The captin 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(time_now - blink_interval_activity > BLINK_INTERVAL){
        blink_interval_activity = time_now;// 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;
     }
     }

@Guru_Of_Nothing I’m breadboarding up some LEDs & buttons now, I’ll let you know if I make any progress and let you know what I can come up with…

That’s very cool. Thanks!

You can completely ignore the PIR and panic buttons and the panic part of the sketch that is called in the last state. That part works. Go figure. But I don’t use any timer functions in that, only delay(). It’s the millis() timer that is hanging me up.

In case you or anyone else that stumbles upon this wants to take a look at what is going on and have 12 minutes you really don’t care about not getting back… The video is HERE:

It should give a decent visual explanation of what is not working correctly.

I think your variable lockdown_timer_activity is not set properly (still 0) when you enter the state.
So the condition:
while (millis() - lockdown_timer_activity >= LOCKDOWN_TIMER){
is true and you run into Timer has exprired

@moinmoin I am guessing you are probably correct as it seems that it is this timer that is having an issue. I am just not sure why or how to change it if that is in fact what is going on. The syntax for it is exactly the same as the rest of the timers that work. What is different that I am not seeing?

Since this is more vexxing than, well… anything recent that I can recall (how is that for non-commital behavior?) I can’t let this go. I climbed back out of bed, watched a movie thinking I was going to give this a break and here I am. :hot_face: I tried yet another idea…

I changed the ‘while’ to an ‘if’ in STATE_TIMER_RUNNING because it seemed like that was waiting for something to happen before the timer could start and added a line to the else phrase of the STATE_LID_OPEN case that says lid_state = 0; with the hope that I could force an update to the lid reopen ‘if’ statement to force the lid state to ‘0’ so the second ‘if’ statement would be validated if the lid was actually reopened. Didn’t work. Actually made the timer flakier. It went through all the rest of the states in mere fractions of a second. Just gonna keep whacking at this. I don’t know what a meth addiction feels like but this has to be a close second. lol

HEY… Guess what??? No, I didn’t fix it but I did figure out that ALL my timers are jacked up! Yep. The first timer is supposed to be 6 seconds and it is consistently about 3’ish. I set the lockdown timer to 60000 and get about 30’ish seconds. So maybe there is something wrong with the clock? Could I have a processor that is hosed? Now I need to try and port this over to an Arduino and see if I can get it to work. Or maybe build another ESP32 test bench board. Ideas?

I would recommend to introduce a TRANSITION state that can be used to prepare things for the next state. In this case the lock down timer is prepared . This transition state is executed only once, so here goes the the logging println, lock down timer is initialized and imediately advances to the next state. In the timer state there is NO while, only the if to make sure the state machine is not blocked by a while.
Could look like this

          digitalWrite(ledR, HIGH);
      current_state = STATE_TRANSITION_TO_TIMER_RUNNING;
    }
  }  
    break;
  

  case STATE_TRANSITION_TO_TIMER_RUNNING:   
 
    Serial.println("Timer Operation Has Begun...");
    Serial.println("______________________");

    lockdown_timer_activity = millis(); 
    current_state = STATE_TIMER_RUNNING;  

	break;
  
  case STATE_TIMER_RUNNING:   //*********************Needs ISR for lid reopen during timer?????
  {
    
    if (millis() - lockdown_timer_activity >= LOCKDOWN_TIMER){         
		Serial.print("Timer has timed out now...");
		current_state = STATE_LOCKING_DOWN;
       }
    if ((lid_state != last_lid_state) && (lockdown_timer_activity < LOCKDOWN_TIMER)) {
		digitalWrite(ledR, LOW);
		current_state = STATE_LID_OPEN;  
		}
    }
  
    break;

Sorry, but English is not my mother tongue …

btw: looks like this “timer not initialized” issue as described above happens for other timers as well. That could be the reason why all of your timing looks too fast …

3 Likes

@Guru_Of_Nothing I did notice that, quick question, aside from the open to closed/lockdown timer are any of the others even necessary for operation? I’ve always had hit or miss timer results but I’m wondering if a simple delay command wouldn’t be a simple workaround? Didn’t have as much time to actually play with the code as I had initially thought I would but gunna try to get a couple hours in this afternoon…

Well, I did not know there was a pause. But I need the timer to run my lock and unlock and I need the timer to Blink my LEDs in the states where they blink. But I am thinking as I have been out here mowing the lawn that @moinmoin may very well be correct because if you look at the top of my sketch I make all of the timer defines as zero so if I don’t initialize them in the state they need to be initialized in they will always start out as zero. By doing so, at least in my understanding, if I go any farther out than my longest timer in operation then none of my timers will work. For instance, if my board has been running for 4 minutes I will not have any timer function at all because the smallest timer is 6 seconds and the largest is 2 minutes. Millis() will continue to count up and millis() - zero will ALWAYS BE a larger number than any counter interval I have. I haven’t had the chance to sit at my bench and play with the timers but I am going to guess that if I remove my definitions above so that they are not zero and then make them equal to millis() at the beginning of each state for initialization my timers will probably work flawlessly. That is an easy oversight to a noob like me.

@Guru_Of_Nothing,in your second to last sentence you are exatcly summarizing what I meant. Sorry for not beeing clear enough from the beginning. Looking forward to hear about the result.