1

I have a Firmata sketch, that accepts string messages, partitions them and sends its' parts back to a Firmata client program. My problem is certainly at the sketch's side. The problem is that Firmata.sendString(argument1) and Firmata.sendString(argument2) methods don't send anything. More information is found in the sketch's code. How can I get the sendString methods to work?

/*
 * This sketch accepts strings in the following form:
 * CMDn(arg1,arg2)
 * CMDn — command name,
 * arg1 и arg2 — unnecessary arguments.
 * Each argument's length can be from 1 to 4 characters,
 * They're saved to separate variables.
 *
 * This sketch sends back the parsed commands partially:
 * the command name, argument #1 (if present), argument #2 (if present)
 */


#include <Firmata.h>


void stringCallback(char *received) {
  //CMDn(arg1,arg2)
  //CMDn(arg1)
  //CMDn(1)
  //CMDn()
  //012345678901234
  //          11111

  //This scheme up there is just for 
  //understanding the indexes of the chars in the string
  //in its various possible variations

  //Let's declare all the necessary variables
  char methodName[5];
  char argument1[5];
  char argument2[5];
  byte commaLocation = 0;

  //If the received string doesn't match the strings' syntax,
  //send an error message
  if (!(received[4] == '(' && strlen(received) <= 15)) {
    Firmata.sendString("ERR;");
    return;
  }

  //Let's take the 0-3 chars in the string and send it back.
  //It's probably the command name
  for (int i = 0; i < 4; i++) {
    methodName[i] = received[i];
  }
  methodName[4] = '\0'; //does or doesn't the compiler add the terminator automatically?
  Firmata.sendString(methodName); //send it
  //This string is being sent without problems.


  //If there is some arguments in the string,
  //it is detectable if the string char 5 is not ')'/
  if (received[5] != ')') {
    //Lent's find out if the input string has a `comma`
    //inside the parentheses. If it does, it means that
    //there are two arguments in the string.

    Firmata.sendString("46 passed");    
    for (int i = 6; i < (strlen(received) - 1); i++) {
      //If there is a comma, save its' index and break the loop
      if (received[i] == ',') {
        commaLocation = i;
        break;
      }
    }

    //If a comma is not present, its location variable equals 0,
    //sincerely we just collect all the chars between the parentheses
    //to the 'argument1' variable and send it.
    if (commaLocation == 0) {
      Firmata.sendString("61 passed");
      for (int i = 5; i < ; i++) {
        argument1[(i - 5)] = received[i];
      }


      argument1[strlen(argument1)] = '\0'; //Either this is executed or not, the outcome doesn't change 

      Firmata.sendString(argument1); //This very command doesn't work. It just does not send anything. 
                                     //Looks like 'argument1' is empty, but I'm awfully sure that it must 
                                     //not be empty. Anyway, the 'argument1' does not show up in the client
    }

    //If there is a comma present, then write chars before the comma
    //to 'argument1', and after the comma - to 'argument2'
    if (commaLocation != 0) {
      Firmata.sendString("77 passed");
      //let's write what is before the comma:
      for (int i = 5; i < commaLocation; i++) {
        argument1[i - 5] = received[i];
        Firmata.sendString("81 iterator");
      }

      //let's write what's after the comma;
      char argument2[5];
      for (int i = (commaLocation + 1); i < (strlen(received)); i++) {
        argument2[(i - (commaLocation + 1))] = received[i];
        Firmata.sendString("87 iterator");
      }
      
      argument1[strlen(argument1)] = '\0'; //Either this is executed or not, the outcome doesn't change 
      argument2[strlen(argument2)] = '\0'; 

      Firmata.sendString(argument1); //Not working
      Firmata.sendString(argument2); //Not working
    }
  }
}


void sysexCallback(byte command, byte argc, byte *argv) {
  Firmata.sendSysex(command, argc, argv);
}


void setup() {
  Firmata.setFirmwareVersion(FIRMATA_FIRMWARE_MAJOR_VERSION, FIRMATA_FIRMWARE_MINOR_VERSION);
  Firmata.attach(STRING_DATA, stringCallback);
  Firmata.attach(START_SYSEX, sysexCallback);
  Firmata.begin(57600);
}

void loop() {
  while (Firmata.available()) {
    Firmata.processInput();
  }
}

Expected output:

What I send What I receive (excluding the debug messages, ; stands for newline)
DHTt() DHTt
DHTt(1) DHTt; 1
DHTt(1,2) DHTt; 1; 2

Real output:

What I send What I receive (excluding the debug messages)
DHTt() DHTt as long as there are no arguments, there are no problems. But the arguments are necessary for my task
DHTt(1) DHTt the argument are not sent
DHTt(1,2) DHTt two arguments are not sent neither
7
  • strlen(argument1) tells you the index of the first '\0' char, so that line doesn't make sense. This would be a lot simpler if you used pointers to the char array so that you can easily set/change a start point (for example with recPtr = strchr(received, ',')) instead of skipping over offsets. Look for helpful tools in cplusplus.com/reference/cstring – dandavis Feb 12 at 16:59
  • @DanDavis, isn't the cstring equivalent to the Arduino String struct, neither the character array? – Starter Feb 12 at 17:15
  • Also, please pinpoint which concrete line of the code? – Starter Feb 12 at 17:17
  • They're probably referring to any of the lines that take the form x[strlen(x)] ='\0'; At best x contains a valid string and this line is unless since it just puts a null terminator where one already exists. If x is not a valid c-string, useless is the best outcome you can hope for, but not the only one. You are going this with argument1 in two places. argument2 in one place. It seems like it would be good to just spend some time learning about strings outside of the context of your project. You can play in a desktop environment and transfer over what you learn. – timemage Feb 12 at 17:42
  • What does your client (the PC) do with the string? Do you use a library there or did you write the client yourself? – PMF Feb 12 at 18:53
1

Here's a simplified version of your command parser using cstrings and pointers:

void parseArgs(char * rec){
  char cmd[32];
  char arg1[32]= {0};
  char arg2[32]= {0};
  char * ptr; // this is a re-usable workhorse pointer
  char * ptrDelim; // another workhorse to split arguments
  
  // first copy all of it to cmd
  strcpy(cmd, rec);
  // find end of command
  ptr = cmd;
  while( isalpha(ptr[0]) ) ptr++;
  ptr[0] = '\0'; // mark end of command string
  
  
  // find first arg
  ptr = strchr(rec, '(')+1; // set ptr to char to right of open paren
  strcpy(arg1, ptr); // copy stuff after open paren to arg1:
  ptr = strchr(arg1, ')');// find max extent of arg1
  if(ptrDelim=strchr(arg1, ',')){
    ptrDelim[0]='\0'; // end arg1's string at delim
    ptrDelim++; // move right one char
    strcpy(arg2, ptrDelim); // copy stuff after comma
    arg2[strlen(arg2)-1] = '\0'; // chop off ")"
  }else{
    ptr[0]='\0';    
  }//end if comma?
  
    
 Serial.println("\nGiven:" + String(rec)+";");   
 Serial.println("Command:" + String( cmd )+";"); 
 Serial.println("Arg1:" + String( arg1 )+";"); 
 Serial.println("Arg2:" + String( arg2 )+";"); 
  
}

Examples:

parseArgs("CMDn()");
parseArgs("CMDn(arg1)");
parseArgs("CMDn(arg1,arg2)");
6
  • Remove the String()-having lines after you get done debugging. – dandavis Feb 13 at 13:03
  • In if(ptrDelim=strchr(arg1, ',')){ statement, did you mean == instead of =? – Starter Feb 13 at 14:50
  • Also, what does your code do if there is no arguments in the incoming string? – Starter Feb 13 at 14:53
  • @Starter: i should have wrapped the expression in parens as the compiler suggested, but it's an intentional two-fer: if the char is not present in the string it returns a falsy null-pointer, and if it finds it, it returns a truthy evaluation whilst assiging the pointer to that char's posistion. – dandavis Feb 13 at 19:31
  • @Starter: then they will be blank, which you can detect by if(strlen(arg1)) or if(arg1[0]) as needed. – dandavis Feb 13 at 19:32
1

Firmata.sendString does not send anything to the client in two cases:

  • If the message is empty;
  • If the message does not contain the \0 terminator in it.

In this case, while you're sending methodName, you're manually assigning the terminator to the string; and the sender works:

methodName[4] = '\0';

The only thing necessary to do to solve this argument problem is making each char variable end with a null terminator. Here you can see the solved code:

#include <Firmata.h>

//setup() and loop() won't be here, there are no changes in them

unsigned long charIndex(const char * findIn, unsigned int symbol) {
  //Find 'symbol' in char array 'findIn'. Unlike
  //strchr(), returns unsigned long or 0 if 'symbol' not found
  
  char * pointer = strchr(findIn, symbol);
  if (pointer != NULL) return (pointer - findIn);
  if (pointer == NULL) return 0;
}


void stringCallback(char *received) {
  //Let's declare all the necessary variables
  char methodName[5];
  char argument1[5];
  char argument2[5];
  int commaLocation = 0;
  int lasti;

  byte nullTerminatorIndex = strlen(received); //For understanding

  //If the received string doesn't match the strings' syntax,
  //send an error message
  if (!(received[4] == '(' && strlen(received) <= 15)) {
    Firmata.sendString("ERR;");
    return;
  }

  //Let's take the 0-3 chars in the string and send it back.
  //It's probably the command name
  for (int i = 0; i < 4; i++) {
    methodName[i] = received[i];
  }
  methodName[4] = '\0'; //**The compiler doesn't add terminators automatically**.
  Firmata.sendString(methodName); //send it
  //This string is being sent without problems.


  //If there is some arguments in the string,
  //it is detectable if the string char 5 is not ')'/
  if (received[5] != ')') {
    //Lent's find out if the input string has a `comma`
    //inside the parentheses. If it does, it means that
    //there are two arguments in the string.

    Firmata.sendString("46 passed");    
    if (charIndex(received, ',') != 0) commaLocation = charIndex(received, ',');

    //If a comma is not present, its location variable equals 0,
    //sincerely we just collect all the chars between the parentheses
    //to the 'argument1' variable and send it.
    if (commaLocation == 0) {
      Firmata.sendString("61 passed");
      for (int i = 5; i < (nullTerminatorIndex - 1); i++) {
        argument1[(i - 5)] = received[i];
        lasti = (i - 5);
      }

      argument1[(lasti + 1)] = '\0';
      lasti = 0;

      Firmata.sendString(argument1); 
    }

    //If there is a comma present, then write chars before the comma
    //to 'argument1', and after the comma - to 'argument2'
    if (commaLocation != 0) {
      Firmata.sendString("77 passed");
      //let's write what is before the comma:
      for (int i = 5; i < commaLocation; i++) {
        argument1[i - 5] = received[i];
        lasti = (i - 5);
      }

      argument1[(lasti + 1)] = '\0';
      lasti = 0;

      //let's write what's after the comma;
      char argument2[5];
      for (int i = (commaLocation + 1); i < (nullTerminatorIndex - 1); i++) {
        argument2[(i - (commaLocation + 1))] = received[i];
        lasti = (i - (commaLocation + 1));
      }

      argument2[(lasti + 1)] = '\0';
      lasti = 0;

      Firmata.sendString(argument1);
      Firmata.sendString(argument2); 
    }
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.