Chris--A / PGMWrap

A concept library for easy use of PROGMEM data.
18 stars 5 forks source link

using with strings ? #4

Open BlackBrix opened 8 years ago

BlackBrix commented 8 years ago

Hi, how can strings be used with a "PGMWrap-like" style ? this will not compile const char_p my_string[] PROGMEM = "my Text"; cause of "invalid conversion from 'char*' to 'const char&" issue

the goal is to be able to use strings from flash like any other string (from RAM): Serial.print(my_string);

Chris--A commented 8 years ago

Hi, this issue is quite difficult to find a nice solution.

However I have come up with a basic workaround. Basically, I have a new type: cstr_p instead of an array of char_p.

First though, if you want an array of char_p, all you can do here is specify the string as a list of characters: const char_p my_string[] PROGMEM = {'t', 'e', 's', 't'};. However this is really only practical if you use small strings.

Before I add this next fix to the lib I want to test it thoroughly, so here is some instructions so you can use it now.

Here is the cstr_p object. Add this below the include for PGMWrap.h in your sketch:

template< unsigned N >
struct cstr_p{
  char_p *begin() const{ return (char_p*) &pstr[0];}
  char_p *end() const{ return (char_p*) &pstr[N]; }
  operator __FlashStringHelper*() const{ return ( __FlashStringHelper* ) &pstr; }
  char_p &operator [] (int addr) const{ return *(const char_p*) &pstr[addr]; }
  const char pstr[N];
};

Then to declare a string, you construct it like this:

const cstr_p< 14 > pStr PROGMEM = {"A test String"};

Notice the size of the string (including the null) is needed in the template brackets.

And the usage is quite straight forward.

  for( auto &c : pStr )  //Ranged loop over whole string (C++11 only, IDE 1.6.2 and above)
   Serial.print( c );

  for( int i = 0 ; i < sizeof(pStr) ; ++i ){ //Standard for loop
    Serial.print( pStr[i] );
  }

  Serial.write(pStr[0]);  //Print single character

  Serial.print(pStr); //Print whole string

And here is a complete sketch if you would like to test it.

#include <PGMWrap.h>

template< unsigned N >
struct cstr_p{
  char_p *begin() const{ return (char_p*) &pstr[0];}
  char_p *end() const{ return (char_p*) &pstr[N]; }
  operator __FlashStringHelper*() const{ return ( __FlashStringHelper* ) &pstr; }
  char_p &operator [] (int addr) const{ return *(const char_p*) &pstr[addr]; }
  const char pstr[N];
};

const cstr_p< 14 > pStr PROGMEM = {"A test String"};

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

  for( auto &c : pStr )  //Ranged loop over whole string
   Serial.print( c );

  Serial.write('\n');  //Move to next line

  for( int i = 0 ; i < sizeof(pStr) ; ++i ){ //Standard for loop
    Serial.print( pStr[i] );
  }

  Serial.write('\n');

  Serial.write(pStr[0]);  //Print single character
  Serial.write('\n');

  Serial.print(pStr); //Print whole string
}

void loop() {}

Let me know how you go.

Cheers.

BlackBrix commented 8 years ago

Hi Chris, thanks a lot for your quick answer.

I will test your solution and let you know.


2 questions about it:


I got a project here with a very huge menu system (for the GUI) with more than 100 lines of text, in the developing phase there's a lot of editing/correcting in the strings, and then (with your solution) you have to count the chars* again each time... Another thing is, that the project will be translated into other languages (e.g. french / german/ ...), UTF-8 is needed for it (the arduino-IDE itself is fully UTF-8 capable (exept it's serial console)) *(how do I count the chars with multibyte UTF-8 chars in it ?)


my solution up to know is as follows:

const char string_0[] PROGMEM = "my text 0 blah blah ...";
const char string_1[] PROGMEM = "my text 1";
const char string_2[] PROGMEM = "UTF-8-test: ä ö ü @ ß ² ³ é à ° _ ~ £ § © µ £ €";
const char string_3[] PROGMEM = "my text 3";
const char string_4[] PROGMEM = "my text 4 blah ...";

// print_P
size_t print_P (const char *str)
  {
  return Serial.print ((const __FlashStringHelper *) str);
  }

// println_P
size_t println_P (const char *str)
  {
  size_t n = print_P (str);
  return (n + Serial.println());
  }

void setup()
{
  Serial.begin(115200);
  while(!Serial);
  println_P (string_0);
  println_P (string_1);
  println_P (string_2);
  println_P (string_3);
  println_P (string_4);
}

void loop() {}
Chris--A commented 8 years ago

Hi, this will need some investigation as I have not used UTF-8 within the Arduino environment.

However to solve the length problem, use a define for your strings, then you can change the size without having to count each time they change:

#define text "Some text"
#define text1 "Some other text "

const cstr_p< sizeof(text) > pStr PROGMEM = {text};
const cstr_p< sizeof(text1) > pStr1 PROGMEM = {text1};

I'll investigate other methods, however this should work for now.

BlackBrix commented 8 years ago

your example works. It works with UTF8 as well:

const cstr_p< 9 > pStr PROGMEM = {"£§©µ"}; // UTF8 2-byte chars
const cstr_p< 7 > pStr1 PROGMEM = {"£€"}; // UTF8 3-byte chars

but of course counting the size of the string (including the null) is challenging for the user... ;-)


using #defines works:

#define text "Some text"
const cstr_p< sizeof(text) > pStr PROGMEM = {text};

using #defines works with UTF-8 as well: (cause sizeof works with UTF-8 (variable byte encoding))

#define text "UTF-8-Test: £§©µ£€"
const cstr_p< sizeof(text) > pStr PROGMEM = {text};
Chris--A commented 8 years ago

Good to hear,

You could shrink it to a single line per string with a define if you have many strings.

#define createPGMStr( name, text ) const cstr_p< sizeof(text) > name PROGMEM = {text}

createPGMStr( pStr, "some text" );
createPGMStr( pStr1, "some other text" );
createPGMStr( pStr2, "even more text" );
BlackBrix commented 8 years ago

Wow, THAT's what I'm looking for !

I think this is really easy to understand by users of every skill level, you can just use the normal Serial.print() within the rest of your sketch then, without having to do this "typecast things" or using special "print_p"-functions everywhere -> see above in my first example ...

So all you have to do is to integrate this in your PGMWrap-library somehow (?)

BlackBrix commented 8 years ago

unfortunately my menu example sketch grows from 13000 bytes to 20000 bytes, when using the #define createPGMStr( name, text ) const cstr_p< sizeof(text) > name PROGMEM = {text} macro on every string in flash that was formerly defined the classical way like const char string_0[] PROGMEM = "my text 0 blah blah ..."; :-/ is this expected ?

before:

const char clrs[] PROGMEM = CLRS;
const char eol[] PROGMEM = EOL;
const char prompt[] PROGMEM = PROMPT;
const char separator[] PROGMEM = "-------------";
const char breadcrumb_separator[] PROGMEM = ">";
const char arrow[] PROGMEM = " > ";
const char arrow_2[] PROGMEM = " >> ";
const char arrow_3[] PROGMEM = " >>> ";
const char arrow_4[] PROGMEM = " >>>> ";
const char arrow_5[] PROGMEM = " >>>>> ";
const char main_menu_headline[] PROGMEM = "== MAIN MENU ==";
const char main_menu[] PROGMEM = "Main Menu";
const char back[] PROGMEM = "(0) Back";
const char item_0[] PROGMEM = "(0) ";
const char item_1[] PROGMEM = "(1) ";
const char item_2[] PROGMEM = "(2) ";
const char item_3[] PROGMEM = "(3) ";
const char item_4[] PROGMEM = "(4) ";
const char item_5[] PROGMEM = "(5) ";

const char calib_sub_1[] PROGMEM = "Show Datatable";
const char calib_sub_2[] PROGMEM = "Offset";
const char calib_sub_3[] PROGMEM = "Gain";
const char calib_sub_4[] PROGMEM = "Edit Datapoint";
const char calib_sub_5[] PROGMEM = "Load Factory Calibration";

const char yes[] PROGMEM = "[Yes]";
const char no[] PROGMEM =  "[No]";

const char custom[] PROGMEM =   "[Custom]";
const char disabled[] PROGMEM = "[Disabled]";

const char txt_1[] PROGMEM = "Info";
const char txt_2[] PROGMEM = "Check Inputs";
const char txt_3[] PROGMEM = "Check Outputs";
const char txt_4[] PROGMEM = "Setup/Configuration";
const char txt_5[] PROGMEM = "Calibration";

const char txt_21[] PROGMEM = "Read Input1 (Fuel Level Sensor)";
const char txt_22[] PROGMEM = "Read Input2 (Oiltemp Sensor)";
const char txt_23[] PROGMEM = "Read Input3 (Internal Sensor Voltage)";
const char txt_24[] PROGMEM = "Read Input4 (Supply Voltage)";
const char txt_25[] PROGMEM = "Read all Inputs";    

const char txt_31[] PROGMEM = "Set Output1 (Fuel Gauge)";
const char txt_32[] PROGMEM = "Set Output2 (Oiltemp Gauge)";
const char txt_33[] PROGMEM = "Set Output3 (optional switching output)";
const char txt_34[] PROGMEM = "Read all Outputs";

const char txt_41[] PROGMEM = "Fuel Level Sensor Type";
const char txt_411[] PROGMEM =  "[Type89]";

const char txt_42[] PROGMEM = "Oiltemp Sensor Type";
const char txt_421[] PROGMEM =  "[VAG-No.]";

const char txt_43[] PROGMEM = "Instrument Cluster";
const char txt_431[] PROGMEM = "Fuel Gauge Type";
const char txt_4311[] PROGMEM = "[A4/B5]";
const char txt_4312[] PROGMEM = "[A6/C5]";

const char txt_432[] PROGMEM = "Oiltemp Gauge Type";
const char txt_4321[] PROGMEM = "[A4/B5 & A6/C5 (TOG)]";

const char txt_44[] PROGMEM = "Output3";
const char txt_441[] PROGMEM = "Function";
const char txt_4411[] PROGMEM = "[Fuel Level Switch]";
const char txt_4412[] PROGMEM = "[Oiltemp Switch]";
const char txt_4413[] PROGMEM = "[Voltage Switch]";
const char txt_4414[] PROGMEM = "[Sensor Error Indicator]";

const char txt_442[] PROGMEM = "Threshold";
const char txt_443[] PROGMEM = "Hysteresis";
const char txt_444[] PROGMEM = "Switching Delay";
const char txt_445[] PROGMEM = "Switching Logic";
const char txt_4451[] PROGMEM = "[normal]";
const char txt_4452[] PROGMEM = "[inverted]";

const char txt_45[] PROGMEM = "Factory reset";

const char txt_51[] PROGMEM = "Input Calibration";
const char txt_511[] PROGMEM = "Resistor Precalibration Input1 (Fuel Level Sensor)";

const char txt_512[] PROGMEM = "Resistor Precalibration Input2 (Oiltemp Sensor)";

const char txt_513[] PROGMEM = "Voltage Precalibration Input4 (Supply Voltage)";

const char txt_52[] PROGMEM = "Sensor Calibration";
const char txt_521[] PROGMEM = "Calibrate Input1 (Fuel Level Sensor)";

const char txt_522[] PROGMEM = "Calibrate Input2 (Oiltemp Sensor)";

const char txt_53[] PROGMEM = "Output Calibration";
const char txt_531[] PROGMEM = "Calibrate Output1 (Fuel Gauge)";

const char txt_532[] PROGMEM = "Calibrate Output2 (Oiltemp Gauge)";    

const char txt_54[] PROGMEM = "Load Factory Calibration (all channels)";

after:

createPGMStr( clrs, CLRS );
createPGMStr( eol, EOL );
createPGMStr( prompt, PROMPT );
createPGMStr( separator, "-------------" );
createPGMStr( breadcrumb_separator, ">" );
createPGMStr( arrow, " > " );
createPGMStr( arrow_2, " >> " );
createPGMStr( arrow_3, " >>> " );
createPGMStr( arrow_4, " >>>> " );
createPGMStr( arrow_5, " >>>>> " );
createPGMStr( main_menu_headline, "== MAIN MENU ==" );
createPGMStr( main_menu, "Main Menu" );
createPGMStr( back, "(0) Back" );
createPGMStr( item_0, "(0) " );
createPGMStr( item_1, "(1) " );
createPGMStr( item_2, "(2) " );
createPGMStr( item_3, "(3) " );
createPGMStr( item_4, "(4) " );
createPGMStr( item_5, "(5) " );

createPGMStr( calib_sub_1, "Show Datatable" );
createPGMStr( calib_sub_2, "Offset" );
createPGMStr( calib_sub_3, "Gain" );
createPGMStr( calib_sub_4, "Edit Datapoint" );
createPGMStr( calib_sub_5, "Load Factory Calibration" );

createPGMStr( yes, "[Yes]" );
createPGMStr( no,  "[No]" );

createPGMStr( custom,   "[Custom]" );
createPGMStr( disabled, "[Disabled]" );

createPGMStr( txt_1, "Info" );
createPGMStr( txt_2, "Check Inputs" );
createPGMStr( txt_3, "Check Outputs" );
createPGMStr( txt_4, "Setup/Configuration" );
createPGMStr( txt_5, "Calibration" );

createPGMStr( txt_21, "Read Input1 (Fuel Level Sensor)" );
createPGMStr( txt_22, "Read Input2 (Oiltemp Sensor)" );
createPGMStr( txt_23, "Read Input3 (Internal Sensor Voltage)" );
createPGMStr( txt_24, "Read Input4 (Supply Voltage)" );
createPGMStr( txt_25, "Read all Inputs" );    

createPGMStr( txt_31, "Set Output1 (Fuel Gauge)" );
createPGMStr( txt_32, "Set Output2 (Oiltemp Gauge)" );
createPGMStr( txt_33, "Set Output3 (optional switching output)" );
createPGMStr( txt_34, "Read all Outputs" );

createPGMStr( txt_41, "Fuel Level Sensor Type" );
createPGMStr( txt_411,  "[Type89]" );

createPGMStr( txt_42, "Oiltemp Sensor Type" );
createPGMStr( txt_421,  "[VAG-No.]" );

createPGMStr( txt_43, "Instrument Cluster" );
createPGMStr( txt_431, "Fuel Gauge Type" );
createPGMStr( txt_4311, "[A4/B5]" );
createPGMStr( txt_4312, "[A6/C5]" );

createPGMStr( txt_432, "Oiltemp Gauge Type" );
createPGMStr( txt_4321, "[A4/B5 & A6/C5 (TOG)]" );

createPGMStr( txt_44, "Output3" );
createPGMStr( txt_441, "Function" );
createPGMStr( txt_4411, "[Fuel Level Switch]" );
createPGMStr( txt_4412, "[Oiltemp Switch]" );
createPGMStr( txt_4413, "[Voltage Switch]" );
createPGMStr( txt_4414, "[Sensor Error Indicator]" );

createPGMStr( txt_442, "Threshold" );
createPGMStr( txt_443, "Hysteresis" );
createPGMStr( txt_444, "Switching Delay" );
createPGMStr( txt_445, "Switching Logic" );
createPGMStr( txt_4451, "[normal]" );
createPGMStr( txt_4452, "[inverted]" );

createPGMStr( txt_45, "Factory reset" );

createPGMStr( txt_51, "Input Calibration" );
createPGMStr( txt_511, "Resistor Precalibration Input1 (Fuel Level Sensor)" );

createPGMStr( txt_512, "Resistor Precalibration Input2 (Oiltemp Sensor)" );

createPGMStr( txt_513, "Voltage Precalibration Input4 (Supply Voltage)" );

createPGMStr( txt_52, "Sensor Calibration" );
createPGMStr( txt_521, "Calibrate Input1 (Fuel Level Sensor)" );

createPGMStr( txt_522, "Calibrate Input2 (Oiltemp Sensor)" );

createPGMStr( txt_53, "Output Calibration" );
createPGMStr( txt_531, "Calibrate Output1 (Fuel Gauge)" );

createPGMStr( txt_532, "Calibrate Output2 (Oiltemp Gauge)" );    

createPGMStr( txt_54, "Load Factory Calibration (all channels)" );
Chris--A commented 8 years ago

I'll have a look and see what is going on.

mandabenjamin commented 3 years ago

hi cris I am benjamin from Indonesia I want to ask for a captive portal so that the beacon from cloning SSID, for example the target clicking on the mock SSID can switch to the login page to enter the password, what should I add in wipwn.ino I have an example of a 4 page entry html internet provider in Indonesia. can it be in html file upload mode. thanks