Pseudo-serial passthrough(?) OR controlling a serial device over the net

I am a postdoc at the EPA working in stormwater / wastewater research. I want to control an ISCO 6712 from my cell phone or computer, telling it to take a sample or getting information on which bottle the distributor arm is positioned over, etc. the device communicates over serial. It is designed to work with the old dec(?) terminals.

I want the spark core to be able to take samples by itself in case it goes offline as well. That makes it more complicate than a serial passthrough. It needs to read and parse serial and then send the data to spark cloud in a way where I can read and write variables that get sent back to the core and then converted into serial for the isco. I’ve managed to get this working with an arduino mega but it’s not as good as having this on the net. Wireless control of the sampler is where it’s at.

Any thoughts? Tips? Things to watch for?

1 Like

TIP: It’s totally possible with the :spark: core :smile:

Sounds cool! Totally do-able, just a matter of writing a small chunk of code to read / save or publish values, and some functions that will control the device via serial for you. You would just want a Wifi network nearby that the core could be online through.

Thanks!
David

ruben, if power consumption is not a big deal, you can also log data to a microSD connected to the Spark. If you have code that works on an arduino, then the fundamentals don’t really change except for the superb Spark features such as spark.variable(), spark.function() and spark.publish(). Don’t hesitate to ask for help, including porting stuff over! :smile:

EDIT: It compiles if I comment out the line:
Spark.function("connect", ISCOConnect);
I blew something up when making the function public

I am not so much logging as I am controlling. The autosampler technically should eventually log when it is drawing a sample but that it all it is doing, turning on a rather pricey pump to draw water. In any event. I think I will need help in porting it over. I am running into all sorts of errors. For example:

In file included from ../inc/spark_wiring.h:30:0,
from ../inc/application.h:31,
from iscoautosampler.cpp:2:
../../core-common-lib/SPARK_Firmware_Driver/inc/config.h:12:2: warning: #warning "Defaulting to Release Build" [-Wcpp]
iscoautosampler.cpp: In function 'void setup()':
iscoautosampler.cpp:5:40: error: invalid conversion from 'int ()()' to 'int ()(String)' [-fpermissive]
In file included from ../inc/spark_wiring.h:34:0,
from ../inc/application.h:31,
from iscoautosampler.cpp:2:
../inc/spark_utilities.h:83:14: error: initializing argument 2 of 'static void SparkClass::function(const char*, int ()(String))' [-fpermissive]
iscoautosampler.cpp: In function 'int ISCOConnect()':
iscoautosampler.cpp:32:36: warning: comparison between signed and unsigned integer expressions [-Wsign-compare]
iscoautosampler.cpp: In function 'char
call()':
iscoautosampler.cpp:51:8: warning: address of local variable 'inSerial' returned [enabled by default]
iscoautosampler.cpp: In function 'int Check_Protocol(char*)':
iscoautosampler.cpp:92:1: warning: control reaches end of non-void function [-Wreturn-type]
iscoautosampler.cpp: In function 'int ISCOConnect()':
iscoautosampler.cpp:46:1: warning: control reaches end of non-void function [-Wreturn-type]
make: *** [iscoautosampler.o] Error 1

Error: Could not compile. Please review your code.

My simplified code is as follows and I am actively trying to troubleshoot now. May need to simplify more. I am using character arrays because I will need to parse the string (comma delimited) for a different function (not shown).

This compiler is complaining whereas my mega compiled ok.

/* This function is called once at start up ----------------------------------*/
void setup()
{
  //Register Spark function in the cloud
  Spark.function("connect", ISCOConnect);
  //Expose variables to the cloud
 // Spark.variable("strCheckCon", &strCheckCon, STRING);//doesn't work here
  
//  pinMode(leftMotorDir, OUTPUT);
//  pinMode(rightMotorEnable, OUTPUT);

  Serial1.begin(9600); //comm with isco
//  Serial.begin(9600); //comm with computer
  
//  pinMode(D7,OUTPUT); //not sure. hanging chad
}

/* This function loops forever --------------------------------------------*/
void loop()
{
//nothing to do
}

 
/*******************************************************************************
 * Function Name  : ISCOConnect
 * Description    : Sends connect messages to the ISCO and feeds back responses
 *******************************************************************************/
int ISCOConnect(void)
{
    char myarray[] = {'?','?','?','\r','\n'};
    for(int n=0; n < sizeof(myarray); n++)
    {
      Serial1.write(myarray[n]);
	  //delay needed?
	  //Serial1.flush();//waits for transmission of outgoing serial data to complete
	  char* responseChars;
	  responseChars = call();
	  if (Check_Protocol(responseChars) == 1){
	    //we are Connected!
		//maybe I should print something to the screen
		//maybe I should break; especially if I want it to automatically loop a few times to conncet
	  return 1;
	  }
    }
}


char* call(void) 
{
  char inSerial[95];   
  int m=0; 
//  delay(1000); 
//Do I just need a delay to make it work? 
//This program is very simplified, so I would have delays all over my "real" program.
  
//  The line below is designed to go until the end of the reading. Seems like a kludge

 if (Serial1.available() > 0)   // This seems redundant, yes. makes it so check protocol isn't called continuously
  {             
//  while(Serial1.peek() != '>') // Can I do this outside of checking fo availability of the serial stream? 
   {
     while (Serial1.available() > 0) {
         m=Serial1.readBytesUntil('?',inSerial,94); //read data  
       delay(5);  
       }
       inSerial[m]='\0'; 
   }    
 }
      return(inSerial);  // the printed string should get longer and longer
}


int Check_Protocol(char* inChar)
{   
//Serial1.flush ??????????

//  Serial.print("Command: ");
//  Serial.println(inStr);       
//  Serial.println("Check_Protocol");
  //if(!strcmp(inStr," ")) Serial.println("got*");
  //if(!strcmp(inStr,"")) Serial.println("off");
   String strCheckCon(inChar);
   int testing = strCheckCon.indexOf('*');

//   Spark.variable("strCheckCon", &strCheckCon, STRING);
//   Serial.println(testing);
  if(testing > -1) {
  //Serial.println("got*");
	return 1;
  }
}

Sorry about all the commented code. It is from earlier work and I should have removed much of it.
Speaking of surfacing the variables, if I want to expose a variable in a function, does is need to be globally declared and then shared in setup or can I use spark.publish right in the function itself? For example, strCheckCon in the Check_Protocol function would be nice to expose where it is published (as an event?) so I can see the serial response from my connected autosampler.

@ruben, I think one of the easiest errors to cure is this

When you change your [quote="ruben, post:5, topic:3865"]
int ISCOConnect(void)
[/quote]

to

int ISCOConnect(String s)

Spark.function() expects as second argument a function that takes a String and returns an int.
This change should solve two of the above errors - if there are any more, they might be follow up errors of this one.

Once I also had a Spark.function which shouldn't need any parameters but still declared it with the String argument but still had troubles when calling it via Spark API Helper with an empty string, so I had to pass some dummy string.
I don't know if this problem still exists, but if you happen to have problems, just try to pass some dmy, too.

Thank you...I just figured it out too.
Changed it to include a string...although I just wrote String, not String s...I am not passing anything to the function and it compiled without a variable after "String". Anyway, couldn't hurt to include the "s". It compiles now

Edit: When I was looking at the RC car example, they have this at the top and I'm not sure why it is needed. Is this something I should do? Put all the function names at the top as well?

/* A Spark function to parse the commands */
int rcCarControl(String command);

Hi @ruben

A couple of things jumped out at me.

  • I see you got part of this one already while I was typing: A Spark function takes an Arduino String object and returns an int. You need to declare ISCOConnect like int ISCOConnect(String myString); You should return a value that means success or failure or something meaningful to the other side. Looks like you are returning 1 which is cool for the good case, but in the bad case, you should return -1 or something. Running off the end without a return value is not a good practice.

  • In your commented out code: It’s unusual to to have a Spark.variable with a string argument and and & since normally it would be declared char *message = "my message"; and would already be a pointer.

  • Returning inSerial, the 95 character char array from the function call is a not a good practice and the compiler is warning you about it. The local or automatic variable inSerial goes out of scope when you return from call and the compiler has to work extra hard to copy it for you, since you are trying to return a pointer to something that won’t exist after call returns. Maybe you should just make it global by declaring it above the setup() function?

1 Like

This line is called a prototye and is supposed to tell the compiler what to do with this new word rcCarControl().

You don’t need to provide prototypes when building on the Web IDE because the preprocessor there does it for you (only on the *.ino file, tho’)
When you build locally you will have to - either in a header *.h file or before the first call to any new function in your *.c/*.cpp files.

Thank you everybody. I ran into one big problem where it is blowing up my core.
Can you have a look at line 368 and tell me what you think I can do to keep it from presumably overflowing? I haven’t tried using strdup because I thought it was less safe.
You can ignore the myriad of other questions I have embedded. I will leave those for another time.
This is more pressing. Maybe I am messing up my understanding of pointers, particularly with char *parsedStatus[15]

This isn’t ready for prime time yet, but when it is, I will put it up on GitHub, so to any interested lurkers, please contribute to or fork from the official release (and provide proper reference please :smile: ) rather than go off of this.

/*
    Program:AutosampleConnect

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

char inSerial[200]; 
//unsigned long currentime;
//bool stbyflag = true;
int nStr;
String stsArName[6];
int stsArVal[6];
String stMessage;
String erMessage;
bool PoweredOn;

/* This function is called once at start up ----------------------------------*/
void setup()
{
  //Register Spark function in the cloud
  Spark.function("connect", ISCOConnect); // if 1 is returned then we connected
  Spark.function("status", ISCOStatus); 
  Spark.function("control", ISCOControl);

  //Expose variables to the cloud
  Spark.variable("strReceived", inSerial, STRING);//doesn't work here
//  Spark.variable("StatusMsg", stMessage, STRING);//doesn't work here
//  Spark.variable("ErrorMsg", erMessage, STRING);//doesn't work here

  Serial1.begin(9600); //comm with isco
  Serial.begin(9600); //comm with computer
//  currentime = millis();
  nStr=0;
  PoweredOn = true;
}


/* This function loops forever --------------------------------------------*/
void loop()
{
//   if(stbyflag == true){
//    spitout();
//  }
}

// This function allows shutdown sentence to be presented to user
/* void spitout(void){
  if (millis() > currentime + 15000){
    Serial.println("spitout");
    if (Serial1.available()){
      while (Serial1.available() > 0) {
        int inByte = Serial1.read();
        Serial.write(inByte);  // need to send "???" to initiate communication
      }
    }
    currentime = millis();
  } 
}*/



/*******************************************************************************
 * Function Name  : ISCOConnect
 * Description    : Sends connect messages to the ISCO and feeds back responses
 *******************************************************************************/
int ISCOConnect(String s)
{
//  stbyflag = false;

  char myarray[] = {'?','?','?','\r','\n','\0'}; 
  // I don't use char myarray[] = ???\r\n\0"; It doesn't seem to escape correctly
  
  // can I just get away with Serial1.write(myarray); instead of iterating??
  for(int n=0; n < sizeof(myarray); n++) 
  {
    Serial1.write(myarray[n]);
  }

  Serial.println("are we connected?");

 for(int i=0;i<6;i++){
       Serial.println(i);
        call(); // get the serial response
        Serial1.flush();
        Serial1.write('\r');
        delay(200);
} 

/*Serial.println("0...");
      call(); // get the serial response
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("1");
      call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("2");
      call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("3");
     call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("4");
      call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("5");
      call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("6");
      call();
delay(100);*/

  if (Check_Protocol() == 1) { 
    Serial.println("we are Connected!");
//    stbyflag = true;
    clear();
    PoweredOn = true; // assume its powered on until proven false
    return 1; /// if we return 1 then we know everything is ready to proceed.
  }
  else { 
    Serial.println("NOT Connected!");
//    stbyflag = true;
    clear();
    return -1; //failure to connect or return ***s
  }
}


void clear(void)
{
  for(int i=0;i<200;i++){
    inSerial[i] = '\0';  // Is there a cleaner way of "zeroing" an array? 
  }                      // I am doing it just to be especially cautious.
  nStr=0;
}


void call(void) 
{
 char subSerial[200]; // perhaps I shouldn't dimension it? I thought it wouldn't take up too much memory so I did anyway.
 int m=0;
 int o=0;
 if (Serial1.available() > 0)   // This seems redundant, yes. makes it so check protocol isn't called continuously
 {
    while (Serial1.available() > 0) {
        delay(15);
     //  199 leaves space for a last null character in case the array fills up
     // this is 199 characters not mem "slot" 199 (it is actually slot 198).
        m=Serial1.readBytes(subSerial,199);  
    }

//I have to keep calling "call" to populate the array because often, the autosampler will not provide 
//all the data before the microcontroller no longer sees serial1 as available, even with the delay.
//If I make the delay even longer, it tends to truncate data at the end of the string for some reason.
//I tried doing the looping inside call but for some reason, it did not work. I had to get out of scope,
//then come back in.

    for(int k=0;k<m;k++){ 
        o=k+nStr;                       //temp variable to use later. Includes the current position in the array (nStr)
        if (o<199){                     // is there a cleaner way than holding the current position in the string as a public
            inSerial[o]=subSerial[k];   //variable? Maybe strlen? or sizeof(if not dimensioned previously)?
            inSerial[o+1]='\0';         //Null
        }
    }
   Serial.println(inSerial); ///////////////USEFUL
   nStr=o+1;    //I stuck the null terminator on there. I'd rather not get rid of it until 
                //the next cycle (if there is one). When k=0, the next cycle of chars will
                //start right over the top of nStr, which will obliterate the null value
                //when this function completes, the final null value (after being called many times) will stay in tact
 }

 /*  if (nStr >= 199){
    Serial.println("may have exceeded capacity to store characters");
  } */

}


int Check_Protocol(void)
{   
  int testing = -1;
  Serial.println("Check_Protocol");

  char *s;
  s = strstr(inSerial, "***");
  Serial.println ("String to check for stars");
  Serial.println(s);

  if (s != NULL)    //not a null string // this is different than a null pointer, correct?
  {                 //because that would be *s (the one character @beginning of mem), vs s (the whole "string"), no?
    testing = (s - inSerial);   //this is pointer arithmetic. I think that s is pointing later in memory(?)
                                // than the pointer to inSerial which starts at the "0" part of that memory address O_o 
  }                             // index of * in buffer can be found by pointer subtraction
  else
  {
    Serial.println("NULL*");
  }

  Serial.println(testing);
  if(testing > -1) {
    Serial.println("Check protocol --> got*");
    delay(15);
    return 1;
  }
  else{
    return -1;
  }
}


/*******************************************************************************
 * Function Name  : ISCOStatus
 * Description    : receives status messages from the ISCO
 *******************************************************************************/
int ISCOStatus(String s)
{
//  stbyflag=false;

typedef struct /// I am using a struct without really understanding the definition. otherwise can't use if statement
{              // with arrays. Probably has to do with the compiler not liking me changing the value of the pointer??? 
    char c[6];
} Components;  // I don't understand the syntax here...why is this necesssary?
Components myarray;

if(PoweredOn == true){
  myarray=(Components){'S','T','S',',','1','\r'}; // it's already on
}
else{
  myarray=(Components){'S','T','S',',','2','\r'}; // not powered on and want to connect
}  

for(int n=0; n < sizeof(myarray); n++)  // can I just use Serial1.write(myarray);??
{
  Serial1.write(myarray.c[n]);
}

Serial1.flush();//waits for transmission of outgoing serial data to complete

Serial.println(sizeof(myarray));
Serial.println("what is the status?");


for(int i=0;i<7;i++){
       Serial.println(i);
        call(); // get the serial response
        Serial1.flush();
        Serial1.write('\r');
        delay(200);
}

/*Serial.println("0...");
      call(); // get the serial response
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("1");
      call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("2");
      call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("3");
     call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("4");
      call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("5");
      call();
            Serial1.flush();
            Serial1.write('\r');
delay(200);
Serial.println("6");
      call();
  delay(100);
*/
  
  if (Parse_Protocol() == 1) {
    Serial.println("DataParse Complete");
//    stbyflag=true;
    clear();
    return 1;
  }
  else{
    Serial.println("DataParse incomplete");
//    stbyflag=true;
    clear();
    return -1;
  }

}

int Parse_Protocol(void){
  //maybe first check for location of "ID ", send remaining chars
  //into a temporary char array and then start parsing
    char *parsedStatus[15];//holds pointers in part of an array
            // each one of these is actually a pointer which will hold
            // the delimited parts of the response message
                Serial.println(inSerial);

    char *TI;
    TI = strstr(inSerial, "TI,"); //skip model...I'll be using 6712s
    if (TI != NULL)                     // if succesfull then
    {
        int endpos = -1;
        char *CS;
        CS = strstr(inSerial, "CS"); 
        if (CS != NULL) {
            endpos = (CS - TI); // not sure why it's backwards
            if(endpos > -1) {
                Serial.println(inSerial);
                Serial.println(CS);
                Serial.println(TI);
              Serial.println ("looks like status string is complete and in right direction/prob not repeated");
              // THERE MUST BE A MORE ELEGANT WAY TO CHECK FOR NAMES AND THEN POPULATE AN ARRAY WITH THE VALUES.
              // THE CURRENT METHOD IS RISKY. WHAT IF THERE IS A CLUSTERMESS
              // AND THE OUTPUT IS CUTOFF AND THEN REPEATED (CAN HAPPEN)
              // THEN I SHOULD START AT THE END AND WORK MY WAY BACK (RECURSIVE) RATHER 
              // THAN READ FORWARD: TI <-- STS <-- STI <-- BTL <-- SVO <-- SOR <-- CS.
              // NEED HELP TO FIGURE OUT HOW TO DO THIS - CHAR? STRING? POINTER AWERSOMENESS
              // Then again, I am indirectly checking for this by checking the names in the stsArName below. 
              // If a value is out of place, the wrong name or a jumbled mess is likely to appear there.

             // char *str;
              int counter=0;
              //while ((str = strtok_r(TI, ",", &TI)) != NULL) { // delimiter is the comma
                    //non-reentrant version at http://forum.arduino.cc/index.php/topic,59813.0.html              
                    //str gives me all of the text until the first (or next) comma
    //TRYING NON REENTRANT VERSION TO SEE IF IT HELPS
                char *token = strtok(TI, ",");
                if(token)
                {
                    delay(100);
    //*******************THIS IS CAUSING ME MAJOR HEADACHES*********************
    // RED FLASHING LIGHT. ONE RED FLASH
    //Does strcpy take toooo much memory?
    //The string I am manipulating looks like this
    //TI,41764.88008,STS,1,STI,41764.60713,BTL,5,SVO,1000,SOR,3,CS,4761
                   strcpy(parsedStatus[counter++],token); // You've got to COPY the data pointed to
    // IF I COMMENT OUT STRCPY, IT DOESN'T CRASH
                    delay(100);
                   token = strtok(NULL, ","); // Keep parsing the same string
                   while(token)
                   {
                      //OLD strcpy(parsedStatus[counter++],token); // You've got to COPY the data pointed to
                      token = strtok(NULL, ",");
                    //OLD Serial.println (parsedStatus[counter]); // We should be seeing complete words here, not one character
                      Serial.println(token);
                   }
                }
                //  Serial.println(counter);
               // strcpy(parsedStatus[counter],str); //I am copying the X# of chars into the pointer????
                  //This is the part I am really confused about. Why would I take the entire length of 
                  //characters from memory (as found via pointer), then copy them and put them in another array of pointers? 
                  // One nice thing about duplication is that I can then take the results and save them to memory for
                  // recall later, which I probably will do in future, more autonomous versions... :-)
                  // We certainly would want to keep track of when we've sampled into which bottles, for example.
                //  Serial.println(str);

                 // ++counter; //increment the counter
                 // TI = NULL; // need null to keep parsing same string http://forum.arduino.cc/index.php/topic,48925.0.htm
                Serial.println("we made it");
              
          
    //           delay(700);
    //           //Log all the parsed data into arrays*******************
    //           String temp0(parsedStatus[0]); //TI
    //           stsArName[0]= temp0;
    //           stsArVal[0]=int(86400*(atof(parsedStatus[1]))-2208988800); //Time ************ uses fractional hour lost as int
    //                 //is it better to use strtod?? or atof?? not sure which
    //           String temp2(parsedStatus[2]); //           
    //           stsArName[1]=temp2; //STS
    //           stsArVal[1]=atoi(parsedStatus[3]); //status

    //           String temp4(parsedStatus[4]); //            
    //           stsArName[2]=temp4; //STI
    //           stsArVal[2]=atoi(parsedStatus[5]); //sampletime

    //           String temp6(parsedStatus[6]); //            
    //           stsArName[3]=temp6; //BTL
    //           stsArVal[3]=atoi(parsedStatus[7]); //bottlenumber

    //           String temp8(parsedStatus[8]); //            
    //           stsArName[4]=temp8; //SVO
    //           stsArVal[4]=atoi(parsedStatus[9]); //volume

    //           String temp10(parsedStatus[10]); //            
    //           stsArName[5]=temp10; //SOR
    //           stsArVal[5]=atoi(parsedStatus[11]); //results

    //           // counter 0 = TI
    //           // counter 1 = time number ... shown in a date-time format based on the number of days since
    //                     //00:00:00 1-Jan-1900, and the time shown as a fraction.
    //              //      GET TO EPOCH days --> seconds + decimal time (fraction of a day) --> seconds
    //              //       NUMBER * 86400 (<--24*60*60) - 2208988800
                        
    //           // counter 2 = STS
    //           // counter 3 = status message, 1-23
    //               /*
    //                 1                    = WAITING TO SAMPLE.
    //                 4                    = POWER FAILED (for short time after power is restored).
    //                 5                    = PUMP JAMMED (must be resolved before continuing).
    //                 6                    = DISTRIBUTOR JAMMED (must be resolved before continuing).
    //                 9                    = SAMPLER OFF.
    //                 12                    = SAMPLE IN PROGRESS.
    //                 20                    = INVALID COMMAND. Possible causes may be:
    //                         · identifier code is not supported.
    //                         · bottle requested is not in current configuration
    //                         · sample volume requested is outside its range (10 - 9990 ml)
    //                         · day (Set_Time) must be 5 digits and more recent than 1977
    //                 21                    = CHECKSUM MISMATCH. (see “Optional check-sum” on page 7-8)
    //                 22                    = INVALID BOTTLE. (bottle requested is not in the current configuration)
    //                 23                    = VOLUME OUT OF RANGE. (the sample volume requested is outside its range (10-9990 ml)
    //               */

    //             if (stsArName[1] = "STS"){ //I really should do something to strip out any errant spaces or other characters before doing comparisons.
    //                                       //However, the entire structure should be space free and if it has a space then something is wrong.
    //                 switch (stsArVal[1]) {
    //                     case 1:
    //                       //do something when var equals 1
    //                       stMessage= "1= WAITING TO SAMPLE";
    //                       break;
    //                     case 4:
    //                       stMessage= "4= POWER FAILED (after power is restored)";
    //                       break;
    //                     case 5:
    //                       stMessage= "5= PUMP JAMMED (resolve before continuing)";
    //                       break;
    //                     case 6:
    //                       stMessage= "6= DISTRIBUTOR JAMMED (resolve before continuing)";
    //                       break;
    //                     case 9:
    //                       stMessage= "9= SAMPLER OFF";
    //                       break;
    //                     case 12:
    //                       stMessage= "12= SAMPLE IN PROGRESS";
    //                       break;
    //                     case 20:
    //                       stMessage= "20= INVALID COMMAND. bot#? vol?";
    //                       break;
    //                     case 21:
    //                       stMessage= "21= CHECKSUM MISMATCH";
    //                       break;  
    //                     case 22:
    //                       stMessage= "22= INVALID BOTTLE";
    //                       break;   
    //                     case 23:
    //                       stMessage= "23= VOLUME OUT OF RANGE";
    //                       break; 
    //                     default: 
    //                       // if nothing else matches, do the default
    //                       // default is optional
    //                       stMessage= "ERROR: no status value";
    //                   }
    //                 if (stsArVal[1] == 9){
    //                     PoweredOn=false;
    //                 }
    //                 Serial.println(stMessage);
    //             }
                

    //           // counter 4 = STI
    //           // counter 5 = most recent sample time
    //           // counter 6 = BTL
    //           // counter 7 = number of the most recentbottle that got water
    //             if (stsArName[3] = "BTL") //I really should do something to strip out any errant spaces or other characters before doing comparisons.
    //             Serial.println("bottle");
    //              Serial.println(stsArVal[3]);
              
    //           // counter 8 = SVO
    //           // counter 9 = most recent sample volue
    //             if (stsArName[4] = "SVO") //I really should do something to strip out any errant spaces or other characters before doing comparisons.
    //              Serial.println("volume");
    //              Serial.println(stsArVal[4]);              
              
    //           // counter 10 = SOR
    //           // counter 11 = results of most recent sampling attempt
    //                 /*
    //                 0= SAMPLE OK
    //                 1= NO LIQUID FOUND
    //                 2= LIQUID LOST (not enough liquid)
    //                 3= USER STOPPED (using the Stop Key)
    //                 4= POWER FAILED
    //                 5= PUMP JAMMED
    //                 6= DISTRIBUTOR JAMMED
    //                 8= PUMP LATCH OPEN
    //                 9= SAMPLER SHUT OFF (while sampling)
    //                 11= NO DISTRIBUTOR
    //                 12= SAMPLE IN PROGRESS
    //                 */
                    
    //             if (stsArName[5] = "SOR"){ //I really should do something to strip out any errant spaces or other characters before doing comparisons.
    //                                       //However, the entire structure should be space free and if it has a space then something is wrong.
    //                 switch (stsArVal[5]) {
    //                     case 0:
    //                       //do something when var equals 1
    //                       erMessage= "SAMPLE OK";
    //                       break;
    //                     case 1:
    //                       erMessage= "NO LIQUID FOUND";
    //                       break;
    //                     case 2:
    //                       erMessage= "LIQUID LOST";
    //                       break;
    //                     case 3:
    //                       erMessage= "USER STOPPED";
    //                       break;
    //                     case 4:
    //                       erMessage= "POWER FAILED";
    //                       break;
    //                     case 5:
    //                       erMessage= "PUMP JAMMED";
    //                       break;
    //                     case 6:
    //                       erMessage= "DISTRIBUTOR JAMMED";
    //                       break;
    //                     case 8:
    //                       erMessage= "PUMP LATCH OPEN";
    //                       break;  
    //                     case 9:
    //                       erMessage= "SAMPLER SHUT OFF";
    //                       break;   
    //                     case 11:
    //                       erMessage= "NO DISTRIBUTOR";
    //                       break; 
    //                     case 12:
    //                       erMessage= "SAMPLE IN PROGRESS";
    //                       break; 
    //                     default: 
    //                       // if nothing else matches, do the default
    //                       // default is optional
    //                       erMessage= "ERROR: no result value";
    //                   }
    //                 Serial.println(erMessage);
    //             }  
    //           return 1; /// end position is <1. === otherwise out of order - multiple incomplete returns possible
    //         }
    //     } 
    //     else //CS = NULL
    //     {
    //       Serial.println("CS=NULL");
    //       return -1;
    //     }
    // }                      // index of "" in buff can be found by pointer subtraction
    // else
    // {
    //   Serial.println("TI=NULL");
    //   return -1;
    // }
    // */
    //}
}}}} //get rid of these when fixed


int ISCOControl(String s)
{
   //let's pretend last bottle was succesful 

  String STRvolume =  "1000";
  String STRbottle =  s;

  Serial1.write("BTL,");
  Serial1.print(STRbottle); // kludge. perhaps use sprintf http://liudr.wordpress.com/2012/01/16/sprintf/
  Serial1.write(",SVO,");
  Serial1.print(STRvolume); // also kludge
  Serial1.write('\r');
}

///Ideally, I would have a function to update the time on the autosampler with the 
//internet time once per day or something but that isn't important.

OK, that is a lot of code to read, but did you mean for *parsedStatus[15] to be an array of pointers to strings (in which case strcpy is not what you want) or did you mean for it to be an array of 15 different strings? I think you meant the latter.

If so, you need something more like this:

#define NSTATUS 15
#define STATUSLEN 32  // I made up this number
char parsedStatus[NSTATUS][STATUSLEN];  // array of 15 char arrays each 32 chars long
...
//copy into parseStatus from token
strcpy(parseStatus[counter++], token);

Does that make sense? I picked 32 characters completely out of the air, here. Your number may be different but remember you using the product of the two dimensions in bytes of RAM.

I thought that I would use pointers to limit the memory load but that doesn’t make as much sense in the end. Are you saying that there is no way to get away without dimensioning the second part of the 2D array? Some of the responses are very short (one two or three characters) some are long (date since 1900). It is a pity to have to dim the whole array to the biggest possible response.

Thank you very much BKO

You don’t have to dimension the second dimension statically, but you would need to call malloc and dynamically allocate the strings. Given the current state of dynamic allocation on the core (it works OK but there is no fancy fragmentation recovery algorithm) you will eventually run out of memory.

If they are disparate sizes, why not make the individual arrays like this:


char parsedStatus0[SIZE0];
char parsedStatus1[SIZE1];
...

Do you need to iterate over them later or is that just convenient?

1 Like

I ended up making them all the same size for now…may be leading to my current problem (perhaps a memory problem?) but to your question, the answer is no, I just end up passing the values into permanent arrays afterwards.

Can I ask you guys a followup question? You have been incredibly helpful and I cannot say how much I appreciate that. Really.

The following chunk of code will execute correctly, all the way to printing “passed gauntlet” and then I’ll get the red flashy light (“no, not the red flashy, noooooooo”)
Here is the function. I’m not sure why my program would break after calling it. I am going to give you the nuggets rather than the whole program. Note that I didn’t try running this with the new firmware.

int Parse_Protocol(void){
  //maybe first check for location of "ID ", send remaining chars
  //into a temporary char array and then start parsing
    char parsedStatus[15][15];//holds pointers in part of an array
            // each one of these is actually a pointer which will hold
            // the delimited parts of the response message
                Serial.println(inSerial);

    char *TI;
    TI = strstr(inSerial, "TI,"); //skip model...I'll be using 6712s
    if (TI != NULL)                     // if succesfull then
    {
        int endpos = -1;
        char *CS;
        CS = strstr(inSerial, "CS"); 
        if (CS != NULL) {
            endpos = (CS - TI); // not sure why it's backwards
            if(endpos > -1) {
                Serial.println(inSerial);
                Serial.println(CS);
                Serial.println(TI);
              Serial.println ("looks like status string is complete and in right direction/prob not repeated");
 
              int counter=0;
                char *token = strtok(TI, ",");
                if(token)
                {
                    delay(100); // seems to crash less when I add a delay
                    strcpy(parsedStatus[counter++],token); // You've got to COPY the data pointed to
                    delay(100);
                    token = strtok(NULL, ","); // Keep parsing the same string
                   while(token)
                   {
                   strcpy(parsedStatus[counter++],token); // You've got to COPY the data pointed to
                      token = strtok(NULL, ",");
                   }
                }
                Serial.println("we made it");
              
          
              delay(700);
              //Log all the parsed data into arrays*******************
              stsArName[0]= parsedStatus[0];
              stsArVal[0]=int(86400*(atof(parsedStatus[1]))-2208988800); //Time ************ uses fractional hour lost as int
                    //is it better to use strtod?? or atof?? not sure which
              stsArName[1]=parsedStatus[2]; //STS
              stsArVal[1]=atoi(parsedStatus[3]); //status
              stsArName[2]=parsedStatus[4]; //STI
              stsArVal[2]=int(86400*(atof(parsedStatus[5]))-2208988800); //sample time converted
              stsArName[3]=parsedStatus[6]; //BTL
              stsArVal[3]=atoi(parsedStatus[7]); //bottlenumber
              stsArName[4]=parsedStatus[8]; //SVO
              stsArVal[4]=atoi(parsedStatus[9]); //volume
              stsArName[5]=parsedStatus[10]; //SOR
              stsArVal[5]=atoi(parsedStatus[11]); //results 

////////////THIS IS THE LAST THING THAT GETS PRINTED TO MY SCREEN//////////////////////

                  Serial.println("passedgauntlet");
              delay(400);
              return 1;
                            Serial.println("1error");
           } 
        }
        else //CS = NULL
        {
          Serial.println("CS=NULL");
                                      Serial.println("2error");
          return -1;
                                      Serial.println("3error");
        }                       // index of "" in buff can be found by pointer subtraction
    }
    else
    {
      Serial.println("TI=NULL");
                                 Serial.println("4error");
      return -1;
                                  Serial.println("5error");
    }
}

The above code is being called from the following. I should be able to see “DataParse Complete” instead of red blinky lights:

/*******************************************************************************
 * Function Name  : ISCOStatus
 * Description    : receives status messages from the ISCO
 *******************************************************************************/
int ISCOStatus(String s)
{

typedef struct 
{              
    char c[6];
} Components;  
Components myarray;

if(PoweredOn == true){
  myarray=(Components){'S','T','S',',','1','\r'}; // it's already on
}
else{
  myarray=(Components){'S','T','S',',','2','\r'}; // not powered on and want to connect
}  

for(int n=0; n < sizeof(myarray); n++)  
{
  Serial1.write(myarray.c[n]);
}

Serial1.flush();//waits for transmission of outgoing serial data to complete

Serial.println(sizeof(myarray));
Serial.println("what is the status?");

for(int i=0;i<7;i++){
       Serial.println(i);
        call(); // get the serial response
        Serial1.flush();
        Serial1.write('\r');
        delay(200);
}

//IT LOOKS LIKE IT IS CRASHING WHEN IT RETURNS

 if (Parse_Protocol() == 1) {
    Serial.println("DataParse Complete");
//    stbyflag=true;
    clear();
      if (DisplayMessage() == 1) {
      delay(200);
       Serial.println("Done");
//    stbyflag=true;
      return 1;
      }
  }
  else{
    Serial.println("DataParse incomplete");
//    stbyflag=true;
    clear();
    return -1;
  }

}

When you are doing this:

 stsArName[0]= parsedStatus[0];

You are setting the pointer in stsArName[0] to be the same as the pointer parsedStatus[0] but that is an automatic or stack allocated variable you declared in your Parse_Protocol function. The lower level variable parsedStatus is no longer allocated when you exit your function, so this points to something random.

You could copy the strings again with strcpy, or you could parse directly into the stsArName arrays. For the values, what I generally do is use a variable like your counter with an if or case statement to handle all the oddball conversions in the while loop on the token. I would use a separate variable to count the name strings, called namecounter below.

while(token) {
  switch(counter) {
    case 1:  //value 1
      stsArVal[0]=int(86400*(atof(token))-2208988800);
      break;
    case 3: //value 2
      stsArVal[1]=atoi(token);
      break;
    case 5:  //value 3
    // etc etc
    default: //for all the others
      strcpy(stsArName[namecounter++], token);
  }
token = strtok(NULL, ",");
counter++;
}

Are you sure?
I thought the assignent operator = of String (String stsArName[6];) does perform a copy action when being passed a char.

You are right that @ruben doesn’t say in his most recent post how stsArName is declared. I assumed it was another 2-d char array like this:

char stsArName[NSTRINGS][STRINGSIZE+1];

I thought he had given up on String due to memory problems.

I see, but if both (stsArName and parsedStatus) were 2D char arrays the assignment would cause an error: invalid array assignment rather than assigning pointers.
AFAK a 2D char array char x[5][10] is not an array of five pointers refering to five arrays of ‘char[10]’ but there only is one pointer x which gives you the reference to an RAM space of 5x10 bytes/chars - more like char x[5*10].