Application Development Guide
This guide explains how to integrate the ESP-Brookesia service framework in an ESP-IDF project, using the Wi-Fi service as an example. A full, buildable sample is in examples/service/wifi.
In a typical project, using a component directly means adding a dependency, including headers, and calling its APIs. As features grow, coupling between modules and components increases; removing a component then requires cleaning up headers and calls as well as the dependency, which raises maintenance cost.
ESP-Brookesia abstracts this behind two unified surfaces: functions and events. In many cases you only need brookesia_service_manager and brookesia_service_helper to reach service features through the Helper; you can also check at runtime whether a service is available. If the concrete service component is not linked, Helper usage usually still compiles, which helps with trimming and maintenance.
The framework also relies on mechanisms such as the Task Scheduler for asynchronous execution on the service side, reducing blocking on the caller. Exposed APIs are designed with thread safety in mind so application code does less ad-hoc synchronization.
The sections below follow this order: dependencies → initialization and binding → calling functions → subscribing to events → event monitors.
Adding Component Dependencies
Declare dependencies in idf_component.yml at the project root or in a component directory. For the Wi-Fi service:
dependencies:
espressif/brookesia_service_wifi: "*"
# espressif/brookesia_service_nvs: "*" # optional, if you need the NVS service
If your project does not link a concrete service implementation and you only need to satisfy “Helper code compiles”, you can depend on brookesia_service_helper alone:
dependencies:
espressif/brookesia_service_helper: "*"
For how to obtain components and version constraints, see Obtaining and using components.
Headers, Namespace, and Type Aliases
#include "brookesia/lib_utils.hpp"
#include "brookesia/service_manager.hpp"
// Include all general service helper headers
#include "brookesia/service_helper.hpp"
// Or include the Helper header for a specific service component, e.g. Wi-Fi
// #include "brookesia/service_helper/wifi.hpp"
// Brookesia data types live under the esp_brookesia namespace
using namespace esp_brookesia;
// Type aliases keep examples short
// Service-related types are under the service namespace
using WifiHelper = service::helper::Wifi;
Starting the Service Manager
Before calling any service API, initialize the global singleton ServiceManager by calling start().
auto &service_manager = service::ServiceManager::get_instance();
service_manager.start();
Starting / Stopping a Service
// Before binding, you can check whether the Helper’s service is linked into the build
if (!WifiHelper::is_available()) {
// Service unavailable: omitting the service component does not break compilation, but this check fails
return;
}
// Bind to the service: starts the service and its dependencies while `binding` is alive;
// when `binding` goes out of scope it is destroyed and the service stops.
// To keep the service running longer, store `binding` outside the stack (e.g. static or heap).
auto binding = service_manager.bind(WifiHelper::get_name().data());
if (!binding.is_valid()) {
// Failed to start the service
return;
}
Calling Service Functions
Before calling a function, confirm parameter types, order, and return value from the Helper contract or headers, for example:
Note
Description: Function behavior, return information, etc. If there is no return value section, the function has no return value.
- Parameter list: Parameter names, types, descriptions, and defaults.
- Types: Six kinds are supported:
String,Number,Boolean,Object,Array,RawBuffer. Mapping to call-time C++ types: String:std::stringNumber:double/int/ any arithmetic typeBoolean:boolObject:boost::json::object(often produced by serializing astruct)Array:boost::json::array(often produced by serializingstd::vector)RawBuffer:service::RawBuffer
- Types: Six kinds are supported:
- Description:
For
Stringwith enumerated values in the description, you pass enums via serialization.For
Object/Array, the description may include examples.
Default: Shown when the parameter is optional.
- Execution requirement: Whether the function requires the Task Scheduler.
Required: Must run after the service has started; sync/async calls go through the scheduler.
- Not required: May run before the service starts; only sync calls are allowed. Typical cases:
The function must run before startup.
The parameter list contains
RawBuffer.The implementation is already thread-safe (locks, etc.), so the scheduler is not required.
Use static Helper methods for synchronous or asynchronous calls. Synchronous calls are described first.
Synchronous Calls
The call blocks until the function finishes or times out.
auto result = WifiHelper::call_function_sync<ReturnType>(WifiHelper::FunctionId::FunctionName [, parameters...] [, timeout]);
if (!result) {
// Call failed, log error
BROOKESIA_LOGE("Failed: %1%", result.error());
} else {
// Call succeeded
// If the function returns a value, use `result.value()`
auto &value = result.value(); // `value` has type `ReturnType`
// ...
}
Note
parametersmust match the schema in type, count, and order; omit them if the function has no parameters.If the return type is
void, omit the<ReturnType>template argument.timeoutis optional; if omitted, the default isBROOKESIA_SERVICE_MANAGER_DEFAULT_CALL_FUNCTION_TIMEOUT_MS.See
esp_brookesia::service::helper::Base::call_function_sync().
Example: Multiple Parameters
SetConnectAp — interface summary and sample code:
Function:
SetConnectAp- Parameters:
SSID:StringPassword:String
Return type:
void
// Pass parameters in schema order; do not skip required parameters
auto set_connect_ap_result = WifiHelper::call_function_sync(
WifiHelper::FunctionId::SetConnectAp, "ssid1", "password1", service::helper::Timeout(100));
if (!set_connect_ap_result) {
// Call failed, log error
BROOKESIA_LOGE("Failed: %1%", set_connect_ap_result.error());
}
Example: Serialized Parameters
TriggerGeneralAction — interface summary and sample code:
Function:
TriggerGeneralAction- Parameters:
Action:String(serialize fromWifiHelper::GeneralAction)
Return type:
void
auto start_result = WifiHelper::call_function_sync(
WifiHelper::FunctionId::TriggerGeneralAction,
BROOKESIA_DESCRIBE_TO_STR(WifiHelper::GeneralAction::Start));
if (!start_result) {
// Call failed, log error
BROOKESIA_LOGE("Failed: %1%", start_result.error());
}
SetScanParams — interface summary and sample code:
Function:
SetScanParams- Parameters:
Param:Object(serialize fromWifiHelper::ScanParams)
Return type:
void
WifiHelper::ScanParams scan_params{
.ap_count = 10,
.interval_ms = 10000,
.timeout_ms = 60000,
};
auto set_scan_params_result = WifiHelper::call_function_sync(
WifiHelper::FunctionId::SetScanParams, BROOKESIA_DESCRIBE_TO_JSON(scan_params).as_object());
if (!set_scan_params_result) {
// Call failed, log error
BROOKESIA_LOGE("Failed: %1%", set_scan_params_result.error());
}
Example: Parsing Return Values
GetConnectedAps — interface summary and sample code:
Function:
GetConnectedApsParameters: none
Return type:
boost::json::array(deserialize tostd::vector<WifiHelper::ConnectApInfo>)
auto get_connected_aps_result =
WifiHelper::call_function_sync<boost::json::array>(WifiHelper::FunctionId::GetConnectedAps);
if (!get_connected_aps_result) {
// Call failed, log error
BROOKESIA_LOGE("Failed to get connected APs: %1%", get_connected_aps_result.error());
}
// Parse return value
std::vector<WifiHelper::ConnectApInfo> infos;
auto parse_result = BROOKESIA_DESCRIBE_FROM_JSON(get_connected_aps_result.value(), infos);
if (!parse_result) {
// Parse failed, log error
BROOKESIA_LOGE("Failed to parse connected APs: %1%", get_connected_aps_result.error());
} else {
// Parse succeeded, log result
BROOKESIA_LOGI("Connected APs: %1%", infos);
}
Asynchronous Calls
The call submits work and returns immediately without blocking the caller. If you pass a result handler, the framework invokes it asynchronously when the function completes.
auto on_function_handler = [](service::FunctionResult &&result) {
// Process return value
if (!result.success) {
// Execution failed, log error
BROOKESIA_LOGE("Failed: %1%", result.error_message);
} else {
// Execution succeeded
// If there is a return value:
// auto &value = result.get_data<ReturnType>();
// ...
}
};
auto result = WifiHelper::call_function_async(WifiHelper::FunctionId::FunctionName [, parameters...] [, on_function_handler]);
if (!result) {
// Call failed
}
Note
parametersmust match the schema; omit if the function has no parameters.on_function_handleris optional; if omitted, results are ignored.Serial async calls to the same service run in submission order inside the service:
Helper::call_function_async(Helper::FunctionId::Function1); Helper::call_function_async(Helper::FunctionId::Function2); Helper::call_function_async(Helper::FunctionId::Function3); // Internal execution order: Function1 -> Function2 -> Function3
See
esp_brookesia::service::helper::Base::call_function_async().
Example
GetConnectedAps — interface summary and sample code:
Function:
GetConnectedApsParameters: none
Return type:
boost::json::array(same asstd::vector<WifiHelper::ConnectApInfo>when serialized)
auto on_get_connected_aps_handler = [](service::FunctionResult &&result) {
if (!result.success) {
// Execution failed, log error
BROOKESIA_LOGE("Failed to get connected APs: %1%", result.error_message);
return;
}
// Execution succeeded, parse return value
std::vector<WifiHelper::ConnectApInfo> infos;
auto &data = result.get_data<boost::json::array>();
auto parse_result = BROOKESIA_DESCRIBE_FROM_JSON(data, infos);
if (!parse_result) {
// Parse failed, log error
BROOKESIA_LOGE("Failed to parse connected APs: %1%", result);
return;
}
// Parse succeeded, log result
BROOKESIA_LOGI("Connected APs: %1%", infos);
};
auto get_connected_aps_result =
WifiHelper::call_function_async(WifiHelper::FunctionId::GetConnectedAps, on_get_connected_aps_handler);
if (!get_connected_aps_result) {
// Call failed
return;
}
Subscribing to Service Events
Confirm item names, types, and order from the Helper contract or headers, for example:
Note
Description: Event behavior and return information. If there is no return value section, the event carries no return payload in that sense.
- Item list: Item names, types, descriptions, and defaults.
- Types: Same six kinds as for functions; mapping is the same:
String:std::stringNumber:double/int/ any arithmetic typeBoolean:boolObject:boost::json::object(often produced by serializing astruct)Array:boost::json::array(often produced by serializingstd::vector)RawBuffer:service::RawBuffer
- Description:
Stringwith enumerated values deserializes to enum-like data.Object/Arraymay include examples.
- Execution requirement: Whether the event requires the Task Scheduler.
Required: Must be published after the service has started.
- Not required: May be published before startup. Typical cases:
The event must fire before startup.
The item list contains
RawBuffer.
Subscribing to Events
Use subscribe_event() to register a callback; when the event fires, callbacks run serially in subscription order.
auto on_event_handler = [](const std::string &event_name[, items...]) {
// Handle the event
};
// `conn` is the subscription; destruction **unsubscribes** (RAII).
// To keep the subscription alive, store `conn` outside the stack (e.g. static or heap).
auto conn = WifiHelper::subscribe_event(WifiHelper::EventId::EventName, on_event_handler);
if (!conn.connected()) {
// Subscribe failed
}
// You can also unsubscribe manually
conn.disconnect();
Note
Call
subscribe_event()only afterServiceManager::start().itemsmust match the event schema; omit if the event has no items.There is no hard limit on subscriptions; all callbacks for an event run in subscription order.
See
esp_brookesia::service::helper::Base::subscribe_event().
Example
GeneralEventHappened — interface summary and sample code:
Event:
GeneralEventHappened- Items:
Event:String(deserialize toWifiHelper::GeneralEvent)IsUnexpected:Boolean
auto on_general_event_happened_handler = [](const std::string &event_name, const std::string &event,
bool is_unexpected) {
// Event fired, parse items
WifiHelper::GeneralEvent general_event;
auto parse_result = BROOKESIA_DESCRIBE_STR_TO_ENUM(event, general_event);
if (!parse_result) {
// Parse failed, log error
BROOKESIA_LOGE("Failed to parse general event: %1%", event);
return;
}
// Parse succeeded, log result
BROOKESIA_LOGI("General event: %1%", general_event);
};
// Unsubscribe automatically when the connection is destroyed (RAII)
auto general_event_happened_connection = WifiHelper::subscribe_event(
WifiHelper::EventId::GeneralEventHappened, on_general_event_happened_handler);
if (!general_event_happened_connection.connected()) {
// Subscribe failed
return;
}
// Trigger Start action
WifiHelper::call_function_async(WifiHelper::FunctionId::TriggerGeneralAction,
BROOKESIA_DESCRIBE_TO_STR(WifiHelper::GeneralAction::Start));
// Wait for the event (example uses sleep; use a proper sync primitive in production)
boost::this_thread::sleep_for(boost::chrono::seconds(5));
ScanApInfosUpdated — interface summary and sample code:
Event:
ScanApInfosUpdated- Items:
ApInfos:Array(deserialize tostd::vector<WifiHelper::ScanApInfo>)
auto on_scan_ap_infos_updated_handler = [](const std::string &event_name, const boost::json::array &ap_infos) {
// Event fired, parse items
std::vector<WifiHelper::ScanApInfo> scanned_aps;
auto parse_result = BROOKESIA_DESCRIBE_FROM_JSON(ap_infos, scanned_aps);
if (!parse_result) {
// Parse failed, log error
BROOKESIA_LOGE("Failed to parse scan AP infos: %1%", ap_infos);
return;
}
// Parse succeeded, log result
BROOKESIA_LOGI("Scanned APs: %1%", scanned_aps);
};
auto scan_ap_infos_updated_connection =
WifiHelper::subscribe_event(WifiHelper::EventId::ScanApInfosUpdated, on_scan_ap_infos_updated_handler);
if (!scan_ap_infos_updated_connection.connected()) {
// Subscribe failed
return;
}
WifiHelper::call_function_async(WifiHelper::FunctionId::TriggerScanStart);
boost::this_thread::sleep_for(boost::chrono::seconds(10));
Event Monitor
EventMonitor adds blocking wait on top of asynchronous events: after triggering an API, you can wait for a matching event or any occurrence to drive control flow.
// Create and start the monitor
WifiHelper::EventMonitor<WifiHelper::EventId::EventName> event_monitor;
event_monitor.start();
// Call the API that raises this event (omitted)
// Wait for any occurrence
auto got_any_event = event_monitor.wait_for_any(timeout_ms);
if (!got_any_event) {
// Timeout, event did not fire
return;
}
// Or wait for an event whose items match
auto got_event = event_monitor.wait_for(std::vector<service::EventItem>{items...}, timeout_ms);
if (!got_event) {
// Timeout, items did not match
return;
}
event_monitor.stop();
Note
itemsmust match the event definition in type, count, and order.See
esp_brookesia::service::helper::EventMonitor.
Example: Wait for Specific Event Items
GeneralEventHappened — monitor usage:
Event:
GeneralEventHappened- Items:
Event:String(serialize fromWifiHelper::GeneralEvent)IsUnexpected:Boolean
WifiHelper::EventMonitor<WifiHelper::EventId::GeneralEventHappened> general_event_monitor;
if (!general_event_monitor.start()) {
// Failed to start general event monitor
return;
}
WifiHelper::call_function_async(WifiHelper::FunctionId::TriggerGeneralAction,
BROOKESIA_DESCRIBE_TO_STR(WifiHelper::GeneralAction::Start));
auto got_event = general_event_monitor.wait_for(
std::vector<service::EventItem>{BROOKESIA_DESCRIBE_TO_STR(WifiHelper::GeneralEvent::Started), false}, 5000);
if (!got_event) {
// Timeout, event did not fire
return;
}
Example: Latest Received Event Items
ScanApInfosUpdated — monitor usage:
Event:
ScanApInfosUpdated- Items:
ApInfos:Array(deserialize tostd::vector<WifiHelper::ScanApInfo>)
WifiHelper::EventMonitor<WifiHelper::EventId::ScanApInfosUpdated> scan_ap_infos_updated_monitor;
if (!scan_ap_infos_updated_monitor.start()) {
// Failed to start scan AP infos updated monitor
return;
}
WifiHelper::call_function_async(WifiHelper::FunctionId::TriggerScanStart);
auto got_event = scan_ap_infos_updated_monitor.wait_for_any(10000);
if (!got_event) {
// Timeout, event did not fire
return;
}
auto last_items = scan_ap_infos_updated_monitor.get_last<boost::json::array>();
if (!last_items.has_value()) {
return;
}
const auto &ap_infos = std::get<0>(last_items.value());
std::vector<WifiHelper::ScanApInfo> scanned_aps;
auto parse_result = BROOKESIA_DESCRIBE_FROM_JSON(ap_infos, scanned_aps);
if (!parse_result) {
BROOKESIA_LOGE("Failed to parse scan AP infos: %1%", ap_infos);
return;
}
BROOKESIA_LOGI("Scanned APs: %1%", scanned_aps);