Skip to content

Debugging Console

Olaf Schreck edited this page Jul 24, 2021 · 3 revisions

G5 Cooler 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.

v1.0

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.

v1.1

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.

Use WIFI Instead Of USB Cable

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

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 our config.h does not define WITH_DEBUG, the SerialDebug library flag DEBUG_DISABLED gets set. If this is set the library will not be compiled with console logging code at all (=> no logging to serial line). The printX() 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

What About The Compile Errors?

[...]

Clone this wiki locally