π‘ Using the EventBus for Module Communication
You've created a self-contained module, but the true power of a modular framework is unlocked when modules can communicate. In Nextino, the primary way to achieve decoupled, one-to-many communication is through the EventBus. π’
This tutorial builds upon the ButtonModule we created previously. We will modify our LedModule to listen for events and react to them, following our best practice of separating logic into dedicated files.
π― The Goalβ
We will create a fun, interactive system where:
- The
ButtonModuledetects short and long presses, postingbutton_short_pressandbutton_long_pressevents. - The
LedModulesubscribes to both of these events. - The event handling logic will live in its own
LedModule_events.cppfile. - A short press will toggle the LED's blinking state.
- A long press will toggle the LED's solid state.
This creates a sophisticated, well-structured system where two modules collaborate without any direct knowledge of each other. β¨
Step 1: Update the LedModule to be Event-Awareβ
First, let's give our LedModule a proper state machine and prepare it for event subscriptions.
1.1. Update the File Structureβ
Let's add a new file to our LedFlasher library for the event handling logic.
lib/LedFlasher/
βββ src/
βββ LedModule.h
βββ LedModule.cpp // Constructor & Lifecycle
βββ LedModule_events.cpp // β¨ NEW: Event handling logic
1.2. Update the Header (LedModule.h)β
The header file defines the module's "contract," including the private methods that will handle the events.
#pragma once
#include <Nextino.h>
class LedModule : public BaseModule {
private:
// Define clear states for the module
enum class LedState {
OFF,
ON,
BLINKING
};
// Configuration
int _pin;
unsigned long _interval;
// Internal State
uint32_t _taskHandle;
LedState _currentState;
// --- Method Declarations ---
// These will be implemented in LedModule_events.cpp
void handleShortPress(void* payload);
void handleLongPress(void* payload);
// This will also be in LedModule_events.cpp as it's part of the core logic
void setState(LedState newState);
public:
LedModule(const JsonObject& config);
static BaseModule* create(const JsonObject& config) {
return new LedModule(config);
}
// --- BaseModule Lifecycle Methods (implemented in LedModule.cpp) ---
const char* getName() const override;
void init() override;
void start() override;
};
1.3. Update the Main Implementation (LedModule.cpp)β
The main .cpp file is the "orchestrator." Its job is to handle the lifecycle and set up the subscriptions. The actual logic is delegated to the methods implemented in LedModule_events.cpp.
#include "LedModule.h"
// --- Constructor and Lifecycle ---
LedModule::LedModule(const JsonObject& config) {
_pin = config["resource"]["pin"];
_interval = config["blink_interval_ms"] | 500;
_taskHandle = 0;
_currentState = LedState::OFF;
}
const char* LedModule::getName() const { return "LedModule"; }
void LedModule::init() {
pinMode(_pin, OUTPUT);
digitalWrite(_pin, LOW);
NEXTINO_LOGI(getName(), "Initialized on pin %d.", _pin);
}
void LedModule::start() {
// The start() method's only job is to set up subscriptions.
// The logic itself is in another file.
NextinoEvent().on("button_short_press", [this](void* p) { this->handleShortPress(p); });
NextinoEvent().on("button_long_press", [this](void* p) { this->handleLongPress(p); });
NEXTINO_LOGI(getName(), "Subscribed to button events.");
}
1.4. Create the Event Logic File (LedModule_events.cpp)β
This new file contains the "brains" of the moduleβthe state machine and the event handlers.
#include "LedModule.h"
// --- Event Handlers ---
void LedModule::handleShortPress(void* payload) {
NEXTINO_LOGI(getName(), "Short press event received!");
// Short press toggles between BLINKING and OFF
if (_currentState == LedState::BLINKING) {
setState(LedState::OFF);
} else {
setState(LedState::BLINKING);
}
}
void LedModule::handleLongPress(void* payload) {
NEXTINO_LOGI(getName(), "Long press event received!");
// Long press toggles between ON and OFF
if (_currentState == LedState::ON) {
setState(LedState::OFF);
} else {
setState(LedState::ON);
}
}
// --- Private State Machine ---
void LedModule::setState(LedState newState) {
if (_currentState == newState) return; // No change
_currentState = newState;
NEXTINO_LOGI(getName(), "Changing state to %d", (int)_currentState);
// First, always clean up the previous state (cancel timers)
if (_taskHandle != 0) {
NextinoScheduler().cancel(_taskHandle);
_taskHandle = 0;
}
// Then, apply the new state
switch (_currentState) {
case LedState::OFF:
digitalWrite(_pin, LOW);
break;
case LedState::ON:
digitalWrite(_pin, HIGH);
break;
case LedState::BLINKING:
_taskHandle = NextinoScheduler().scheduleRecurring(_interval, [this]() {
digitalWrite(_pin, !digitalRead(_pin));
});
break;
}
}
Step 2: Put It All Togetherβ
The process to get your project running is the same as before.
-
Create the
ButtonReadermodule in yourlibfolder as described in the previous tutorial. -
Update
platformio.inito include both modules as dependencies.platformio.inilib_deps =
https://github.com/magradze/Nextino.git
lib/LedFlasher
lib/ButtonReader -
The
bootstrap.pyscript will do the rest! Yourmain.cppdoes not need to change at all. That's the power of plug-and-play! π
Step 3: Run and Test π§ͺβ
Upload the code to your board and open the Serial Monitor.
- Press the button briefly: The LED should start blinking. Press it briefly again, and it should stop.
- Press and hold the button: The LED should turn on and stay on. Press and hold it again, and it should turn off.
You have now successfully created a system where two modules communicate via events, following best practices for code organization.
Next Stepsβ
Now that you've mastered event-based communication, let's look at how modules can request services from each other directly using the ServiceLocator.
β‘οΈ Using the ServiceLocator