-
Notifications
You must be signed in to change notification settings - Fork 0
Debugging Console
The G5 cooler should have a system console to display boot messages, errors, warnings and debug messages, as well as periodic sensor readings. The standard console on Arduino and ESP modules is the USB port, which provides a COM interface to the Arduino IDE (or others). Messages were printed to the console using Serial.print() / Serial.println()
.
The cooler is not expected to be connected to the Arduino IDE all the time, so there is no reason to compile in this code when no one will be listening. It can be useful for debugging and should be configurable.
A compile time option #define USE_SERIAL_DEBUG
was used, and the calls to Serial.print()
were wrapped in #ifdef USE_SERIAL_DEBUG
compile time conditionals, in order to not compile the code if is known that there is no serial console.
In v1.0 this code was roughly like this:
// config.h
#define WITH_SERIAL
#define WITH_DEBUG
// cooler.ino
#if defined(WITH_SERIAL) && defined(WITH_DEBUG)
#define USE_SERIAL_DEBUG
#endif
setup() {
#ifdef USE_SERIAL_DEBUG
Serial.println("here");
#endif
}
loop() {
#ifdef USE_SERIAL_DEBUG
Serial.println("doing");
#endif
}
This worked, but all these ifdefs were bloating the code. So be it.
We wanted some interactive runtime control when debugging the system with a console attached, so we could set the fan speed or read sensor values on demand. We introduced the SerialCommands
library, which is quite sophisticated using callback functions. This worked fine, this is the sample code for a status
command:
// config.h
#define WITH_SERIAL_COMMANDS
// cooler.ino
#ifdef WITH_SERIAL_COMMANDS
#include "SerialCommands.h"
char serial_command_buffer_[16];
//// handler callback functions must be defined here, not in the functions section hear the end
void cmd_unrecognized(SerialCommands* sender, const char* cmd) {
// error unknown command
}
SerialCommands serial_commands_(&Serial, serial_command_buffer_, sizeof(serial_command_buffer_), "\n", " ");
void cmd_get_status(SerialCommands* sender) {
serial_report_values();
}
SerialCommand cmd_get_status_("status", cmd_get_status);
#endif
loop() {
#ifdef WITH_SERIAL_COMMANDS
serial_commands_.ReadSerial();
#endif
}
void serial_report_values() {
// Serial.println() when "status" command from console
}
We could change program behaviour with commands like fan 20
from the console. Very nice.
In rev2 of our hardware spec, we introduced ESP8266 hardware which have WIFI on board and provide nice APIs to run an embedded web server and OTA (over-the-air) updates. Now there is no USB COM console connected anymore.
Which means there is no console anymore. Too bad, would have been nice. Looking around, we stumbled over the RemoteDebug
library https://github.com/JoaoLopesF/RemoteDebug which addresses this problem. Looking around even more, we found that the author has a companion library SerialDebug
which seems to use the same API. This looked nice, because it addresses a few issues:
- unified interface for console over serial or WIFI
- (almost) drop-in replacement for
Serial.print()
- conditional logging based log levels (always, errors, debug)
- support for basic profiling and remote debugger instance - we did not use this yet
- configurable for low-mem systems
Good enough to give it a try.
v1.2 replaced all calls to Serial.print()
wrapped in #ifdef USE_SERIAL_DEBUG
. It includes and initializes the SerialDebug
library and use the printX()
family of calls to print output to the console. The X
character denotes the log level for the messages, where X
can be one of
-
A
(always) - print this message unconditionally -
E
(error) - conditionally print this error message (if log level is "error") -
W
(warning) - conditionally print this warning message (if log level is "warning" or higher) -
I
(info) - conditionally print this informational message (if log level is "info" or higher) -
D
(debug) - conditionally print this debug message (if log level is "debug" or higher) -
V
(verbose) - conditionally print this verbose debug message (if log level is "verbose" or higher)
Basically, there are increasing log levels: a printI()
will be shown only if the log level has been set to info
, debug
or verbose
. It will not be shown at log levels warning
or error
.
Compare this v1.2 code with the v1.0 code above:
// config.h
#define WITH_SERIAL
#define WITH_DEBUG
// cooler.ino
#if defined(WITH_SERIAL)
//// defines must come before the include
#ifndef WITH_DEBUG
#define DEBUG_DISABLED
#endif
//#define DEBUG_MINIMUM true
#define DEBUG_DISABLE_DEBUGGER true
#define DEBUG_AUTO_FUNC_DISABLED true
#include "SerialDebug.h"
#endif
setup() {
printlnA("here");
}
loop() {
printlnA("doing");
printlnD("debugging");
debugHandle();
}
- note the use of
DEBUG_DISABLED
: if ourconfig.h
does not defineWITH_DEBUG
, theSerialDebug
library flagDEBUG_DISABLED
gets set. If this is set the library will not be compiled with console logging code at all (=> no logging to serial line). TheprintX()
family of function calls are defined as void macros in this case i.e. they don't do anything. - corollary: no need to wrap the
printX()
in ifdefs, funtions are "just there", code is more simple
[...]