Getting confused with Strings, char, etc

Hi,

I'm still very new to Arduino, but steadily working on improving my skills. However, I've reached a block with the latest one, using ESP32 and SIM800L.

Basically, I'm using Preferences.h to store the phone numbers and passwords which the rest of the code will use to send messages as necessary. However, I keep getting errors relating to string/char/char* incompatibility. Could anybody take a lookj, and see where I am going wrong please? I've been round teh hjouses so many times, I can't see the wood for the trees!

#include <Sim800L.h>
#include <SoftwareSerial.h>
#include <Preferences.h>

Preferences Prefs;  // Instance of Preferences to store details

#define RX 10  // RX pin for Software Serial
#define TX 11  // TX pin for Software Serial


bool error;  // Return from sendSms command

Sim800L GSM(RX, TX);  // Set up SIM800 module

void setup() {
  Serial.begin(9600);
  Prefs.begin("RoccoMax", true);  // Open namespace in Read-only mode

  bool prefinit = Prefs.isKey("tpinit");  // Check for first run

  if (prefinit == false) {
    Prefs.end();
    Prefs.begin("RoccoMax", false);

    Prefs.putString("UserNo", "+44xxxxxxxxxx");
    Prefs.putString("UserPW", "123456");
    Prefs.putString("UnitID", "SKMT001");
    Prefs.putBool("tpinit", true);
    Prefs.end();
    Prefs.begin("RoccoMax", false);
  }

  number =  Prefs.getString("UserNo") ;

  GSM.begin(9600);  // Start GSM module
  GSM.delAllSms();  // Clear any texts in memory



  error = GSM.sendSms(number, "GSM Module Ready");
  Serial.println(error);
  Serial.println("GSM ready");
}

void loop() {

  byte index = GSM.checkForSMS();
  if (index != 0) {
    Serial.println(GSM.readSms(index));
  }
}

Thanks
Gary

Posting those error messages would be helpful to those trying to help you.

Good point, well made!

C:\Users\UserPC\AppData\Local\Temp\.arduinoIDE-unsaved2026111-10516-160li1c.sukz\sketch_feb11a\sketch_feb11a.ino: In function 'void setup()':
C:\Users\UserPC\AppData\Local\Temp\.arduinoIDE-unsaved2026111-10516-160li1c.sukz\sketch_feb11a\sketch_feb11a.ino:40:23: error: cannot convert 'String' to 'char*'
   40 |   error = GSM.sendSms(number, "GSM Module Ready");
      |                       ^~~~~~
      |                       |
      |                       String
In file included from C:\Users\UserPC\AppData\Local\Temp\.arduinoIDE-unsaved2026111-10516-160li1c.sukz\sketch_feb11a\sketch_feb11a.ino:1:
c:\Users\UserPC\Documents\Arduino\libraries\Sim800L_Library_Revised/Sim800L.h:133:24: note:   initializing argument 1 of 'bool Sim800L::sendSms(char*, char*)'
  133 |     bool sendSms(char* number,char* text);
      |                  ~~~~~~^~~~~~
exit status 1

Compilation error: cannot convert 'String' to 'char*'

Are sure you posted your current code completely? Your variable number is not defined anywhere.

number =  Prefs.getString("UserNo") ;

There are two types of string in C++. One is a character array referred to as lower case string. The other is the String class indicated by upper case String. They are not the same. The String contains a character array (lower case string) and control information. The String class has several useful functions for manipulating Strings.

https://docs.arduino.cc/language-reference/en/variables/data-types/stringObject/

To access the character array in a String a String class function c_str() is used. To get the address of the character array in a String do something like this. The pointer is declared const because you should never modify the character array outside of the String functions.

String someString = "test"
const char* cstr = someString.c_str()`

On an unrelated note the ESP32 has multiple UARTs so software serial is usually an unnecessary complication.

You're right - losing track of the current code. This is the current code, relating to the error message posted earlier:


#include <Sim800L.h>
#include <SoftwareSerial.h>
#include <Preferences.h>

Preferences Prefs;  // Instance of Preferences to store details

#define RX 10  // RX pin for Software Serial
#define TX 11  // TX pin for Software Serial

String number = "";
bool error;  // Return from sendSms command

Sim800L GSM(RX, TX);  // Set up SIM800 module

void setup() {
  Serial.begin(9600);
  Prefs.begin("RoccoMax", true);  // Open namespace in Read-only mode

  bool prefinit = Prefs.isKey("tpinit");  // Check for first run

  if (prefinit == false) {
    Prefs.end();
    Prefs.begin("RoccoMax", false);

    Prefs.putString("UserNo", "+44xxxxxxxxxx");
    Prefs.putString("UserPW", "123456");
    Prefs.putString("UnitID", "SKMT001");
    Prefs.putBool("tpinit", true);
    Prefs.end();
    Prefs.begin("RoccoMax", false);
  }

  number =  Prefs.getString("UserNo") ;

  GSM.begin(9600);  // Start GSM module
  GSM.delAllSms();  // Clear any texts in memory



  error = GSM.sendSms(number, "GSM Module Ready");
  Serial.println(error);
  Serial.println("GSM ready");
}

void loop() {

  byte index = GSM.checkForSMS();
  if (index != 0) {
    Serial.println(GSM.readSms(index));
  }
}

if your function expects a c-string, then extract it from the String

  error = GSM.sendSms(number.c_str(), "GSM Module Ready");

Not sure which Sim800L library @gcavie is using. But, This One has:

	bool sendSms(char* number,char* text);	 

With the stricter compiler settings of ESP32, there may be errors trying to pass const char *.

Yes that could be a problem indeed (stupid idea to write the library this way).

To elaborate, consider

size_t strlcpy(char *dst, const char *src, size_t size);

copies one C-string to another place, with a maximum size; returning the size copied. The destination is not const, because the function is going to write there. But the source is const: it is only going to read from there, and not modify anything.

Typically, strings in your sketch are compiled to be part of program storage, similar to the instruction bytes executed by the CPU. They get uploaded onto the board, and not writable during execution in the way dynamic memory (RAM) is. To save space, the same string in multiple places can then be de-duplicated to point to the same address, since nothing can change that string. This is why literal strings like "GSM ready" are considered const char *

Serial.println is actually

size_t Print::println(const char c[])

(as a function argument, c[] effectively "decays" to be *c i.e. array to pointer). Again, by declaring const the compiler ensures that the function only reads from the string. It's just printing it, so no need to modify the string. But for the function in question

bool sendSms(char* number,char* text);

neither argument is const, so the compiler made no such guarantee when the library was being compiled. It may not in practice try to write to that memory, but now when you try to use that library, all your compiler has to go on is that function signature: you cannot pass "const char *", a string that you are not allowed to modify, to a function that takes "char *", a string that the function might modify.

Unless the library intends to modify either string -- which seems unlikely -- this is a bug in the library. They should fix the declaration, and with no other changes, everyone will be happy. Going back to the error

   40 |   error = GSM.sendSms(number, "GSM Module Ready");
      |                       ^~~~~~
      |                       |
      |                       String
Compilation error: cannot convert 'String' to 'char*'

Adding the call to c_str()

error = GSM.sendSms(number.c_str(), "GSM Module Ready");

to get a const char * from the String, changes the error to

invalid conversion from 'const char*' to 'char*'

so you could in theory do

error = GSM.sendSms(const_cast<char *>(number.c_str()), "GSM Module Ready");

The const_cast removes "const-ness":

  • The String number has a buffer
  • A const pointer to that buffer is returned by c_str(): nothing else is supposed to modify that buffer's contents. Something might be relying on that fact.
  • sendSms does not make that guarantee
  • const_cast lets you say: "I'm going to trust you anyway"

Now, if the function -- directly, or in turn by passing the pointer to other functions -- actually attempts to modify the string, that is undefined behavior, which is bad. But it could work. However, that just "fixes" the first argument. The second argument, a literal string, is again const char *, and this time, you get a warning (unless you disabled all warnings)

warning: ISO C++ forbids converting a string constant to 'char*'

Will it still work? In this case, probably.

Many thanks, both for the step-by-step explanation, and also the solution to my problem. It all compiles beautifully now, and hopefully when I get the hardware re-assembled, that will also work OK.

In the meantime, I'm off to spend a bit of time working through your explanation, and try to get it embedded in my brain!

Thanks again,
Gary

Not beautifully but it should compile.

The right fix would be to go in the library and add const to the function’s parameters.