sprintf() with different inputs

Hi there, so I have a function in my .cpp file that is declared in a .h file and I am calling it in the .ino sketch with something like this:

char ID = "blah123";
  float value = 123.45;
  myFunction(ID, value);

so the input to the function is String and float. (However, I will take any suggestions as to what I should pass to the function). In the .cpp file I have something like this:

void myClass::myFunction(char ID, float value) {
  char charBuffer[64];
  sprintf(charBuffer, "Device ID is %s and value is %d", ID, value);
  Serial.print("charBuffer: ");
  Serial.println(charBuffer);
  Serial.print("charBuffer size: ");
  Serial.println(sizeof(charBuffer)); // This prints out 100 but I would like to get the actual size neglecting empty spaces in the buffer
}

I suppose you could test this in a single .ino sketch like this:

void setup() {
  Serial.begin(9600);
  
  char ID = "blah123";
  float value = 123.45;
  myFunction(ID, value);
}

void loop() {
  // Nothing here
}

void myFunction(char ID, float value) {
  char charBuffer[64];
  sprintf(charBuffer, "ID is %s and value is %d", ID, value);
  Serial.print("charBuffer: ");
  Serial.println(charBuffer);
  Serial.print("charBuffer size: ");
  Serial.println(sizeof(charBuffer)); // This prints out 100 but I would like to get the actual size neglecting empty spaces in the buffer
}

I am expecting something like "charBuffer: ID is blah123 and value is 123.45" but instead I get "charBuffer: ID is and value is -6554". Also, sizeof(charBuffer) obviously returns 64 because I had to set it's size, but I would really like to find out the actual size that is being occupied in the buffer. If I just say "char charBuffer[]" it gives me an error saying that the buffer size is unknown. I basically want a String that is of the following form (logically; the code below doesn't work):

String output2 = "ID is " + String(ID) + " and value is " + String(value);
  Serial.println(output2);

But that gives me "ID is a and value is 123.45". Why would String(ID) give me "a" instead of "blah123"? Ultimately my goal is to get a char array but I know how to use string.toCharArray() to get a char array from a String.

Why am I not getting what I'm expecting? Thanks!

this:

char ID = "blah123";

maybe should be an array.

sprintf(charBuffer, "Device ID is %s and value is %d", ID, value);

floats are not implemented for sprintf() on the Arduino IDE ;(

BulldogLowell:
this:

char ID = "blah123";

maybe should be an array.

sprintf(charBuffer, "Device ID is %s and value is %d", ID, value);

floats are not implemented for sprintf() on the Arduino IDE ;(

They COULD be... with about 5 lines of code... adding an option in Preferences to enable or disable floating point.

To say that floating point support uses a bit more memory is true.

To leave floating point support off by default in order to save memory makes sense.

To not have the simple user option to enable or disable it at the user's choice is criminal.

We see new releases of the IDE every few weeks or so, and still nothing IMPORTANT is fixed. The code is only made more bloated and convoluted. So much programming time spent, and not ONE SECOND spent on implementing features that users NEED.

Instead of encouraging people to eventually learn C and C++, the Arduino system only encourages users to accept mediocrity with a smile.

krupski:
They COULD be... with about 5 lines of code... adding an option in Preferences to enable or disable floating point.

it seems more like a standard on AVR/gcc where there is no floating point processor, cut back support for floats

OK so I scrapped the sprintf idea and just went with this test code:

void setup() {
  Serial.begin(115200);

  String ID = "blah123";
  float value = 123.45;
  myFunction(ID, value);
}

void loop() {
  // Nothing here
}

void myFunction(String ID, float value) {
  String outputStr = "ID is " + ID + " and value is " + value;
  Serial.print("outputStr: "); Serial.println(outputStr); // This gives me what I expect

  char outputChar[128];
  outputStr.toCharArray(outputChar, outputStr.length()+1);
  Serial.print("outputChar: "); Serial.println(outputChar); // This yields the same as above, as expected
}

This works great when testing it in a single .ino sketch, BUT for some strange reason when I put myFunction in the .cpp and declare it in the .h, it prints out everything as being empty. I even tried re-initializing them inside the function and they still printed out empty!

What could be going on here?

Well since there are 3 files and I'm actually editing pre-existing files, it's hard to show all the code here, but I will tell you that I'm editing this Adafruit FONA code. All I am doing is adding another case statement so that when "z" is pressed in the serial monitor, it runs this:

if (!fona.postData(deviceID, temperature))
    Serial.println(F("Failed to post data!"));
  break;

Then in the Adafruit_FONA.cpp I added this function:

boolean Adafruit_FONA_3G::postData(String deviceID, float temperature) {

	String requestStr = "GET /dweet/for/" + deviceID + "?temp=" + temperature + " HTTP/1.1\r\nHost: dweet.io\r\nContent-Length: 0\r\n\r\n";
	Serial.print("requestStr: "); Serial.println(requestStr);

 	char requestChar[128];
	requestStr.toCharArray(requestChar, requestStr.length()+1);
	Serial.print("requestChar: "); Serial.println(requestChar);

	// set bearer profile access point name
	if (apn) {
		// Send command AT+CGSOCKCONT=1,"IP","<apn value>" where <apn value> is the configured APN name.
		if (! sendCheckReplyQuoted(F("AT+CGSOCKCONT=1,\"IP\","), apn, ok_reply, 10000))
			return false;
	}

	// Configure bearer profile 1
	if (! sendCheckReply(F("AT+CSOCKSETPN=1"), ok_reply, 10000))
      return false;

  	// Start HTTPS stack
  	if (! sendCheckReply(F("AT+CHTTPSSTART"), ok_reply, 10000))
      return false;

  	// Connect to HTTPS server
  	if (! sendCheckReply(F("AT+CHTTPSOPSE=\"www.dweet.io\",443,2"), ok_reply, 10000)) // Use port 443 and HTTPS
      return false;

    String auxStr = "AT+CHTTPSSEND=" + String(requestStr.length());
    Serial.print("auxStr: "); Serial.println(auxStr);
    char auxChar[16];
    auxStr.toCharArray(auxChar, auxStr.length()+1);
    Serial.print("auxChar: "); Serial.println(auxChar);


  	if (! sendCheckReply(auxChar, ">", 10000))
      return false;

  	if (! sendCheckReply(requestChar, ok_reply, 10000))
      return false;

  	// Request URL
  	if (! sendCheckReply(F("AT+CHTTPSSEND"), ok_reply, 10000))
      return false;

	return true;
}

Then in Adafruit_FONA.h I added this at line 253:

boolean postData(String deviceID, float temperature);

Pretty simple. I press "z" in the serial monitor, it runs that case statement and calls the function in .cpp. I think it might actually be a memory issue like you mentioned. That's the only explanation that would make sense at the moment.

The function class isn't the issue because at the very top of the .ino there is a line that declares it:

Adafruit_FONA_3G fona = Adafruit_FONA_3G(FONA_RST);

so "fona" refers to "Adafruit_FONA_3G". But even if I manually declare the variables deviceID and temperature inside the .cpp function, the strings print out as empty, so it's not a value-passing problem.

If you go to the github repo I linked a couple posts above you will see all the code. In that same post I mentioned the exact changes I made to the code, which should be quite self-explanatory. You are referring to the first set of code I posted in this thread, which was the example code to explain my general idea at the start.

OK, I have attached them here

  • For the .ino file I added a another case statement starting at line 618.
  • In the .cpp file I added a new function at line 1279
  • In the .h file I added a single line at line 253

FONAtest_modified.ino (21.6 KB)

Adafruit_FONA.cpp (54 KB)

Adafruit_FONA.h (9.17 KB)

On a separate note, here is code purely to try to eliminate String-related issues and to test sprintf:

void setup() {
  Serial.begin(115200);

  char deviceID[] = "blah123";
  float temperature = 123.45;
  myFunction(deviceID, temperature);
}

void loop() {
  // Nothing here
}

void myFunction(char deviceID, float temperature) {
  char request[128];
  char tempBuff[7]; // A temperature value shouldn't get past this length: 123.456
  
  String tempStr = String(temperature); // First convert from float to String (since it's small, memory shouldn't be an issue)

  // Turn "temperature" into a char array
  for (int i = 0; i < sizeof(tempBuff); i++) {
    tempBuff[i] = tempStr.charAt(i);
  }
  Serial.print("tempBuff = "); Serial.println(tempBuff); // This prints out "tempBuff = 123.45" which is OK
  
  sprintf(request, "GET /dweet/for/%s?temp=%s HTTP/1.1\r\nHost: dweet.io\r\nContent-Length: 0\r\n\r\n", deviceID, tempBuff);
  Serial.print("request = "); Serial.println(request); // This prints out "request = GET /dweet/for/⸮⸮xGET /dwe"
}

At least now there are no "+" operators or crazy extended Strings. However, I can't get the sprintf working, even with only char arrays.

BulldogLowell:
it seems more like a standard on AVR/gcc where there is no floating point processor, cut back support for floats

The IDE is just a "wrapper" or "front end" for AVR-GCC. When you compile a program, the IDE takes care of scanning the source, seeing what libraries were included, compiles them (as well as the core), then finally links them all together and produces a HEX file that AVRDUDE can upload.

AVR-GCC has both floating point and non-floating point support libraries (named "libprintf_min.a" for non FP printf support, "libscanf_min.a" for non FP scanf support (rarely needed), "libprintf_flt.a" for FP support of printf and "libscanf_flt.a" for FP support of scanf (rarely used)).

In the IDE, it's very simple to add a checkbox to enable or disable FP support:

        // [ ] printf Floating point support
        useFFloatingPointBox = new JCheckBox (("  Enable (f)printf floating point"));
        pane.add (useFFloatingPointBox);
        d = useFFloatingPointBox.getPreferredSize();
        useFFloatingPointBox.setBounds (left, top, d.width + 10, d.height);
        right = Math.max (right, left + d.width);
        top += d.height + GUI_BETWEEN;

... and an entry in "Preferences.txt":

setBoolean ("build.printf_floating_point", useFFloatingPointBox.isSelected()); // save it
useFFloatingPointBox.setSelected (getBoolean ("build.printf_floating_point")); // get it

...and a simple addition to "Compiler.java":

        if (Preferences.getBoolean ("build.printf_floating_point")) {
            baseCommandLinker.add ("-Wl,-u,vfprintf,-lprintf_flt");
        }

Now, realize that I am NOT saying that floating point should be enabled BY DEFAULT, nor am I saying that it should be the only option. I UNDERSTAND the idea of saving space in a small memory microcontroller system.

I'm sure you and most everyone else can agree with the following two statements:

(1) Adding the OPTION to compile with or without FP support appears to be a simple matter (see the code examples above).

(2) As seen in many posts on this forum, many people do need, once in a while, floating point support and us other members are forced to tell them "It's not supported" and/or "use the dtostrf function".

If you agree with the above 2 statements, then my question is WHY ON EARTH ISN'T THE OPTION AVAILABLE IN THE IDE YET????????

Updates ARE being periodically released as can be seen by the new version numbers we see every few weeks. What are the programmers writing, and why? Are they in a vacuum? Are they writing what THEY assume people need without even reading a forum post here or elsewhere? Is it a big deep dark secret that many people sometimes DO need FP support? Do the Arduino programmers KNOW? If so, then why not add the option?

I am beyond baffled. It's almost like there's some kind of law against floating point usage in Arduino.

Let's look at another example (totally unrelated to "programming", but relevant nonetheless).

I'm sure we all have purchased breadboards for our projects and found them to be almost useless because the holes are too small, the busses are laid out wrong, etc, etc... (the point being that the engineer who designed the board had never used one and had no clue what the USER NEEDS).

Now, take a look at the Adafruit "Perma-Proto" boards.

THEY were most obviously designed by someone who has used proto-boards before and knows what the users NEED. For example, LARGE holes that can handle several components, INTELLIGENTLY placed mounting holes with adequate size AND keep-out space, quality construction (gold plated thick copper that doesn't lift off after one modification) and power buses that are labelled and intelligently located.

My point is that it's easy to see when something is designed to be USEFUL, by someone who knows what the product needs TO BE useful.

Looking at the Arduino IDE, it's equally easy to see that whoever is writing the new code has no clue how ARDUINO USERS are using the ARDUINO IDE because if they did, I wouldn't be writing this and people wouldn't be constantly asking "why can't I print a float?" or "why does it only print a question mark?"

OK... rant mode off

Haha yea, it's really annoying. So I changed the code to use the dtostrf function (and thanks for reminding me about the null char).

void setup() {
  Serial.begin(115200);

  char deviceID[] = "blah123";
  float temperature = 123.456;
  myFunction(deviceID, temperature);
}

void loop() {
  // Nothing here
}

void myFunction(char deviceID, float temperature) {
  char request[128];
  char tempBuff[8]; // A temperature value shouldn't get past this length: 123.456

  dtostrf(temperature, 6, 3, tempBuff); // value, total_length, digits_after_decimal, buffer
  Serial.print("tempBuff = "); Serial.println(tempBuff); // This prints out "tempBuff = 123.45" which is OK

  // Now I can use sprintf with only char arrays:
  sprintf(request, "GET /dweet/for/%s?temp=%s HTTP/1.1\r\nHost: dweet.io\r\nContent-Length: 0\r\n\r\n", deviceID, tempBuff);
  Serial.print("request = "); Serial.println(request); // This prints out "request = GET /dweet/for/⸮⸮xGET /dwe"
}

However, what's the easiest way to combine the messages to get what I want (instead of the sprintf which doesn't work)?

androidfanboy:
On a separate note, here is code purely to try to eliminate String-related issues and to test sprintf:

At least now there are no "+" operators or crazy extended Strings. However, I can't get the sprintf working, even with only char arrays.

You are making it a lot more difficult than it needs to be. Assuming that you already have a temperature reading and you want to print a 3 digit number with 3 decimal places, all you need to do is this:

char buf1 [16]; // buffer for TEMPERATURE number
char buf2 [32]; // buffer for FINAL STRING TO PRINT
// convert float "temperature" to " nnn.ddd" (7+1=8 accounts for possible minus sign)
dtostrf (temperature, 8, 3, buf1); // now, buf1 is the numeric string
sprintf (buf2, "The temperature is %s\r\n", buf1);
Serial.print (buf2); // print the whole sentence

Those "wasted" 48 bytes go back into the pool as soon as the function ends, so don't cry about them.

Delta_G:
@krupski

Can you take this bitch session somewhere else? <-- Agreed.

We all know you hate the Arduino IDE. <-- Not really I don't.

What I don't get is why you are still here if it really is that god awful. <-- I'm trying to help USERS (and I usually do)

But really, can we work on this OP's problem here and bitch about the IDE somewhere else? <-- Again, agreed

Or write your own. <-- I did (well, not really... I fixed all the problems in the original) :slight_smile:

I'm not arguing against you, or even disagreeing. Just saying this isn't really the place for it. <-- Agree.

Reply added to your quote.

Here is my latest code. The comments say what the outputs are. sprintf still isn't working, even though both are char arrays:

void setup() {
  Serial.begin(115200);

  char deviceID[] = "blah123";
  float temperature = 123.456;
  myFunction(deviceID, temperature);
}

void loop() {
  // Nothing here
}

void myFunction(char deviceID, float temperature) {
  char request[128];
  char tempBuff[16];

  dtostrf(temperature, 8, 3, tempBuff); // value, total_length, digits_after_decimal, buffer
  Serial.print("tempBuff = "); Serial.println(tempBuff); // This prints out "tempBuff = 123.456"

  // Now I can use sprintf with only char arrays:
  sprintf(request, "GET /dweet/for/%s?temp=%s HTTP/1.1\r\nHost: dweet.io\r\nContent-Length: 0\r\n\r\n", deviceID, tempBuff);
  Serial.print("request = "); Serial.println(request); // This prints out "request = GET /dweet/for/⸮⸮xGET /dwe"
}

androidfanboy:
Here is my latest code. The comments say what the outputs are. sprintf still isn't working, even though both are char arrays:

Found your problem. Here's the fixed code: (note the change in the declaration of "blah123" and the first parameter of "myFunction":

void setup() {
  Serial.begin(115200);

  char *deviceID = "blah123";
  float temperature = 123.456;
  myFunction(deviceID, temperature);
}

void loop() {
  // Nothing here
}

void myFunction(const char *deviceID, float temperature) {
  char request[128];
  char tempBuff[16];

  dtostrf(temperature, 8, 3, tempBuff); // value, total_length, digits_after_decimal, buffer
  Serial.print("tempBuff = "); Serial.println(tempBuff); // This prints out "tempBuff = 123.456"

  // Now I can use sprintf with only char arrays:
  sprintf(request, "GET /dweet/for/%s?temp=%s HTTP/1.1\r\nHost: dweet.io\r\nContent-Length: 0\r\n\r\n", deviceID, tempB$
  Serial.print("request = "); Serial.println(request); // This prints out "request = GET / dweet / for /
}

Compiled, it returns this:

[b]tempBuff =  123.456
request = GET /dweet/for/blah123?temp= 123.456 HTTP/1.1
Host: dweet.io
Content-Length: 0[/b]

Thanks! Why is there a space between the "=" and the "123.456" though?

Oh, I see, that makes sense. How would I extract the number itself and fix the number of decimal places? For example, I would always like 1 or 2 decimal places but the number of digits could vary but I wouldn't want that space to appear (12.3 vs 2.3). So I would basically fix the second numerical parameter of dtostrf and have a variable total length. Is that possible?

Also, how would I get the actual filled length of a character array buffer without getting the length it was initialized to?

Never mind, that was a noob question. strlen() does the trick.

androidfanboy:
Thanks! Why is there a space between the "=" and the "123.456" though?

In the example I posted previously, I suggested "8" to handle a possible negative sign.

The idea is, a positive number would look like this (the bar symbol represents the left edge):

[b]
| 123.456[/b]

A negative value would print like this:

[b]
|-123.456[/b]

If you don't care about that, or never expect negative values, then change the "8" to "7" in the "dtostrf" call. You will then get this:

Positive:

[b]|123.456[/b]

Negative:

[b]|-123.456[/b]

See how they don't "line up" anymore? That was the point of using "8".