Simple way to decode ESP8266 / UART incoming transmission?

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm getting data from an ESP8266, some of it are generic messages regarding connections like: "0,CONNECT" but the ones I am after are formatted: "+IPD,0,3:D" those are the ones I am after, the part after : is potentially a command I like to capture.

 

Incoming UART data goes into a buffer which is checked and read in the main while(!) loop, one character at a time.

 

What would be the best strategy for this?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I would make sure the incoming data is null terminated in the buf [1], then use standard C string handling functions (assuming you're writing in C) eg. strchr, strtok, strcmp etc.

You could roll your own functions, but I would tend to stick to the standard functions unless there is a good reason not to.

 

[1] EDIT I was imagining you would capture a complete 'line' in the buf, where the end of line is indicated by CR or LF, then stick a '\0' on the end (or in place of the CR / LF).

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The incoming data is always terminated by the new line character (0xa), so I can "wait" for that.

However, in general, I would like to save the incoming characters into a buffer and wait for the termination character, than look for the beginning of the command using strchr (it starts with a ':'), extract the command up to the terminating character and decode it + execute some code according to that command.

reset the buffer and start over.

 

That's pretty much it?

A primitive version would be fine at this point, no "safety" required.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

When "parsing" a data stream there are two main approaches you might usually use:

 

1) per character processing using a state machine. If you are looking for "+IPD" then start in an idle state then when a '+' goes by switch to a "waiting for I state". If the next character is 'I' then switch to "waiting for P" state, but if it isn't return to idle (unless you are also looking for "+I(something else)"

 

2) per line. Decide on a line delimiter (usually \n). When you see that you have a whole sentence. So now things like strncpy() or, my favourite, strstr() come into play. For example strstr(line, "+IPD") will be non 0 if the line contains "+IPD". In fact it's not just non 0 , it's a pointer to the +. So p = strrstr(line, "+IPD") + 4 would set p to the character after 'd'.

Last Edited: Sat. Jul 13, 2019 - 12:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I prefer the 'per line' approach unless you really need some micro-optimisation.

If you capture incoming chars into buf, then when you get the 0xa you write a '\0' into the buf  (to null terminate) and call your 'process_line' function.

This keeps things separate and you can easily change the 'process_line' function.

If you are interesetd in lines like <command> : <data>, this is a typical case for use of strtok, split the line into tokens separated by ':'

The first token is your command, the second is data.

 

Something like (untested. haven't used strtok for a while)

static void process_line(char *line)
{
    char *cmd_tok = strtok(line, ":");
    if (cmd_tok == NULL)
        return;

    char *data_tok = strtok(NULL, ":");
    if (data_tok == NULL)
        return;

    /* now have a cmd and data tok, process as required */
}

 

EDIT  changed deliimiter list in strtok (it should be a string ":" rather than a character ':');

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Alternatively, having read your post more carefully, if you are interesetd in line like

<stuff> : <cmd>

and you just want whatever comes after the : you could do other things like (again all untested)

static void process_line (char *line)
{
    char *colon =  strchr(line, ':');
    if (colon == NULL)
        return;
    /* colon now points to the : in line so the command part starts at colon + 1 */

    if (strcmp(colon + 1, "XYZ") == 0)
    {
        /* receievd comand XYZ */
    }

}

The main point is it's easy to update the process_line function to do whatever it is you need to do.

Last Edited: Sat. Jul 13, 2019 - 01:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
while (!(RingBuffer_IsEmpty(&RXBuffer)))
{
    tempChar = RingBuffer_Remove(&RXBuffer);
    append (lineBuffer, tempChar);

    // terminating char
    if (tempChar == '\n')
    {
        startIndex = strstr(lineBuffer, "PD:");

        if (startIndex == NULL)
            {
                // reset buffer
                lineBuffer[0] = '\0';
            }
        else
        {
            strncpy(ESPcommand, startIndex + 3 , strstr(lineBuffer,'\r\n') - 1 - startIndex + 3);
            LCDGWriteString(ESPcommand);
            // TODO PROCESS COMMAND
        }
    }
}

OK so I went with "line processing", still trying to work out the bugs. This code is currently in the main while(1) loop.

 

When a new char was received via UART it is read into tempChar.

tempChar is appended to a string (/0 terminated) called lineBuffer.

 

When I get '\n' (line terminator) I check if PD: is an expression I can find in the line buffer. If not (== NULL), I resent the buffer. <-- not sure this part is correct

string buffer reset was stupid... changed to set the first char to '/0'.

If I get a valid index, I copy from (startindex + 3) up to where end of line is - 1.

 

This is the idea anyway, this does not work right now :)

 

UPDATE: the line buffer is "cleared" by writing '\0' to index 0. I did not use strncpy correctly, now I think it is correct. strstr does not seem to work as I think it does.

 

 

Last Edited: Sat. Jul 13, 2019 - 02:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

slow_rider wrote:

            strncpy(ESPcommand, startIndex + 3 , strstr(lineBuffer,'\r\n') - 1 - startIndex + 3);

The 3rd argument to strncpy is wrong.

The 3rd argument to strncpy needs to be a length (it's actually a size_t, that's essentially an unsigned integer value).

You're giving it a pointer ie. result of strstr (which is pointer to char) with some pointer arithmetic, the result is still a pointer.

EDIT: I was wrong, now you've changed it, you have startIndex in there which is also a pointer, so you have ptrtype - integer - ptrtype + integer, which does result in an integer value. But the calculation still looks wrong to me.

 

(and also the  '\r\n' would need double quotes "\r\n")

Also strncpy is a bit weird and it doesn't always guarantee to null terminate.

Almost certainly not the right function to use, but it will work. (strncat is generally better).

 

You seem to be retaining the CRLF in the line buffer. You can do it that way, personally I would tend to overwrite the CR with a 0 so I had a null terminated string ending after the last char in the command

char *c;
c = strchr(line, '\r');
if (c != NULL)
{
    *c = '\0';
}

You can then simply do

LCDGWriteString(start_index + 3);

When you're resetting the line buffer, do you have an index that needs to be reset to 0 as well ?

Last Edited: Sat. Jul 13, 2019 - 03:49 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

OK I have went over the code and corrected the mistakes you had pointed out and now it seems to be working fine!

while (!(RingBuffer_IsEmpty(&RXBuffer)))
{
    tempChar = RingBuffer_Remove(&RXBuffer);
    append (lineBuffer, tempChar);
            
    // terminating char
    if (tempChar == '\n')
    {
        startIndex = strstr(lineBuffer, "+IPD");
                
        if (startIndex == NULL)
            lineBuffer[0] = '\0'; // reset buffer
        else
        {
            strncpy(ESPcommand, startIndex + 9 , strlen(startIndex + 9) - 2);
            lineBuffer[0] = '\0'; // reset buffer
            LCDGWriteString(ESPcommand);
            // TODO PROCESS COMMAND
        }
    }
}

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

We can't see what your append function does, but unless the append function null terminates after each char is added, I don't see how you know that lineBuffer is null terminated.

If you are specifying that a CRLF pair must be sent to terminate line, you could simply not append a CR, and append 0 in place of appendng LF. Then you have a nicely null terminated string to process without the CRLF at the end.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
// with automatic termination
void append(char* s, char c)
{
	uint8_t len = strlen(s);
	
	s[len] = c;
	s[len+1] = '\0';
}

sorry I was sure I've posted it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok that's one way to do it.

I think most folks would sacrifice a byte of RAM to maintain a write_index into the buffer (and then make sure you append a 0 before using it as a string, and the reset becomes write_index = 0), rather than having to run through strlen each time you append, but if the bufer is small it's not a big deal.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To test the string processing stuff I'd forget the AVR for now and use a PC test harness feeding fixed test strings for processing. Once you have ironed out all the bugs in the much easier environment then rebuild for AVR