sscanf

I'm trying to read a string of serial data
here is an example of the data coming in from the Soft serial port

N 1450556518 660 192.168.1.2 5 24.625 24.500 24.625 24.500 25.187

this is my complete code:

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <AltSoftSerial.h>
#include <SoftwareSerial.h>
#include <Time.h> 
#include <Streaming.h>

#define LCD_COLS 20 //LCD is 20x4
#define LCD_ROWS 4

//AltSoftSerial mySerial; //tx9 rx8
SoftwareSerial mySerial(8, 9); // RX, TX removed to use bidirectinal read.
LiquidCrystal_I2C lcd(0x27,LCD_COLS,LCD_ROWS);


char Ip_string[15];
unsigned long SerialStartTime = 0, time_UTC = 0; // storage for onset of serial data
bool firstChar = true;  // if first char = or not. 
uint8_t numberOfDevices; // get the actual number of temperatures per line of text. global as its used by many things. 
float temps[8]; //up to 8 devices 
const char *monthName[12] = 
  {
	  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
	  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  };
const char *dayName[7] =
{
	"Sun","Mon","Tue","Wed","Thu","Fri","Sat"
};

//String to gather string of data that can be split up process. 
//Flag to mark the end of the input string. 
//need to used string because varible length.
tmElements_t tm;
char inputString[200] ="";
bool stringComplete = true;

void setup ()
{
	if(timeStatus() == timeNotSet)
	{
		getDate(__DATE__); //get compile time update tm (tmElements)
		getTime(__TIME__);
		setTime(makeTime(tm)); //set the system time with it. 
	}
	mySerial.begin(9600);
	Serial.begin (115200);
	Wire.begin(); // intitiate wire library
	lcd.init();
	lcd.backlight();
	//state = NONE;
} 


void serialEvent(void)
{
	while(mySerial.available())
	{
		if (firstChar)
		{
			SerialStartTime = millis();
			firstChar = false; //set the serialstart only once.
		}
		
		char  d = mySerial.read();//convert d into char
		char * str2 = &d;//convert into pointer and then obtain address
		if (*str2 == '\n')
				return;
		if (*str2 == '\r' ) // look for the carriage return
		{
			strncat(inputString,char(0),1);
			stringComplete = true; //set complete flag to true. 
		}
		else
		{
			strncat(inputString,str2,1); //concantenate it
		}
	}
}

void processCstring()
{
	//inputString contains formatted string
	Serial.print(">>>");Serial.print(inputString);Serial.println("<<<");
	char display_record[2]; //determine if N for display or R for record. 
	char temp_UTCString[12];
	unsigned long time_UTC;
	unsigned int time_DST;
	char *ptr_inputString;
	ptr_inputString = inputString;
	uint8_t rem_string;
	sscanf(inputString,"%s %s %d %s %d %n", display_record, temp_UTCString, &time_DST , Ip_string, &numberOfDevices , &rem_string);
	time_UTC = atol(temp_UTCString);//convert string into long 
	float delay = 0.5 + (millis() - SerialStartTime) / 1000.0; //round up serial start time 
	unsigned long currTime  = time_UTC + time_DST * 60 +1 ; //cant work out why the 1 is needed. 
	
	for (int n = 0; n < numberOfDevices; n++) //process the number of floating points. 
	{
		ptr_inputString += rem_string;
		char tempCString[10];
		sscanf(ptr_inputString,"%s", tempCString); 
		Serial.print(">>");Serial.print(ptr_inputString);Serial.println("<<");
		ptr_inputString += strlen(tempCString); // pointer is null terminated. and moves along by remstring. 
		Serial.print("tempCString ");
		Serial.println(tempCString);
		Serial.print("ptr_inputString ");
		Serial.println((int)ptr_inputString);
		Serial.println();
		
		//temps[n] = atof(tempCString); 
	}
	setTime(currTime);
	Serial.println(now());
	Serial.println();
	firstChar = true; //set the first char for the next read to start millis counter. 
}
void loop ()
{
	serialEvent();
	if(stringComplete)
	{
		//process input string. 
		//Serial.print(inputString);
		processCstring();
		inputString[0]=0;
		stringComplete = false;
	}
	printLCDtime();
	lcd.setCursor(0,1);
	lcd.print(Ip_string);
	// do other stuff in loop as required

}  // end of loop

void printLCDtime(void)
{
	char buffe[50];
	sprintf(buffe,"%02d:%02d:%02d %3s %02d%03s%02d",hour(), minute(), second() ,dayName[weekday() - 1],day(), monthName[(month() - 1)], (year() % 100));
	lcd.setCursor(0,0);
	lcd.print(buffe);
}

//----- function to convert compile time to tm.element
bool getDate(const char *str)
{
  char Month[12];
  int Day, Year;
  uint8_t monthIndex;

  if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
  for (monthIndex = 0; monthIndex < 12; monthIndex++) {
    if (strcmp(Month, monthName[monthIndex]) == 0) break;
  }
  if (monthIndex >= 12) return false;
  tm.Day = Day;
  tm.Month = monthIndex + 1;
  tm.Year = CalendarYrToTm(Year);
  return true;
}

bool getTime(const char *str)
{
  int Hour, Min, Sec;

  if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
  tm.Hour = Hour;
  tm.Minute = Min;
  tm.Second = Sec;
  return true;
}

as you will have worked out from looking at this far from elegant code I'm still a bit of noob.
the problem I am having is this section:

void processCstring()
{
	//inputString contains formatted string
	Serial.print(">>>");Serial.print(inputString);Serial.println("<<<");
	char display_record[2]; //determine if N for display or R for record. 
	char temp_UTCString[12];
	unsigned long time_UTC;
	unsigned int time_DST;
	char *ptr_inputString;
	ptr_inputString = inputString;
	uint8_t rem_string;
	sscanf(inputString,"%s %s %d %s %d %n", display_record, temp_UTCString, &time_DST , Ip_string, &numberOfDevices , &rem_string);
	time_UTC = atol(temp_UTCString);//convert string into long 
	float delay = 0.5 + (millis() - SerialStartTime) / 1000.0; //round up serial start time 
	unsigned long currTime  = time_UTC + time_DST * 60 +1 ; //cant work out why the 1 is needed. 
	
	for (int n = 0; n < numberOfDevices; n++) //process the number of floating points. 
	{
		ptr_inputString += rem_string;
		char tempCString[10];
		sscanf(ptr_inputString,"%s", tempCString); 
		Serial.print(">>");Serial.print(ptr_inputString);Serial.println("<<");
		ptr_inputString += strlen(tempCString); // pointer is null terminated. and moves along by remstring. 
		Serial.print("tempCString ");
		Serial.println(tempCString);
		Serial.print("ptr_inputString ");
		Serial.println((int)ptr_inputString);
		Serial.println();
		
		//temps[n] = atof(tempCString); 
	}
	setTime(currTime);
	Serial.println(now());
	Serial.println();
	firstChar = true; //set the first char for the next read to start millis counter. 
}

Here is an example of my output to the hardware serial port. For some reason the pointer the to string now shows no characters for sscanf to read on the second pass and for the last 5 hours I can't work out why.

>>>N 1450557902 660 192.168.1.2 5 24.812 24.750 24.812 24.625 25.625 <<<
>>24.812 24.750 24.812 24.625 25.625 <<
tempCString 24.812
ptr_inputString 637

>><<
tempCString 24.812
ptr_inputString 674

>><<
tempCString 24.812
ptr_inputString 711

>><<
tempCString 24.812
ptr_inputString 748

>><<
tempCString 24.812
ptr_inputString 785

1450597503

If you are already using floats, it would probably be easier to load the floating point version of sscanf. With that, you could read and convert the string in about two lines of code. Google can help you figure out how to load the proper version.

Hello,

There are countless of answered and solved topics like yours on this forum about handling/buffering/parsing Serial inputs. Maybe one per day since I visit this forum. Maybe another one is being written as I write this!

Your code is hard to follow and too complicated for such simple task. Maybe try this. :slight_smile:

The reason that I have a string that I parse over is that I don't know how many Floats I am going to get with each string, neither at compile time nor until the string arrives.
This is allowing for the addition of extra temp probes (I have allowed for 8 here). or if a temp probe fails.

I was reluctant to use the sscanf for floats a there are numerous links here to the difficulty of converting a string to a float unless you use the dtosrf function as the sprintf version does not process floats properly (or didn't when I last this an extensive search on this forum and this site LMGTFY - Let Me Google That For You :slight_smile: )

so as I don't know how many floats I am parsing, the first serial number before the series of floats is the number of floats :slight_smile:

also there is more information that is parsed depending on the first character which is a status flag.

so as I don't know how many floats I am parsing, the first serial number before the series of floats is the number of floats

That tiny complication would add another 2-3 lines to the sscanf code.

As with sscanf, sprintf works just fine with floats, if you load the correct version. See the signature line of reply #2 for how to do it.

Have a look at function strtok

I also recommend that you read this.

jremington:
That tiny complication would add another 2-3 lines to the sscanf code.

As with sscanf, sprintf works just fine with floats, if you load the correct version. See the signature line of reply #2 for how to do it.

thanks for the link :slight_smile: but 2k is too much of an overhead as I have much more to do and I wanted to keep the memory use small. I am using a pro-mini and don't want to run out of memory as that happened when I attempted this last time. I need to add LCD output and build a GUI to scroll through historical data using a rotary encoder :frowning:

I can't see the extra 2-3 lines of code, sorry. I can only see scanf as a fixed command, that will fail if there are too few or too many values especially as the variables have to be declared first. I can't google logical errors for my code, and the problem is a logical error, not a syntax or semantic one.

The reason I opted to send the floats unchanged as they will be saved to a sd card that will be read by a card reader into an excel document. Rather than make code that changes the floats into ints and then change them back to floats I just left them as they were.

As far as the Nick Gammon code - even had his comments at the top of the page - I was using a state machine, but I decided re-assembling the entire string was a better approach as I was struggling with what to do with am IP string. (I removed his comments at the top out of embarrassment. but planned to do an attribution).

Rather than make code that changes the floats into ints and then change them back to floats I just left them as they were.

And look at all the trouble that has caused! No consumer grade sensor needs floats, because you can't buy any that are even as accurate as 1 part in 10,000. You just aren't thinking this through.

@guix: I think that strtok will do exactly what I am trying to do already. So that should help a great deal.

as far as accuracy of the ds18b20. No I haven't calibrated them using a triple point. But I do need sub integer accuracy as the temperatures measured are between 1.5°c and 8.5 °c. No I don't need 3 decimal points, or 5 signficant figures either, but What the hey? It's way over engineered with 5 temp probes, 2 displays, logging to thingspeak and to a local webserver
In fact I don't need to any of it as my vaccine fridge and my commercial datalogger has everything to already set up. I just wanted to do it better. I don't really see the harm in learning to deal with floats from the get-go.

		char  d = mySerial.read();//convert d into char
		char * str2 = &d;//convert into pointer and then obtain address
		if (*str2 == '\n')
				return;
		if (*str2 == '\r' ) // look for the carriage return
		{
			strncat(inputString,char(0),1);
			stringComplete = true; //set complete flag to true.
		}
		else
		{
			strncat(inputString,str2,1); //concantenate it
		}

Geez, what a mess. That is unnecessarily complex code. There is NO reason to create a pointer and then derefernce the pointer. Just use the variable directly:

     if(d == '\n')

As for concatenating a NULL, that is pointless.
Use strcat() instead of strncat(), and the string will be NULL terminated at all times.

Using an index variable means that you don't need strcat(), either.

char buffer[40];
byte index = 0;

...

   buffer[index++] = d; // Add the character to the specified location and increment the location
   buffer[index] = '0'; // NULL terminate the string

inputString is a stupid name for a string. Avoid having String appear ANYWHERE in your code.

But I do need sub integer accuracy as the temperatures measured are between 1.5°c and 8.5 °c.

Nonsense. Multiply the sensor output by 100 to get an integer and transmit that.

@ PaulIS. I was hoping nobody would notice that bit of code. I was mainly struggling with the syntax of the code at that point. It was the first point that I tried to use a pointer De-novo for a useful purpose, and not copying another persons code. I was planning to figure out where I was going wrong later, and just excepted that it was working, till I sorted out the problem at hand. I did comment what I was doing so I could follow the logical thread as to why it was working but not as expected.

It is also why most people are reluctant to putting up their ENTIRE CODE as its a bit like being naked :slight_smile:
Not that nakedness is particularly troubling to me.I apologised in advance for the in-elegant code. So I forgive myself :slight_smile:

Having only just discovered srcat and strncat I was trying to work out which functions add trailing NULL and which don't.

@jremington. Just to let you know I already did that multiplying and dividing trick. But what happens when you have a number with variable amount of decimal places? Sure the probe is nowhere near accurate enough at +/- 0.5°K accuracy. However looking at it from a pure data conversion, you can have the following range of numbers:

100.000 , 10.000 , 1.000, 0.100 , -10.000

so If I get the string 1001 is that 100.1 or 10.01 or 1.001 ?

I can add ,though a platforms.txt, floating point ability to sscanf and sprintf but at a 2k premium. I could have used the String object from the start and the code was easier to do but again at a 2k premium. I thought that price was too high so I sought to use cstrings. the Code was a great deal more difficult for me and I'm not even sure I haven't eaten my 2k premium with in-efficient code. But I can hack at it till it fits a smaller bundle: something that I can't do with Strings.

But what happens when you have a number with variable amount of decimal places?

It doesn't matter.

Convert the temperature to an integer, in units of 0.1 degree (101 = 10.1 degrees). That representation is far is more precise than your sensor.