Design - denis-stepanov/esp-ds-system GitHub Wiki

This page explains several design decisions behind the current implementation.

Namespace

All functions are placed into a namespace ds. This means, the functions need to be prefixed with the namespace:

ds::System::begin();

unless you activate the namespace explicitly:

using namespace ds;
...
System::begin();

The rest of the documentation often assumes the namespace ds to be active.

Static / Public Interface

Whereas all functions and methods are collected in a single class ds::System, most of the members and methods are deliberately declared static and public. This is not a typical C++ design; it makes the code somewhat closer to traditional C. Use of static qualifier avoids need to declare an instance of ds::System at runtime and allows passing function pointers around without the need of tricks like std::bind. public interface to members allows for direct manipulation with member objects such as JLed's LED or AceButton's button. The library does not aim to replace or hide the underlying object's interface, so the objects are fully exposed.

Use of C Preprocessor

The library uses pre-processor extensively (#define, #ifdef, etc). The main idea is to compile in only the code which will be actually used by the sketch, so that its size would not be increased because of unneeded features. While being very powerful, this has a couple of drawbacks:

  1. Functions supporting multiple capabilities quickly get cluttered with #ifdef's;
  2. Compiling the library in Arduino IDE is not straightforward.

Arduino IDE compiles sketches and all supporting code using a fixed set of compiler macros. It is not possible (or I do not know how) to compile a specific external library with a user-defined set of #define's. The only way I am aware of is making my own plugin to the IDE (similar to board support packages) - something I am not feeling hot to do. This means that it makes little sense to distribute this code as a regular Arduino library. Instead, the code (System.h and System.cpp) has to be copied locally in the sketch folder, and wrapped into extra header MySystem.h, which will provide the necessary #define's. Note that MySystem.h behaves as if it was a System.h header itself; no user code should ever include System.h directly.

Good news with this approach is that since the library sits together with a sketch, it can be easily hacked to change its behavior, should the original behavior be found unsatisfactory.

Weak Initializations

User-settable members of system class are initialized with defaults using "weak" assignment. For instance, a hostname is defined as:

const char *System::hostname PROGMEM __attribute__ ((weak)) = "espDS";

This means, in a user code it is possible to override the default as follows:

const char *System::hostname PROGMEM = "myNodeMCU";          // My hostname

Unlike weak function assignment, assignment of global variables in libraries is considered unsafe and may cause undefined behavior. In practice, this seems to work pretty well, except for references &. This is the reason syslog is provided as a pointer: System::log->printf() ... -- its static initialization via reference did not work properly.

Optimizations

The library was not specifically designed with the smallest footprint in mind. Nevertheless, several things might help keeping its size contained:

  1. Use of C pre-processor (see above) allows to compile in only the code that is needed;
  2. All strings in the library are declared as PROGMEM strings (i.e., put in flash memory instead of RAM);
  3. Web server implementation includes a web page construction cache with preallocated size (2 kiB). This means if your page is below this size, its construction via += will not result in numerous memory reallocations. The downside, though, is that this cache is lost for other tasks in the system;
  4. Large JavaScript code is compressed; see DS_CAP_WEB_TIMERS for an example.