most functionality to splashdwon, find neutral and start mission. short timeouts still in code for testing, will adjust to go directly to sit_idle after splashdown

Dependencies:   mbed MODSERIAL FATFileSystem

Revision:
17:7c16b5671d0e
Parent:
16:3363b9f14913
Child:
18:85a7535af8fd
--- a/StateMachine/StateMachine.cpp	Mon Nov 06 22:57:56 2017 +0000
+++ b/StateMachine/StateMachine.cpp	Tue Nov 21 22:03:26 2017 +0000
@@ -2,7 +2,7 @@
 #include "StaticDefs.hpp"
 
 StateMachine::StateMachine() {
-    timeout = 60;             // generic timeout for every state, seconds
+    _timeout = 20;             // generic timeout for every state, seconds
     depthTolerance = 0.25;    // depth tolerance for neutral finding exit critera
     pitchTolerance = 1.0;     // pitch angle tolerance for neutral finding exit criteria
     bceFloatPosition = 300;   // bce position for "float" states
@@ -10,41 +10,52 @@
     
     depthCommand = 3.5;       // user keyboard depth
     pitchCommand = -20.0;     // user keyboard depth
+    
+    _neutral_sink_timer = 10;
+    _neutral_rise_timer = 5;
+    
+    previousPosition_mm = 220;      //centered, overwritten by the state machine (LOAD THIS FROM CONFIG?)
+    _state = SIT_IDLE;               // select starting state here
+    
+    isTimeoutRunning = false;       // default timer to not running
+    isSubStateTimeoutRunning = false;
+    
+    _neutral_buoyancy_bce_pos_mm = 0;
+    _neutral_buoyancy_batt_pos_mm = 0;
+    
+    //new
+    _next_state = -1;    //next state is used to prevent states from changing as the FSM executes
+    _state_counter = 0;
 }
 
-void StateMachine::runStateMachine() {
-    static int state = SIT_IDLE; // select starting state here
-    static bool isTimeoutRunning = false; // default timer to not running
-    
+//Finite State Machine (FSM)
+void StateMachine::runStateMachine() { 
+    //use the _next_state when the state machine is run again (so that it cannot change states while the FSM executes)
+
+    static bool runFirstNeutral = false;
+            
+    //use the _next_state when the state machine is run again (so that it cannot change states while the FSM executes)   
+    if (_next_state > -1)
+        _state = _next_state;   //current state comes from the state at the completion of the FSM
+        
     // finite state machine ... each state has at least one exit criteria
-    switch (state) {
+    switch (_state) {
     case SIT_IDLE :
         // there actually is no timeout for SIT_IDLE, but this enables some one-shot actions
         if (!isTimeoutRunning) {
             showMenu();
             pc().printf("\r\n\nstate: SIT_IDLE\r\n");
-            isTimeoutRunning = true;
+            isTimeoutRunning = true; 
 
             // what is active?
             bce().pause();
             batt().pause();
+            
+            //reset sub FSM
+            isSubStateTimeoutRunning = false;
         }
         // how exit?
-        if (pc().readable()) {
-            state = KEYBOARD;
-            isTimeoutRunning = false;
-        }
-        break;
-        
-    case KEYBOARD :
-        pc().printf("\r\n\nstate: KEYBOARD\r\n");
-        if (pc().readable()) {
-            state = keyboard(); // get new state command
-            if (state == -1) { // error, that wasn't a new state command
-                state = SIT_IDLE;
-            }
-            //pc().printf("new state is: %d \r\n",state);
-        }
+        // separate keyboard function will change the states
         break;
     
     case EMERGENCY_CLIMB :
@@ -53,7 +64,7 @@
             pc().printf("\r\n\nstate: EMERGENCY_CLIMB\r\n");
             timer.reset(); // timer goes back to zero
             timer.start(); // background timer starts running
-            isTimeoutRunning = true;
+            isTimeoutRunning = true; 
             
             // what needs to be started?
             bce().unpause();
@@ -64,64 +75,72 @@
             batt().setPosition_mm(0.0);
         }
         // how exit?
-        if (timer > timeout) {
+        if (timer > _timeout) {
             pc().printf("EC: timed out\r\n");
-            state = FLOAT_LEVEL;
+            _next_state = FLOAT_BROADCAST;
             timer.reset();
             isTimeoutRunning = false;
         }
-        else if (depthLoop().getPosition() < 0.2) {
+        else if (depthLoop().getPosition() < 0.2) { //if the depth is greater than 0.2 feet, go to float broadcast
             pc().printf("EC: depth: %3.1f, cmd: 0.5 [%0.1f sec]\r",depthLoop().getPosition(), timer.read());
-            state = FLOAT_LEVEL;
+            _next_state = FLOAT_BROADCAST;
             timer.reset();
             isTimeoutRunning = false;
         }
         break;
 
     case FIND_NEUTRAL :
-        // start local state timer and init any other one-shot actions
+        /* start local state timer and init any other one-shot actions */
         if (!isTimeoutRunning) {
             pc().printf("\r\n\nstate: FIND_NEUTRAL\r\n");
             timer.reset(); // timer goes back to zero
             timer.start(); // background timer starts running
-            isTimeoutRunning = true;
+            isTimeoutRunning = true; 
             
             // what needs to be started?
             bce().unpause();
             batt().unpause();
-
-            // what is active?
-            depthLoop().setCommand(depthCommand);
-            pitchLoop().setCommand(0.0);
+            
+            //first iteration sends SINKING sub-state
+            runFirstNeutral = true;            
         }
-        // how exit?
-        if (timer > timeout) {
-            pc().printf("FN: timed out\r\n");
-            state = FLOAT_LEVEL;
+        
+        /* how exit? (exit with the timer, if timer still running continue processing sub FSM) */
+        if (timer > _timeout) {
+            pc().printf("FN: timed out [time: %0.1f sec]\r\n", timer.read());
+            _next_state = EMERGENCY_CLIMB;         //new behavior (if this times out it emergency surfaces)
             timer.reset();
             isTimeoutRunning = false;
         }
         
-        //depth tolerance of 0.25 feet, pitch tolerance of 1.0 degree
-        else if ((abs(depthLoop().getPosition() - depthLoop().getCommand()) < depthTolerance) and
-                  (abs(imu().getPitch() - pitchLoop().getCommand()) < pitchTolerance)) {
-            state = RISE;
-            timer.reset();
-            isTimeoutRunning = false;
+        // what is active? (call neutral finding sub-function every iteration)
+        if (runFirstNeutral) {
+            //pc().printf("First iteration of find neutral\n\r"); // debug (confirmed working)
+            _sub_state = findNeutralSubState(NEUTRAL_SINKING);
+            _previous_sub_state = _sub_state;       //save previous sub-state
+            runFirstNeutral = false;
         }
-        // what is active?
-        pc().printf("FN: bce pos: %3.1f mm, batt pos: %3.1f mm (pitchLoop: 0.0 deg)(depthLoop POS: %3.1f ft) [%0.1f sec]\r", bce().getPosition_mm(), batt().getPosition_mm(), depthLoop().getPosition(), timer.read());
-        bce().setPosition_mm(depthLoop().getOutput());
-        batt().setPosition_mm(pitchLoop().getOutput());
-        break;
-    
+        
+        //second and further iterations this is running
+        else {
+            _sub_state = findNeutralSubState(_previous_sub_state);
+            _previous_sub_state = _sub_state;       //save previous sub-state
+            
+            //if sub-FSM has completed its state machine
+            if (_sub_state == NEUTRAL_EXIT) {
+                pc().printf("DEBUG: FIND_NEUTRAL sub FSM completed!\n\r");
+                _next_state = RISE;  //your next state will be RISE in the FSM
+            }
+        }
+        break;   
+        
     case DIVE :
         // start local state timer and init any other one-shot actions
         if (!isTimeoutRunning) {
             pc().printf("\r\n\nstate: DIVE\r\n");
             timer.reset(); // timer goes back to zero
             timer.start(); // background timer starts running
-            isTimeoutRunning = true;
+            isTimeoutRunning = true; 
             
             // what needs to be started?
             bce().unpause();
@@ -134,20 +153,20 @@
             pc().printf("DIVE: pitch cmd: %3.1f\r\n",pitchLoop().getCommand());
         }
         // how exit?
-        if (timer > timeout) {
-            pc().printf("DIVE: timed out\r\n");
-            state = EMERGENCY_CLIMB;
+        if (timer > _timeout) {
+            pc().printf("DIVE: timed out\n\n\r");
+            _next_state = RISE; //new behavior 11/17/2017
             timer.reset();
             isTimeoutRunning = false;
         }
         else if (depthLoop().getPosition() > depthLoop().getCommand()) {
             pc().printf("DIVE: depth: %3.1f, cmd: %3.1f\r\n", depthLoop().getPosition(), depthLoop().getCommand());
-            state = RISE;
+            _next_state = RISE;
             timer.reset();
             isTimeoutRunning = false;
         }
         // what is active?
-        pc().printf("DIVE: bce pos: %3.1f mm, batt pos: %3.1f mm (depthLoop POS: %3.1f ft) [%0.1f sec]\r", bce().getPosition_mm(), batt().getPosition_mm(), depthLoop().getPosition(), timer.read());
+        pc().printf("DIVE: bce pos: %3.1f mm, batt pos: %3.1f mm (depthLoop POS: %3.1f ft) [%0.2f sec]\r", bce().getPosition_mm(), batt().getPosition_mm(), depthLoop().getPosition(), timer.read());
         bce().setPosition_mm(depthLoop().getOutput());
         batt().setPosition_mm(pitchLoop().getOutput());
         break;
@@ -158,7 +177,7 @@
             pc().printf("\r\n\nstate: RISE\r\n");
             timer.reset(); // timer goes back to zero
             timer.start(); // background timer starts running
-            isTimeoutRunning = true;
+            isTimeoutRunning = true; 
             
             // what needs to be started?
             bce().unpause();
@@ -171,22 +190,22 @@
             pc().printf("RISE: pitch cmd: %3.1f\r\n",pitchLoop().getCommand());
         }
         // how exit?
-        if (timer > timeout) {
+        if (timer > _timeout) {
             pc().printf("RISE: timed out\r\n");
-            state = FLOAT_LEVEL;
+            _next_state = EMERGENCY_CLIMB;
             timer.reset();
             isTimeoutRunning = false;
         }
         else if (depthLoop().getPosition() < depthLoop().getCommand()) {
             pc().printf("RISE: depth: %3.1f, cmd: %3.1f\r\n", depthLoop().getPosition(), depthLoop().getCommand());
-            state = FLOAT_LEVEL;
+            _next_state = FLOAT_LEVEL;
             timer.reset();
             isTimeoutRunning = false;
         }
         pc().printf("RISE: bce pos: %3.1f mm, batt pos: %3.1f mm (depthLoop POS: %3.1f ft) [%0.1f sec]\r", bce().getPosition_mm(), batt().getPosition_mm(), depthLoop().getPosition(), timer.read());
         // what is active?
-        bce().setPosition_mm(depthLoop().getOutput());
-        batt().setPosition_mm(pitchLoop().getOutput());
+        bce().setPosition_mm(depthLoop().getOutput());  //constantly checking the Outer Loop output to move the motors
+        batt().setPosition_mm(pitchLoop().getOutput()); 
         break;  
     
     case FLOAT_LEVEL :
@@ -195,7 +214,7 @@
             pc().printf("\r\n\nstate: FLOAT_LEVEL\r\n");
             timer.reset(); // timer goes back to zero
             timer.start(); // background timer starts running
-            isTimeoutRunning = true;
+            isTimeoutRunning = true; 
             
             // what needs to be started?
             bce().unpause();
@@ -206,15 +225,15 @@
             pitchLoop().setCommand(0.0);
         }
         // how exit?
-        if (timer > timeout) {
+        if (timer > _timeout) {
             pc().printf("FL: timed out\r\n");
-            state = FLOAT_BROADCAST;
+            _next_state = FLOAT_BROADCAST;
             timer.reset();
             isTimeoutRunning = false;
         }
-        else if (abs(imu().getPitch() - pitchLoop().getCommand()) < abs(pitchTolerance)) {
+        else if (fabs(imu().getPitch() - pitchLoop().getCommand()) < fabs(pitchTolerance)) {
             pc().printf("FL: pitch: %3.1f mm, set pos: %3.1f mm, deadband: %3.1f mm\r\n",imu().getPitch(), pitchLoop().getCommand(), pitchTolerance);
-            state = FLOAT_BROADCAST;
+            _next_state = FLOAT_BROADCAST;
             timer.reset();
             isTimeoutRunning = false;
         }
@@ -229,7 +248,7 @@
             pc().printf("\r\n\nstate: FLOAT_BROADCAST\r\n");
             timer.reset(); // timer goes back to zero
             timer.start(); // background timer starts running
-            isTimeoutRunning = true;
+            isTimeoutRunning = true; 
             
             // what needs to be started?
             bce().unpause();
@@ -240,31 +259,147 @@
             batt().setPosition_mm(battFloatPosition);
         }
         // how exit?
-        if (timer > timeout) {
+        if (timer > _timeout) {
             pc().printf("FB: timed out\r\n");
-            state = SIT_IDLE;
+            _next_state = SIT_IDLE;
             timer.reset();
             isTimeoutRunning = false;
         }
-        if ( (abs(bce().getPosition_mm() - bce().getSetPosition_mm()) < bce().getDeadband()) and
-             (abs(batt().getPosition_mm() - batt().getSetPosition_mm()) < batt().getDeadband()) ) {
+        if ( (fabs(bce().getPosition_mm() - bce().getSetPosition_mm()) < bce().getDeadband()) and
+             (fabs(batt().getPosition_mm() - batt().getSetPosition_mm()) < batt().getDeadband()) ) {
             pc().printf("FB: position: %3.1f mm, set pos: %3.1f mm, deadband: %3.1f mm\r\n",bce().getPosition_mm(), bce().getSetPosition_mm(), bce().getDeadband());
-            state = SIT_IDLE;
+            _next_state = SIT_IDLE;
             timer.reset();
             isTimeoutRunning = false;
         }
         pc().printf("FB: bce pos: %3.1f mm, batt pos: %3.1f mm (depthLoop POS: %3.1f ft) [%0.1f sec]\r", bce().getPosition_mm(), batt().getPosition_mm(), depthLoop().getPosition(), timer.read());
         break;
+        
+    case MULTI_DIVE :
+        // start local state timer and init any other one-shot actions        
+        if (!isTimeoutRunning) {
+            pc().printf("\r\n\nstate: MULTI-DIVE\r\n");
+            timer.reset(); // timer goes back to zero
+            timer.start(); // background timer starts running
+            isTimeoutRunning = true; 
+                
+            // what needs to be started?
+            bce().unpause();
+            batt().unpause();
+
+            //NEW: retrieve depth and pitch commands from config file struct
+            // concept is to load this each time the multi-dive restarts
+            
+            //keyboard starts sequence at 0 from keyboard function
+            //stateMachine().getDiveSequence();
+            
+            //retrieve commands from structs
+            float sequenceDepthCommand = currentStateStruct.depth;
+            float sequencePitchCommand = currentStateStruct.pitch;
+    
+            // what are the commands?            
+            depthLoop().setCommand(sequenceDepthCommand);
+            pitchLoop().setCommand(sequencePitchCommand);
+            pc().printf("MULTI-DIVE: depth cmd: %3.1f\r\n",depthLoop().getCommand());
+            pc().printf("MULTI-DIVE: pitch cmd: %3.1f\r\n",pitchLoop().getCommand());
+        }
+        // how exit?
+        if (timer > _timeout) {
+            pc().printf("MULTI-DIVE: timed out\n\n\r");
+            _next_state = MULTI_RISE; //new behavior 11/17/2017
+            timer.reset();
+            isTimeoutRunning = false;
+        }
+        else if (depthLoop().getPosition() > depthLoop().getCommand()) {
+            pc().printf("MULTI-DIVE: depth: %3.1f, cmd: %3.1f\r\n", depthLoop().getPosition(), depthLoop().getCommand());
+            _next_state = MULTI_RISE;
+            timer.reset();
+            isTimeoutRunning = false;
+        }
+        // what is active?
+        pc().printf("MULTI-DIVE: bce pos: %3.1f mm, batt pos: %3.1f mm (depthLoop POS: %3.1f ft) [%0.1f sec]\r", bce().getPosition_mm(), batt().getPosition_mm(), depthLoop().getPosition(), timer.read());
+        bce().setPosition_mm(depthLoop().getOutput());
+        batt().setPosition_mm(pitchLoop().getOutput());
+        break;
+    
+    case MULTI_RISE :
+        // start local state timer and init any other one-shot actions
+        if (!isTimeoutRunning) {
+            pc().printf("\r\n\nstate: MULTI-RISE\r\n");
+            timer.reset(); // timer goes back to zero
+            timer.start(); // background timer starts running
+            isTimeoutRunning = true; 
+            
+            // what needs to be started?
+            bce().unpause();
+            batt().unpause();
+ 
+            //NEW: retrieve depth and pitch commands from config file struct
+            // concept is to load this each time the multi-dive restarts
+            stateMachine().getDiveSequence();
+            
+            //retrieve just pitch command from struct
+            float sequencePitchCommand = currentStateStruct.pitch;
+ 
+            // what are the commands? (send back to 0.5 feet, not surface) // 11/21/2017
+            depthLoop().setCommand(0.5);
+            pitchLoop().setCommand(-sequencePitchCommand);
+            pc().printf("MULTI-RISE: depth cmd: 0.0\r\n");
+            pc().printf("MULTI-RISE: pitch cmd: %3.1f\r\n",pitchLoop().getCommand());
+        }
+        // how exit?
+        if (timer > _timeout) {
+            pc().printf("MULTI-RISE: timed out\r\n");
+            _next_state = EMERGENCY_CLIMB;
+            timer.reset();
+            isTimeoutRunning = false;
+            
+            //reset multi-dive sequence to start
+            _state_counter = 0;
+        }
+        
+        //depth is less than 0.5 (zero is surface level)
+        else if (depthLoop().getPosition() < 0.5) {
+            pc().printf("MULTI-RISE: depth: %3.1f, cmd: %3.1f\r\n", depthLoop().getPosition(), depthLoop().getCommand());
+            
+            //going to next state            
+            isTimeoutRunning = false;
+            
+            //successful dive-rise sequence CONTINUES the multi-dive sequence
+            _state_counter++;
+            
+            //UPDATE THE SEQUENCE DATA HERE
+            stateMachine().getDiveSequence();
+            
+            //check if this is the end of the dive sequence
+            //CHECK BEFORE ANYTHING ELSE that you have reached the "exit" state (Float_Level)
+            if (currentStateStruct.state == FLOAT_LEVEL) {
+                _next_state = FLOAT_LEVEL;
+                return;
+            }
+            
+            else 
+                _next_state = MULTI_DIVE;
+            
+            //have to stop this with the _state_counter variable!
+        }
+        pc().printf("MULTI-RISE: bce pos: %3.1f mm, batt pos: %3.1f mm (depthLoop POS: %3.1f ft) [%0.1f sec]\r", bce().getPosition_mm(), batt().getPosition_mm(), depthLoop().getPosition(), timer.read());
+        // what is active?
+        bce().setPosition_mm(depthLoop().getOutput());  //constantly checking the Outer Loop output to move the motors
+        batt().setPosition_mm(pitchLoop().getOutput()); 
+        break; 
     
     default :
-        state = SIT_IDLE;
-    }
+        pc().printf("DEBUG: SIT_IDLE\n\r");
+        _next_state = SIT_IDLE;
+    } 
 }
 
 // output the keyboard menu for user's reference
 void StateMachine::showMenu() {
     pc().printf("\r\r\n\nKEYBOARD MENU:\r\r\n");
     pc().printf(" N to find neutral\r\n");
+    pc().printf(" M to initiate multi-dive cycle\r\n");
     pc().printf(" D to initiate dive cycle\r\n");
     pc().printf(" R to initiate rise\r\n");
     pc().printf(" L to float level\r\n");
@@ -276,9 +411,7 @@
     pc().printf("</> to change batt neutral position\r\n");
     pc().printf("Q/W to decrease/increase pitch setpoint: %3.1f\r\n",pitchCommand);
     pc().printf("A/S to decrease/increase depth setpoint: %3.1f\r\n",depthCommand);
-    //pc().printf("Q/W to decrease/increase pitch setpoint: %3.1f\r\n",pitchLoop().getCommand());
-    //pc().printf("A/S to decrease/increase depth setpoint: %3.1f\r\n",depthLoop().getCommand());
-    pc().printf("+/- to decrease/increase timeout: %d s\r\n",timeout);
+    pc().printf("+/- to decrease/increase timeout: %d s\r\n",_timeout);
     pc().printf(" 1 BCE PID sub-menu\r\n");
     pc().printf(" 2 BATT PID sub-menu\r\n");
     pc().printf(" 3 Depth PID sub-menu\r\n");
@@ -287,42 +420,195 @@
     pc().printf(" ? to reset mbed\r\n");
 }
 
+//Find Neutral sub finite state machine
+// Note: the sub-fsm only moves the pistons once at the start of each timer loop
+//  (timer completes, move piston, timer completes, move piston, etc)
+int StateMachine::findNeutralSubState(int input_substate) {  
+    float pitch_angle = pitchLoop().getPosition();    
+    float pitch_rate = pitchLoop().getVelocity();
+     
+    float abs_pitch_angle = fabs(pitch_angle);
+    float abs_pitch_rate = fabs(pitch_rate);
+    
+    //output substate is the input substate unless it is changed in the sub FSM
+    static int output_substate = NEUTRAL_SINKING;   //to start
+    //int output_substate = input_substate;    
+    
+    switch (input_substate) {
+        case NEUTRAL_SINKING:            
+            //start the 10 second timer
+            if (!isSubStateTimeoutRunning) { 
+                pc().printf(" input_substate: %d\n\r", input_substate);   //debug              
+                _neutral_sink_timer = timer.read()+ 10; //record the time when this block is first entered and add 10 seconds
+                pc().printf("\r\n\n(8) START NEUTRAL_SINKING (_neutral_sink_timer will go off at %0.1f sec)  %3.1f\r\n", _neutral_sink_timer,timer.read());
+                
+                // what is active?
+                //move piston at start of sequence (retract 10 mm)
+                pc().printf("Neutral Sinking: Retracting piston 10 mm\n\r");
+                previousPosition_mm -= 10;
+                bce().setPosition_mm(previousPosition_mm);  //no depth command
+                
+                isSubStateTimeoutRunning = true;    //disable this block after one iteration
+            }
+                        
+            //once the 10 second timer is complete move the piston with code above             
+            if (timer.read() >= _neutral_sink_timer) {
+                pc().printf("\r\n\n(8) NEUTRAL_SINKING TIMER COMPLETE! %3.1f\r\n", timer.read());
+                pc().printf("\r\n\n(8) (BATT POS: %0.1f) retract 10 mm (time: %3.1f)\r\n", previousPosition_mm, timer.read());
+                
+                //when you finish executing the motor controls, reset the neutral sinking timer in the next state
+                isSubStateTimeoutRunning = false;
+            }
+            
+            
+
+            //depth >= depth command, go to the next substate the next iteration
+            if (depthLoop().getPosition() > depthCommand) {
+                pc().printf("Next substate: NEUTRAL_SLOWLY_RISE\n\r");
+                output_substate = NEUTRAL_SLOWLY_RISE;
+                
+                //when you finish executing the motor controls, reset the neutral sinking timer in the next state
+                isSubStateTimeoutRunning = false;
+            }
+            
+            //before the sub-fsm runs again, make sure the piston is in a safe position
+            //NEW: need to double-check this behavior
+            //need to set a max travel position ? or just hardcode? or use bceFloatPosition?
+            if (bce().getPosition_mm() >= 320) {
+                output_substate = NEUTRAL_EXIT;     
+                //the "case" NEUTRAL_EXIT is used to tell the greater FSM that this sub-FSM has completed
+            }
+            break;
+            
+        case NEUTRAL_SLOWLY_RISE:
+            if (!isSubStateTimeoutRunning) {                
+                pc().printf("\r\n\n(9) START NEUTRAL_SLOWLY_RISE (will go off at %0.1f sec)\r\n", timer.read());
+                _neutral_sink_timer = timer.read()+ 5; //record the time when this block is first entered and add 10 seconds
+                
+                isSubStateTimeoutRunning = true;    //disable this block after one iteration
+                
+                // what is active?
+                //move piston at start of sequence (retract 10 mm)
+                pc().printf("Neutral Slowly Rise: Extending piston 1 mm\n\r");
+                previousPosition_mm += 1;
+                bce().setPosition_mm(previousPosition_mm);  //no depth command
+            }
+            
+            //once 5 second timer complete move the piston with code above  
+            if (timer.read() >= _neutral_sink_timer) {
+                pc().printf("\r\n\n(9)NEUTRAL_SLOWLY_RISE TIMER COMPLETE! (time: %3.1f)\r\n", timer.read());
+                pc().printf("\r\n\n(9) (BATT POS: %0.1f) extend 1 mm %3.1f\r\n", previousPosition_mm, timer.read());
+                
+                //when you finish executing the motor controls, reset the neutral sinking timer in the next state
+                isSubStateTimeoutRunning = false;
+            }
+            
+            //depth rate or sink rate < 0 ft/s, go to the next substate the next iteration
+            if (depthLoop().getVelocity() < 0) { //less than zero ft/s
+                pc().printf("\r\n\n(9) NEUTRAL_SLOWLY_RISE: Sink Rate below 0 ft/s [time: %0.1f]\r\n", timer.read());
+                
+                pc().printf("Next substate: NEUTRAL_CHECK_PITCH\n\r");
+                output_substate = NEUTRAL_CHECK_PITCH;
+            }
+            
+            //before the sub-fsm runs again, make sure the piston is in a safe position
+            //NEW: need to double-check this behavior
+            //need to set a max travel position ? or just hardcode? or use bceFloatPosition?
+            if (bce().getPosition_mm() >= 320) {
+                output_substate = NEUTRAL_EXIT;     
+                //the "case" NEUTRAL_EXIT is used to tell the greater FSM that this sub-FSM has completed
+            }
+            break;   
+                
+        case NEUTRAL_CHECK_PITCH:
+            //no timeout in this sub FSM state (find_neutral timer still running)        
+            
+            //what is active?
+            //the BCE piston is maintaining its position 
+            //the battery is maintaining the pitch at zero degrees
+            
+            //check sink rate < 0.5 ft/s, go to the next substate the next iteration (pitch velocity)
+            //check pitch angle is less than 1.0 degree
+            
+            pc().printf("(10) NEUTRAL_CHECK_PITCH (time: %3.1f) [%0.1f deg, %0.1f deg/s] \r", timer.read(), pitch_angle, pitch_rate); //debug  
+            output_substate = NEUTRAL_CHECK_PITCH;
+            
+            //benchtop tests confirm it needs to be around 2 degrees
+            if ((abs_pitch_rate < 0.5) && (abs_pitch_angle < 2.0)) { //less than zero ft/s and 1 degree
+                //SAVE POSITIONS
+                _neutral_buoyancy_bce_pos_mm = bce().getPosition_mm();
+                _neutral_buoyancy_batt_pos_mm = batt().getPosition_mm();
+                
+                //save to neutral.cfg
+                ConfigFileIO().saveNeutralPositions(_neutral_buoyancy_bce_pos_mm,_neutral_buoyancy_batt_pos_mm); //BCE, BATT
+                
+                pc().printf("Saving Positions: BCE: %0.1f mm, BATT: %0.1f\n\r",_neutral_buoyancy_bce_pos_mm,_neutral_buoyancy_batt_pos_mm);
+                pc().printf("Next substate: NEUTRAL_EXIT\n\r");
+                output_substate = NEUTRAL_EXIT;     //the "case" NEUTRAL_EXIT is used to tell the greater FSM that this sub-FSM has completed
+            }
+            break;
+  
+        default:
+            pc().printf("DEFAULT: check_pitch (state 10)\n\r"); //debug
+            output_substate = NEUTRAL_CHECK_PITCH;  //a default within the sub-state machine
+            break;    
+    }
+    return output_substate;
+}
+
 // keyboard currently handles a key at a time
 // returns -1 if not a state command
 // returns a positive number to command a new state
-int StateMachine::keyboard() {
+void StateMachine::keyboard() {
     char userInput;
     
     // check keyboard and make settings changes as requested
-    if (pc().readable()) {
+    // states can be changed only at the start of a sequence (when the system is in SIT_IDLE)
+    if (pc().readable() && (_state == SIT_IDLE)) {
         // get the key
-        userInput = pc().getc();
+        userInput = pc().getc();    
+        
+        isTimeoutRunning = false;   //keyboard resets timer each time it's used
+        
+        pc().printf("KEYBOARD isTimeoutRunning: %d\n\r", isTimeoutRunning);
+        
+        //_keyboard_state = SIT_IDLE; //new
 
         // check command against desired control buttons
         // change state
         if (userInput == 'D' or userInput == 'd') {
-            return DIVE;
+            _keyboard_state = DIVE;
         }
         else if (userInput == 'N' or userInput == 'n') {
-            return FIND_NEUTRAL;
+            _keyboard_state = FIND_NEUTRAL;
+        }
+        else if (userInput == 'M' or userInput == 'm') {
+            //currently does not run if there is no file.
+            
+            //need to add method to Sequence Controller that returns -1 
+            //   or some check that insures you cannot run the dive sequence without a file
+            
+            stateMachine().getDiveSequence();               //get first sequence on keyboard press
+            _keyboard_state = currentStateStruct.state;
+            
+            pc().printf("Starting Dive Sequence Controller! (state: %d)\n\r", _keyboard_state);  //neutral sequence and dive cycles
         }
         else if (userInput == 'R' or userInput == 'r') {
-            return RISE;
+            _keyboard_state = RISE;
         }
         else if (userInput == 'L' or userInput == 'l') {
-            return FLOAT_LEVEL;
+            _keyboard_state = FLOAT_LEVEL;
         }
         else if (userInput == 'B' or userInput == 'b') {
-            return FLOAT_BROADCAST;
+            _keyboard_state = FLOAT_BROADCAST;
         }
         else if (userInput == 'E' or userInput == 'e') {
-            return EMERGENCY_CLIMB;
+            _keyboard_state = EMERGENCY_CLIMB;
         }
         else if (userInput == 'H' or userInput == 'h') {
             pc().printf("running homing procedure\r\n");
             bce().unpause();  bce().homePiston();  bce().pause();
             batt().unpause(); batt().homePiston(); batt().pause();
-            return SIT_IDLE;
         }
         else if (userInput == 'T' or userInput == 't') {
             pc().printf("taring depth sensor\r\n");
@@ -377,12 +663,12 @@
             pc().printf(">>> new depth setpoint: %0.3f ft (rise)\r\n", depthLoop().getCommand());
         }
         else if (userInput == '-') {
-            timeout -= 10.0;               //decrement the timeout
-            pc().printf(">>> timeout decreased: %d\r\n", timeout);
+            _timeout -= 10.0;               //decrement the timeout
+            pc().printf(">>> timeout decreased: %d\r\n", _timeout);
         }
         else if (userInput == '=' or userInput == '+') {
-            timeout += 10.0;               //increment the timeout
-            pc().printf(">>> timeout increased: %d\r\n", timeout);
+            _timeout += 10.0;               //increment the timeout
+            pc().printf(">>> timeout increased: %d\r\n", _timeout);
         }
         
         // add keyboard commands to move the neutral zero offsets, both bce and batt
@@ -412,12 +698,26 @@
             pc().printf("pitchLoop().getCommand(): %3.1f\r\n",pitchLoop().getCommand());
         }
         else {
-            return (-1);
+            _keyboard_state = -1;
         }
+        
+        //when you read the keyboard successfully, change the state
+        _next_state = _keyboard_state;   //set state at the end of this function
     }
-    return (-1);
 }
 
+//11/19/2017
+//you want to modify the keyboard to return the values that will be used in the state machine
+//BUT the state machine does not call they keyboard
+//the keyboard runs independently and only allows input when the state is "idle"
+//therefore keyboard should run maybe 1/10th of a second when pc readable
+//and it will change a class variable that says what the current state is
+
+//make it void
+
+
+
+
 void StateMachine::keyboard_menu_BCE_PID_settings() {    
     char PID_key;
     float gain_step_size = 0.01;    // modify this to change gain step size
@@ -691,4 +991,48 @@
 
 float StateMachine::getPitchCommand() {
     return pitchCommand;
+}
+
+void StateMachine::setState(int input_state) {
+    //pc().printf("input_state: %d\n\r", input_state); //debug
+    //_state = input_state;     //changing wrong variable
+    _next_state = input_state;
+}
+
+int StateMachine::getState() {
+    return _state;  //return the current state of the system
+}
+
+void StateMachine::setTimeout(float input_timeout) {
+    _timeout = input_timeout;
+}
+
+void StateMachine::setDepthCommand(float input_depth_command) {
+    depthCommand = input_depth_command;
+}
+
+void StateMachine::setPitchCommand(float input_pitch_command) {
+    pitchCommand = input_pitch_command;
+}
+
+void StateMachine::setNeutralPositions(float batt_pos_mm, float bce_pos_mm) {
+    _neutral_buoyancy_batt_pos_mm = batt_pos_mm;
+    _neutral_buoyancy_bce_pos_mm = bce_pos_mm;
+    
+    pc().printf("Neutral Buoyancy Positions: batt: %0.1f, bce: %0.1f\n\r",_neutral_buoyancy_batt_pos_mm,_neutral_buoyancy_bce_pos_mm);
+}
+
+int StateMachine::timeoutRunning() {
+    return isTimeoutRunning;
+}
+
+//process one state at a time
+void StateMachine::getDiveSequence() {
+    //iterate through this sequence using the FSM
+    currentStateStruct.state = sequenceController().sequenceStructLoaded[_state_counter].state;
+    currentStateStruct.timeout = sequenceController().sequenceStructLoaded[_state_counter].timeout;
+    currentStateStruct.depth = sequenceController().sequenceStructLoaded[_state_counter].depth;
+    currentStateStruct.pitch = sequenceController().sequenceStructLoaded[_state_counter].pitch;
+    
+    _timeout = currentStateStruct.timeout;  //set timeout before exiting this function
 }
\ No newline at end of file