Particle.function with multiple arguments

I’m trying to control a few simple motors with my Argon, and since the Particle.function only accepts a single string argument, I’m passing it a comma delimited string with [functionName],[motorNumber],[direction],[speed].

I’m not sure what I’m doing wrong (still learning C++), but I’m not parsing it correctly with sscanf right now. This is my code (stripped of the code used to drive the motors):

int triggerFunction(String args);

void setup()
{
 Particle.function("triggerFunction", triggerFunction);
}

void loop()
{

}

int triggerFunction(String args) {
    String functionName, direction;
    int motorNumber, speed;
    sscanf(args, "%20[^,],%d[^,],%20[^,],%d", functionName, &motorNumber, direction, &speed);
    if (functionName == "moveMotor") {
        moveMotor(motorNumber, direction, speed);
    }
    return 1;
}

int moveMotor(int motorNumber, String direction, int speed) {
    // code to move motor
   return 0;
}
1 Like

I think your main issue is that sscanf is not working with String objects how you would expect. The only kind of strings that sscanf can operate properly with are char arrays (c-strings).

I would suggest trying this modified code:

int triggerFunction(const char *args)
{
    char functionName[20];
    char direction[20];
    int motorNumber, speed;

    // [functionName],[motorNumber],[direction],[speed]
    sscanf(args, "%20[^,],%d[^,],%20[^,],%d", functionName, &motorNumber, direction, &speed);

    // Why not try without brackets?
    // functionName,motorNumber,direction,speed
    // sscanf(args, "%s,%d,%s,%d", functionName, &motorNumber, direction, &speed);
    
    if (strcmp(functionName, "moveMotor") != 0)
    {
        moveMotor(motorNumber, direction, speed);
    }
    return 1;
}

Also, is there any reason why you want to enclose each of your comma-separated items in square brackets instead of just having them be comma-separated? Adding the square brackets will add unnecessary complexity on both the client and on the device.

I’m getting this to trigger my moveMotor function now, but doing a Particle.publish() to print out the functionName from the sscanf gives me a null. Also my moveMotor function fails at the if (motorNumber == 1) despite me sending moveMotor,1,forwards,100 via POST.

// Defines Arduino pins for motor 1
int motor1_IN1 = A0;
int motor1_IN2 = A1;
int motor1_speedPin = D8; // PWM Pin

int triggerFunction(const char *args);

void setup()
{
 // Set the output pins
 pinMode(motor1_IN1, OUTPUT);
 pinMode(motor1_IN2, OUTPUT);
 pinMode(motor1_speedPin, OUTPUT);
 Particle.function("triggerFunction", triggerFunction);
}

void loop()
{

}

int triggerFunction(const char *args) {
    char functionName[20];
    char direction[20];
    int motorNumber, speed;
    sscanf(args, "%s,%d,%s,%d", functionName, &motorNumber, direction, &speed);
    Particle.publish("functionName", functionName);
    if (strcmp(functionName, "moveMotor") != 0) {
        moveMotor(motorNumber, direction, speed);
    }
    return 1;
}

int moveMotor(int motorNumber, char* direction, int speed) {
    Particle.publish("moveMotor");
    analogWrite(motor1_speedPin, speed);
    if (motorNumber == 1) {
        Particle.publish("gets here");
        if (strcmp(direction, "forwards") != 0) {
            Particle.publish("forwards");
            digitalWrite(motor1_IN1, HIGH);
            digitalWrite(motor1_IN2, LOW);
        }
        if (strcmp(direction, "backwards") != 0) {
            Particle.publish("backwards");
            digitalWrite(motor1_IN1, LOW);
            digitalWrite(motor1_IN2, HIGH);
        }
        return 1;
    } else return 0;
}

I think there is still an issue with the sscanf. Sorry about that.

Since the formatting string is not correct, the variables are not getting filled in properly, but the strcmp != 0 is still evaluating to true. I’ll try to post the fixed code soon.

This should work better with moveMotor,1,forwards,100

int triggerFunction(const char *args)
{
        char functionName[20];
        char direction[20];
        int motorNumber, speed;

        sscanf(args, "%20[^,],%d,%20[^,],%d", functionName, &motorNumber, direction, &speed);
        Particle.publish("functionName", functionName);

        if (strcmp(functionName, "moveMotor") == 0)
        {
                moveMotor(motorNumber, direction, speed);
        }
        return 1;
}

int moveMotor(int motorNumber, const char *direction, int speed)
{
        Particle.publish("moveMotor");
        analogWrite(motor1_speedPin, speed);
        if (motorNumber == 1)
        {
                Particle.publish("gets here");
                if (strcmp(direction, "forwards") == 0)
                {
                        Particle.publish("forwards");
                        digitalWrite(motor1_IN1, HIGH);
                        digitalWrite(motor1_IN2, LOW);
                }
                if (strcmp(direction, "backwards") == 0)
                {
                        Particle.publish("backwards");
                        digitalWrite(motor1_IN1, LOW);
                        digitalWrite(motor1_IN2, HIGH);
                }
                return 1;
        }

        return 0;
}

It turns out you had the right idea with the sscanf formatting string but there was just a small syntax error. I’m still learning C++ too. :wink:

1 Like

While this can be confusing // [functionName],[motorNumber],[direction],[speed] the format string suggests the sqare brackets are just there to denote "the content of the variable" (e.g. like often used in SQL syntax samples).
But the square brackets in the format string are place holders meaning "scan a string of max 20 characters not containing a comma" - that's required since "%s" would read till if finds a whitespace or when it exceedes 20 characters.
But the "%d[^,] is wrong, since the format place holder already ends with d, so the format string should rather look like this

"%20[^,],%d,%20[^,],%d"

(as Nathan already pointed out)

BTW, when using sscanf() it is good style to catch the return value and check how many variables could successfully be populated and only act when you get the anticipated result.
Especially when not initialising your token items you may end up with garbage data in an unpopulated variable.

And please don't spam the public event stream but rather use

 Particle.publish("functionName", functionName, PRIVATE);

PUBLIC is the default scope but should be avoided when not explicitly required.

Another style tip would be to make most use of the return value of a function (and reorder some things)

int moveMotor(int motorNumber, const char *direction, int speed)
{
  int  retVal = 0;
  char msg[32];

  snprintf(msg, sizeof(msg), "%d: %s (%d)", motorNumber, direction, speed);
  Particle.publish("moveMotor", msg, PRIVATE);
  analogWrite(motor1_speedPin, 0);     // stop before changing things
  switch(motorNumber)                  // I'd expect more than just one motor
  {
    case 1:
      if (strcmp(direction, "forwards") == 0)
      {
        digitalWrite(motor1_IN2, LOW);  // for safety first off as we don't know which H-bridge is used
        digitalWrite(motor1_IN1, HIGH); // then the other on
        retVal = speed; 
      }
      else if (strcmp(direction, "backwards") == 0)
      {
        digitalWrite(motor1_IN1, LOW);
        digitalWrite(motor1_IN2, HIGH);
        retVal = -speed;
      }
      analogWrite(motor1_speedPin, speed); // after direction is set engage new speed
      break;
    default:
      break; 
    }
  }
  return retVal;
}

(when more motors are expected to be added, probably doing mostly the same things I'd put the pins for each motor into an array and then use the motorNumber as index to that array to avoid copy/pasting the same logic over and over just to replace the pin names
I'd also do away with the direction string in moveMotor() and rather let the sign of speed hold that info)

2 Likes

These are great tips! Thanks for the help - I got the basic code working and I’m now starting to optimize it!

2 Likes