Skip to main content

Roll-up Doors with a State Machine

Roll-up a door please! 

(with a State Machine in code, part 1)

MHO Junction’s Molson Brewery in a 3D Layout View (all photos and drawings by Lloyd Henchey, Ontario, Canada)


We all know we can open and close close something with an Arduino and a Servo, but what if we want to a little bit more operating fun with some lights and sounds, all after a manager gives approval to open or close the door to the beer brewery before your engine is allowed to pick up or deliver a car?


Control Panel at the brewery

 

Six doors under construction at the Molson brewery on the MHO Junction 


Engine pulls up to the red light, shift manager (operator) grants permission, door raises up with alarms and a flashing red light, door goes all the way up, light turns green, engine or car can proceed! Same flashing red light going down and maybe different door squeaking noises.


All good articles start with a YouTube video: youtube.com/watch?v=-8EnpUgTqR4 !



This article will introduce you to a “Finite State Machine” or FSM, where you describe or plan everything that needs to happen with circles for states where things are in and with lines and arrows for when things happen.


What you need (all the links embedded here, but also typed out at the end of this document, in case you printed the article and can’t click on it here): 

  1. An Arduino Nano with a USB mini-B cable (or Uno will do, but the Nano is less expensive)

  2. Something to power it with, since you can’t leave that borrowed PC in the train room.

To avoid soldering and splicing wires: 

  1. Nano I/O Expansion Sensor Shield Module has more than enough ground pins, one with each I/O pin.

And:

  1. A small servo for every door you need to open, we can do 3 if your power supply has enough “oomf”.

  2. A button, key, light sensor or infrared sensor used for input for every door you need to allow access or interrupt light to open the door.

  3. A common cathode bi-color 3mm LED and 2 resistors, 1 kOhm each (at first), one set for each door.

  4. Optional sound system from last article with an MP3 uSD card and 3 sound files!

And as before,

  1. The Arduino IDE, free download  Version 1.8.13 is the latest at the time of writing.

  2. Maybe the CH340 Device Driver: plug the Nano in with the USB cable and see if Device Manager gives you a Serial COM port (/dev/ttyACMx or /dev/ttyUSBx on a Mac or Linux).  If Windows shows you a yellow icon, find the driver with Google, download and install the driver and then reboot the computer.


And last, but not least, the code to install for this project.  


With all the bells and whistles, we need 5 pins per door (and two more to turn sounds on and off):

Input: access granted, a simple, normally open push button for the manager.


Output: the red signal, like a 3 mm or smaller red LED in a black disk.

Output: the green permission granted LED, which, combined with the red one, could be a bi-color LED as used in a search light.

PWM Output: the door’s motion is controlled with a servo, needing a PWM capable pin to control this.

Output: an optional output pin to turn some sounds on, using the uSD card mp3 player from the last article (a serial interface might work better, so either a software UART or a clock and data pin, we’ll do this instead).  So two pins (RX and TX) for the whole system.


In order to start planning the code, we draw up a finite state machine.  A state machine represents all the states your door and lights can be in and it shows how it transitions from one state to another based on what input has changed.  Of course there are 3 sets of these, one for each door, sensor and signal...but since they do not have anything to do with each other, we only need to draw one of the sets.  Oops, feature creep, we only want a door to open if the other doors are all closed.  So we will not leave that state unless all doors are closed.  


FSM (Finite State Machine)


In our setup the door only opens on startup or with manager approval.  All good state diagrams start with a darker circle to show where it all begins, Power On in our case.  The dotted lines indicate actions that happen without human intervention, from Power On, it goes to the next state, POST, by itself, and so forth.


Again, from Power On, we move immediately to the POST (Power On Self Test) where we check if the sound module is happy, uSD card inserted and we play the first sound file as a part of the test.  From here the state diagram transitions to the “Door Opening, Light Flashing Red” state, simply to avoid an accident when the power went out at the wrong time.  We flash the red light and play the second sound file.  


When a door is completely opened, the flashing red light will turn green, now in the “Door Open, Light Green” state.  And this will happen to all the doors, but is not shown.  Just know that not all doors have to be in the same state at the same time.  It is only on power up that we send them through the same states.


Here the Arduino waits for the Manager to press and release one or more of the Approval Buttons for each door.  Upon press and release, the specific door will start closing, with the red light flashing and the “closing” sound playing, shown in the “Closing Door, Light Flashing Red” state.  


Once closed, we enter the “Door Closed, Light Red” state, with the light now solid red (you could also change the requirements and have the light turned to off).  Also note that not all our doors have to close at the same rate, or from the same height, so some might be closed before others.


From here, only one door can be opened at one time due to safety regulations at the plant.  Silly rule, but the union is in charge, sorry.  When the button is pressed for any door, it will only move to the “Door Opening, Light Flashing Red” state if all were closed.  If the button is pressed and there is already an open door, we stay in the same state.  But, if all closed, the door starts opening, flashing the light red, and we are back where we started when the power turned on, except only one door can be here now.  


The state machine keeps the current state each door is in, and only moves to another state when the input with the line and arrow leaving the circle gets activated.  There is no “Door Closed” or “Door Opened” sensor, but the software inside knows the position of the door, since it is setting the servo position, and compares it to the commanded position.  When a door reaches the open or close commanded position, the state machine follows the dotted line automatically to the next state.


For part 1 of this article, we only show the wiring and code needed for 1 door to keep it a little shorter.  You wire up the  servo, the push button (approval) and the bi-color LED.  We want to use a PWM pin for the servo, since we don’t want the extra code needed for a software servo (just know that you could control a servo from any pin).  PWM pins, those labeled with the ‘~’ (tilde) symbol (D3, D5, D6, D9, D10 and D11) make life a bit easier.  


 

Servo, bi-color LED and button connections to a Nano on the Expansion Board (thank you fritzing)

(The light sensor shown is another feature creep for a future article to dim the lights a bit at night time)

Servo installed behind the brewery door


Back to the State Machine, how do we create and use it?


First, you create an enumeration or enum for short to indicate which state you are in.  This is literally just assigning a number to a word for each state (DoorOpening) that is a lot easier to read in the code, than remembering it with a number, like 4 for “Door Opening, Flashing Red Light”.  So enum defines a new type in C or C++, so just like you have int, bool and Serial or Servo, after the next few lines of code, you will also have DoorState as a type.


enum DoorState {

  POST = 0,

  DoorOpening,

  DoorOpen,

  DoorClosing,

  DoorClosed

};


And then we use this to create an array of DoorState types, to keep track of the state for each door.


#define NUM_DOORS 3

DoorState breweryDoorStates[ NUM_DOORS ];


Technically, we won’t use the POST state much, since we think of it as the code that runs in the setup( ) routine, which only happens after Power On, or if someone pressed the Reset button.  Just note that the code won’t set it back to POST ever again.  Setting and checking the states, here we go:


#include "SlowDoors.h"

#include "BreweryLites.h"


#define NUM_DOORS 3


enum DoorState {

  POST,

  DoorOpening,

  DoorOpen,

  DoorClosing,

  DoorClosed

};  // all these need to be unique words and not strings used in the C/C++ language or your code. 

    // Example: setup is not allowed ->  error: 'setup' redeclared as different kind of symbol


DoorState breweryDoorStates[ NUM_DOORS ];   // State Machine for each door


// SlowDoor( uint8_t servoPin, unint8_t buttonPin, uint16_t start, uint16_t stop, uint16_t updateRate )

SlowDoor *breweryDoors[ NUM_DOORS ] = {

  new SlowDoor( 3, A0, 70, 120, 50 ),

  new SlowDoor( 5, A1, 75, 125, 50 ),

  new SlowDoor( 6, A2, 60, 180, 30 )  // servo moves a little faster, since further travel

};


//BreweryLite( uint8_t redPin, uint8_t greenPin, uint16_t flashRate )

BreweryLite *doorLites[ NUM_DOORS ] = {

  new BreweryLite(  7,  8, 500 ),

  new BreweryLite(  9, 10, 400 ),  // flash a little faster

  new BreweryLite( 11, 12, 500 )

};


void setup( ) {

  Serial.begin( 115200 );

  Serial.println( F("\nRoll-up Doors version 1.0, 2020.11.01") );

  

  for( uint8_t i; i=0; i<NUM_DOORS ) {

    breweryDoorStates[i] = POST;

    breweryDoors[i]->Setup( );  // attach servo pin and INPUT_PULLUP button pin

    doorLites[i]->Setup( );     // pinMode both LED pins as OUTPUTs

  } // for  

  // setup DFPlayer (not shown, see prior article)

  // play 001.mp3 

} // setup( ) 


void loop() {  

  // For each door, use switch to manage the state machine

  for( uint8_t i; i=0; i<NUM_DOORS ) {

    switch( breweryDoorStates[ i ] ) {

      case( POST ):

        // if playing, stop and play 002.mp3 

        breweryDoors[i]->Open( );               // command the door to ...

        doorLites[i]->FlashingRed( );           // command the light to ...

        breweryDoorStates[i] = DoorOpening;     // new state is ...   

        break;

      case( DoorOpening ):

        if( breweryDoors[i]->isOpen( ) ) {      // check if the door is ...

          doorLites[i]->Green( );               // set the light to ...

          breweryDoorStates[i] = DoorOpen;      // new state is ...

        } // if

        break;

      case( DoorOpen ):

        if( breweryDoors[i]->wasButtonPushed( ) ) {

          breweryDoors[i]->Close( );

          doorLites[i]->FlashingRed( );

          breweryDoorStates[i] = DoorClosing;   // new state is …

          // if playing, stop and play 003.mp3 

        } // if

        break;

      case( DoorClosing ):

        if( breweryDoors[i]->isClose( ) ) {

          doorLites[i]->Red( );

          breweryDoorStates[i] = DoorClosed;    // new state is ... 

        } // if

        break;

      case( DoorClosed ):

        if( breweryDoors[i]->wasButtonPushed( ) ) {

          // count number of closed doors

          uint8_t closedCount = 0;

          for( uint8_t k; k=0; k<NUM_DOORS ) {   

            if( breweryDoors[k]->isClose( ) ) closedCount++;

          } // for

          // and only open if all are closed

          if( closedCount == NUM_DOORS ) {  

            breweryDoors[i]->Open( );

            doorLites[i]->FlashingRed( );

            breweryDoorStates[i] = DoorOpening; // new state is ... 

            // if playing, stop and play 002.mp3

          } // if all closed

        } // if

        break;


      default: // should never get here

        Serial.println( F("oops...") );

        break;      

    } // switch


    doorLites[i]->Update( );        // update the lights, green, red or flashing

    breweryDoors[ i ]->Update( );   // update the servo to move a step closer 

                                    // or stay at its commanded position

  } // for

} // loop( )


Note that we only have one sound player, and whichever action happens last, will stop the previous sound and start the sound just requested.  Not perfect, but better than nothing, I guess!


Once you have all this working, you can add a few more things like checking if the track under the door is clear before closing it.  You could mix some sound files on your computer and play the sounds for opening and closing a door at the same time.  You can disable the sounds after 3 or 4 times for those of us who are not fond of screaming alarms, or have an enable/disable input to turn it on and off.  Or you could install and enable that light sensor to change the brightness of the red and green light?  The height of the door is the limit!


Speed


LINKS:

An Arduino Nano

https://smile.amazon.com/REXQualis-Board-ATmega328P-Compatible-Arduino/dp/B07WK4VG58/ref=sr_1_1_sspa

Power it:

https://www.walmart.com/ip/iPhone-Wall-Pro-Air-Port-Plug-Pro-Max-8-7-X-More-Mini-Super-PowerPort-Phone-Output-iPad-Charger-Foldable-Compact-2-4A-USB-11-11-Anker-4-2-Mini-Samsun/799628659

Nano I/O Expansion Sensor Shield Module:

https://smile.amazon.com/WYPH-Expansion-Arduino-Compatible-Duemilanove/dp/B07KCVJ6RR/ref=sr_1_3

Small servo:

https://hobbyking.com/en_us/hobbykingtm-hk15178-analog-servo-1-4kg-0-09sec-10g.html

Light sensor:

https://www.amazon.com/eBoot-Photoresistor-Sensitive-Resistor-Dependent/dp/B01N7V536K/ref=sr_1_20_sspa

Infrared sensor:

https://www.amazon.com/Willwin-Infrared-Obstacle-Avoidance-Intelligent/dp/B0776RCLH6/ref=sr_1_8

3mm bi-color LED:

https://www.mouser.com/ProductDetail/Lumex/SSL-LX3059IGW?qs=sGAEpiMZZMtmwHDZQCdlqVsz6Sp3LtaXwScsujkhKSY%3D

1 kOhm resistor:

https://smile.amazon.com/Noblik-0-25W-Carbon-Resistor-Postage/dp/B085PVRV7V/ref=sr_1_2

Arduino IDE:

https://www.arduino.cc/en/Main/Software

Sound system in prior Marker Lamp:

https://archive.org/details/marker_lamp_winter_20/page/n12/mode/1up


Finite State Machines: https://brilliant.org/wiki/finite-state-machines/




Comments

Popular posts from this blog

Signal Masts in JMRI 4.14, for beginners...

Here are all the notes and files from the Plano Train Show Clinic. You need to have an internal sensor ( IMCOMPORT ) with the port in the value like COM10 or /dev/ttyUSB0 , whatever Device Manager (or /dev/ttyUSBx) shows for the connected Arduino. If you have Panels -> Script Output open, you will see the message:  !!! Please add IMCOMPORT with the port in the value!!! The basic idea here is to use the Comment in the Signal Head field to define the position of the Pixel in the string commented to the Arduino, as well as the RGB colors to show for Clear, Approach and Stop (00:00 FF 00: BBAA 00: FF 0000). With all the Heads in the Table, run the python script and now you can update the colors in the Comment "live", keeping in mind that a message is only sent when a Head changes color. We then put the Heads in Masts and use the Signal Mast Logic to do the magic. I like the original Panel Editor more and it works fine as long as you keep using Mast pairs, which require

The One Pin Signal System, using an Arduino

The One (RRRduino) Pin Signal System! Part I NeoPixels: Yes, you read that right, you can control a whole signal system with up to 200 lights by using a single Arduino pin. And this was not my idea, all the credit and the patent goes to my friend Tom, but since I already had something using the same technology up and running using only a Nano, I do not feel too bad telling you about it. Short video first! There is a tiny 8 pin chip out there called a WS2811 which is a three channel LED driver, and you might find them on the interweb as NeoPixel LED Driver Chips. They need power and ground, typically 5 Vdc, and data to be put in the three registers inside the chip, one for each LEDs it can control. So, in most cases out there today, the LEDs connected are Red, Green and Blue (RGB for short) and by setting the three registers each with an 8 bit value (which in English means a value from 0 to 255, since 2 8 = 256, but since 0 is an acceptable value too, the maximum is 2 8 - 1

Not RRRduino, unless you have an MQTT connection via Ethernet or WiFi

JMRI code to connect to an MQTT broker, to publish and subscribe to the messages for signal masts. Of course, we agree to a standard for the MQTT topics, and since you were not here, we settled on "mast.xxxx" where xxxx is a number starting at zero. This MQTT topic is used as the User Name in a JMRI signal mast. Created as a Virtual Mast with the Aspect used as the payload in the message. And, yes, the device under the real mast needs to be programmed with the same name and code for each of the "aspect" payloads to be implemented. A yellow and red LED'd dwarf signal can not do a "Clear" aspect, so make sure "Clear" is maybe set to yellow for "Approach" as well.    Virtual Masts with agreed upon User Names. Comments are for the One Wire Signals on the NeoPixel string (see prior post)   Virtual Mast The Jython (or Python) code shown here below, is to attach a Listener to each Signal Mast, and to publish a message