Stumped at string comparission fail

I am kind of stumped as to why the code fails when testing for the strings for temperature.

setTempP1 to setTempp4 seems to all trigger the setTemp test instead of looking for the whole string. I tried changing to just a straight string compare of if datrq = "setTemp" but it still fails. If i comment out the test for setTemp all the others work as expected.

I am i missing something about string comparisions on the arduino mega 2560 the code below is the function i am working on. Everything works perfectly until i get to the setTempPx sec

I have attached the code to file as it is too long to post

function.txt (65 KB)

I don't look at your code but the proper way to compare cstrings is using: strcmp() (or strncmp() for safety)

Use of Strings on Arduino is a bad idea. They cause memory problems and program crashes.

Hi,

Please read the first post in any forum entitled how to use this forum.
http://forum.arduino.cc/index.php/topic,148850.0.html then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.

Thanks.. Tom.. :slight_smile:

TomGeorge:
Hi,

Please read the first post in any forum entitled how to use this forum.
http://forum.arduino.cc/index.php/topic,148850.0.html then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.

Thanks.. Tom.. :slight_smile:

64 kiloByte code; that will not work unless they have removed the limit :wink:

This is a classic example of why you should not use the String class.

I'm glad you asked for help, because there are several fairly simple techniques you can use to completely eliminate String usage. You should use char arrays instead (aka C strings). Your sketch will be smaller, faster and more reliable.

1) The first thing is to just use the packetBuffer for all the parsing. There's no reason to copy it to other buffers (or a String). However, to make this char array into a C string, you must NUL-terminate it. That means you need to put a zero byte at the end. All the C string manipulation functions watch for that last zero byte. It tells them when they have reached the last character in the array. Simply declare the packet to be one byte larger, and set the last byte to a NUL character after you read it:

char packetBuffer[ UDP_TX_PACKET_MAX_SIZE+1 ]; // leave room for 0 byte (NUL terminator for C string)

void udpEvent (){

  packetSize = Udp.parsePacket(); //Read the packetSize

  if (packetSize>0){ //Check to see if a request is present

    Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); //Reading the data request on the Udp
    packetBuffer[packetSize] = '\0'; // NUL-terminate the packet

    Serial.print( F("Received packet! '") );
    Serial.print(packetBuffer);
    Serial.println( '\'' );

You now have a C string (the packetBuffer) instead of a String object (copied from the packetBuffer).

2) Watching for specific commands is one of the main functions of udpEvent. You can use the strstr function to compare two C strings:

 if (strstr( packetBuffer, "Relay1 off" ) != NULL) {
    // matched!

You do this for many different strings, so let's make a function that makes it easier to read:

bool isCommand( const char *command )
{
  return strstr( packetBuffer, command ) != NULL;
}

Now all your comparisons will read very nicely:

    if ( isCommand("Relay1 on") && (Relay1_isAuto == 0)) {
      Relay1_State = 1;
      EEPROM.write(6,1);
      turnRelay(1, 1);
      insert_sql (Relays, 2);
    }
    else if ( isCommand("Relay1 off") && (Relay1_isAuto == 0)) {
      Relay1_State = 0;
      EEPROM.write(6,0);
      turnRelay(1, 0);
      insert_sql (Relays, 2);
    }

    //    same for relays 2..8

    else if ( isCommand("Relay1 isAuto 1") ) {
      Relay1_isAuto = 1;
      EEPROM.write(22,1);
      insert_sql (Relays, 2);
    }
    else if ( isCommand("Relay1 isAuto 0") ) {
      Relay1_isAuto = 0;
      EEPROM.write(22,0);
      insert_sql (Relays, 2);
    }

    // same for relays 2..8

    else if ( isCommand("restoredefaults") ) {
      Serial.println("RestoreDefaults");
      RestoreDefaults();
    }

... and if none of these comparisons match, you can print an error message:

    } else {
      Serial.println( F("Invalid command") );
    }

  }

  memset(packetBuffer, 0, sizeof(packetBuffer) );

} // udpEvent

3) udpEvent also parses a series of values from some commands. For example, the setdate command has 6 numbers for the various date/time fields. You can use the strtok function to step through the comma-separated fields:

      const size_t MAX_FIELDS = 7;
      int          field[ MAX_FIELDS ]; // parsed ints

      char *token = strtok( packetBuffer, ", " ); // skip past command
      for (int i = 0; i < MAX_FIELDS; i++) {
        if (token) {
          field[i] = atoi( token );
          token    = strtok( NULL, "," ); // step to the next comma separator
        } else {
          field[i] = 0;
        }
      }

You pass the string to parse in the first call to strtok, along with the field delimiters, and it returns the first field. In your case, the first "field" is actually the command, like "setdate".

Next, the atoi function tries to parse an integer from that field. Iit will return a 0 for "setdate", but the next fields should give non-zero values for the various date & time fields. Those are stored in the elements of the integer array, [nobbc]field[i][/nobbc].

Notice that the second call to strtok is passed a NULL for the first argument. That tells it to continue stepping through the previous string (packetBuffer). When there are no more fields, strtok returns NULL. The for loop will simply set that field to 0 when token is NULL.

When the for loop exits, all the field values have been set.

There are several commands that take these arguments, so let's make a common routine that can be called for each of these commands:

void parseFields( int field[], const size_t MAX_FIELDS )
{
  char *token = strtok( packetBuffer, ", " ); // skip past command
  for (int i = 0; i < MAX_FIELDS; i++) {
    if (token) {
      field[i] = atoi( token );
      token    = strtok( NULL, "," ); // step to the next comma separator
    } else {
      field[i] = 0;
    }
  }
}

Processing the "setdate" command looks like this:

    else if ( isCommand("setdate")) {
      Serial.println("setdate");

      const size_t MAX_FIELDS = 7;
      int          field[ MAX_FIELDS ]; // parsed ints

      parseFields( field, MAX_FIELDS );

You can use those array elements to set global variables:

      int parsed_month  = field[1];
      int parsed_day    = field[2];
      int parsed_year   = field[3];
      int parsed_hour   = field[4];
      int parsed_minute = field[5];
      int parsed_second = field[6];

      EEPROM.write(0,parsed_hour);
      EEPROM.write(1,parsed_minute);
      EEPROM.write(2,parsed_second);
      EEPROM.write(3,parsed_day);
      EEPROM.write(4,parsed_month);
      EEPROM.write(5,parsed_year);

      RTC.adjust(DateTime(parsed_year, parsed_month, parsed_day, parsed_hour, parsed_minute, parsed_second));

4) For testing, it is much easier to enter commands in the Serial Monitor window. We can add some code that simulates all the external code, like the Ethernet Udp class, RTC class, insert_sql routine, etc. Here is what I used for testing:

class FakeUdp
{
public:
  size_t parsePacket() { return packetSize; }
  void   read( char *, size_t ) {};
};
FakeUdp Udp;
  
class DateTime
{
public:
  DateTime(int,int,int,int,int,int) {}
};

class FakeRTC
{
public:
  void adjust( const DateTime & ) {}
};
FakeRTC RTC;

class FakeEEPROM
{
public:
  void write( int address, int value )
  {
    Serial.print( F("EEPROM[") );
    Serial.print( address );
    Serial.print( F("] = ") );
    Serial.println( value );
  }
};
FakeEEPROM EEPROM;

void RestoreDefaults() {}

void turnRelay( int r, int state )
{
  Serial.print(F("turnRelay "));
  Serial.print(r);
  Serial.print( ' ' );
  if (state == 0)
    Serial.println(F("off"));
  else
    Serial.println(F("on"));
}

enum unknownVariable { Relays };
void insert_sql( int, int ) {}

int Relay1_State, Relay1_isAuto;
int Light_ON_hour, Light_ON_min, Light_OFF_hour, Light_OFF_min;
int NextRunLOG, NextRunLCD, NextRunTX;

I have attached the complete test program that should allow you to use the Serial Monitor window for command input and debug output. Notice that EEPROM writes don't actually go into the EEPROM; they are displayed instead. For example, entering "setdate 6,2,18,11,35,23" in the Serial Monitor window (press Send) causes this debug output:

Received packet! 'setdate 6,2,18,11,35,23'
setdate
EEPROM[0] = 11
EEPROM[1] = 35
EEPROM[2] = 23
EEPROM[3] = 2
EEPROM[4] = 6
EEPROM[5] = 18

You should be able to test all the commands very quickly. When they all work, merge the helper routines (isCommand and parseFields) and udpEvent back into your real sketch.

If there's some portion that you're not sure about how to convert, please feel free to ask for ideas. The C string library is very extensive, and it can be overwhelming to first-time users.

Cheers,
/dev


P.S. Relays 1-8 really should be implemented as an array, but you should start with these changes first.

ve3sjk.ino (5.46 KB)

Thanks you for the code and explanation I have been trying to remove all the string usage from my sketch as i go since it was eating up all the ram at run time as i added more code. I have been struggling with using c strings instead of the string class where possible. I would have posted the code in blocks as should be done on the forum but as you stated the function i was working on was over 9000 lines and the full sketch is upto 41 percent of program mem and 69 percent of dynamic.

I can follow your suggestion fairly well and will work your suggestion into my sketch. the system originally used a serial link to communicate with a python script on a server which then stored data to an sql database, i am converting my code to send data directly to a remote sql database to store readings instead of having the python script act as middle man while it also logs the data to sd card for backup. As you can imagine i promptly ran out of ram when i tried creating sql queries inside an arduino i did figure out how to store them all in progmem and read them back when needed. You suggestion are going to clean up the ram usage a lot more thank you.

I have one question. In the example using the setdate command the parsed out values are integers and its clear how you get them in the example.

Most of the other commands the parsed values are floats I assume they would need another way of parsing.

Thanks again i will drop a note here to say how much ram your suggestions saved me as soon as i make the changes. Any you are correct the relays should be using an array. I have converted that section in hardware from using 8 outputs to using a shift register to run a relay board. I will have to rewrite the relay section once i get the command section up and running.

There is atof (ascii to float).

Like sterretje said, atof is the way to convert a C string to a float. You were using that after several String operations to get a C string. :smiley:

If your arguments can be of different types, then you might as well pull atoi out of parseFields. Just make it find the char *fields:

void parseFields( char *field[], const size_t MAX_FIELDS )
{
  field[0] = strtok( packetBuffer, ", " ); // skip past command

  for (int i = 1; i < MAX_FIELDS; i++) {
    field[i] = strtok( NULL, "," ); // step to the next comma separator
  }
}

Then the caller will convert each field, according to the type it is expecting. Either ints:

   else if ( isCommand("setlightschedule") ) {
      //        Serial.println("setlightschedule");

      const size_t  MAX_FIELDS = 5;
      char         *field[ MAX_FIELDS ];

      parseFields( field, MAX_FIELDS );

      Light_ON_hour  = atoi( field[1] );
      Light_ON_min   = atoi( field[2] );
      Light_OFF_hour = atoi( field[3] );
      Light_OFF_min  = atoi( field[4] );

      EEPROM.write(93, Light_ON_hour);
      EEPROM.write(94, Light_OFF_hour);
      EEPROM.write(95, Light_ON_min);
      EEPROM.write(96, Light_OFF_min);

    }

... or floats:

   else if ( isCommand("setTemp") ) {
      Serial.println("setTemp :");

      const size_t  MAX_FIELDS = 7;
      char         *field[ MAX_FIELDS ];

      parseFields( field, MAX_FIELDS );

      float parsed_TempValue_Low  = atof( field[1] );
      float parsed_TempValue_High = atof( field[2] );
      float parsed_Heater_ON      = atof( field[3] );
      float parsed_Heater_OFF     = atof( field[4] );
      float parsed_AC_ON          = atof( field[5] );
      float parsed_AC_OFF         = atof( field[6] );

      TempValue_Low  = parsed_TempValue_Low;
      TempValue_High = parsed_TempValue_High;
      Heater_ON      = parsed_Heater_ON;
      Heater_OFF     = parsed_Heater_OFF;
      AC_ON          = parsed_AC_ON;
      AC_OFF         = parsed_AC_OFF;

      eepromWriteFloat(129, parsed_TempValue_Low);
      eepromWriteFloat(133, parsed_TempValue_High);  
      eepromWriteFloat(137, parsed_Heater_ON);
      eepromWriteFloat(141, parsed_Heater_OFF);  
      eepromWriteFloat(145, parsed_AC_ON);
      eepromWriteFloat(149, parsed_AC_OFF);
      insert_sql (Temp, 14);

... or a mix. Complete testing sketch attached.

ve3sjk v2.0.ino (6.71 KB)

I did find the atof command and changed the function by adding a new parameter for switching between atoi and atof when i call it. 0 for int and 1 for float. Seems to be working great with the suggestions here. I did have to change the name of one variable const size_t MAX_FIELDS as for some reason it conflicted with a variable with the same name in the SQL connector library.

I have changed most of the RX command function to work using the examples here but i still have some issues with my logic.

So the way its supposed to work is a command is received from the udpEvent1 function which parses the command and stores the values in eprom.

udpEvent1 then passes a command to the insert_sql function this function gets the name of the string in progmem and an index value.

insert_sql creates the query string by calling progmen_sql by passing the progmem strings and the index for the switch case so that it can create whole string required for the sql query.

Almost all the stuff is working as i would expect i can receive all the commands and they all seem to parse ok in the udpEvent1 funtion.

The issue starts with setTDS1 and setTDS2.

What happens is the commands are received good but for some reason the sql query seems to keep sending the output to the sql database for the setT command.

So instead of getting

UPDATE yieldbuddy_0.TDS2 SET

I keep getting

UPDATE yieldbuddy_0.Temp SET

Which is wrong and i cannot find the errors causing this.

I have attached a text file with all the relevant functions as the code is very long.

codeList.txt (56 KB)

I have attached a text file with all the relevant functions as the code is very long.

No, you haven't. Clearly, isCommand() is returning true, for setT, when passed setTDS, which is wrong, but you couldn't be bothered to include the isCommand() function, so we could tell why that was happening.

Is there some part of "POST ALL OF YOUR CODE" that you can't understand or grasp the reasoning behind?

PaulS:
No, you haven't. Clearly, isCommand() is returning true, for setT, when passed setTDS, which is wrong, but you couldn't be bothered to include the isCommand() function, so we could tell why that was happening.

Is there some part of "POST ALL OF YOUR CODE" that you can't understand or grasp the reasoning behind?

Yes i did forget the isCommand function here it is below.

bool isCommand( const char *command )
{
  return strstr( packetBuffer, command ) != NULL;
}

All the test pass perfectly via the udpevent1 function since i have the function printing the recieved command to serial port before it does anything with the command. The issue is in the functions that are getting the data stored in progmem.

Smoker_Controller_BASE.zip (42.2 KB)

Read the documentation on strstr(). It is NOT the same as strcmp().

As i had already stated the isCommand function is fully working. the issue is that the function retrieving the commands from program memory is NOT working. I get the proper command from isCommand. I get the wrong progmem string from the other functions.

The issue seems to be that progmem_sql gets the right variables on the call but pulls the wrong string out of progmem.

I cannot post the code for progmem_sql its over 9000 lines please see other attachment for full code previously posted.

isCommand works perfectly is is not the issue i am having.

The issue seems to be that progmem_sql gets the right variables on the call but pulls the wrong string out of progmem.

Post the entirety of an example that demonstrates the problem, as instructed in the "How to use this forum" post.

It is likely that while paring down your monstrosity, you will discover the error.

As i had already stated the isCommand function is fully working.

That is a load of horseshit.

Your original complaint was:

setTempP1 to setTempp4 seems to all trigger the setTemp test instead of looking for the whole string.

You pass the string "setTempP1" to isCommand(), and it tells you that "setTempP1" CONTAINS (NOT IS) "setTemp".

If you were to do what I said, and use strcmp() instead of strstr(), your problem would be solved.

I changed the isCommand function as follows

boolean isCommand( const char *command )
{

//  return strstr( packetBuffer, command ) != NULL;
  
    if (strcmp(packetBuffer,command) == 0){
    return 1;
    }
    else{
    return 0;
   }
  
}

I get the same behavior in my if else statements for the command check.

    else if ( isCommand("setT")  ) {

      const size_t MAX_FIELDS1 = 7;
      float field[ MAX_FIELDS1 ]; 

      parseFieldsF( field, MAX_FIELDS1 );

      TempValue_Low = field[1];
      TempValue_High = field[2];
      Heater_ON = field[3];
      Heater_OFF = field[4];
      AC_ON = field[5];
      AC_OFF = field[6];

      eepromWriteFloat(129, TempValue_Low);
      eepromWriteFloat(133, TempValue_High);  
      eepromWriteFloat(137, Heater_ON);
      eepromWriteFloat(141, Heater_OFF);  
      eepromWriteFloat(145, AC_ON);
      eepromWriteFloat(149, AC_OFF);
      Serial.println ("This is setT test");
      insert_sql (Temp, 14);  

    }
    else if ( isCommand("setTDS1")) {
      const size_t MAX_FIELDS1 = 6;
      float field[ MAX_FIELDS1 ]; 
      int mytest;
      
      parseFieldsG( field, mytest, MAX_FIELDS1 );

      TDS1Value_Low = field[1];
      TDS1Value_High = field[2];
      NutePump1_ON = field[3];
      NutePump1_OFF = field[4];
           
//      parseFields( field, MAX_FIELDS1 );
      
      if (mytest == 1) {
        MixPump1_Enabled = true;
        EEPROM.write(193, 1);
      } 
      else if(mytest == 0) {
        MixPump1_Enabled = false;
        EEPROM.write(193, 0);            
      }

      eepromWriteFloat(177, TDS1Value_Low);
      eepromWriteFloat(181, TDS1Value_High);  
      eepromWriteFloat(185, NutePump1_ON);
      eepromWriteFloat(189, NutePump1_OFF);
      insert_sql (TDS1, 8);  

    }

If i send the command setT i get the proper sql statement send to the sever

If i send any command that has setT in it like the command setTDS1, setTDS2, setTempP1 etc i get the output for the setT command instead.

I suppose i could change the commands so that they are all unique to make it work.

Is this the normal behavior for strcmp.

Should it be able to tell the difference between setT and setTDS1

What happens if you use strncmp?

Edit: And try changing

else if ( isCommand("setT")  ) {

      const size_t MAX_FIELDS1 = 7;
      float field[ MAX_FIELDS1 ]; 

      parseFieldsF( field, MAX_FIELDS1 );

      TempValue_Low = field[1];
      TempValue_High = field[2];
      Heater_ON = field[3];
      Heater_OFF = field[4];
      AC_ON = field[5];
      AC_OFF = field[6];

      eepromWriteFloat(129, TempValue_Low);
      eepromWriteFloat(133, TempValue_High);  
      eepromWriteFloat(137, Heater_ON);
      eepromWriteFloat(141, Heater_OFF);  
      eepromWriteFloat(145, AC_ON);
      eepromWriteFloat(149, AC_OFF);
      Serial.println ("This is setT test");
      insert_sql (Temp, 14);  

    }

to

else if ( isCommand("setT")  ) {
      const size_t MAX_FIELDS1 = 6;
      float field[ MAX_FIELDS1 ]; 
      int mytest;
      
      parseFieldsG( field, mytest, MAX_FIELDS1 );

      TDS1Value_Low = field[1];
      TDS1Value_High = field[2];
      NutePump1_ON = field[3];
      NutePump1_OFF = field[4];
           
//      parseFields( field, MAX_FIELDS1 );
      
      if (mytest == 1) {
        MixPump1_Enabled = true;
        EEPROM.write(193, 1);
      } 
      else if(mytest == 0) {
        MixPump1_Enabled = false;
        EEPROM.write(193, 0);            
      }

      eepromWriteFloat(177, TDS1Value_Low);
      eepromWriteFloat(181, TDS1Value_High);  
      eepromWriteFloat(185, NutePump1_ON);
      eepromWriteFloat(189, NutePump1_OFF);
      insert_sql (TDS1, 8);  

    }

Basically, try avoiding any calls to insert_sql with (Temp, 14) as the argument.
Does that still print the wrong command? If so, then there is a problem with retrieving the strings from progmem.

Edit 2:
You can also reorder your if statements so that setTemp is checked after setTDS1, etc..

Should it be able to tell the difference between setT and setTDS1

Yes, We STILL need some PROOF that you are passing what you think you are to isCommand() and some PROOF that it is not working properly. That is NOT at all difficult to provide. Stop wasting our time with snippets and unfounded allegations.