Signal is an event bus and publish-subscribe library for ESP32.
Signal helps Arduino ESP32 applications decouple modules with typed events, bounded queues, task-side callback dispatch, future-only waits, and runtime diagnostics. It is designed for products that need predictable event flow without direct dependencies between components.
- Typed events - publish enum or integral event IDs with optional trivially copyable payloads.
- Bounded core - queue storage, payload bytes, waiters, and raw callback subscriptions are configured up front.
- Task-side callbacks - subscriber callbacks run from the internal Signal task.
- Future-only waits -
waitFor()wakes on future matching posts and does not consume subscriber events. - Production-minded - serialized lifecycle transitions, shutdown-safe blocked producers, result-based errors, diagnostics, thread-safe internals, and no exceptions.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
https://github.com/ZekStack/signal.git
build_flags =
-std=gnu++20
build_unflags =
-std=gnu++11Signal is not published to Arduino Library Manager yet.
Install it by downloading the repository ZIP or cloning it into your Arduino libraries folder.
Arduino/libraries/Signal#include <Arduino.h>
#include <Signal.h>
Signal bus;
enum class AppEvent : uint16_t {
Booted,
};
void setup() {
Serial.begin(115200);
SignalResult initResult = bus.init();
if (!initResult) {
Serial.println(initResult.message);
return;
}
bus.subscribe(AppEvent::Booted, []() {
Serial.println("boot event received");
});
bus.post(AppEvent::Booted);
}
void loop() {
delay(1000);
}Important
post() only enqueues events. Subscriber callbacks run later from the internal Signal task.
- Payloads must be trivially copyable and fit inside
maxPayloadSize. - Do not put
std::string,std::vector, heap pointers, references, or destructor-owned resources inside payloads. - Use
zek::signal::Signalfor namespaced code; global aliases such asSignalremain enabled by default for Arduino friendliness. - Raw function-pointer callbacks are the bounded callback path. Lambda,
std::bind, andstd::functionsubscriptions are convenience APIs and may allocate duringsubscribe(). - Do not call
end()or destroy aSignalinstance from a Signal callback. waitFor()only waits for future posts; it does not read from a global event history.- A posted event wakes all matching waiters and is also delivered to subscribers.
- Stack sizes are FreeRTOS byte sizes on ESP32 and must be at least 1024 bytes.
SignalStackType::Autoprefers a PSRAM task stack and retries with internal RAM if external task creation fails.- With
BlockCaller, a post made from a Signal callback never blocks. It succeeds only when queue space is immediately available; otherwise it returnsBusy. end(timeoutMs)can returnTimeoutwhile shutdown continues. A laterend()call can complete cleanup.
| Example | Description |
|---|---|
Basic |
Minimal init, subscribe, post, and shutdown. |
Payloads |
Trivially copyable payload publish-subscribe. |
WaitFor |
Blocking until a future event or timeout. |
Unsubscribe |
Removing a subscription. |
OverflowPolicies |
Queue limits and overflow behavior. |
Diagnostics |
Runtime counters and configured limits. |
BindableCallbacks |
std::bind with private class methods. |
Configuration |
Stack, queue, payload, subscription, and waiter limits. |
Stress |
Compile-covered stress sketch for manual runtime concurrency checks. |
Start with:
examples/BasicDetailed documentation is available in the docs/ folder.
| Document | Description |
|---|---|
docs/getting-started.md |
Step-by-step setup and first event flow. |
docs/configuration.md |
Config options, queue limits, waiters, and stack behavior. |
docs/api.md |
Public classes, result types, callbacks, and diagnostics. |
docs/examples.md |
Explanation of all included examples. |
docs/troubleshooting.md |
Common issues and solutions. |
Signal bus;
bus.init();
SignalSubResult sub = bus.subscribe(AppEvent::Booted, []() {});
bus.unsubscribe(sub.id);
bus.subscribeRaw(AppEvent::Booted, [](void *) {
Serial.println("bounded raw callback");
});
bus.post(AppEvent::Booted);
bus.postWithTimeout(AppEvent::Booted, 100);
SignalDiag diag = bus.getDiagnostics();For the full API, see docs/api.md.
| Item | Support |
|---|---|
| Framework | Arduino ESP32 |
| Platform | espressif32 |
| Language | C++20 |
| Filesystem | none |
| PSRAM | Optional for task stacks when ESP-IDF support is available |
| Dependencies | none |
| Exceptions | Not used |
| Status | 0.1.0 release candidate |
MIT - see LICENSE.md.
Part of the ZekStack ESP32 library stack.