Skip to content

Using projectMM as a Library

projectMM can be consumed as a PlatformIO library. Your project gets the full module runtime (Module, StatefulModule, ModuleManager, Scheduler, PAL, HTTP/WS server) plus all infrastructure modules (WiFi, network, system status, device discovery, tasks, file manager) without copying any source. LED-domain modules (effects, layers, drivers) are excluded from the library build by default — your project provides only what it needs.


Add to your project

In your platformio.ini:

lib_deps =
    https://github.com/ewowi/projectMM
    bblanchon/ArduinoJson @ ^7
    ESP32Async/ESPAsyncWebServer#v3.10.3

PlatformIO fetches the repo, compiles src/ (excluding main.cpp and modules/ModuleRegistrations.cpp), and links it into your firmware. Add these flags to match projectMM's build requirements:

lib_compat_mode = strict
build_flags =
    -std=c++17
    -DBUILD_TARGET=\"$PIOENV\"

lib_compat_mode = strict avoids silent dependency resolution surprises. -DBUILD_TARGET is required by Pal.h's platform_version() string.


Provide your own entry point

The library omits src/main.cpp. Your project must provide setup() and loop(). The minimal form using pal::embeddedSetup():

#include "core/AppSetup.h"
#include "core/Scheduler.h"
#include "core/ModuleManager.h"
#include "core/HttpServer.h"
#include "core/WsServer.h"

static Scheduler     scheduler;
static ModuleManager mm(scheduler);
static HttpServer    server(80);
static WsServer      ws;

void setup() {
    Serial.begin(115200);
    pal::embeddedSetup(mm, scheduler, server, ws, nullptr, nullptr);
}

void loop() { pal::suspend_forever(); }

pal::embeddedSetup() wires HTTP routes, starts the WebSocket server, and creates the FreeRTOS tasks. The optional last argument is a firstBootFn(ModuleManager&) callback invoked once when no saved state exists, which is the right place to register default modules.


Register your own modules

src/modules/ModuleRegistrations.cpp is excluded from the library build. Your project supplies its own registrations:

// src/MyRegistrations.cpp  (in your project, not the library)
#include "core/TypeRegistry.h"
#include "MyEffect.h"
#include "MyDriver.h"

REGISTER_MODULE(MyEffect)
REGISTER_MODULE(MyDriver)

REGISTER_MODULE must be in a translation unit compiled into the executable, not a static library. Infrastructure modules (WiFi, network, system status, device discovery, tasks, file manager) are registered automatically via the library's src/core/CoreRegistrations.cpp — you do not need to list them.


ProducerModule and ConsumerModule

For domain-specific pixel pipelines, extend ProducerModule (writes a pixel buffer) and ConsumerModule (reads one):

// src/core/ProducerModule.h
class ProducerModule : public StatefulModule {
public:
    // Call once in setup() to declare the pixel buffer.
    void declareBuffer(void* buf, size_t len, size_t elemSize);

    void*  bufferPtr()      const;   // pointer to the first element
    size_t bufferLen()      const;   // number of elements
    size_t bufferElemSize() const;   // bytes per element
    size_t bufferBytes()    const;   // total bytes (len * elemSize)
};

// src/core/ConsumerModule.h
class ConsumerModule : public StatefulModule {
public:
    // Wire at runtime: setInput("producer", &myEffect)
    void setInput(const char* key, Module* m) override;

protected:
    ProducerModule* producer_ = nullptr;  // available in loop()
};

Typical pattern (FastLED example):

class WaveRainbow2DEffect : public ProducerModule {
public:
    void setup() override {
        declareBuffer(leds, NUM_LEDS, sizeof(CRGB));
        addControl(speed_, "speed", "slider", 1.0f, 10.0f);
    }
    void loop() override { /* write to leds[] */ }
    // ...
private:
    CRGB  leds[NUM_LEDS];
    float speed_ = 3.0f;
};

class MyDriver : public ConsumerModule {
public:
    void loop() override {
        if (!producer_) return;
        // producer_->bufferPtr() gives the CRGB array
        FastLED.show();
    }
};

The buffer is owned by the ProducerModule subclass. The ConsumerModule reads it via producer_->bufferPtr() or, when both share a global array, directly.

Note: EffectsLayer and DriverLayer (the built-in LED pipeline) do not extend these base classes. They use a double-buffered atomic Channel* designed for the dual-core handoff. ProducerModule / ConsumerModule are for new single-buffer domain consumers.


What the library includes

Path Contents
src/core/ Module, StatefulModule, ModuleManager, Scheduler, TypeRegistry, HTTP/WS servers, KvStore, Logger, ProducerModule, ConsumerModule
src/core/CoreRegistrations.cpp Auto-registered infrastructure: Network, WifiAp, WifiSta, SystemStatus, DeviceDiscovery, Tasks, FileManager
src/pal/ Pal.h — platform abstraction (timing, GPIO, heap, WiFi, UDP, OTA, filesystem)
src/modules/ Built-in LED modules (compiled but NOT auto-registered; excluded from library.json srcFilter)
src/frontend/ frontend_bundle.h — inlined HTML/CSS/JS served over HTTP

src/main.cpp and src/modules/ModuleRegistrations.cpp are excluded from the library build via library.json's srcFilter.


Pin a specific release

lib_deps =
    https://github.com/ewowi/projectMM#v1.4.0

See GitHub releases for available tags.