Is there a "line receiver" library for e.g. the serial ports?

Status
Not open for further replies.
I've extracted the parser parts of a library I made myself (of which I do not want to post the whole thing) and added them to a sketch using your LineReceiver - to say this implementation is a bit kludgy might be like saying sewers are a little smelly but in my defense I can't really spend a lot of time on it right this second and my 'strfig' stuff was made to be called like 'strfig(&myBuffer[myPtr],&myPtr[,cmds])' and by using it that way you didn't have to fix the buffer pointer (accord the offset) and zero the offset for each little call - you could literally just go "Serial.printf("3 values=%f,%f,%f\n",strfig(&myBuffer[myPtr],&myPtr),strfig(&myBuffer[myPtr],&myPtr),strfig(&myBuffer[myPtr],&myPtr));" and it would take care of itself to the end of the buffer.

Maybe it is all just ugly and nobody wants anything like it, but just in case:
Code:
#include <LineReceiver.h>

float strHex(char* buf, unsigned char* ptr) // I don't usually leave strHex directly callable, it is a float hidden away in the .cpp file because I figure I just want to call strfig
{
	unsigned char go=1;
	unsigned char breaker=0;
	float tmp=0;

	while((*buf<48)&&(*buf!=0)) // while the buffer content at pointer is any of the symbolic or special characters advance unless you hit \0
	{
		buf++; // the pointer into the buffer the user passed us
		*ptr+=1; // the user's copy of the buffer pointer is updated
	}
	if((*buf=='0')&&(*(buf+1)=='x'))
	{
		buf+=2; // just skipping over '0x' in the string
		*ptr+=2; // I'm not going to explain this again.
	}
	if(*buf==0) return 0; // what number?
	while(go!=0&&(++breaker)) { 
		if((*buf>64)&&(*buf<71)) *buf-=7; // 'A'-'F' modify appropriately for basic math below
		if((*buf>96)&&(*buf<103)) *buf-=39; // 'a'-'f' lower case has to be catered - tempting to just add necessary & (& brackets!) in line above to line above but nvm.
		if((*buf>47)&&(*buf<64)) { // so, if this could have been valid hex in text before it got mangled by either of preceeding lines we multiply and add appropriately.
			tmp=(tmp*16)+((*buf)-48);
			buf++;
			*ptr+=1;
		} else {
			go=0; // we are done, stop looping.
		}
	}
	return tmp;
}

float strfig(char* buf, unsigned char* ptr) // floating point number parser.
{
	unsigned char go=1;
	unsigned char breaker=0;
	float tmp=0;
	float frac=0;
	float sign1=1; // I'm not going to comment this one up atm, soz.

	while(go!=0&&(++breaker)) {
		if(*buf!=0) {
			if((*buf<33)||(*buf=='=')||(*buf==',')||(*buf==';')||(*buf==':')) {
				buf++;
				*ptr+=1;
			} else {
				go=0;
			}
		} else {
			go=0;
		}
	}

	if(((*buf==0)||(*buf<48)||(*buf>57))&&(*buf!='-')) {
		return 0;
	}
	if(*buf=='-')
	{
		sign1=-1;
		buf++;
		*ptr+=1;
	}
	if((*buf=='0')&&(*(buf+1)=='x')) {
		return sign1*strHex(buf,ptr);
	}
	go=1;
	while(go!=0&&(++breaker)) {
		if((*buf>47)&&(*buf<58)) {
			if(frac)
			{
				tmp=tmp+(((*buf)-48)/frac);
				frac*=10;
			} else {
				tmp=(tmp*10)+((*buf)-48);
			}
			buf++;
			*ptr+=1;
		} else if(*buf=='.') {
			if(frac) return sign1*tmp; // second decimal point in a number is bullshit.
			frac=10;
			buf++;
			*ptr+=1;
		} else {
			go=0;
		}
	}
	return sign1*tmp;
}

unsigned char strfig(char* buf1, unsigned char* ptr, const char* cPtr) // parses looking for a match in a list held in obvious enough format,
{                                                                     // returns location in list counting from 1 where 0 means no match and the ptr was not advanced
	const char * sPtr; // temporary copy for cPtr
	char * tPtr; // temporary copy of buf1
	unsigned char count=0,cnt2,go=1; // some counter variables to track stuff.
	while(go)
	{
		count++; // this counter will be returned to the user's sketch if there is a match
		sPtr=cPtr; // copy the pointer to the const char cmds[] array
		while(*cPtr!=0) { // while the content of the array at this pointer is not 0
			cPtr++; // advance it
		}
		cPtr++; // now shift it onto the start of the next command, or the trailing \0 terminator
		tPtr=buf1; // we use a copy of the Buf1 pointer because we need to be able to reset here repeatedly
		cnt2=0; // cnt2 will track along how much to shift the end user's pointer by.
		while((*sPtr==*tPtr)&&(*tPtr!=0)) // While the cmd being compared now matches and we don't hit the \0 expected at the end of *Buf1[]
		{
			cnt2++;
			sPtr++; // just all advance along
			tPtr++;
		}
		if(*sPtr==0) // the cmd matched if the temporary pointer for cmd[] is 'parked' on a terminator at this point in the processing.
		{
			*ptr+=cnt2; // adjust the end user's pointer kindly for them
			return count; // count= which of "first\0second\0third\0forth\0so-on\0last\0\0" matched at the position in the buffer the routine was handed as buf1.
		}
		go=*cPtr; // at this point if there is another cmd then this pointer in cmd[] will be pointing at the first character, if not it will be on the second terminator which is expected.
	}
	return 0; // none matched, we did not mangle anything, you can try something else on the buffer if you like.
}

// commands are stored simply enough
const char cmds[]="test1\0test2\0test3\0tested\0\0";

void LineIn(const char* line)
{
  // Serial.printf("callback; %s\n", line);
  uint8_t ptr=0; // I need to find out how much strfig routines would have adjusted the pointer each time.
  uint16_t temp1,temp2; // not owning the buffer means I cannot use strfig as easily as intended without some 'mucking about', I chose this way for the time I felt I could spare it.
  char* line1=(char*)line; // The darn const won't work the way I need it to for the methods I am applying.
  while(*line1!=0) // while we don't hit the end of the line.
  {
    switch(strfig(line1,&ptr,cmds)) { // check for cmds[]
    case 1: // test1
      line1+=ptr; // just moving my non const pointer to the position after the cmd[] because it needs doing.
      ptr=0; // Letting this accumulate would be really !funny
      Serial.printf("test1=%u\n",(uint16_t)strfig(line1, &ptr)); // strfig is a float and I told printf I was going to give it an unsigned integer of some sort, I figure 16 bits will do and casting is cool.
      line1+=ptr; // lol, I wish I owned that darn buffer!
      ptr=0;
    break;
    case 2: // test2
      line1+=ptr;
      ptr=0;
      temp1=(uint16_t)strfig(line1, &ptr);
      line1+=ptr;
      ptr=0;
      Serial.printf("test2=%u,%u\n",temp1,(uint16_t)strfig(line1, &ptr));
      line1+=ptr;
      ptr=0;
    break;
    case 3: // test3
      line1+=ptr;
      ptr=0;
      temp1=(uint16_t)strfig(line1, &ptr);
      line1+=ptr;
      ptr=0;
      temp2=(uint16_t)strfig(line1, &ptr);
      line1+=ptr;
      ptr=0;
      Serial.printf("test3=%u,%u,%u\n",temp1,temp2,(uint16_t)strfig(line1, &ptr));
      line1+=ptr;
      ptr=0;
    break;
    case 4: // tested
      line1+=ptr;
      ptr=0;
      Serial.println("Kludgy but tested :)");
    break;
    default:
      line1+=(ptr+1);
      ptr=0;
    }
  }
}

LineReceiver LineR(64,LineIn);


void setup() {
  Serial.begin(Serial.baud());
}

elapsedMillis heartbeat;

void loop() {
  if(Serial.available()) {
    LineR.push(Serial.read());
    heartbeat=0; // if you are talking to me I don't need to invite you so much
  }

  /* if(Serial.available())
  {
    if(LineR.push(Serial.read()))
    {
      Serial.println("LineR.push(Serial.read()) returned 'true'");
    }
  } */
  
  
  if(heartbeat>10000)
  {
    Serial.println("(send me: test1=# or test2 # # or test3:#,#;# or tested coz I am pretty forgiving...)");
    heartbeat=0;
  }
}
Compiled and worked for me (not too embarrassingly) using Arduino 1.6.1 & Teensyduino 1.21 - it should happily show decimal results of you entering hex like 0x40; I was mistaken thinking I bothered making any code for converting numbers the other way, I just used printf :)
 
Last edited:
btw: It will accept (and (imho) do the right thing with) lines like
Code:
test3:15,12,0x0a test1=5 test2;1634:1508 tested test2 12
Right down to coping easily (not crashing) with the missing parameter on the last 'test2' there.

You can even strip out all the space characters in the string
Code:
test3:15,12,0x0atest1=5test2;1634:1508testedtest212
and expect a good result because I made it to be as forgiving as possible without inflating it more than I could bare.
 
Last edited:
Can you please add some comments describing what each function does? I'll try your example later to find out what exactly happens when I feed it with keypresses, but for now I'm having a hard time figuring out what the purpose of that code is. Sample input and expected output would be good.

Is it a simple command interpreter that routes "test3" to some function that reads a number of arguments and does something with them?
 
I've commented the code above as much as I could make me atm, I'm open to queries, suggestions and comments of any kind.


The result of compiling, uploading to Teensy, and then sending either of the lines I posted in #27, via any terminal which receives the heartbeat message every 10 seconds when not sending, when I try it is
Code:
test3=15,12,10
test1=5
test2=1634,1508
Kludgy but tested :)
test2=12,0
(Tho it is late and I am not plugging Teensy in right now to check so I have typed that out rather than the copy pasting of actual output I would prefer - pretty sure I have it verbatim tho.)
 
Here; I've tidied it a great deal and made a much better example of it. I've added some actual functionality to it too, just to try to make clearer how useful it can be; dW = digitalWrite and, of course, takes two arguments. dR = digitalRead and aR = analogRead, these only take one argument each. It does not echo the line sent so I add those in blue above the responses;
Code:
[COLOR="#0000CD"]test3:15,12,0x0atest1=5test2;1634:1508testedtest212[/COLOR]
test3=15,12,10
test1=5
test2=1634,1508
Kludgy but tested :)
test2=12,0
[COLOR="#0000CD"]aR 15[/COLOR]
analogRead(15)=158
[COLOR="#0000CD"]dR 13[/COLOR]
digitalRead(13)=0
[COLOR="#0000CD"]dW13,1dR13[/COLOR]
digitalRead(13)=1
Note: It does not reply for dW command (and the LED on my Teensy 3.1 did turn on for that last command :))

Edit: Tidied some more, fixed *my* non const conflict- fully tested, code below works OK.
Code:
#include <LineReceiver.h>

/****************************************** add to <somelibrary>.h **********************************/

float strfig(const char* buf1, unsigned char* ptr);
unsigned char strfig(const char* buf1, unsigned char* ptr, const char* cPtr);

/****************************************************************************************************/

const char cmds[]="test1\0test2\0test3\0tested\0dW\0dR\0aR\0pM\0help\0\0";

void LineIn(const char* line)
{
  // Serial.printf("callback; %s\n", line);
  uint8_t ptr=0,tmp1=0;
  while(line[ptr]!=0)
  {
    switch(strfig(&line[ptr],&ptr,cmds)) {
    case 1: // test1
      Serial.printf("test1=%u\n",(uint16_t)strfig(&line[ptr], &ptr));
    break;
    case 2: // test2
      Serial.printf("test2=%u,%u\n",(uint16_t)strfig(&line[ptr], &ptr),(uint16_t)strfig(&line[ptr], &ptr));
    break;
    case 3: // test3
      Serial.printf("test3=%u,%u,%u\n",(uint16_t)strfig(&line[ptr], &ptr),(uint16_t)strfig(&line[ptr], &ptr),(uint16_t)strfig(&line[ptr], &ptr));
    break;
    case 4: // tested
      Serial.println("Not so kludgy anymore, appears OK in testing :)");
    break;
    case 5: // dW (digitalWrite)
      tmp1=(uint8_t)strfig(&line[ptr], &ptr);
      digitalWrite(tmp1,(uint8_t)strfig(&line[ptr], &ptr));
    break;
    case 6: // dR (digitalRead)
      tmp1=(uint8_t)strfig(&line[ptr], &ptr);
      Serial.printf("digitalRead(%u)=%u\n",tmp1,digitalRead(tmp1));
    break;
    case 7: // aR (analogRead)
      tmp1=(uint8_t)strfig(&line[ptr], &ptr);
      Serial.printf("analogRead(%u)=%u\n",tmp1,analogRead(tmp1));
    break;
    case 8: // pM (pinMode)
      tmp1=(uint8_t)strfig(&line[ptr], &ptr);
      pinMode(tmp1,(uint8_t)strfig(&line[ptr],&ptr));
    break;
    case 9: // help
      Serial.println("'test1 #1','test2 #1 #2','test3 #1 #2 #3' and 'tested' can be sent, test1 expects 1\nnumeric parameter, test3 expects 3...\n");
      Serial.println("'dR 5' will reply with digitalRead(5)=#VALUE");
      Serial.println("'dW 5,1' will execute digitalWrite(5,1); but there is no reply atm.");
      Serial.println("'aR #1' will reply with analogRead(#1)=#VALUE");
      Serial.printf("'pM #1,#2' will execute pinMode(#1,#2); possible modes are\n%u=INPUT, %u=INPUT_PULLUP and %u=OUTPUT\n\n",INPUT,INPUT_PULLUP,OUTPUT);
      Serial.println("Parser is very forgiving, try sending\n'test3:15,12,0x0atest1=5test2;1634:1508testedtest212'\n");
    break;
    default:
      ptr++;
    }
  }
}




LineReceiver LineR(64,LineIn);


void setup() {
  Serial.begin(Serial.baud());
}

elapsedMillis heartbeat;

void loop() {
  if(Serial.available()) {
    LineR.push(Serial.read());
    heartbeat=0; // if you are talking to me I don't need to invite you so much
  }

  /* if(Serial.available())
  {
    if(LineR.push(Serial.read()))
    {
      Serial.println("LineR.push(Serial.read()) returned 'true'");
    }
  } */
  
  
  if(heartbeat>30000)
  {
    Serial.println("(send 'help' if you need it.)");
    heartbeat=0;
  }
}



/****************************************** Add to <somelibrary>.cpp ************************************/

float strHex(const char* buf, unsigned char* ptr)
{
	unsigned char go=1;
	unsigned char breaker=0;
	float tmp=0;
	uint8_t tmp1=0;
	while((*buf<48)&&(*buf!=0))
	{
		buf++;
		*ptr+=1;
	}
	if((*buf=='0')&&(*(buf+1)=='x'))
	{
		buf+=2;
		*ptr+=2;
	}
	if(*buf==0) return 0;
	while(go!=0&&(++breaker)) {
		tmp1=(uint8_t)*buf;
		if((tmp1>64)&&(tmp1<71)) tmp1-=7;
		if((tmp1>96)&&(tmp1<103)) tmp1-=39;
		if((tmp1>47)&&(tmp1<64)) {
			tmp=(tmp*16)+tmp1-48;
			buf++;
			*ptr+=1;
		} else {
			go=0;
		}
	}
	return tmp;
}

float strfig(const char* buf, unsigned char* ptr)
{
	unsigned char go=1;
	unsigned char breaker=0;
	float tmp=0;
	float frac=0;
	float sign1=1;

	while(go!=0&&(++breaker)) {
		if(*buf!=0) {
			if((*buf<33)||(*buf=='=')||(*buf==',')||(*buf==';')||(*buf==':')) {
				buf++;
				*ptr+=1;
			} else {
				go=0;
			}
		} else {
			go=0;
		}
	}

	if(((*buf==0)||(*buf<48)||(*buf>57))&&(*buf!='-')) {
		return 0;
	}
	if(*buf=='-')
	{
		sign1=-1;
		buf++;
		*ptr+=1;
	}
	if((*buf=='0')&&(*(buf+1)=='x')) {
		return sign1*strHex(buf,ptr);
	}
	go=1;
	while(go!=0&&(++breaker)) {
		if((*buf>47)&&(*buf<58)) {
			if(frac)
			{
				tmp=tmp+(((*buf)-48)/frac);
				frac*=10;
			} else {
				tmp=(tmp*10)+((*buf)-48);
			}
			buf++;
			*ptr+=1;
		} else if(*buf=='.') {
			if(frac) return sign1*tmp; // second decimal point in a number is bullshit.
			frac=10;
			buf++;
			*ptr+=1;
		} else {
			go=0;
		}
	}
	return sign1*tmp;
}

unsigned char strfig(const char* buf1, unsigned char* ptr, const char* cPtr)
{
	const char * sPtr;
	const char * tPtr;
	unsigned char count=0,cnt2,go=1;
	while(go)
	{
		count++;
		sPtr=cPtr;
		while(*cPtr!=0) {
			cPtr++;
		}
		cPtr++;
		tPtr=buf1;
		cnt2=0;
		while((*sPtr==*tPtr)&&(*tPtr!=0))
		{
			cnt2++;
			sPtr++;
			tPtr++;
		}
		if(*sPtr==0)
		{
			*ptr+=cnt2;
			return count;
		}
		go=*cPtr;
	}
	return 0;
}

/********************************************************************************************************/
 
Last edited:
Oh that's cool! Certainly cooler than implementing all those "what's the pin state" debugging messages all over the place...
 
Thanks Christoph, I've reworked it (in place) and it is heaps less embarrassing looking now. I fixed the problem my archaic crap had with a const buffer - never occurred to me to make something I would continuously change into any form of const, even after seeing others do it a fair bit; suddenly I like the idea tho, at least the above is much neater now imho.

To be honest I haven't a Teensy with me to try it with the changes but I expect it is OK; I will know for sure in 12 hours and 30 minutes (or so), don't hesitate to let me know if you try it in the mean time and I have broken it.
 
I've tested it now, what I left there last night worked fine but I have extended it with 'pM #,#' (pinMode) and 'help' commands coz I didn't like what I did for dR and dW before.

strfig isn't a super name; maybe I could rename it 'charParse' or 'parseLite' and add it to a pull request Christoph will have to decide whether or not to accept :)
 
Well, what does it do? It parses full lines of input, doesn't it? So parseLine would be ok, or LineParser if you turn it into an object.
 
I wouldn't make an object of them in a hurry, I suppose I could consider github'ing them as the start of a library for parsing but I think, unless there is something that delivers more or uses even less, they are obvious partner functions for LineReceiver.

Will you reject them if they are included in a pull request?
 
Status
Not open for further replies.
Back
Top