Pointer to Print Class (DualWriter) / TelnetServer [solved]

Hi Community,

i have build a test sketch for a 'menu control' for debugging things..
this sketch is reachable in two ways:
Serial
Telnet (arduino ethernet)
the Telnet thing is based on the concept of this post
for windows you can use PuTTY a telnet client.

at the moment i need a extra menu handler function for both communication ways.
but i would like to use the same menu handler function.
so i had two ideas to get this to work:
(1) give the handler function a pointer to the Object it should use for print
(2) have a 'own' myprint function that prints to both outputs.
(just calls the orig. Serial.print() and EthernetClient.print() with the same content)

so with version (2) i would need a function that imitates the orig. print function -
but thats not so trivial - because i think there are multiple versions to get the different data-types printed -
so if i am right i would have to build that behavior too :wink: (minimal for the types i use..)

for (1) i think it should be really easy - but i don't know which pointer type to use-
one time i could use HardwareSerial the other i need EthernetClient - so how to get this together?
i know that they are both based on Stream and for printing Stream is based on Print ??

so if someone of you have an idea how to do this pleas let me know !
i have added a demosketch with the simple Serial and Telnet Menus.
(edit: updated title and updated the sketch attachment)

sunny greetings
stefan

test_MultiOut.ino (25.5 KB)

Extending the print class is easy:

  class DualWriter : public Print{
    public:
      DualWriter( Print &p_Out1, Print &p_Out2 ) : OutputA( p_Out1 ), OutputB( p_Out2 ){}
        
      size_t write( uint8_t u_Data )
        {
          OutputA.write( u_Data );
          OutputB.write( u_Data );
          return 0x01;
        }
    protected:
      Print &OutputA;
      Print &OutputB;
  };

Hopefully this should work (class completely untested):

EthernetClient c;

DualWriter d( Serial, c );

d.print( "this will write to Serial and EthernetClient"  );

Just gave it a quick test, it works fine:

void setup() {
  Serial.begin(9600);
  
  DualWriter d0( Serial, Serial );
  DualWriter d1( d0, Serial );

  d1.println( F( "PROGMEM!" ) );
  d1.println( 1234, HEX );
  d1.println( 1.23f );
}

void loop() {}

Produced this output ( multiple \r\n removed ):

PPPRRROOOGGGMMMEEEMMM!!!
444DDD222
111...222333

thanks for this thing!
it just works nice!!

with your help i found out that my idea also worked :

void handle_Menu_Main(char *caCommand, Print &pOUT) {
	pOUT.println(F("MainMenu:"));
	char bFirstChar = caCommand[0];
	
	switch (bFirstChar) {
		case 'h':
		case 'H':
		case '?': {
				// help
				pOUT.println();
				pOUT.println(F("Help for pOUT Commands:"));
				pOUT.println(F("\t '?': this help"));
...

i just head the problem that i tried to use a pointer?!

void handle_Menu_Main(char *caCommand, Print *pOUT) { ...

i dont really know what it is doing now..
but i can call it like this:

handle_Menu_Main(sSerialInputCurrent, Serial);
// or
handle_Menu_Main(sTelnetInputCurrent, ethTelnet_Client);

and it is working as expected.

the DualWriter would be also very nice - but there is one problem with this approach:
it only works if both objects are 'available' - for the EthernetClient this is not always (in time) the case -
its only available when an client has connected to the server..
i tried to do something like this -
but it is a try and error in the dark :wink:

class DualWriter : public Print {
	public:
		DualWriter( Print &p_Out1, Print &p_Out2 ) : OutputA( p_Out1 ), OutputB( p_Out2 ){}
		
		size_t write( uint8_t u_Data ) {
			OutputA.write( u_Data );
			//check if OutputB is available
			if ( &OutputB != NULL ) {
				OutputB.write( u_Data );
			}
			return 0x01;
		}
		
	protected:
		Print &OutputA;
		Print &OutputB;
};  

DualWriter dwOUT( Serial, ethTelnet_Client);

when arduino tries to write the first byte to the OutputB it crashes..
that is 'normal' because OutputB is just 0 at this time and definitely not an EthernetClient object..

but how could i check this?

thanks for your point(er) in the right direction - at the moment i will use the first approach..
i attached the current version - so if someone want to test this out - both variants are included (one commented out)
sunny greetings
stefan

test_MultiOut.ino (23.7 KB)

Yeah, that class was laid out as a one-time initialiser, here it is modified to take a pointer reference.

I must ask, why are you using a pointer to an EthernetClient, it should be an object, then the original code would work as the EthernetClient won't send when it is not connected. client.stop() resets and it will no longer send if you call print.

Are you using dynamic memory?
Or is it because the DualWriter is global and the EthernetClient is local?
These would explain why the pointer is not available.

This assumes that the pointer variable passed in always exists ( is global or static, just its contents changes ).
This also assumes bad pointers are reset to 0 when done.

#include <Ethernet.h>
#include <SPI.h>
  class DualWriter : public Print{
  
    public:
    
      DualWriter( Print *p_Out1, Print *p_Out2 ) : OutputA( p_Out1 ), OutputB( p_Out2 ){}

      size_t write( uint8_t u_Data )
        {
          if( OutputA ) OutputA->write( u_Data );
          if( OutputB ) OutputB->write( u_Data );
          return 0x01;
        }
        
    protected:
    
      Print *&OutputA;
      Print *&OutputB;
  };  

void setup() {
  Serial.begin(9600);
  
  EthernetClient *c = 0x00;
  
//Skips EthernetClient as pointer is 0
  DualWriter d( &Serial, c );
  d.println( F( "PROGMEM!" ) );
  d.println( 1234, HEX );
  d.println( 1.23f );

  //Point c somewhere valid.
  EthernetClient e;

  c = &e;
  
  //Now prints to Serial and EthernetClient
  d.println( F( "PROGMEM!" ) );
  d.println( 1234, HEX );
  d.println( 1.23f );
}

void loop() {}

There is another change you can make if the pointer has a short life ( DualWriter exists longer than the pointer variable, i.e local variable destroyed on function return. ).

class DualWriter : public Print{

  public:
  
    DualWriter( Print *p_Out1, Print *p_Out2 ) : OutputA( p_Out1 ), OutputB( p_Out2 ){}
  
    void Set( Print *p_Out1, Print *p_Out2 )
      {
        OutputA = p_Out1;
        OutputB = p_Out1;
      }
      
    size_t write( uint8_t u_Data )
      {
        if( OutputA ) OutputA->write( u_Data );
        if( OutputB ) OutputB->write( u_Data );
        return 0x01;
      }
      
  protected:
  
    Print *OutputA;
    Print *OutputB;
};

This includes a set function to change the internal ( non-referenced ) pointers.

void setup() {
  Serial.begin(9600);
  
  EthernetClient c;
  
  //Print twice to serial.
  DualWriter d( &Serial, &Serial );
  d.println( F( "PROGMEM!" ) );
  d.println( 1234, HEX );
  d.println( 1.23f );
  
  //Switch to use ethernet client.
  DualWriter d( &Serial, &c );
  d.println( F( "PROGMEM!" ) );
  d.println( 1234, HEX );
  d.println( 1.23f );  
}

Just saw your included code, gotta go to work, but I will have a look through the source later on.

Hi pYro_65,

i have tested your last version with the Set
is Set automatically called?

i added the current test file.
it crashes if you try to use the serial menu as long as no TelnetClient is connected:
--> it prints the first char 'M' from "MainMenu" on OutputA (Serial)
than tries to print the first char 'M' on OutputB (EthernetClient) and crashes.

the problem is - i use a global scope variable for the EthernetClient;
this is set to the Client that the EthernetServer.available() (line 577) returns.
so if the client left the EthernetClient object is stopped.
if the EthernetClient was connected on time it is all fine.
---> i had an idea during writing this:
up to know i had:

EthernetClient ethTelnet_Client = 0;

i just changed this to

EthernetClient ethTelnet_Client = EthernetClient();

:slight_smile: and now the DualWriter just works :slight_smile:
i had the memory that i have read in the reference that if (EthernetClient) returns false if it is not ready..
so if i initialize the variable with an EthernetClient Object that would just give a boolean false..

one way would be to parse the pointer to the EthernetClient to every function that uses it - so the DualWriter could be set to the right pointers..?!
i don't know if it would make sens to pass all values (as reference) in an uC environment.. and have no Global values..
on 'big' computers normally there is the thing: as local as possible, as global as needed.

now it is working :slight_smile:
thanks for your help pYro_65!!
it would be interesting why it did not work when i initialized this with 0 ?!
and i tested it - only your second version with the Set worked.
what does this Set do?! who/when is it called?

attached the working example.

sunny greetings
stefan

test_MultiOut.ino (24.3 KB)

Hi, thanks for the praise, I like helping out.

I realize now why the original code didn't work, also, the original code is the one you want to use, if you weren't doing so.
I'll explain why.

Firstly you are best to initialize the ethernet client like this:

EthernetClient ethTelnet_Client;

The reason that assigning 0 didn't work, i.e:

EthernetClient ethTelnet_Client = 0;

Is because this set the internal socket to 0, which is a valid socket number.
The default constructor sets the socket to MAX_SOCK_NUM, and this value is used in the class to decide weather to react to calls that use a real socket.

This is why using an ' anonymous temporary' object worked, i.e:

EthernetClient ethTelnet_Client = EthernetClient();

This works due to the default constructor in the anonymous temporary setting MAX_SOCK_NUM, then simply passing it on to your object.

So you can safely use the version below as well, but is implicitly done if left to do so.

EthernetClient ethTelnet_Client = MAX_SOCK_NUM;

So it was this reason my original version seemed to fail, where It was actually incorrect initialization. :stuck_out_tongue:

thanks :slight_smile:

it is working with your original!! :wink:

can you explain why to use the & (reference) thing?

protected:
		Print &OutputA;
		Print &OutputB;

if have understand it right its like this:

byte bAValue = 5;
byte *pPointer; // that is a place to store a pointer
pPointer = &bAValue; //pPointer gets the place where bAValue is stored in memory
Serial.print("print destination of Pointer: ");
Serial.println(*pPointer); // get the value that is stored at the place pPointer points to

so with &xx you get the pointer to the location where xx is stored.
with *xx you get the value 'real value'
so why do you use the & to define the variable?!

attached the updated testsketch - i have added the pointer test in the menu entry 't' test

sunny greetings
stefan

test_MultiOut.ino (25.5 KB)

The ampersand (&) character has different meanings depending on where it is. There may not be much logic behind it, it just looks good I suppose. It can specify a reference, be an address-of operator, bitwise and logical 'and' operators ( T &a, &a, a & b, a && b ), the same as * can be used in maths and creating/accessing pointers ( a * b, T *a, *a ).