C++ sscanf seams not to working as intended

Viewed 80

I'm currently working on project for school with an Arduino Uno and came across an issue with C sscanf function. The code bellow is parsing a char array sent over the serial interface to the Arduino. Each data block is separated with a blankspace. I also checked similar question on Stackoverflow but found nothing helpful.

    char testInput[] = "t 1 3 65 1";

    int registerIndex;
    int locoAddr;
    int locoSpeed;
    int locoDirection;

   if(sscanf(testInput, "t %d %d %d %d", &registerIndex, &locoAddr, &locoSpeed, &locoDirection) != 4) {
        return;
    }

    // print for demo:
    Serial.print(registerIndex);
    Serial.print(": ");

    Serial.print("loco:");
    Serial.print(locoAddr);

    Serial.print(" speed:");
    Serial.print(locoSpeed);

    Serial.print(" direction:");
    Serial.println(locoDirection);

// expected output: "1: loco:3 speed:65 direction:1"
// output: "-18248: loco:-18248 speed:-18248 direction:-18248"

the sscanf function returns the expected integer 4 but doesn't fill the variables with the values so only the default values are printed.

I'm out of ideas what the issue could be. Maybe someone on the internet has a solution.

Edit 1: For better understanding of the complexity of the sketch this might help to understand the code a bit better:

  1. loop in .ino file calls static Commander::process().
  2. Commander::_readLine(...); get called and reads the data from serial.
  3. some more validation is done but should not matter in this case
  4. Commander::parseCommand(command); get called with the command.
  5. registers->setThrottle(command); gets called when the command starts with a "t". registers is volatile so I'm able to call it from the interupt.
  6. in PackageRegisterList::setThrottle is the sscanf method located.
1 Answers

I usually use this version stolen from the Linux kernel:

int ASCII_vsscanf(const char * buf, const char * fmt, va_list args)
{
    const char *str = buf;
    char *next;
    char digit;
    int num = 0;
    int qualifier;
    int base;
    int field_width;
    int is_sign = 0;

    while(*fmt && *str) {
        /* skip any white space in format */
        /* white space in format matchs any amount of
         * white space, including none, in the input.
         */
        if (isspace(*fmt)) {
            while (isspace(*fmt))
                ++fmt;
            while (isspace(*str))
                ++str;
        }

        /* anything that is not a conversion must match exactly */
        if (*fmt != '%' && *fmt) {
            if (*fmt++ != *str++)
                break;
            continue;
        }

        if (!*fmt)
            break;
        ++fmt;

        /* skip this conversion.
         * advance both strings to next white space
         */
        if (*fmt == '*') {
            while (!isspace(*fmt) && *fmt)
                fmt++;
            while (!isspace(*str) && *str)
                str++;
            continue;
        }

        /* get field width */
        field_width = -1;
        if (isdigit(*fmt))
            field_width = skip_atoi(&fmt);

        /* get conversion qualifier */
        qualifier = -1;
        if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' ||
            *fmt == 'Z' || *fmt == 'z') {
            qualifier = *fmt++;
            if (unlikely(qualifier == *fmt)) {
                if (qualifier == 'h') {
                    qualifier = 'H';
                    fmt++;
                } else if (qualifier == 'l') {
                    qualifier = 'L';
                    fmt++;
                }
            }
        }
        base = 10;
        is_sign = 0;

        if (!*fmt || !*str)
            break;

        switch(*fmt++) {
        case 'c':
        {
            char *s = (char *) va_arg(args,char*);
            if (field_width == -1)
                field_width = 1;
            do {
                *s++ = *str++;
            } while (--field_width > 0 && *str);
            num++;
        }
        continue;
        case 's':
        {
            char *s = (char *) va_arg(args, char *);
            if(field_width == -1)
                field_width = INT_MAX;
            /* first, skip leading white space in buffer */
            while (isspace(*str))
                str++;

            /* now copy until next white space */
            while (*str && !isspace(*str) && field_width--) {
                *s++ = *str++;
            }
            *s = '\0';
            num++;
        }
        continue;
        case 'n':
            /* return number of characters read so far */
        {
            int *i = (int *)va_arg(args,int*);
            *i = str - buf;
        }
        continue;
        case 'o':
            base = 8;
            break;
        case 'x':
        case 'X':
            base = 16;
            break;
        case 'i':
                        base = 0;
        case 'd':
            is_sign = 1;
        case 'u':
            break;
        case '%':
            /* looking for '%' in str */
            if (*str++ != '%')
                return num;
            continue;
        default:
            /* invalid format; stop here */
            return num;
        }

        /* have some sort of integer conversion.
         * first, skip white space in buffer.
         */
        while (isspace(*str))
            str++;

        digit = *str;
        if (is_sign && digit == '-')
            digit = *(str + 1);

        if (!digit
                    || (base == 16 && !isxdigit(digit))
                    || (base == 10 && !isdigit(digit))
                    || (base == 8 && (!isdigit(digit) || digit > '7'))
                    || (base == 0 && !isdigit(digit)))
                break;

        switch(qualifier) {
        case 'H':   /* that's 'hh' in format */
            if (is_sign) {
                signed char *s = (signed char *) va_arg(args,signed char *);
                *s = (signed char) ASCII_simple_strtol(str,&next,base);
            } else {
                unsigned char *s = (unsigned char *) va_arg(args, unsigned char *);
                *s = (unsigned char) ASCII_simple_strtoul(str, &next, base);
            }
            break;
        case 'h':
            if (is_sign) {
                short *s = (short *) va_arg(args,short *);
                *s = (short) ASCII_simple_strtol(str,&next,base);
            } else {
                unsigned short *s = (unsigned short *) va_arg(args, unsigned short *);
                *s = (unsigned short) ASCII_simple_strtoul(str, &next, base);
            }
            break;
        case 'l':
            if (is_sign) {
                long *l = (long *) va_arg(args,long *);
                *l = ASCII_simple_strtol(str,&next,base);
            } else {
                unsigned long *l = (unsigned long*) va_arg(args,unsigned long*);
                *l = ASCII_simple_strtoul(str,&next,base);
            }
            break;
        case 'L':
            if (is_sign) {
                long long *l = (long long*) va_arg(args,long long *);
                *l = ASCII_simple_strtoll(str,&next,base);
            } else {
                unsigned long long *l = (unsigned long long*) va_arg(args,unsigned long long*);
                *l = ASCII_simple_strtoull(str,&next,base);
            }
            break;
        case 'Z':
        case 'z':
        {
            size_t *s = (size_t*) va_arg(args,size_t*);
            *s = (size_t) ASCII_simple_strtoul(str,&next,base);
        }
        break;
        default:
            if (is_sign) {
                int *i = (int *) va_arg(args, int*);
                *i = (int) ASCII_simple_strtol(str,&next,base);
            } else {
                unsigned int *i = (unsigned int*) va_arg(args, unsigned int*);
                *i = (unsigned int) ASCII_simple_strtoul(str,&next,base);
            }
            break;
        }
        num++;

        if (!next)
            break;
        str = next;
    }
    return num;
}

Limited but small and good enough for me.

Related