Saturday, August 16, 2014

Stories Modular Code and Unit Testing

Stories are a way to define the functionality of software from the perspective of the user of the software. Why use a story instead of a function statement or a requirement? The simple answer is that a story is interesting and starts a conversation. Do you go to a movie at the movie theater to listen to a set of functions or requirements? If you did, people would fall asleep or start to play with their phones after a couple of minutes. No, people go to a movie because the story draws them in and involves them. So what is a story when it relates to software? A great description of a story can be found at Mountain Goat Software. A story takes the form of:

As a <user role/name> I want <goal of function> so that <reason why>.

Stories are a great way to discuss what the software should do. Details or specifics that result from this discussion are captured in the form of short statements called requirements. Requirements are what gets tested.

Modular code is all about taking a big problem, breaking it apart into smaller pieces, and solving the small problems one at a time. Software modules can then be quickly assembled to solve bigger problems. Think of a software module like a tool in your tool box. Is your command to aggressive? Try adding a limiter or a rate limiter. Is your signal noisy? Try adding a lowpass filter. Good software modules can also easily evolve or specialize over time without impacting the software in a negative way. To do this, software modules have 3 very important parts, the function or goal, the interface, and the strategy. Each of these parts needs to exist for the module to be useful as a building block over time. For example, I can take put the software in one module or file that implements one story. Robot C provides one tool to help with modular code, the include statement.

Include will insert any file into my Robot C software.

Unit testing tests the story or function a module of code implements.

Put it all together - Why the introduction to stories, modular code, and unit testing? Because these 3 elements are what makes up the contents of one modular file. Lets walk through the example of a function that I used in the last blog post called "riseedge.c" as shown below:
// Story: As a software designer I want to find the rising edge of a signal so that I can trigger an event
// Usage: Make sure globalBoolEqF is initialized equal to false
//   bool globalBoolEqF = false; //global
//   outBool = RiseEdge( inBool, globalBoolEqF );
bool RiseEdge(bool in, bool &z1)
{
  bool out;

  out=false;
  if ((in==true) && (z1==false)) out=true;
  z1=in;

  return(out);
}

#define TEST
#ifdef TEST //-------------------------------------------------
// Add this code to a while loop in main
// Unit Test
// [x] Rise Edge of input results in a true output for 1 frame
// [x] Fall Edge of input results in no change to the output
// [x] True input for more than one frame results in a false output
// [x] False input results in a false output

task main(){
 char str[20];
 
  int iFrameCnt=0;
  bool z=false;
  bool out,in=false;
  short DebugFile;
  //TFileHandle DebugFile;
  TFileIOResult isOk;
  int nFileSize=4000;
  OpenWrite(DebugFile, isOk, "debug.txt", nFileSize);
  while(iFrameCnt<10){

    if (iFrameCnt<2) in=false;
    else if (iFrameCnt<4) in=true;
    else if (iFrameCnt<6) in=false;
    else if (iFrameCnt<8) in=true;

    out=RiseEdge(in, z);
  
    if (iFrameCnt==0){
      sprintf(str,"Frame,In,Out\n");
      WriteString(DebugFile, isOk, str);   
      writeDebugStream(str);
    }
    
    sprintf(str, "%2d,%2d,%2d\n", iFrameCnt, in, out);
    WriteString(DebugFile, isOk, str);
    writeDebugStream(str);
 
    wait1Msec(50);
    iFrameCnt++;
  }
  Close(DebugFile,isOk);
}
#endif
The first four lines of the file contain the story and the usage of the software module as shown below:
// Story: As a software designer I want to find the rising edge of a signal so that I can trigger an event
// Usage: Make sure globalBoolEqF is initialized equal to false
//   bool globalBoolEqF = false; //global
//   outBool = RiseEdge( inBool, globalBoolEqF );
The story quickly describes the purpose of the function. The usage helps a person understand how to add this module to their software. With the usage, it is important to describe the details or requirements of the interface. In this case, the interface is a boolean and it must be defined as a global. If the interface contained an integer, it is important to understand the expected range of the interface (i.e. -179 to 180 degrees, positive turns the robot to the left) the units of the interface, and the context. I often find reusable components defined without the the interface details. I end up running experiments to figure out the range of values, the units, and the context. This is part of software programming that is not that much fun. The function called RiseEdge follows the story and usage. This is followed by the software needed for unit testing. The software #ifdef begins the test. If "#define TEST" is commented out, the module can be inserted into your code. If it is not commented out, as shown below, the software can be loaded directly onto the NXT for unit testing. For unit testing use:
#define TEST
#ifdef TEST
To turn off the unit testing and use the file as an insert, use:
//#define TEST
#ifdef TEST
The unit test code begins with the requirements for the use case as shown below:
// Add this code to a while loop in main
// Unit Test
// [x] Rise Edge of input results in a true output for 1 frame
// [x] Fall Edge of input results in no change to the output
// [x] True input for more than one frame results in a false output
// [x] False input results in a false output
The requirements use a [x] to indicate that the test has been run and passes this requirement test. I start with a [] to indicate that the test has not passed yet. This pass/fail marking gives confidence that the function has been tested and should work as advertised. The main portion of the unit test is shown below:
    if (iFrameCnt<2) in=false;
    else if (iFrameCnt<4) in=true;
    else if (iFrameCnt<6) in=false;
    else if (iFrameCnt<8) in=true;
This software toggles the input true and false for 2 frames. This is enough to check the requirements for the test. The results are output to both the debug stream and to a local file on the NXT. If I want a time-history plot of the test output I open the Robot C program. I use the main menu "Robot->NXT Brick->File Management Utility" to copy the "Debug.txt" file to my PC. I can then use MS Excel to plot the results.

State Machines

State Machines

A state machine is a design mechanism that places the software system into one (or more) states. Triggers are used to move from one state to another state.

What can we use this concept for in Robotics? We can use a state machine any time we want to perform a sequential set of steps or any time we want to perform a set of steps driven by an event or behavior. For example, a sequential set of steps may be following a path. An event may be the robot reaching a wall or seeing a line on the floor.

State machines are often drawn as circles with lines joining the circles together. This is called the software design. Design is very useful for understanding the big picture of how the software works. Complex software statements are simplified down into pseudo code (English statements).The lines are events that move the state machine from one state to the next. In a statemachine, the behaviors of interest can be run whenever the state is active, or on transition from one state to the next. Here is an example of a very simple state machine that is used to turn a light on and off:



The state machine begins in the LIGHT_OFF state. (See the black dot with the arrow pointing into the LIGHT_OFF state). In this state, the word "Off is displayed on the NXT screen. When button 1 (the x button) is pressed on the joystick and a rising edge is detected (the symbol before joy1Btn), the state machine transitions into LIGHT_ON.

In the LIGHT_ON state, the word "ON" is displayed on the NXT screen. When button 1 (the x button) is pressed on the joystick and a rising edge is detected (the symbol before joy1Btn), the state machine transitions into LIGHT_OFF.

Why use a rising edge detector instead of looking from a true value on joy1Btn(1)? If a TRUE value from joy1Btn(1) caused the transition, the state machine would jump back and forth between LIGHT_ON and LIGHT_OFF until the joystick button is de-selected. Try this out in the code to see what it does. This is called a race condition. The race condition can be prevented in a number of ways. For example, I could have used a different button to transition back.

Now that the design is complete, it is time to write software. The statemachine needs to be called continually. To do this, the periodic task program, described in a previous blog post, or a while(true) is used. See the following software:

//
// As a home owner, I want to turn a light on and off with my joystick so I can see where I am going when it is dark.
//
#include "JoystickDriver.c"
#include "i_riseEdge.c"

bool btn_z1=false;   // Used with FallEdge

// My states
#define LIGHT_ON 1
#define LIGHT_OFF 2
int sm_light = LIGHT_OFF; //The default state

task main(){
  while(true){

    getJoystickSettings(joystick);     // update buttons and joysticks
    eraseDisplay();

//-------------------------- Start of the State Machine ----------------//
    switch (sm_light) {
    case LIGHT_OFF:                     //State
      // Do this when the light is off
      nxtDisplayString(2, "Light Off");
      if (RiseEdge(joy1Btn(1),btn_z1)){ //Transition
        // Do this on a transition
   sm_light=LIGHT_ON;
      }
      break;
    case LIGHT_ON:                      //State
      nxtDisplayString(2, "Light On");
      if (RiseEdge(joy1Btn(1),btn_z1)){ //Transition
        // Do this on a transition
        sm_light=LIGHT_OFF;
      }
      break;
    }
// ------------------------- End of the State Machine ------------------//
    wait1Msec(50);
  }// While Loop
}// Foreground Task

The states are defined using a "#define STATE_NAME 1" statement. If you want an additional state, just add a new #define statement. Make sure each # define has a unique integer number assigned to it. The current active state is defined as an integer in the line "int sm_light = LIGHT_OFF;"  This line says the "LIGHT_OFF" state is the initial state the machine "sm_light" will enter when called.

The work of the state machine is done with a switch/case structure. Each case is a state for the state machine. Transitions are implemented with an if (){ sm_light=LIGHT_OFF} statement. Any code placed within the case statement will execute every iteration. This is where I placed the nxtDisplayString statement.

The function called RiseEdge looks for a rising edge of a boolean signal. RiseEdge returns a true value only when the input signal is true and the past frame signal is false. Note: the & in front of z1 in the function argument passes the address of z1 into the function call. This allows z1 to act as both an input and an output from the function call. See the following code:

// Story: As a software designer I want to find the rising edge of a signal so that I can trigger an event
// Usage: Make sure globalBoolEqF is initialized equal to false
//   bool globalBoolEqF = false; //global
//   outBool = RiseEdge( inBool, globalBoolEqF );
bool RiseEdge(bool in, bool &z1)
{
  bool out;

  out=false;
  if ((in==true) && (z1==false)) out=true;
  z1=in;

  return(out);
}

This describes the basics behind creating a state machine design and implementing it in software. Here is an example of a state machine that drives a robot in on a path in the shape of a square box 5 inches in size:



Try to develop a state machine that does this. Note: the /num=0 is software that executes on a transition. This means the "num=0" is done when the variable is defined and the /num++ incriments this variable on the transition from Drive_Straight to Turn_Right. The transition "5 inches" and "90 degrees" can be done using the distance of the encoders. Let me know how you do.