During development and troubleshooting I need info from my ESP-12 based board so I have lots of calls inside where Serial.print() and Serial.println() are called. A big part of the code is not written by me, I am adapting a GitHub download to my use case.
Now I want to find a way to silence this output when not needed and do it in one place so I created these:
But when I want to use them I run into difficulties because the code I am working on is calling Serial.print and Serial.println with a number of different argument types which Serial manages to decode successfully but the functions above cannot.
Examples of current use in the sketch (snippets from various places in the sketch):
int rssi = WiFi.RSSI(); //Get signal strength in dB
itoa(rssi, buf, 10);
Serial.printf("WiFi signal strength: %s\n", buf);
--------------
String outdata = "";
outdata += " IPaddr=" + WiFi.localIP().toString(); // 11 char
outdata += "; MACaddr=" + String(WiFi.macAddress()); // 18 char
Serial.println("WiFi: " + outdata);
--------------
char message[32];
memset(message, 0, sizeof(message)); //Zero out the array
GetTemperature(message); //Read sensor value
Serial.println("Now sending temperature via MQTT: " + String(message));
--------------
Serial.println(F("Entered config mode"));
Serial.println(WiFi.softAPIP());
Serial.println(myWiFiManager->getConfigPortalSSID());
So it seems like Serial.print() and Serial.println() can manage a big set of argument types which the simple SerialLog() I tried to create cannot....
Is there some already existing function I can use which enables me to switch it off/on in one place?
Note that the Serial port is used for incoming data all the time so Serial cannot be switched off altogether.
Disclaimer:
I am not versed in C++ but can handle C code. My main programming language has been Pascal.
So if this is "simple" in C++ then this is the problem for me.
#define DEBUG 1 // SET TO 0 OUT TO REMOVE TRACES
#if DEBUG
#define D_SerialBegin(...) Serial.begin(__VA_ARGS__);
#define D_print(...) Serial.print(__VA_ARGS__)
#define D_write(...) Serial.write(__VA_ARGS__)
#define D_println(...) Serial.println(__VA_ARGS__)
#else
#define D_SerialBegin(bauds)
#define D_print(...)
#define D_write(...)
#define D_println(...)
#endif
instead of Serial.print() or Serial.write() you use D_print() and D_write(). You get to keep the exact same parameters as the original functions since it's a textual replacement, for example
if you set the #DEBUG macro to 0 then all traces will go away, the code won't even make it to the compiler as the macro basically get rids of the text. The above example is transformed into
// nothing here :)
Opening of the Serial port would disappear too, so don't use D_SerialBegin() and keep your Serial.begin() if you want to keep the Serial port for incoming data
Interesting!
I did not know one could do it this way....
But it seems like a sensible method rather than trying to create a set of functions like I did.
Is the identifier __VA_ARGS__ something that will match whatever is inside the arguments ()?
And the (...) should be literally like that?
Now replaced almost all of the Serial.print() and Serial.println() calls with D_print() and D_println() in the sketch and uploaded it to my device with the DEBUG enabled.
And it works just as it should, the same logging as before!
Next I changed the define:
#define DEBUG 0
And now all the debug output from the main cpp file was gone, just some from a utils.cpp file I did not modify are still printed!
This is exactly how I wanted it to be! MANY THANKS for the suggestion!
And the OP might be interested to know that Serial.print on a ESP32 is slow when compared to the ESP32's log_x macro.
void MQTTkeepalive( void *pvParameters )
{
sema_MQTT_KeepAlive = xSemaphoreCreateBinary();
xSemaphoreGive( sema_MQTT_KeepAlive ); // found keep alive can mess with a publish, stop keep alive during publish
// setting must be set before a mqtt connection is made
MQTTclient.setKeepAlive( 90 ); // setting keep alive to 90 seconds makes for a very reliable connection, must be set before the 1st connection is made.
for (;;)
{
//check for a is-connected and if the WiFi 'thinks' its connected, found checking on both is more realible than just a single check
if ( (wifiClient.connected()) && (WiFi.status() == WL_CONNECTED) )
{
xSemaphoreTake( sema_MQTT_KeepAlive, portMAX_DELAY ); // whiles MQTTlient.loop() is running no other mqtt operations should be in process
MQTTclient.loop();
xSemaphoreGive( sema_MQTT_KeepAlive );
}
else {
log_i( "MQTT keep alive found MQTT status %s WiFi status %s", String(wifiClient.connected()), String(WiFi.status()) );
if ( !(wifiClient.connected()) || !(WiFi.status() == WL_CONNECTED) )
{
connectToWiFi();
}
connectToMQTT();
}
vTaskDelay( 250 ); //task runs approx every 250 mS
}
vTaskDelete ( NULL );
}
I will have a look at this too, the advantage being not having to build a special version of the firmware with debug enabled, since it can be enabled/disabled during execution of the sketch.
However, this again means that I have to find a way to transfer the various arguments into it...
if you want to switch on/off debug messages during runtime give that a try:
/* Optionale Debug.print Ausgabe zu Serial - Veränderbar zur Laufzeit
* http://forum.arduino.cc/index.php?topic=575044.0
*
*/
class debugPrint : public Print {
uint8_t _debugLevel = 1;
public:
void setDebugLevel(byte newLevel) {
_debugLevel = newLevel;
}
size_t write (uint8_t value)
{
if (_debugLevel)
{
Serial.write(value);
}
return 1;
}
} Debug;
void setup() {
// put your setup code here, to run once:
Serial.begin(4800);
Serial.println(F("Debug Print Test"));
Debug.println("Debug 1 is on");
Debug.setDebugLevel(0);
Debug.println("Debug 2 is off");
Debug.setDebugLevel(1);
Debug.println("Debug 3 is on again");
Debug.setDebugLevel(0);
Debug.println(F("Debug 4 is off"));
Debug.setDebugLevel(1);
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);
}
void loop() {
// put your main code here, to run repeatedly:
}
It's just a simple class inherting from print, hence the implementation of a write function, but it only writes when the variable is set.
When you write your code use the common class.print interface. A print is still a "one liner" like when you would have used some kind of precompiler D_PRINTLN.
You still can use all formating options from print.
You can switch on/off the debug messages during runtime.
if you dislike the "setDebugLevel" it should be very easy to implement two member functions for .on() and .off()
edit:
* Optionale Debug.print Ausgabe zu Serial - Veränderbar zur Laufzeit
* http://forum.arduino.cc/index.php?topic=575044.0
*
*/
class debugPrint : public Print {
uint8_t _debugLevel = 1;
public:
void on()
{
setDebugLevel(1);
}
void off()
{
setDebugLevel(0);
}
void setDebugLevel(byte newLevel) {
_debugLevel = newLevel;
}
size_t write (uint8_t value)
{
if (_debugLevel)
{
Serial.write(value);
}
return 1;
}
} Debug; // this instance starts with a capital letter to make it look similar to "Serial"
void setup() {
// put your setup code here, to run once:
Serial.begin(4800);
Serial.println(F("Debug Print Test"));
Debug.println("Debug 1 is on");
Debug.setDebugLevel(0);
Debug.println("Debug 2 is off");
Debug.setDebugLevel(1);
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);
}
void loop() {
// put your main code here, to run repeatedly:
}
it could indeed be convenient to activate or not the debug mode at run time but it means your code is always loaded with the debug info that will use up some memory.
not info, but code. is there a need to save memory?
what if you application requires so much memory that there is not enough when enabling the DEBUG stuff when actually needed?
i had the experience of creating a rather verbose debug macro which could be pared down when we exceeded memory on a DSP. sort of reserved memory for future use
but in our experience with users, we've told them to enable various (100s) debug modes and then return the log files (gBs)
In my case there is no way I can send a command to the ESP, its serial port is occupied for incoming data from an electricity meter.
I would have to implement some kind of socket server to which I could connect remotely and then send some command for log level etc.
What I do have is an OTA server so I can upload a new f/w to it with the DEBUG define set to 1.
I wonder how one could implement a TCP socket server to handle the setting of debugLevel? But then again, I should also make it possible to redirect logging to that server such that if a client connects then the data would be sent via TCP rather than serial...
Maybe the class debugPrint : public Print can be provided with a 3rd state where it would send data out to a connected TCP socket client?
Do you not need to instantiate a class before use in C++?
I have never really programmed C++, just ANSI C and for OOP I have used ObjectPascal such as Delphi and FreePascal, where you most definitely need to instantiate and object before use.
OK, I did not know that is how you do stuff in C++.
In my world you define the class and then in another place where it is used you create and use it.
Very good new knowledge.
// define the class
class debugPrint : public Print {
..
} ;
// instantiate
debugPrint Debug; // this instance starts with a capital letter to make it look similar to "Serial"