New to FSM, can someone explain how the flow works? If I use a case scenario in a loop, how is an external change detected and brought back to the loop? Is all of the code/inputs, etc to be included within the case statements?
Still having a few problems. I have modified my code to include some of the suggestions. You can see that I added some print statements to allow me to monitor execution, but I can't get it to obey, such as print "trigger" when the PIR sensor is HIGH. Anyone available to explain my error(s)? I added a void check() just to bypass the keypad (not connected).
//////////////////////////// Initialize & Includes //////////////////////////
#include "SIM900.h"
#include "sms.h"
#include "Keypad.h"
#include <GSM.h>
SMSGSM sms;
//To change pins for Software Serial, use the two lines in GSM.cpp.
int PIR_SensorPin = 2;
int LED_OutPin = 11; //green
int Alarm_OutPin = 12; //red
int alarm_count = 0;
int switchPIN = 3; // alarm on test
char keypadPIN;
int z = 0;
int i = 0;
int waitTime = 5000;
//////////////////////////// Setup Keypad //////////////////////////
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] =
{
{
'1', '2', '3', 'A'
}
,
{
'4', '5', '6', 'B'
}
,
{
'7', '8', '9', 'C'
}
,
{
'*', '0', '#', 'D'
}
};
byte rowPins[ROWS] = {
6, 7, A2, A3 // row pin# on keypad to Arduino pin#, ie Row Pin #1 goes to Arduino Pin #6, etc.
}; // connect to the row pinouts of the keypad
byte colPins[COLS] = {
3, A0, A1, A4
}; // connect to the column pinouts of the keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS );
const char PIN[5] = {
'9', '6', '7', '9', '#'
}; // PIN number
char key_input[5] = {
0, 0, 0, 0, 0
}; // used for comparison
//////////////////////////////// Setup ///////////////////////////////////
void setup() {
Serial.begin (9600); // Establish Serial connection;
pinMode (LED_OutPin, OUTPUT); // Set pinMode.
pinMode (Alarm_OutPin, OUTPUT); // Set pinMode.
pinMode (PIR_SensorPin, INPUT); // Set pinMode.
digitalWrite (LED_OutPin, LOW); // Set output pins to LOW for start.
digitalWrite (Alarm_OutPin, LOW); // Set output pins to LOW for start.
digitalWrite (PIR_SensorPin, LOW);
Serial.println ("Studio Alarm starting up."); // Serial message that GSM shield is starting up
if (gsm.begin(4800)) { // Set GSM shield to recommended 4800 baud rate.
Serial.println ("Status = Network READY");
digitalWrite (LED_OutPin, HIGH);
}
attachInterrupt (digitalPinToInterrupt (2), trigger, CHANGE); // PIR as interrupt
// Could make use of the interrupts by checking in the interrupt if the state is "Ready" and
// then setting the state to "Alarm".
Serial.println ("System Ready");
keypadPIN = HIGH;
}
//////////////////////////// State Setup //////////////////////////
enum STATE {
Ready, //system acknowledges trigger
Unarmed, //system unarmed via keypad entry setting keypadPIN = LOW
JustActivated, //PIR-triggered interrupt
Alarm // confirm system unarmed then goto unarmed state else start alarm
};
STATE systemState = Unarmed;
long timeGettingArmed;
/////////////////////////// Trigger ///////////////////////////////
void trigger() {
systemState = JustActivated;
}
//////////////////////////// State Machine //////////////////////////
void loop() {
check ();
switch (systemState) {Serial.println("switch");
case Ready:
if (PIR_SensorPin == HIGH) {Serial.println("PIR_Sensor");
Serial.println("triggered");
systemState = Alarm;
} else if (keypadPIN == LOW) {
systemState = Unarmed;
}
break;
case Unarmed:
if (keypadPIN == HIGH) {Serial.println("keypadPIN=HIGH");
// Serial.println("Unarmed");
timeGettingArmed = millis();Serial.println(timeGettingArmed);
systemState = JustActivated;
}
break;
case JustActivated:Serial.println("JustActivated");
if (timeGettingArmed + waitTime < millis()) {
Serial.println("Ready");
systemState = Ready;
}
break;
case Alarm:
if (keypadPIN == LOW) {Serial.println("keypadPIN=LOW");
systemState = Unarmed;
} else {
beep();
}
break;
}
}
//////////////////////////// Alarm //////////////////////////
void beep() {
Serial.println("at beep");
// (sms.SendSMS("8656171435", "*** Motion Detected in Studio! ***"));
Serial.println ("MOTION detected: SMS Sent");
for (alarm_count = 0; alarm_count <= 5; alarm_count++) { // Cycle outputs if triggered
digitalWrite (Alarm_OutPin, HIGH); delay (1000); // On/Off/On/Off/Off
digitalWrite (Alarm_OutPin, LOW); delay (500);
digitalWrite (LED_OutPin, HIGH); // delay (1000);
Serial.println (alarm_count);
}
systemState = Ready;
}
void check() {
if (switchPIN == HIGH) {
keypadPIN = LOW; // Unarmed
}
else {
keypadPIN = HIGH; // Ready/Armed
}
}
1 Answer 1
MCU State Flow
- State ( Model )
- User/Machine Output ( View )
- User/Machine Interruption ( Events )
- State Changers ( Controller )
- goto 1.
Breakdown of how it works:
#1. State. You start with a known finite state, which is the overall mode of operation for that piece or pieces. For a switch()
-driven machine, it's a number, typically named by an integer constant for better readability.
#2. Output. Based on the state, you make certain things happen. These adjustments can be done in the switch()
, or in the loop()
as they notice state changes. You can also write explicit sub-routines to call from the switch() to keep code tidy. You can read the state from any point in the program, even interrupts if needed.
#3. Events/Interrupts. Lying in wait, these parts spring into action when something happens that the program needs to know. Both human-origin (ex: button press) and machine-origin (ex: SD card error) fall into this group. They can set flags and publish notifications to the controller that something has happened, but they MUST NOT set the state directly. The last point cannot be under-emphasized. If you start "cheating" and updating state outside of the controller, you create another point of failure and another stop along the debug/enhancement tour.
#4. State Changing switch()
(Controller). This is where the actions meet the state. Only here should you change state to a new one, based on outside flags set by events. If your switch()
is at the top of loop()
, and you return
in each case
, you can call loop()
from other points in the code to fire a state change immediately, but i like to name it in a void()
and call it from loop()
. The switch code should be lightweight and never use delay()
. Lastly, most of the time a case
does nothing, because no outside condition has changed that merits reaction, and it break
s or return
s right there. When something important does change, the code below that early-exit conditional performs as a state change event handler.
Why bother
It can be a bit much at first, and seem like it's not worth the 5-10 mins up-front to name constants and write out all the switch
bolierplate. I'm a lazy coder, and I still use this because it saves me a ton of time at every point in time besides those first 10 minutes.
Since things can only change in one place, you know exactly where to debug. It's simple to dump the outside values a case
examines to the serial (remove before "launch"), rather than finding that chunk of code in the ether. Doing it all in one place makes it trivial to log when and what with each state change. With minor additional code you can even undo state changes, which can be a boon for users. Such actions are not risky because every part of the outside code knows what to do.
It breaks up tedious chains of semi-related action into independent pieces. You won't need lots of nested if()
s if you follow the above guidelines. Even if you're not a long-time developer, simply following this pattern and slopping/pasting all the other code together, your program will be better in the end and faster and easier to build along the way.
-
Thanks. Looks like your idea is proving out well. Gave me another tool.nkuck– nkuck2017年04月15日 06:30:14 +00:00Commented Apr 15, 2017 at 6:30
ready,1stDown,1stUp,2ndDown,stale
states, which vary from theshort,click,dblclick
outputs. when in one state, check if you need to change to another state, and if not, do nothing. usually it's nothing.case
, if needed, sets the state to anothercase
or does nothingswitch
to avoid spaghetti flow as the project grows. It's an adjustment for sure, but i loved it. ;)