Passing I/O functions to a class.

I experimented with your suggestions, wrote a stub for the menu just as a simple test if I can use serial I/O from both, the Menu class and the Arduino sketch.

It worked right ahead, and like always afterwards you cannot understand why I didn't get that right first time...
Didn't I try about the same thing as one of my first test? Well obviously not exactly the same :wink:

/*
  stub.ino
  Confused about passing I/O functions from the sketch to the menu class
  I wrote this TestStub class to make some very basic I/O tests.
  Based on insights and suggestions from a discussion in Arduino forums.
  Special thanks to Nick Gammon, Chaul, johncc.
*/

/* **************************************************************** */
/* Library: 	*/

class TestStub {
public:
 TestStub(Stream & port, int (*maybeInput)(void))
   : port_ (port),
    maybe_input(maybeInput)
  { }

  // Basic tests for menu output:
  void out(const char *str) {
    port_.print(str);
  }

  // Testing overloading out():
  void out(const char c) { port_.print(c); }
  void out(const int i)  { port_.print(i); }

  // Some basic output functions:
  void ln()	{ port_.print('\n'); }
  void tab()	{ port_.print('\t'); }

  bool lurk() {
    /* int maybe_input()  
       must be a function returning one byte data
       or if there is no input returning EOF		*/
    int INP=(*maybe_input)();

    if ( INP != EOF ) {	// there *is* input
      out("Lurker sees input:\t");
      out((char) INP);        // I don't care about newline here ;)
      tab();
      out(INP);
      ln();

      return true;
    }
    return false;
  }

private:
  Stream & port_; 
  int (*maybe_input)(void);	// maybe_input()  Must return EOF or next char
};


/* **************************************************************** */
/* Arduino program:	*/

int men_input() {		// men_input()    Must return EOF or next char
  if (!Serial.available())
    return EOF;

  return Serial.read();
}

TestStub STUB(Serial, &men_input);

void setup() {
  Serial.begin(115200);
  STUB.out("Basic c++ I/O test...\tTesting maybe_input(), string out(), lurk():\n");
}

long cnt=0;

void loop() {
  STUB.lurk();	// lurks for input, then trolls.

  // Test if I can still call Serial.print() from main():
  if (! (cnt++ % 100000L)) {
    Serial.print("Serial.print() from main().\t");
    Serial.println(cnt -1);
  }
}

/* **************************************************************** */

Changing existing git code to this implementation should be straight ahead. I hope to do that today.
Thanks a lot for all the input.

Well, yes changing the existing git code to this implementation was quick, but...
it gave the same re-definition problems as before :wink:

After a lot of trying this&that turns out that it (probably) was some #include logic,
like not having loaded Arduino.h in a file when I thought I had,
resulting in names changing meaning while compiling or similar.

Starts working now on Arduino.

I still have some details to chew on:

  • PROGMEM strings
    I have used them so far for constant strings in the user interface.
    Things would get easier if I could drop them (without using RAM)

I have read that they could be replaced by string constants. Searching a bit does not sound so simple.
If I only use them for string output functions it seems a save bet that the compiler would optimize them away.
Is that true?

  • How can I use (test) the code on the PC? (Linux, in my case).
    Still not really understanding that stream stuff...
    What do I #include that it will know 'Stream' on the PC, and what about 'Serial'?

To use program memory for strings, just use the F macro, as in this modified example:

class myMenu 
  {
  private:
    Stream & port_; 
  public:
    myMenu (Stream & port) : port_ (port) { }
    void begin ();
  };

void myMenu::begin ()
  {
  port_.println (F("Menu initialized."));    // <-------- keep string in PROGMEM
  }

myMenu menu (Serial);

void setup ()
  {
  Serial.begin (115200);
  menu.begin ();
  }  // end of setup

void loop () { }

As for testing on the PC, I personally just upload to my board each time. Trying to do stuff on the PC is going to mean getting the libraries included etc.

@Nick Thank you for all your help.

I'd prefer keeping the possibility to compile test code for the PC for a while, if it does not prove to be too difficult.
I plan to do some stuff where I will need fast edit-compile-test cycles to find my way through :wink:
Still feeling very new to that c++ stuff.

I have found a way to have both now, doing an ugly hack called preprocessor magic.
I think I will drop that once feeling more comfortable with c++ after doing some more coding.
You are right: it is a hassle to maintain...

Not decided on PROGMEM yet, will do some more tests.
F("string") macro is fine for Arduino, but another thing to be taken care of as long as I want to use a PC as test platform.

My current version is now online, comments welcome.

OutOfLine:
F("string") macro is fine for Arduino, but another thing to be taken care of as long as I want to use a PC as test platform.

Hardly a lot of trouble though, just get the macro to return the original string.

True.

For strings used multiple times I think declaring them as a named constant used only to pass as const parameter to const output functions should produce very similar results as declaring them as PROGMEM.

I'm not definitely sure about that yet, though.

The compiler will do that anyway, I believe. Example:

const char * foo = "But does it get goat's blood out?";
const char * bar = "But does it get goat's blood out?";

void setup ()
  {
  Serial.begin (115200);
  Serial.println (foo);
  Serial.println (bar);
  Serial.println ((int) foo);
  Serial.println ((int) bar);
  }  // end of setup

void loop () { }

The second pair of lines shows that foo and bar are actually the same string:

But does it get goat's blood out?
But does it get goat's blood out?
256
256

Interesting, and a nice trick to test that.

I have started some testing too,
testing RAM usage and sketch size influence of Arduino F() macro:

Not using F(): Using Arduino F() macro:

Binary sketch size: 7,206 bytes Binary sketch size: 7,534 bytes
RAM: 465 RAM: 1525

nice :slight_smile:

Proof?

Continued testing...

Testing RAM usage and sketch size:

Using Arduino F() macro only,
eliminating const strings and all PROGMEM stuff:

Binary sketch size: 7,534 bytes
RAM: 1685

even better :slight_smile:

Comment/uncomment '#define USE_F_MACRO' in Menu.h
to switch between using Arduino F() macro or a noop replacement.

I told the menu to display free RAM for these tests, btw.

For testing PROGMEM support you would need the latest version with PROGMEM storage support.
Log message says "Switching PROGMEM for tests.".

(Yes I do know that this version reads progmem strings as char instead of unsigned char, breaking string display.
I don't care, should not influence the test.)

The numbers you get will differ by a few bytes, as the versions are not absolutely identical.
The "Testing using Arduino F() macro only. " commit has some additional functionality even.

RAM usage increases by 6 bytes when using the menu the very first time. I don't even know why.
I gave startup values for RAM usage.

Binary sketch size: 7,206 bytes Binary sketch size: 7,534 bytes
RAM: 465 RAM: 1525

OK, so that is free RAM? The higher the better?

Yes, I hope so :wink:

The code in Menu::get_free_RAM() is code I once copied (probably from the Arduino page) and have used for several years. I did not do real testing about the exact meaning of the result. Would be interested to learn more about that, btw.

int Menu::get_free_RAM() const {
  int free;
  extern int __bss_end;
  extern void *__brkval;

  if ((int) __brkval == 0)
    return ((int) &free) - ((int) &__bss_end);
  else
    return ((int) &free) - ((int) __brkval);
}