@BosseB
over 2h later:
I played around and came up with that
/* Optional Debug.print
https://forum.arduino.cc/t/how-to-create-a-seriallog-function-that-can-be-switched-off/1057755/10
disable/enable debug messages during runtime
use several debug channel in parallel
store incoming data in a buffer for channels which need one single transmission
by noiasca
*/
class DebugPrint : public Print {
protected:
uint8_t debugLevel = 1 << SERIAL0; // store the activated debug outputs in a bitmask - you can debug print to several interfaces
uint32_t previousMillis = 0; // the last received "write" command
const uint16_t autoPublish = 500; // autoflush ("publish") after this period. Set to 0 if you don't need a autoPublish and call flush on your own
byte dirty = 0; // how many bytes are in the buffer
char buffer[255] {""}; // internal buffer
public:
enum destination {OFF,
SERIAL0,
LCD,
MQTT,
SERIAL1,
SERIAL2, // I'm using an ESP32 with 3 Serials, currently I have Serial2 connected with an USB-TTL interface
};
void off()
{
debugLevel = OFF; // off is realy a switch off
//Serial.print(F("debugLevel=")); Serial.println(debugLevel, BIN);
}
void on() // activate only on Serial(0)
{
debugLevel = 1 << SERIAL0;
//Serial.print(F("debugLevel=")); Serial.println(debugLevel, BIN);
}
void addDebugLevel(destination newDestination)
{
debugLevel |= (1 << newDestination); // "add" the received destination to the bitmask
//Serial.print(F("debugLevel=")); Serial.println(debugLevel, BIN);
}
void setDebugLevel(byte newLevel)
{
debugLevel = newLevel;
//Serial.print(F("debugLevel=")); Serial.println(debugLevel, BIN);
}
size_t write (uint8_t value)
{
if (debugLevel & (1 << SERIAL0))
{
Serial.write(value); // passthrough the value to the output channel
}
if (debugLevel & (1 << LCD))
{
//lcd.write(value); // not implemented but you get the idea
}
if (debugLevel & (1 << MQTT))
{
// print to buffer
previousMillis = millis();
char in[2] {'\0', '\0'}; // prepare null terminated c-string (for later strcat)
in[0] = value; // copy the received value to first byte
strcat(buffer, in); // concat to the buffer
dirty++;
// avoid overflow of buffer
if (dirty >= sizeof(buffer) - 1) flush(); // force flush because buffer is full (or handle the buffer overflow differently ...
}
if (debugLevel & (1 << SERIAL1))
{
Serial1.write(value); // only activate if you really have a Serial1
}
//if (debugLevel & (1 << SERIAL2))
//{
// Serial2.write(value); // only activate if you really have a Serial2
//}
return 1; // you must return the number of "printed" bytes. this write processes 1 byte. therefore return 1 (even if it is not printed)
}
void flush() // output buffer to mqtt
{
if (debugLevel & (1 << MQTT))
{
// put here the "publish" function of MQTT
// mqtt_client.publish("source/data/wifistrength", buf);
//Serial1.print(F("mqtt flush>")); Serial1.println(buffer); // this line is just for my testing
strcpy(buffer, ""); // delete internal buffer
dirty = 0; // reset
}
else
{
//Serial.println(F("I: no publish necessary"));
}
}
// check if there is something to flush
void update()
{
if (dirty && autoPublish && millis() - previousMillis > autoPublish)
{
flush();
}
}
} debug;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial1.begin(115200);
//Serial2.begin(115200);
Serial.println(F("Debug Print Test"));
debug.on();
debug.println("Debug 1 is on");
debug.off();
debug.println("Debug 2 is off");
debug.addDebugLevel(DebugPrint::SERIAL0);
debug.println("Debug 3 is on again");
debug.off();
debug.println(F("Debug 4 is off"));
debug.on();
debug.println(F("Debug 5 is on again"));
float afloat = 123.45;
debug.println(afloat);
char achararray [] = {"bla bla\0"};
debug.println(achararray);
String anArduinoStringObject = "bla blub";
debug.println(anArduinoStringObject);
}
// this is just to change the debug state during runtime.
void serialRead()
{
byte in = Serial.read();
switch (in)
{
case 'o' : debug.off(); break;
case 's' : debug.addDebugLevel(DebugPrint::SERIAL0); break;
case 'l' : debug.addDebugLevel(DebugPrint::LCD); break;
case 'm' : debug.addDebugLevel(DebugPrint::MQTT); break;
case '1' : debug.addDebugLevel(DebugPrint::SERIAL1); break;
//case '2' : debug.addDebugLevel(DebugPrint::SERIAL2); break;
}
}
// this is just a simulation to do some periodic action
void doSomething()
{
static uint32_t previousMillis = 0;
uint32_t currentMillis = millis();
if (currentMillis - previousMillis > 1000)
{
previousMillis = currentMillis;
int value = random(1000); // simulate some sensor reading
debug.print(F("value=")); debug.println(value);
//debug.flush(); //optional if you want to force a flush
}
}
void loop() {
doSomething(); // your main program, sensor readings, getting data ...
serialRead(); // just for test purpose
debug.update(); // this will call flush() if necessary
}
for example you can activate Output to 2 (3.., 4..) Serials if available:
You can switch on debug messages via Serial interface:
o for off
s for Serial
1 for Serial1
2 for Serial2 (I'm using a ESP32)
l is just an example for a LCD
m would be prepared for MQTT
This is done in the function serialRead(). Therefore I call this function in loop() over and over again. If you have other possibilities to switch on debug messages (buttons, incoming MQTT messages, a HTML page ...) you don't need that function.
The class can have several active channels (=outputs) at the same time. The active channels are stored in the variable debugLevel (in "binary"/as a bitmap).
"Print" interfaces like Serial or LCD would be straight forward, just pass through the write to the activated channel.
For MQTT we add each incoming value to an internal buffer.
The member function flush() sends the buffer to MQTT and empties the buffer.
so this means you would need to do a "flush()" at the end of your debug.prints in case of MQTT.
As I didn't want to add this flush() all over, I came up with a "timeout" function update(): is there something in the buffer and haven't we received new characters for some time, I do a autoflush. Idea is, you might need several prints to get your result (for example you want to print a warning and an actual value). This update() must be called over and over again in loop().
I have simulated this "MQTT call" via a second serial, and it looks like it would work:
The screenshot was taken after the last (direct) print to Serial was already visible, but the timeout for the MQTT was not reached so far. Therefore you see this slight "delay" between the two output channels.
For a production sketch there might be needed more improvements. But this should give you an idea how it could be done. I hope this will make sense for you. Yes I believe you will need to read this several times and play around with the sketch.