[SOLVED] Sscanf() not saving sting sections to variables

I am working with a heltec LoRa32 board. I created a sensor that sends data in the form of a string to this LoRa Board. The data in the string is complete from the sender (I've verified in the serial monitor of that board using a serial.print statement). I have also verified the on the receiver that the data is correct by running another serial.print statement on this board. The issue I'm having is spitting the string up and storing them as variables to be used later. Any suggestions?


#include "heltec.h"

#define BAND    915E6  //you can set band here directly,e.g. 868E6,915E6

int id = 0;
int hourRead = 0;
int minRead = 0;
int secRead = 0;
float latRead = 0;
float lonRead = 0;
float altRead = 0;
int sensorRead = 0;
float voltRead = 0.0;
float percRead = 0.0;
String rc = "";
void setup() {
  //WIFI Kit series V1 not support Vext control
  Heltec.begin(true /*DisplayEnable Enable*/, true /*Heltec.LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);
  Serial.begin(115200);
}

void loop() {
  // try to parse packet

  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.println("Received packet '");
    // read packet
    while (LoRa.available()) {
      rc = LoRa.readString(); 
      int buffer_len = rc.length() + 1;
      char buffer[buffer_len];
      rc.toCharArray(buffer, buffer_len);
      sscanf(buffer, "%d,%d,%d,%d,%f,%f,%d,%f,%f", &id, &hourRead, &minRead, &secRead, &latRead, &lonRead, &altRead, &sensorRead, &voltRead, &percRead);
      Serial.println(rc);
      showData();
    }
  }
}

void showData() {
  Serial.print("ID: "); Serial.println(id);
  Serial.print("Time: "); Serial.print(hourRead); Serial.print(":"); Serial.print(minRead); Serial.print(":"); Serial.println(secRead);
  Serial.print("Location: "); Serial.print(latRead); Serial.print(","); Serial.println(lonRead);
  Serial.print("Altitude: "); Serial.println(altRead);
  Serial.print("Sensor Reading: "); Serial.println(sensorRead);
  Serial.print("Voltage: "); Serial.println(voltRead);
  Serial.print("Battery: "); Serial.print(percRead); Serial.println("%");
}

this is the output I'm getting( the 0's in the rc serial.println are because the gps module on the sender cant reach the satellites in my apartment, but I went outside with my laptop to verify that it is still reading) It should still give me the data for the sensor reading, voltage and battery percentage in the apartment but it is not.

Received packet '
1,0,0,0,0.000000,0.000000,0.00,-252,14.41,1100.00
ID: 1
Time: 0:0:0
Location: 0.00,0.00
Altitude: 0.00
Sensor Reading: 0
Voltage: 0.00
Battery: 0.00%

Make a small test sketch to test just that.

Which Heltec board ? This one: https://heltec.org/project/wifi-lora-32/.
The 'sscanf()' returns the number of filled items of the argument list when successful, you should use that.
Are you sure that your board can use 'float' with 'sscanf()' ?

This is how a test sketch can look that tests just that:

// A message without CR or LF at the end
char message[] = "1,0,0,0,0.000000,0.000000,0.00,-252,14.41,1100.00";

int id = 0;
int hourRead = 0;
int minRead = 0;
int secRead = 0;
float latRead = 0;
float lonRead = 0;
float altRead = 0;
int sensorRead = 0;
float voltRead = 0.0;
float percRead = 0.0;

void setup() 
{
  Serial.begin(115200);
  Serial.println( "The sketch has started");

  // ----------------------------------------------------------
  // Test sscanf with integer
  // ----------------------------------------------------------
  Serial.print("Can I parse a integer ? ");
  int i;
  if( sscanf( "91", "%d", &i) == 1)
  {
    Serial.print( "Yes, it is: ");
    Serial.print( i);
  }
  else
  {
    Serial.print( "No");
  }
  Serial.println();


  // ----------------------------------------------------------
  // Test sscanf with float
  // ----------------------------------------------------------
  Serial.print("Can I parse a float ? ");
  float f;
  if( sscanf( "123.456", "%f", &f) == 1)
  {
    Serial.print( "I don't know, the value is: ");
    Serial.print( f);
  }
  else
  {
    Serial.print( "No");
  }
  Serial.println();


  // ----------------------------------------------------------
  // Try to parse the message
  // ----------------------------------------------------------
  if( sscanf( message, "%d,%d,%d,%d,%f,%f,%f,%d,%f,%f", &id, &hourRead, &minRead, &secRead, &latRead, &lonRead, &altRead, &sensorRead, &voltRead, &percRead) == 10)
  {
    Serial.println( "Parse was a success");
    showData();
  }
  else
  {
    Serial.println( "Can not parse");
  }
}

void loop() 
{
}

void showData() {
  Serial.print("  ID: "); 
  Serial.println(id);

  Serial.print("  Time: "); 
  Serial.print(hourRead); 
  Serial.print(":"); 
  Serial.print(minRead); 
  Serial.print(":"); 
  Serial.println(secRead);

  Serial.print("  Location: "); 
  Serial.print(latRead); 
  Serial.print(", "); 
  Serial.println(lonRead);

  Serial.print("  Altitude: "); 
  Serial.println(altRead);

  Serial.print("  Sensor Reading: "); 
  Serial.println(sensorRead);

  Serial.print("  Voltage: "); 
  Serial.println(voltRead);

  Serial.print("  Battery: "); 
  Serial.print(percRead); 
  Serial.println("%");
}

The sketch in Wokwi with ESP32:

I just got to work I'll run the test sketch when I get home. I saw a post on here that said in order to run sscanf() you have to go to /hardware/tools/arv/arv/lib and replace the contents of the lib folder with the contents of the downloaded zip folder. Converting floating point to a string, most efficient way? - #10 by krupski

The AVR scanf() functions most certainly do NOT support floating point by default.
(You can add it as described here: sprintf for floats to AVR cpu request: avr boards.txt enhancement by sprintf/scanf menu options about float formatting by default · Issue #8574 · arduino/Arduino · GitHub )
(Other CPU types are more ambiguous. The problem is that the nature of printf/scanf is such that if you use it AT ALL, it will suck in all the format supports. If that includes floating point, you'll end up with a goodly amount of the floating point math libraries. Probably fine if you're on a larger AVR and are using floating point anyway, but not good on a small avr with a program that doesn't even use floats.)

Please, do not change the Arduino system files. If you have ever done such a thing, remove and re-install the Arduino IDE.

You are only supplying 9 formats for 10 values?!?

sscanf(buffer, "%d,%d,%d,%d,%f,%f,%d,%f,%f", &id, &hourRead, &minRead, &secRead, &latRead, &lonRead, &altRead, &sensorRead, &voltRead, &percRead);

You can parse ints and floats without using String and sscanf():

    while (LoRa.available()) { 
      id         = LoRa.parseInt();
      hourRead   = LoRa.parseInt();
      minRead    = LoRa.parseInt();
      secRead    = LoRa.parseInt();
      latRead    = LoRa.parseFloat();
      lonRead    = LoRa.parseFloat();
      altRead    = LoRa.parseInt();
      sensorRead = LoRa.parseFloat();
      voltRead   = LoRa.parseFloat();
      // percRead???  No format provided!
      showData();
    }

I saved a back up of the original files and have changed it back to the original.

You are correct @westfw, I ended up going with a different method to achieve the desired results and it's working now. And @johnwasser I corrected that issue. Thank you guys! @johnwasser @Koepel @westfw

new format:


#include "heltec.h"

#define BAND    915E6  //you can set band here directly,e.g. 868E6,915E6

char str[] = "";
int id = 0;
int hourRead = 0;
int minRead = 0;
int secRead = 0;
float latRead = 0.0;
float lonRead = 0.0;
float altRead = 0.0;
int sensorRead = 0;
float voltRead = 0.0;
float percRead = 0.0;
String rc = "";
void setup() {
  //WIFI Kit series V1 not support Vext control
  Heltec.begin(true /*DisplayEnable Enable*/, true /*Heltec.LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);
  Serial.begin(115200);

}

void loop() {
  // try to parse packet

  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.println("Received packet '");
    // read packet
    while (LoRa.available()) {
      rc = LoRa.readString();
      int str_len = rc.length() + 1;
      rc.toCharArray(str, str_len);
      char * strtokIndx; // this is used by strtok() as an index
      strtokIndx = strtok(str, ","); // this continues where the previous call left off
      id = atoi(strtokIndx);     // convert this part to an integer

      strtokIndx = strtok(NULL, ",");
      hourRead = atoi(strtokIndx);     // convert this part to a float

      strtokIndx = strtok(NULL, ",");
      minRead = atoi(strtokIndx);     // convert this part to a float

      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      secRead = atoi(strtokIndx);     // convert this part to an integer

      strtokIndx = strtok(NULL, ",");
      latRead = atof(strtokIndx);     // convert this part to a float

      strtokIndx = strtok(NULL, ",");
      lonRead = atof(strtokIndx);     // convert this part to a float

      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      altRead = atof(strtokIndx);     // convert this part to an integer

      strtokIndx = strtok(NULL, ",");
      sensorRead = atoi(strtokIndx);     // convert this part to a float

      strtokIndx = strtok(NULL, ",");
      voltRead = atof(strtokIndx);     // convert this part to a float

      strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
      percRead = atof(strtokIndx);     // convert this part to an integer

      Serial.println(rc);
      showData();
    }
  }
}

void showData() {
  Serial.print("ID: "); Serial.println(id);
  Serial.print("Time: "); Serial.print(hourRead); Serial.print(":"); Serial.print(minRead); Serial.print(":"); Serial.println(secRead);
  Serial.print("Location: "); Serial.print(latRead,6); Serial.print(","); Serial.println(lonRead,6);
  Serial.print("Altitude: "); Serial.println(altRead);
  Serial.print("Sensor Reading: "); Serial.println(sensorRead);
  Serial.print("Voltage: "); Serial.println(voltRead);
  Serial.print("Battery: "); Serial.print(percRead); Serial.println("%");
}

Your compiler maybe supports this via extension but this is not c++

@currydem23 You have to spend time to learn more about C++.

This is an array of 1 byte. That byte has all bits set to zero (it is an empty string with zero terminator '\0').

char str[] = "";

That means that this is not possible:

rc.toCharArray(str, str_len);

because you need some memory to store the data.

The ESP32 runs FreeRTOS. Using strtok() with a multitasking operating system is not a good idea.

Im still fairly new to programming, what would be the best way to go about splitting an incoming string from my other LoRa module and saving that data into variables?

The "old" C functions are : strcpy, strcat, strlen, sscanf, and so on.
They operate on a zero-terminated string. That is an array with text and a zero at the end.
They are often called: a string.
They operate on buffers in memory. You have to provide those buffers.
https://www.arduino.cc/reference/en/language/variables/data-types/string/

The Arduino String class is something totally different.
They allocate and release memory when needed.
https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/

Your Heltec LoRA32 software made a bad choice for getting the data.
In your sketch and in the examples, they retrieve data via the Arduino String class and then they convert that to a zero-terminated string. If they would give a zero-terminated string, that would make it easier.

This is another way to get that LoRa data into a normal zero-terminated string.
What is the maximum length of the data ?
Let's say it is 80.

byte buffer[80];

void loop()
{
  int packetSize = LoRa.parsePacket();
  if( packetSize > 0) 
  {
    if( (packetSize + 1)> sizeof( buffer))  // plus one, because of the zero-terminator
    {
      Serial.println( "The message is too long, it does not fit in the buffer");
    } 
    else
    {
      int index;
      for( index = 0; index < packetSize; index++)
      {
         buffer[index] = LoRa.read();
      }
      buffer[index] = '\0';           // add the zero-terminator
    }
  }  
}

Can you develop, please ? Isn't strtok thread-safe ?

No, but Arduino runs on the second core and the Wifi and other things on the first core of the ESP32. I assume that the strtok() is not used in the first core. If no other tasks are created, then the sketch runs in one task and strtok() is safe to use.

However, if a few tasks are created in the sketch and each task uses strtok(), then trouble can be expected. The strtok() stores the position locally to be able to continue from there the next time.
Here is an example of the source code with the local 'static' variable: https://github.com/lattera/glibc/blob/master/string/strtok.c

1 Like

Thanks, I rarely use strtok anyway but will use strtok_r from now :slight_smile:

1 Like

So I ran a test of the example you showed me to get the same results without using strtok(). I also have been reading though the links you sent. In the example code string - Arduino Reference it shows how to make an array of string arrays. When I tried to use the asterisk to accomplish the same effect I received a syntax error "invalid conversion from 'int' to 'char*' [-fpermissive]" . Any suggestions on how to set up this incoming data as an array of strings?

#include "heltec.h"

#define BAND    915E6  //you can set band here directly,e.g. 868E6,915E6

char *myData[80];

String deviceType = "";
int id = 0;
int hourRead = 0;
int minRead = 0;
int secRead = 0;
float latRead = 0.0;
float lonRead = 0.0;
float altRead = 0.0;
int sensorRead = 0;
float voltRead = 0.0;
float percRead = 0.0;
float percReadTru = 0.0;

void setup() {
  //WIFI Kit series V1 not support Vext control
  Heltec.begin(true /*DisplayEnable Enable*/, true /*Heltec.LoRa Disable*/, true /*Serial Enable*/, true /*PABOOST Enable*/, BAND /*long BAND*/);
  Serial.begin(115200);

}
void loop()
{
  int packetSize = LoRa.parsePacket();
  if( packetSize > 0) 
  {
    if( (packetSize + 1)> sizeof(myData))  // plus one, because of the zero-terminator
    {
      Serial.println( "The message is too long, it does not fit in the buffer");
    } 
    else
    {
      int i;
      for( i = 0; i < packetSize; i++)
      {
         myData[i] = LoRa.read();
      }
      myData[i] = '\0';           // add the zero-terminator

      deviceType = myData[0];
      id = myData[1];
      hourRead = myData[2];
      minRead = myData[3];
      secRead = myData[4];
      latRead = myData[5];
      lonRead = myData[6];
      altRerad = myData[7];
      sensorRead = myData[8];
      voltRead = myData[9];
      percRead = myData[10];
      percReadTru = percRead /10.0
    }
  }  
}

void showData() {
  Serial.print("Device Type: "); Serial.println(deviceType);
  Serial.print("ID: "); Serial.println(id);
  Serial.print("Time: "); Serial.print(hourRead); Serial.print(":"); Serial.print(minRead); Serial.print(":"); Serial.println(secRead);
  Serial.print("Location: "); Serial.print(latRead); Serial.print(","); Serial.println(lonRead);
  Serial.print("Altitude: "); Serial.println(altRead);
  Serial.print("Sensor Reading: "); Serial.println(sensorRead);
  Serial.print("Voltage: "); Serial.println(voltRead);
  Serial.print("Battery: "); Serial.print(percReadTru); Serial.println("%");
}

A buffer of 80 characters is: char buffer[80];
This creates 80 pointers: char *myData[80];

A pointer should point to something. In most cases they point to a piece of memory that you have reserved.

The characters are put in the buffer one by one with LoRa.read();
After that, the received text is in the array 'buffer'.
Then you still have to convert them to integers and float.

My example was just an alternative with a for-loop.
You can still use the String with the rc.toCharArray.
If you want, you can still create a buffer with: char buffer[buffer_len];
However, that is not recommended as was already written in Reply #11.
The LoRa.readString() that you used before does some waiting and keeps on reading until a timeout. That is not the best function, but there is for example LoRa.readBytes() which captures the data at once, but it does not add a zero-terminator.
Because of all of this, I thought it was a good idea to fall back to a basic simple for-loop to get the data.

okay thank you, I'm going to work with this for a bit on my own before I ask anything else.