Skip to content

Repository files navigation

ESPressio Serial

Serial and console-oriented components for the Flowduino ESPressio Development Platform.

Version 0.3.0 adds a reusable Stream/Print command console and an opt-in operator-facing Event Console capable of discovering, describing, composing as JSON, validating, and dispatching runtime-registered Serializable Events through ESPressio Event 5.6.1.

Latest Stable Version

The latest stable version is 0.4.0.

ESPressio Development Platform

ESPressio is a collection of discrete, composable component libraries designed around a common development ethos:

  • Light-weight
  • Ease of use
  • Object-oriented design
  • SOLID design principles
  • Pay only for the functionality an application selects

License

Licensed under the Apache License 2.0. See LICENSE.

Namespace

The public API resides beneath:

ESPressio::Serial

Because Arduino exposes a global object named Serial, fully qualified ESPressio Serial names are recommended:

ESPressio::Serial::EventMonitor monitor;

while the Arduino serial port remains:

::Serial

ESPressio Library Dependencies

The core ESPressio Serial library has no required ESPressio library dependencies.

The Event Monitor is deliberately opt-in and requires:

ESPressio Event >= 5.7.1 < 6.0.0
ESPressio Serializable >= 0.10.0 < 1.0.0

For the complete ecosystem hierarchy, see:

ESPressio Library Dependency Chart

In the dependency chart:

  • Solid relationships represent required dependencies.
  • Dashed relationships represent opt-in dependencies introduced only when the associated feature/header is used.

Version 0.3.1 — Event 5.6.1 compatibility

Version 0.3.1 updates the optional EventConsole integration baseline to ESPressio Event 5.6.1.

Event 5.6.1 corrects EventDispatchContext equality semantics required by ESPressio Threads 3.1 ReadWriteMutex<T> change detection. No ESPressio Serial console, monitoring, logging, or EventConsole public API changes are required.

Applications using EventConsole should therefore target:

flowduino/ESPressio-Event@^5.7.1

The core Serial library and generic Console remain independent of ESPressio Event.


Version 0.3.0 — Interactive Runtime Serializable Event Console

Version 0.3.0 adds the interactive operator/service-console layer.

The architecture deliberately preserves library ownership:

operator
   |
   v
ESPressio Serial Console
   |
   | JSON
   v
ESPressio Serializable JsonArchive
   |
   | SerializationNode
   v
ESPressio Event 5.6 runtime registry/factory
   |
   v
concrete Serializable Event
   |
   v
normal Queue / Stack dispatch
   |
   +--> local listeners
   |
   +--> EventTransportManager
            |
            +--> any configured outbound transport

Serial does not create a second Event registry or remote-dispatch mechanism.

Generic Console

The generic console is available independently of Event:

#include <ESPressio_Console.hpp>

ESPressio::Serial::Console console;

void setup() {
    ::Serial.begin(115200);

    ESPressio::Serial::ConsoleConfig config;
    config.Prompt = "espressio> ";

    console.Initialize(
        ::Serial,
        ::Serial,
        config
    );

    console.RegisterCommand(
        "hello",
        "Print a greeting",
        [](const auto& context) {
            // Handle context.Arguments.
        }
    );
}

void loop() {
    console.Poll();
}

Input uses Arduino Stream; output uses Arduino Print.

The console therefore works with Hardware Serial, USB CDC, or another compatible implementation.

The line buffer is bounded through:

ConsoleConfig::MaximumLineLength

and the console supports:

command registration
command unregistration
help
arguments
prompt configuration
optional input echo
multiple interactive line interceptors
backspace/delete handling
CR/LF handling

Multiple line interceptors are intentional: future console extensions can maintain independent interactive states without replacing one global input handler.

EventConsole

The Event Console is opt-in:

#include <ESPressio_EventConsole.hpp>

and requires:

ESPressio Event >= 5.7.1
ESPressio Serializable >= 0.10.0 < 1.0.0
ArduinoJson (through the optional Serializable JsonArchive)

Initialize it over an existing Console:

ESPressio::Serial::Console console;
ESPressio::Serial::EventConsole eventConsole;

console.Initialize(
    ::Serial,
    ::Serial
);

eventConsole.Initialize(
    console
);

Safe-by-default Event authorization

Runtime Event discovery does not imply permission to dispatch an Event.

The default access policy is:

EventConsoleAccessPolicy::AllowListedOnly

Allow specific Event types:

eventConsole.AllowEvent<
    CameraShutterEvent
>();

eventConsole.AllowEvent(
    "flowduino.motor.move.v1"
);

For a controlled development environment, explicitly enable all registered types:

eventConsole.SetAccessPolicy(
    ESPressio::Serial::
        EventConsoleAccessPolicy::
            AllRegistered
);

Deny-list entries override allow-all:

eventConsole.DenyEvent<
    FactoryResetEvent
>();

This prevents a registered administrative/destructive Event from becoming operator-dispatchable merely because a console is enabled.

Event discovery

List runtime-registered Serializable Events:

espressio> events

Registered Serializable Events:
  flowduino.camera.shutter.v1 [constructible] [allowed] schema=1 defaultRouting=Outbound
  flowduino.motor.move.v1 [constructible] [allowed] schema=2 defaultRouting=Bidirectional
  flowduino.system.factory-reset.v1 [constructible] [denied] schema=1 defaultRouting=None

The equivalent command is:

event list

Event schema description

espressio> event describe flowduino.motor.move.v1

uses Event 5.6's runtime descriptor and Serializable schema metadata to report:

stable Event type name
stable Event type ID
schema version
runtime constructibility
operator access
default Event Transport direction
property names
property types
required state
read-only state
sensitive metadata
default-value availability
aliases

Per-transport route names are not fabricated: Event 5.6 currently exposes the default routing direction through the public runtime descriptor.

One-line JSON dispatch

Queue:

event queue flowduino.motor.move.v1 {"axis":"pan","position":45,"speed":20}

Stack:

event stack flowduino.motor.move.v1 {"axis":"pan","position":45,"speed":20}

event dispatch is a Queue alias.

JSON is parsed through ESPressio Serializable's JsonArchive, converted to a representation-neutral SerializationNode, and passed to Event 5.6's runtime factory.

Interactive composition

event compose flowduino.motor.move.v1

or:

event compose flowduino.motor.move.v1 stack

prompts for a one-line JSON object:

Enter one-line JSON object for flowduino.motor.move.v1 (or 'cancel'):
{"axis":"pan","position":45,"speed":20}

Serializable validation diagnostics

Runtime-created Events use the normal ESPressio Serializable validation path.

Validation errors are presented to the operator with:

property path
serialization error code
diagnostic message

For example:

Event payload validation failed with 2 issue(s):
  speed: NumericOutOfRange - Property failed its numeric range constraint
  axis: UnknownEnumValue - Value is not a registered enum mapping

No separate Serial-specific Event validation system exists.

Confirmation

Confirmation is enabled by default:

Dispatch Event 'flowduino.motor.move.v1' via Queue priority=Normal? [y/N]

Only y or yes proceeds; any other response cancels the dispatch.

It can be disabled explicitly:

EventConsoleConfig config;
config.RequireConfirmation = false;

Dispatch semantics

Event Console uses Event 5.6's ownership-safe runtime dispatch API.

Once dispatched, the Event follows the normal Event system:

runtime-created Event
        |
        v
Queue / Stack
        |
        v
local Event dispatch
        |
        v
EventTransportManager
        |
        v
existing per-transport outbound routing

Event Console therefore knows nothing about ESP-NOW, UDP, TCP, WebSocket, MQTT, or another concrete Event transport.

Audit logging

EventConsole can optionally send security/operation audit records to any existing:

ILoggerSink

using:

eventConsole.SetAuditSink(
    &history
);

Useful audit conditions include:

successful operator dispatch
denied dispatch
unregistered type
malformed JSON
oversized JSON
construction/validation failure
dispatch failure

The Event payload itself is deliberately not copied into the audit message by default, avoiding accidental logging of sensitive properties.

Event Monitor integration

Console-created Events naturally flow through the ordinary Event Transport pipeline.

If EventMonitor is enabled, the same operator-created Event appears in its normal outbound/inbound transaction diagnostics without any special integration code.

Limits

Operator JSON is bounded by:

EventConsoleConfig::MaximumJsonLength

and the enclosing generic Console independently bounds total input line length.

Queue and Stack dispatch can be independently disabled:

config.AllowQueue = true;
config.AllowStack = false;

Examples

Version 0.3.0 adds:

examples/
├── Console/
│   └── Console.ino
│
├── EventConsole/
│   └── EventConsole.ino
│
└── EventConsoleLoopback/
    └── EventConsoleLoopback.ino

EventConsoleLoopback combines the operator console, Event Console, Event Monitor, Serializable Event, and a local loopback IEventTransport to demonstrate the complete:

Serial JSON
    -> runtime Event
    -> local dispatch
    -> Event Transport
    -> inbound reconstruction
    -> Serial Event Monitor

pipeline on one ESP32.

Tests

The repository includes host-side tests for:

generic Console command dispatch
argument preservation
multiple interactive line interceptors
interceptor removal
Stream polling
runtime Event listing
Event schema description
allow-list enforcement
JSON command processing
pre-dispatch confirmation
type-erased dispatch

The Event Console contract test uses narrow test doubles for Event 5.6 and the Serializable JSON adapter, while release preparation verifies compatibility with the real public API surface.


Version 0.2.0 — Diagnostics & Logging

Version 0.2.0 adds a general diagnostics foundation alongside the existing Event Monitor.

Logging

#include <ESPressio_Logging.hpp>

ESPressio::Serial::Logger<> logger;
ESPressio::Serial::SerialLogSink serialSink(::Serial);
ESPressio::Serial::DiagnosticRingBuffer<64> history;

void setup() {
    ::Serial.begin(115200);

    logger.AddSink(serialSink);
    logger.AddSink(history);

    logger.SetMinimumLevel(
        ESPressio::Serial::LogLevel::Debug
    );

    logger.Info("Application", "Boot complete");
}

Supported levels are:

Trace
Debug
Info
Warning
Error
Critical
Off

Logger supports multiple simultaneous ILoggerSink implementations. Logging data is therefore separated from its output destination: Serial is one sink, not the logging architecture itself.

ESPRESSIO_SERIAL_COMPILETIME_LOG_LEVEL may be defined to remove lower-severity calls from runtime delivery, while SetMinimumLevel() provides runtime filtering.

Diagnostic flight recorder

DiagnosticRingBuffer<Capacity> is both an ILoggerSink and a bounded in-memory history.

ESPressio::Serial::DiagnosticRingBuffer<64> history;

logger.AddSink(history);

// Later, after a fault:
history.Dump(::Serial);

Entries are copied into fixed-size storage; the oldest entry is overwritten when capacity is exhausted. This makes it suitable for retaining the diagnostic events immediately preceding a failure without unbounded heap growth.

System Clock Monitor

#include <ESPressio_SystemClockMonitor.hpp>

ESPressio::Serial::SystemClockMonitor<> clockMonitor;

clockMonitor.Initialize(::Serial);

This integration directly consumes ESPressio Timing 2.2.2's ISystemClockObserver notifications. It reports time-setting, synchronization acceptance/rejection, synchronization state changes, resets/configuration changes, and callback scheduling/execution.

Synchronization output includes the clock value before correction, the value after correction, and the immediate nanosecond difference.

This is an opt-in Timing dependency; ESPressio Event is not involved.

Thread Monitor

#include <ESPressio_ThreadMonitor.hpp>

ESPressio::Serial::ThreadMonitor threadMonitor;

threadMonitor.Initialize(::Serial);

ThreadMonitor directly observes the process-wide ESPressio Threads 3.1.2 infrastructure:

ThreadManager
ThreadGarbageCollector
ThreadTerminationDispatcher

It reports registration, cleanup, garbage collection, termination dispatch, initialization, and failure lifecycle notifications.

This is an opt-in Threads dependency; Event bridges are not required merely to display Thread diagnostics.

Aggregate Diagnostic Monitor

When the corresponding dependency headers are available, the convenience monitor can compose all supported subsystem monitors:

#include <ESPressio_DiagnosticMonitor.hpp>

ESPressio::Serial::DiagnosticMonitor diagnostics;

void setup() {
    ::Serial.begin(115200);

    ESPressio::Serial::DiagnosticMonitorConfig config;

    config.SystemClock = true;
    config.Threads = true;
    config.Events = true;

    diagnostics.Initialize(
        ::Serial,
        config
    );
}

The aggregate uses compile-time feature detection. It does not itself make Timing, Threads, Event, or Serializable mandatory package dependencies.

Dependency model

ESPressio Serial core
    -> no mandatory ESPressio dependency

Logging
    -> no additional ESPressio dependency

SystemClockMonitor
    - - -> ESPressio Timing >= 2.2.2 < 3.0.0

ThreadMonitor
    - - -> ESPressio Threads >= 3.1.2 < 4.0.0

EventMonitor
    - - -> ESPressio Event >= 5.7.1 < 6.0.0
    - - -> ESPressio Serializable >= 0.10.0 < 1.0.0

All ESPressio relationships remain opt-in.


Core include

#include <ESPressio_Serial.hpp>

The core header contains common ESPressio Serial types only.

It does not include ESPressio Event or ESPressio Serializable.

Event monitoring is selected explicitly:

#include <ESPressio_EventMonitor.hpp>

or through the feature batch header:

#include <ESPressio_SerialEventMonitoring.hpp>

Event Transport Monitor

EventMonitor consumes the Event Transport Transaction Observation API introduced by ESPressio Event 5.5.0.

It does not implement an Event Transport and does not alter Event routing.

Conceptually:

Serializable Event
        |
        v
EventTransportManager
        |
        +-----------------------> concrete transport
        |
        +--> transaction Observer
                    |
                    v
              EventMonitor
                    |
                    v
              Arduino Print
                    |
           +--------+--------+
           |                 |
           v                 v
        Serial            USB CDC

Any Print implementation may be used. The monitor is therefore not tied specifically to HardwareSerial.

Initialization

#include <ESPressio_EventMonitor.hpp>

ESPressio::Serial::EventMonitor
    monitor;

void setup() {
    ::Serial.begin(115200);

    ESPressio::Serial::
        EventMonitorConfig
            config;

    monitor.Initialize(
        ::Serial,
        config
    );
}

Initialize() registers the monitor with the selected EventTransportManager.

It does not initialize the Event Transport Manager itself. The application remains responsible for its normal Event Transport setup and initialization.

The monitor unregisters automatically when destroyed or when:

monitor.Shutdown();

is called.


Default monitoring behaviour

The default mode is:

EventMonitorMode::Events

This is intended to provide one useful record per logical transported Event rather than printing every internal lifecycle transition.

It reports:

outbound Event after concrete transport handoff
inbound Event after successful deserialization
inbound rejection
transport processing failure

For example:

[ESPressio Event] [OUT] [OutboundHandedToTransport] type=flowduino.example.serial.monitored-counter.v1 typeId=0x... schema=1 message=3 transport=0x... dispatch=Queue priority=Normal origin=Local hops=0 accepted=true payloadBytes=...
  payload: {
    "__schemaVersion": 1,
    "counter": 3,
    "source": "local"
  }

and the looped-back inbound Event may then appear as:

[ESPressio Event] [IN] [InboundDeserialized] type=flowduino.example.serial.monitored-counter.v1 typeId=0x... schema=1 message=3 transport=0x... dispatch=Queue priority=Normal origin=Remote hops=0 payloadBytes=...
  payload: {
    "__schemaVersion": 1,
    "counter": 3,
    "source": "local"
  }

Lifecycle mode

For deeper diagnostics:

config.Mode =
    ESPressio::Serial::
        EventMonitorMode::Lifecycle;

prints every Event 5.5 transaction stage exposed by EventTransportManager:

OutboundAccepted
OutboundSerialized
OutboundHandedToTransport

InboundAccepted
InboundRejected
InboundDeserialized
InboundDispatched

Failed

Lifecycle mode is intentionally verbose and is most useful while debugging the transport pipeline itself.


Payload formatting

The Event Monitor supports:

EventMonitorPayloadFormat::None
EventMonitorPayloadFormat::Summary
EventMonitorPayloadFormat::Hex
EventMonitorPayloadFormat::Structured

None

Only transaction metadata is printed.

Summary

Reports payload size without printing payload contents.

Hex

Prints the Serializable Binary Archive bytes in hexadecimal.

The maximum number of bytes is controlled by:

config.MaximumHexPayloadBytes

Structured

Structured is the default.

ESPressio Event Transport serializes Event payloads using ESPressio Serializable's BinaryArchive.

The monitor decodes that Binary Archive into Serializable's generic SerializationNode tree and renders it directly as JSON-like structured text.

This has two important advantages:

  1. the monitor does not need to know the concrete C++ Event type;
  2. it does not require ArduinoJson merely to present human-readable diagnostics.

The monitor is therefore able to inspect arbitrary transported Serializable Event payloads using the schema already encoded in the Binary Archive.


Structured-output limits

Diagnostic output should not be allowed to grow without bound.

Configuration includes:

MaximumCollectionItems
MaximumStringLength
MaximumStructuredDepth
IndentSpaces
PrettyStructuredPayload

These provide deterministic limits when monitoring large or deeply nested Event payloads.


Transaction metadata

The monitor can independently enable or disable:

stable Event type name
stable Event type ID
schema version
message ID
transport address
dispatch method
priority
origin
hop count
transport acceptance result

using EventMonitorConfig.

Inbound and outbound monitoring can also be enabled independently.


Borrowed Event Transport data

ESPressio Event 5.5 transaction snapshots expose borrowed Event/payload references valid only during the Observer callback.

EventMonitor consumes those values synchronously and does not retain borrowed transaction pointers after the callback returns.

Structured decoding is therefore performed while the payload is valid.


Performance considerations

Event Transport transaction observation is synchronous.

Serial/USB output can be comparatively slow.

Enabling Event Monitor—particularly Lifecycle mode or large structured payload output—can therefore add diagnostic latency to the Event Transport execution path.

This is intentional for a developer-facing monitor, but applications with tight real-time requirements should:

  • disable monitoring in production;
  • use Summary or None payload modes;
  • use an appropriately fast Print destination;
  • avoid Lifecycle mode except during diagnosis.

The Event Monitor changes observation only; it does not change Event routing or transport semantics.


Example

The repository includes:

examples/
└── EventMonitor/
    └── EventMonitor.ino

The example uses a small local LoopbackEventTransport so both outbound and inbound transactions can be demonstrated on a single ESP32 without networking or additional hardware.

It defines a Serializable counter Event, transports it through Event 5.5, and renders the Binary payload as structured text.


PlatformIO

A project using only the core Serial library:

lib_deps =
    flowduino/ESPressio-Serial@^0.4.0

An application using Event Monitor requires:

lib_deps =
    flowduino/ESPressio-Serial@^0.4.0
    flowduino/ESPressio-Event@^5.7.1
    flowduino/ESPressio-Serializable@^0.10.0

The Event/Serializable dependencies are intentionally not declared as mandatory package dependencies of ESPressio Serial because they are required only by the opt-in Event Monitor feature.


PlatformIO: Event Console

The generic console requires only ESPressio Serial:

lib_deps =
    flowduino/ESPressio-Serial@^0.4.0

The Event Console additionally requires the runtime Event and JSON stacks:

lib_deps =
    flowduino/ESPressio-Serial@^0.4.0
    flowduino/ESPressio-Event@^5.7.1
    flowduino/ESPressio-Serializable@^0.10.0
    bblanchon/ArduinoJson

ArduinoJson is required only because EventConsole selects ESPressio Serializable's optional JsonArchive; it remains unnecessary for core Serial, logging, diagnostics, and the generic Console.

Future direction

ESPressio Serial is intended to contain Serial/console-oriented ESPressio integrations rather than becoming a general communications catch-all.

Potential future components include:

Serial Event Transport
structured Event-based remote log sinks
persistent diagnostic sinks
additional subsystem console commands
serial configuration interfaces
serial protocol adapters
operator authentication/session policy where appropriate

Network/socket implementations belong in ESPressio Sockets.

ESP-NOW implementations belong in ESPressio ESP-Now.

Hardware-radio implementations belong in the planned ESPressio Radio library.


Summary

ESPressio Serial 0.3.0 provides three complementary layers:

CORE
    ESPressio_Serial.hpp
    diagnostic types
    no mandatory ESPressio dependency

DIAGNOSTICS / LOGGING
    Logger
    SerialLogSink
    DiagnosticRingBuffer
    SystemClockMonitor          [opt-in Timing]
    ThreadMonitor               [opt-in Threads]
    EventMonitor                [opt-in Event + Serializable]
    DiagnosticMonitor

OPERATOR CONSOLE
    Console
        Stream input
        Print output
        extensible commands

    EventConsole                [opt-in Event 5.6 + Serializable JSON]
        runtime Event discovery
        schema description
        JSON composition
        validation diagnostics
        allow/deny policy
        confirmation
        Queue / Stack dispatch

The central rule remains unchanged:

ESPressio Serial owns human/operator interaction and presentation; the upstream ESPressio libraries continue to own their underlying runtime semantics.

ESPressio Command Integration (0.4.0)

Serial 0.4.0 adds an opt-in bridge to ESPressio Command >= 0.2.0 < 1.0.0. Core Serial remains usable without Command. Include ESPressio_CommandConsole.hpp only when the integration is required.

lib_deps =
    flowduino/ESPressio-Serial@^0.4.0
    flowduino/ESPressio-Command@^0.2.0
#include <ESPressio_Console.hpp>
#include <ESPressio_CommandConsole.hpp>
#include <ESPressio_Commands.hpp>

ESPressio::Serial::Console console;
ESPressio::Serial::CommandConsole commandConsole;

void setup() {
    console.Initialize(Serial, Serial);
    commandConsole.Initialize(console);

    auto& commands = ESPressio::Command::CommandRegistry::GetInstance();
    commands.Command("system").Command("status")
        .OnExecute([](const ESPressio::Command::CommandContext&) {
            return ESPressio::Command::CommandResult::Ok("System OK");
        });
}

void loop() { console.Poll(); }

CommandConsole reuses Serial's existing input/prompt handling and forwards resolvable lines into the shared transport-neutral Command registry. Unknown roots fall through so other Console interceptors and legacy commands can continue to coexist.

EventConsole on the shared Command tree

When Command integration is selected, initialize EventConsole with CommandConsole:

ESPressio::Serial::EventConsole eventConsole;
eventConsole.Initialize(commandConsole);

EventConsole then registers the following shared Command tree with ownership-safe registration handles:

event list
event describe <type>
event compose <type> [queue|stack]
event queue <type> <json>
event stack <type> <json>
event dispatch <type> <json>
event cancel
events

Shutdown removes the registered subtrees, preventing callbacks from outliving the EventConsole instance. The previous Initialize(Console&, ...) overload remains available for compatibility with existing applications.

About

Serial Components of the ESPressio Development Platform

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages