Selecting action based upon string comparison - how?

Hi there,

I'm working on a project for a colleague of mine, and it's going quite good, actually - BUT...

To make the project interact with an application in the PC I thought of creating some sort of commandset on the Arduino. It's also understanding my commands when I send then from the serial monitor, however there's one minor flaw...

In an attempt to make som kind of errorhandling I want it to reply something like if the recieved command doesn't exist in the commandset :

Unknown command : (inString)

My problem is that allthough it understands my existing commands it still replies "Unknown command" to the existing commands!?

  if (inString == "somestring")
    {
    // do this actions when this particular command is recieved
    }
    if (inString == "someotherstring")
    {
    // do this actions when this particular command is recieved
    }
    else if (inString != "")  // if inString doesn't exist in this scope of commands tell user we don't know it
    {
      Serial.print ("Unknown command: ");
      Serial.println (inString);
    }

What am I missing here?

  if (inString == "somestring")
    if (inString == "someotherstring")
    else if (inString != "")
    {
      Serial.print ("Unknown command: ");
      Serial.println (inString);
    }

Suppose inString IS somestring. The block of code is executed. Then, inString is compared to someotherstring. It isn't someotherstring, so, it's an unknown command.

You need else if for every block except the first one.

if(inString == "one")
else if(inString == "two")
else ifIinString == "three")
else
   Serial.print("Error!");

Another thing your are missing is the declaration of inString.

If you say:

char inString[100];

or

char *inString;

your code will not work because the address of inString is not equal to the address of a string constant.

Try using the String object:

String inString;

That way the '==' operation will know that you want to compare the VALUE of inString rather then the address.

The non-object forms of strings will work if you use a string comparison function:

char *inString;
if (strcmp(inString, "somestring") == 0)

The compare function takes two pointers and steps through them comparing each character. It returns 0 if all characters of the two strings match.

@PaulS : Of course! Thanks! I knew I had forgotten something...

@johnwasser : Heh... you had no chance to know that I'd already declared the inString on the global scope. :open_mouth:

But thanks again, guys. I'm still learning... :wink:

While you're at it...

Why not use chars instead of String?

It doesn't matter at all to Arduino if it is an understandable command as "LightON", or ">L1<".
This would, of course, make your code smaller in memory footprint and probably easier to program, especially if data transmission and not just commands is going to happen.

However, this tends to work well when you have an interface software running on your PC, otherwise you'll probably end up using a manual to issue commands to your own system.

I think I'll stick to strings for now. I work with instrumentation automation in my daytime job, so I think I can manage a few commands.
That's also why I prefer string-commands over char-commands, as it help me stick to something I know.
You know... :wink:

But I have one other question, though. :open_mouth:

I have a subroutine for getting sensor-readback (I have 8 identical sensors in the system) like this :

void readSensor(int Sensor)
{
  delay(1);
  if (debug == true)
  {
    Serial.print ("Reading sensor ");
    Serial.print (Sensor);
    Serial.println (": ");
  }
  if (mySensors[Sensor] == 1)
  {
    Wire.beginTransmission(address);   // Begin communication with TC74
    if (Wire.endTransmission () == 0)
    {
      Wire.send(0x00);                 // Ask TC74 for its temperature
      Wire.endTransmission();
      Wire.requestFrom(address, 1);    // Request 1 byte
      while(Wire.available() < 1);     // Wait for byte to arrive
      int data = Wire.receive();       // Get byte
      myReadings[Sensor] = data;       // Store the reading in an array
    }
    if (Wire.endTransmission () != 0)
    {
      if (debug == true)
      {
        Serial.print ("Sensor not responding!!");
        Serial.println ();
      }
      myReadings[Sensor] = -255;
    }
  }
  else
  {
    if (debug == true)
    {
      Serial.print ("Sensor not present. Skipping...");
      Serial.println ();
    }
    myReadings[Sensor] = -255;
    // just skip this one
  }
}

It's doing what it's supposed to do. In standalone-mode it just loops through all sensors and printing the readings to an LCD.
I just want to be able to pause the reading-loop and do some direct measurements by setting the program in manual-mode by sending it a specific command. I'm also able to do so, but not the way I want to.
Here's how I do it know :

void serial_listen()
{
    while (Serial.available()) 
  {
    delay(10);  //small delay to allow input buffer to fill
    char c = Serial.read();  //gets one byte from serial buffer
//    if (c == ',') {
//	break;
//    }  //breaks out of capture loop to print readstring
    readString += c;
  } //makes the string readString
  if (readString.length() >0) {
    // Serial.println(readString); //prints string to serial port out
    String inString = readString.toUpperCase();
    action(inString);
    readString=""; //clears variable for new input
  }
}

void action(String inString)
{
  if (inString == "MANUAL")
  {
    standAlone = false;
//    Serial.print ("Stand Alone mode: ");
//    Serial.println (standAlone, BIN);
    lcd.clear();
    lcd.cursorTo(2,8);
    lcd.print("REMOTE");
    lcd.cursorTo(3,7);
    lcd.print("OPERATION");
    Serial.println ("Going into manual mode. Please wait...");
    delay(2000);
    Serial.println ("Master!! Your wish is my command. I'm listening...?");
  }
  else if (inString == "STANDALONE")
  {
    if (standAlone != true)
    {
      standAlone = true;
      Serial.println ("I'm leaving you now. Bye!!");
      Serial.println ("Resuming scan. Please wait...");
      lcd.clear();
      lcd.cursorTo(2,4);
      lcd.print("Resuming scan.");
      lcd.cursorTo(3,5);
      lcd.print("Please wait...");
      delay(2000);
      init_disp();
    }
  }
  else if (inString == "GETSENSOR_0")
  {
    if (standAlone == false)
    {
      DEMUX(1);
      selectSensor(0);
      readSensor(0);
      DEMUX(0);
      Serial.println (myReadings[0]);
    }
    else Serial.println ("Must be en manual mode to execute commands!");
  }
  else if (inString == "GETSENSOR_1")
  {
    if (standAlone == false)
    {
      DEMUX(1);
      selectSensor(1);
      readSensor(1);
      DEMUX(0);
      Serial.println (myReadings[1]);
    }
    else Serial.println ("Must be en manual mode to execute commands!");
  }
  /* -------------- and so on for 8 sensors... ------------------- */
  else
  {
    if (standAlone == false)
    {
      Serial.print ("Unknown command: ");
      Serial.println (inString);
    }
    else Serial.println ("Must be en manual mode to execute commands!");
  }
}

I'd like to "talk" directly to the void readSensor from the terminal by just typing, say :

getsensor(1)

and let the program scan the incoming string and firstly recognize the command "getsensor" and secondly getting the integer from the string and passing it to readSensor(1), and thereby get the value for sensor 1.

I know I can do it in the environment I use at work by doing :

if (index(inString, "getsensor")) {
  int i = substr(inString,11);
  i = trim(1,1);
  readSensor(i);
}

or something like that. My apologies - it's getting late overhere, and I'm not thinking anymore...
I know I kan do substr and trim (not sure about the syntax yet, but I will be when I get there) but I'm not sure about the index part... ?

I know I kan do substr and trim (not sure about the syntax yet, but I will be when I get there) but I'm not sure about the index part... ?

Why not? String() - Arduino Reference

As a general recommendation, I would create at least a basic lookup table of commands. You can then iterate through this table for the 'specified' command, and use the index of the found command to perform the necessary actions, say in a switch statement.

Or alternatively, you could get a little more advanced, and utilize a table of function pointers to call the associated command function. The code is cleaner, more compact, and easier to add/remove/modify commands.

Also, if you plan on having a lot of commands, it would be best to either keep command names very short, or push your command table into progmem to save on the Arduino's very limited RAM. Below is some example code I put together a while back for a similar request (it's not uncommon at all for people to want to be able to send commands to the Arduino from their computer.

#define NUM_COMMANDS  3

//Declare a basic struct type that contains our command string and function pointer
struct funcLookup{
  char name[10];
  void (*function)(int);
};

//Declare our lookup table
funcLookup funcTable[NUM_COMMANDS];

void setup(){
  //Setup our function or command names
  //These do not have to match the source function names at all
  strcpy(funcTable[0].name, "func1");
  strcpy(funcTable[1].name, "func2");
  strcpy(funcTable[2].name, "func3");
  
  //Setup our function pointers
  funcTable[0].function = &func1;
  funcTable[1].function = &func2;
  funcTable[2].function = &func3;
  
  Serial.begin(115200);
  
  //example calling function based on text input using lookup table
  char funcName[] = "func2";
  callFunc(funcName, 10);
  
  //Iterate through our function table
  for(int i = 0; i < NUM_COMMANDS; i++)
  {
    funcTable[i].function(i);
  }
  
}

void loop(){
  
  
}


//This function walks through our lookup table for the passed in command
//Then calls it's associated function
void callFunc(char* funcName, int val){
  for(int i = 0; i < NUM_COMMANDS; i++){
    if(strcmp(funcName, funcTable[i].name) == 0){
      funcTable[i].function(val);
      return;
    }
  }
  
  //If we reach here, the command did not exist
  //Can report unknown command if wanted
}

void func1(int val){
  Serial.println("Function 1");
  Serial.println(val);
}

void func2(int val){
  Serial.println("Function 2");
  Serial.println(val);
}

void func3(int val){
  Serial.println("Function 3");
  Serial.println(val);
}

Very interesting, jraskell. Very interesting, indeed! I'll look into that some other day when I get to digg into the code again. My wife's home tonight - if you know what I mean.... :wink:

Not sure about all that stuff in "setup" - why not let the compiler do the legwork?

//Declare our lookup table
funcLookup funcTable[NUM_COMMANDS] = {
 {"func1", func1},
 {"func2", func2},
 {"func3", func3} 
};

Honestly, I'm not sure about all that setup stuff myself. It may have been related to the original 'related' request when I first wrote it. At the time, I'd just saved it away on the off chance a post like this would pop up.

There's absolutely no reason for all those strcpy()s though. I can only guess that the previous request was perhaps getting that list of commands from some other source, thus unavailable at compile time, but then the functions for it wouldn't be available either. I've modified my saved example accordingly, though.