In the WString.h file, there is a constructor: String(const __FlashStringHelper *str);
Internally this will use the strlen_P() to determine the length of the PROGMEM string, then it will use strcpy_P() to copy the PROGMEM into a RAM buffer. The String class also has a c_str() method which returns a const char* pointer to the internal buffer.
This means you can reuse the String() class to do the heavy lifting:
String apName(F("My Wifi AP Name"));
String apPassword(F("My Wifi Password"));
wifiManager.autoConnect(apName.c_str(), apPassword.c_str());
You can also take it one step further and do it as a one liner:
wifiManager.autoConnect( String(F("My Wifi AP Name")).c_str(), String(F("My Wifi Password")).c_str());
It's possible to put the whole thing into a macro to make it easier to use:
#define G(string_literal) (String(F(string_literal)).c_str())
Then you can use G("") to pass a char* into the method:
wifiManager.autoConnect( G("My Wifi AP Name"), G("My Wifi Password"));
There are a couple of caveats:
1) The print and println methods are optimized to only read one byte from PROGMEM at a time and then write one byte at a time. The G("") macro allocates a buffer to hold the entire string so will use more RAM. Therefore it's better to use F("") for those methods. The String class can also use the F("") macro directly, there is no need to use this G("") macro because it would end up allocating the String memory twice.
2) The G("") macro uses a temporary String object to allocate the RAM buffer. Temporary objects are given expression scope. This means they are de-allocated at the end of the expression (where the semi-colon is). The memory is only valid on the line that it is declared. You can't store the result of the G("") macro, you can only use it to pass a char* into a function.
I did a test to make sure it worked:
#include <ESP.h>
#define G(string_literal) (String(F(string_literal)).c_str())
void setup() {
Serial.begin(115200);
Serial.println();
Serial.println();
Serial.print("Setup Start Free Heap: ");
Serial.println(ESP.getFreeHeap());
charMethod(G("Test long string literal +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++......"));
Serial.print("Setup End Free Heap: ");
Serial.println(ESP.getFreeHeap());
}
void charMethod(const char* var) {
Serial.print("charMethod Free Heap: ");
Serial.println(ESP.getFreeHeap());
Serial.print("charMethod var value: ");
Serial.println(var);
}
void loop() {}
The compiler output was:
Sketch uses 225769 bytes (21%) of program storage space. Maximum is 1044464 bytes.
Global variables use 32268 bytes (39%) of dynamic memory, leaving 49652 bytes for local variables. Maximum is 81920 bytes.
The output while running was:
Setup Start Free Heap: 48048
charMethod Free Heap: 47912
charMethod var value: Test long string literal +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++......
Setup End Free Heap: 48048
So you can see that it only allocates space for the string literal while it's performing the charMethod() function call. Once the function returns, the space gets deallocated and is available again.
If you did the same thing without the G macro, the compiler output is:
Sketch uses 225529 bytes (21%) of program storage space. Maximum is 1044464 bytes.
Global variables use 32396 bytes (39%) of dynamic memory, leaving 49524 bytes for local variables. Maximum is 81920 bytes.
So it's allocated an extra 128 bytes of global variables in order to hold the string.
Setup Start Free Heap: 47920
charMethod Free Heap: 47920
charMethod var value: Test long string literal +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++......
Setup End Free Heap: 47920
While running there is less heap space available, because the string is sitting in RAM the whole time.