From 4422cb5c5c796caadb3f2095e0be1e6eb793544c Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Mon, 3 Aug 2026 10:52:02 +0500 Subject: [PATCH 1/5] allow coder to run clang-format --- .opencode/agents/coder.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.opencode/agents/coder.md b/.opencode/agents/coder.md index 0817df15..6a5371e2 100644 --- a/.opencode/agents/coder.md +++ b/.opencode/agents/coder.md @@ -14,6 +14,7 @@ permission: "rm *.h": allow "rm *.hpp": allow "rm *.cmake": allow + "clang-format *": allow external_directory: deny repo_clone: deny --- From bce62848eea94d1ffa1548e9996b9d7205fa5206 Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Mon, 3 Aug 2026 10:52:17 +0500 Subject: [PATCH 2/5] add action wait sender --- aether/executors/action_wait_sender.h | 192 ++++++++++++++++++++++++++ aether/executors/async_waiter.h | 17 +-- aether/executors/executors.h | 7 +- 3 files changed, 205 insertions(+), 11 deletions(-) create mode 100644 aether/executors/action_wait_sender.h diff --git a/aether/executors/action_wait_sender.h b/aether/executors/action_wait_sender.h new file mode 100644 index 00000000..c15bc1e3 --- /dev/null +++ b/aether/executors/action_wait_sender.h @@ -0,0 +1,192 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ +#define AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ + +#include +#include +#include +#include + +#include "aether-miscpp/types/result.h" + +#include "aether/warning_disable.h" + +// IWYU pragma: begin_exports +DISABLE_WARNING_PUSH() +IGNORE_IMPLICIT_CONVERSION() +#include +DISABLE_WARNING_POP() + +#include "aether/actions/action.h" +#include "aether/events/event_subscription.h" +#include "aether/events/events.h" + +namespace ae::ex { +namespace action_wait_sender_internal { + +template +struct IsResultEvent : std::false_type {}; +template +struct IsResultEvent)>> : std::true_type {}; +template +struct IsResultEvent)>> : std::true_type { +}; + +template +concept ResultEvent = IsResultEvent::value; + +template +concept ActionResultEvent = requires(A& a) { + { a.result_event() } -> ResultEvent; +}; + +template +struct ResultEventTrait {}; + +template +struct ResultEventTrait)>> { + using type = Ok; + using error = Err; +}; + +template +class OpState { + using ResultEvent = + typename decltype(std::declval().result_event())::EventType; + using EventTrait = ResultEventTrait; + using ResultType = + Result; + + public: + constexpr OpState(A& action, R&& recv) noexcept + : receiver_{std::move(recv)}, + event_sub_{action.result_event().Subscribe([&](auto&& r) noexcept { + EventHandler(std::forward(r)); + })} {} + + OpState(OpState const&) = delete; + OpState(OpState&&) noexcept = delete; + auto& operator=(OpState const&) = delete; + auto& operator=(OpState&&) noexcept = delete; + + constexpr void start() noexcept { + // check if stop was requested + auto token = stdexec::get_stop_token(stdexec::get_env(receiver_)); + if constexpr (std::is_same_v) { + if (token.request_stop()) { + event_sub_.Reset(); + stdexec::set_stopped(std::move(receiver_)); + return; + } + } + // first check saved result before start + if (res_) { + HandleResult(std::move(res_.value())); + } else + // wait for event handler is called or stop is requested + { + started_ = true; + if constexpr (std::is_same_v) { + stop_cb_.emplace(token, StopCb{.self = this}); + } + } + } + + private: + template + void EventHandler(ResType&& res) noexcept { + if (started_) { + HandleResult(std::forward(res)); + } else { + res_.emplace(std::forward(res)); + } + } + + template + void HandleResult(ResType&& res) noexcept { + event_sub_.Reset(); + stop_cb_.reset(); + if (res) { + stdexec::set_value(std::move(receiver_), + std::forward(res).value()); + } else { + stdexec::set_error(std::move(receiver_), + std::forward(res).error()); + } + } + + struct StopCb { + void operator()() const noexcept { + self->event_sub_.Reset(); + stdexec::set_stopped(std::move(self->receiver_)); + } + OpState* self; + }; + + bool started_{false}; + R receiver_; + Subscription event_sub_; + std::optional res_; + std::optional> stop_cb_; +}; + +template +class Sender { + using ResultEvent = + typename decltype(std::declval().result_event())::EventType; + + using EventTrait = ResultEventTrait; + + public: + using sender_concept = stdexec::sender_t; + + template + requires(std::is_same_v, Sender>) + static consteval auto get_completion_signatures() + -> stdexec::completion_signatures< + stdexec::set_value_t(typename EventTrait::type), + stdexec::set_error_t(typename EventTrait::error), + stdexec::set_stopped_t()> { + return {}; + } + + constexpr explicit Sender(A& action) noexcept : action_{&action} {} + + template + constexpr auto connect(R&& r) noexcept { + return OpState{*action_, std::forward(r)}; + } + + private: + A* action_; +}; + +struct ActionWait { + template + constexpr auto operator()(A& action) const noexcept { + return Sender{action}; + } +}; +}; // namespace action_wait_sender_internal + +static constexpr inline auto action_wait = + action_wait_sender_internal::ActionWait{}; + +} // namespace ae::ex + +#endif // AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ diff --git a/aether/executors/async_waiter.h b/aether/executors/async_waiter.h index 82f3a0dd..7af6152a 100644 --- a/aether/executors/async_waiter.h +++ b/aether/executors/async_waiter.h @@ -17,9 +17,9 @@ #ifndef AETHER_EXECUTORS_ASYNC_WAITER_H_ #define AETHER_EXECUTORS_ASYNC_WAITER_H_ -#include -#include #include +#include +#include #include "aether/warning_disable.h" @@ -32,9 +32,9 @@ DISABLE_WARNING_POP() #include "aether-miscpp/types/result.h" #include "aether-miscpp/types/small_function.h" -#include "aether/executors/waiter_traits.h" #include "aether/executors/async_context.h" #include "aether/executors/scheduler_on_tasks.h" +#include "aether/executors/waiter_traits.h" namespace ae::ex { namespace async_waiter_internal { @@ -143,9 +143,9 @@ class AsyncWaiter { using CompletionsTraits = CompletionsTraitsImpl; using ValueType = CompletionsTraits::ValueType; using ErrorType = CompletionsTraits::ErrorType; - using ResultType = std::conditional_t< - !std::is_same_v, - Result, ValueType>; + using ResultType = + std::conditional_t, + Result, ValueType>; using HandlerCb = SmallFunction)>; using StateType = State; @@ -153,8 +153,9 @@ class AsyncWaiter { public: AsyncWaiter(AC const& ac, S&& s, HandlerCb&& handler_cb) - : state_{ - .ac = ac, .wait_result = std::nullopt, .cb = std::move(handler_cb)}, + : state_{.ac = ac, + .wait_result = std::nullopt, + .cb = std::move(handler_cb)}, op_state_{stdexec::connect(std::move(s), Receiver{&state_})} { // run operation and wait for result asynchronously stdexec::start(op_state_); diff --git a/aether/executors/executors.h b/aether/executors/executors.h index c95adafe..d6b8b9f2 100644 --- a/aether/executors/executors.h +++ b/aether/executors/executors.h @@ -27,14 +27,15 @@ IGNORE_IMPLICIT_CONVERSION() #include DISABLE_WARNING_POP() -#include "aether/executors/for_range.h" +#include "aether/executors/action_wait_sender.h" #include "aether/executors/any_sender.h" #include "aether/executors/any_waiter.h" +#include "aether/executors/async_waiter.h" +#include "aether/executors/for_range.h" #include "aether/executors/make_sender.h" +#include "aether/executors/scheduler_on_tasks.h" #include "aether/executors/sync_waiter.h" -#include "aether/executors/async_waiter.h" #include "aether/executors/with_timeout.h" -#include "aether/executors/scheduler_on_tasks.h" // IWYU pragma: end_exports namespace ae::ex { From 67e2a672b6e4099e6eec51b3baed77a0c5aeb065 Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Mon, 3 Aug 2026 10:52:30 +0500 Subject: [PATCH 3/5] improve logs in client server conection --- aether/server_connections/client_server_connection.cpp | 7 ++++--- aether/server_connections/client_server_connection.h | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/aether/server_connections/client_server_connection.cpp b/aether/server_connections/client_server_connection.cpp index 6603eabb..d6a96675 100644 --- a/aether/server_connections/client_server_connection.cpp +++ b/aether/server_connections/client_server_connection.cpp @@ -143,6 +143,7 @@ ClientServerConnection::ClientServerConnection(AeContext const& ae_context, Ptr const& server) : ae_context_{ae_context}, server_{server}, + uid_{client->uid()}, ephemeral_uid_{client->ephemeral_uid()}, crypto_provider_{std::make_unique< client_server_connection_internal::ClientCryptoProvider>( @@ -150,8 +151,8 @@ ClientServerConnection::ClientServerConnection(AeContext const& ae_context, client_api_unsafe_{protocol_context_, *crypto_provider_->decryptor()}, login_api_{protocol_context_, *crypto_provider_->encryptor()}, server_connection_{ae_context_, server} { - AE_TELED_DEBUG("Client server connection from {} to {}", ephemeral_uid_, - server->server_id); + AE_TELED_DEBUG("Client server connection from {}:e-{} to {}", uid_, + ephemeral_uid_, server->server_id); server_connection_.out_data_event().Subscribe( MethodPtr<&ClientServerConnection::OutData>{this}); @@ -160,7 +161,7 @@ ClientServerConnection::ClientServerConnection(AeContext const& ae_context, ClientServerConnection::~ClientServerConnection() { auto server = server_.Lock(); assert(server); - AE_TELED_DEBUG("Destroy client server connection from {} to {}", + AE_TELED_DEBUG("Destroy client server connection from {}:e-{} to {}", uid_, ephemeral_uid_, server->server_id); } diff --git a/aether/server_connections/client_server_connection.h b/aether/server_connections/client_server_connection.h index 83a0aac2..cb654899 100644 --- a/aether/server_connections/client_server_connection.h +++ b/aether/server_connections/client_server_connection.h @@ -17,15 +17,15 @@ #ifndef AETHER_SERVER_CONNECTIONS_CLIENT_SERVER_CONNECTION_H_ #define AETHER_SERVER_CONNECTIONS_CLIENT_SERVER_CONNECTION_H_ -#include "aether/common.h" #include "aether/ae_context.h" +#include "aether/common.h" #include "aether/crypto/icrypto_provider.h" #include "aether/write_action/buffer_write.h" -#include "aether/work_cloud_api/work_server_api/login_api.h" #include "aether/work_cloud_api/client_api/client_api_safe.h" #include "aether/work_cloud_api/client_api/client_api_unsafe.h" #include "aether/work_cloud_api/work_server_api/authorized_api.h" +#include "aether/work_cloud_api/work_server_api/login_api.h" #include "aether/server_connections/server_connection.h" @@ -79,6 +79,7 @@ class ClientServerConnection { AeContext ae_context_; PtrView server_; + Uid uid_; Uid ephemeral_uid_; std::unique_ptr crypto_provider_; From 02ca47654eefa9019a85257a8ac4294bdf5016fa Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Mon, 3 Aug 2026 11:41:25 +0500 Subject: [PATCH 4/5] make async waiter configurable with callable --- aether/executors/async_waiter.h | 15 ++++++++++----- aether/serial_ports/at_support/at_stage.h | 15 +++++++++------ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/aether/executors/async_waiter.h b/aether/executors/async_waiter.h index 7af6152a..74f41928 100644 --- a/aether/executors/async_waiter.h +++ b/aether/executors/async_waiter.h @@ -30,7 +30,6 @@ IGNORE_IMPLICIT_CONVERSION() DISABLE_WARNING_POP() #include "aether-miscpp/types/result.h" -#include "aether-miscpp/types/small_function.h" #include "aether/executors/async_context.h" #include "aether/executors/scheduler_on_tasks.h" @@ -136,7 +135,7 @@ struct Receiver { State* state; }; -template +template class AsyncWaiter { using Completions = decltype(stdexec::get_completion_signatures>()); @@ -147,12 +146,14 @@ class AsyncWaiter { std::conditional_t, Result, ValueType>; - using HandlerCb = SmallFunction)>; - using StateType = State; + static_assert(std::is_invocable_v&&>, + "Callback should handle result"); + + using StateType = State; using OpState = stdexec::connect_result_t>; public: - AsyncWaiter(AC const& ac, S&& s, HandlerCb&& handler_cb) + AsyncWaiter(AC const& ac, S&& s, Cb&& handler_cb) : state_{.ac = ac, .wait_result = std::nullopt, .cb = std::move(handler_cb)}, @@ -165,6 +166,10 @@ class AsyncWaiter { StateType state_; OpState op_state_; }; + +template +AsyncWaiter(AC const&, S&&, Cb&&) -> AsyncWaiter; + } // namespace async_waiter_internal using async_waiter_internal::AsyncWaiter; diff --git a/aether/serial_ports/at_support/at_stage.h b/aether/serial_ports/at_support/at_stage.h index b584a341..0495f3c0 100644 --- a/aether/serial_ports/at_support/at_stage.h +++ b/aether/serial_ports/at_support/at_stage.h @@ -17,27 +17,30 @@ #ifndef AETHER_SERIAL_PORTS_AT_SUPPORT_H_ #define AETHER_SERIAL_PORTS_AT_SUPPORT_H_ -#include -#include #include #include +#include +#include -#include "aether/ae_context.h" #include "aether/actions/action.h" +#include "aether/ae_context.h" #include "aether/executors/executors.h" namespace ae::at_stage_internal { +struct ActionFinishCb { + void operator()(auto&&...) const noexcept { self->Finish(); } + Action* self; +}; template class AtStageAction final : public Action { public: AtStageAction(AeContext const& ae_context, RequestSender&& sender) - : waiter_{ae_context, std::move(sender), - [&](auto const&...) noexcept { Finish(); }} {} + : waiter_{ae_context, std::move(sender), ActionFinishCb{.self = this}} {} AE_CLASS_MOVE_ONLY(AtStageAction) private: - ex::AsyncWaiter waiter_; + ex::AsyncWaiter waiter_; }; template From 109469a20a478340fcc4b162fa7370bcb032e143 Mon Sep 17 00:00:00 2001 From: BartolomeyKant Date: Mon, 3 Aug 2026 10:52:47 +0500 Subject: [PATCH 5/5] add new example a_b_message_exchange --- CMakeLists.txt | 2 + examples/a_b_message_exchange/CMakeLists.txt | 24 ++ .../a_b_message_exchange.cpp | 216 ++++++++++++++++++ .../config/cloud_config.ini | 2 + examples/a_b_message_exchange/main.cpp | 27 +++ examples/cloud/CMakeLists.txt | 4 +- examples/cloud/aether_construct_lora_module.h | 75 ------ examples/cloud/aether_construct_modem.h | 69 ------ examples/cloud/cloud_test.cpp | 30 +-- examples/common/CMakeLists.txt | 5 + examples/{cloud => common}/aether_construct.h | 21 +- .../aether_construct_esp_wifi.h | 44 ++-- .../aether_construct_ethernet.h | 13 +- .../common/aether_construct_lora_module.h | 62 +++++ examples/common/aether_construct_modem.h | 55 +++++ 15 files changed, 448 insertions(+), 201 deletions(-) create mode 100644 examples/a_b_message_exchange/CMakeLists.txt create mode 100644 examples/a_b_message_exchange/a_b_message_exchange.cpp create mode 100644 examples/a_b_message_exchange/config/cloud_config.ini create mode 100644 examples/a_b_message_exchange/main.cpp delete mode 100644 examples/cloud/aether_construct_lora_module.h delete mode 100644 examples/cloud/aether_construct_modem.h create mode 100644 examples/common/CMakeLists.txt rename examples/{cloud => common}/aether_construct.h (61%) rename examples/{cloud => common}/aether_construct_esp_wifi.h (63%) rename examples/{cloud => common}/aether_construct_ethernet.h (81%) create mode 100644 examples/common/aether_construct_lora_module.h create mode 100644 examples/common/aether_construct_modem.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ed9e877b..90aaa722 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -372,7 +372,9 @@ if(AE_BUILD_EXAMPLES) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_subdirectory(examples/common) add_subdirectory(examples/cloud) + add_subdirectory(examples/a_b_message_exchange) add_subdirectory(examples/capi/oddity) add_subdirectory(examples/benches/send_message_delays) add_subdirectory(examples/benches/send_messages_bandwidth) diff --git a/examples/a_b_message_exchange/CMakeLists.txt b/examples/a_b_message_exchange/CMakeLists.txt new file mode 100644 index 00000000..6c0e3e74 --- /dev/null +++ b/examples/a_b_message_exchange/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.16.0) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(NOT CM_PLATFORM) + project("ab-message-exchange" VERSION "1.0.0" LANGUAGES C CXX) + set(TARGET_NAME ${PROJECT_NAME}) + add_executable(${TARGET_NAME} main.cpp a_b_message_exchange.cpp) + target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(${TARGET_NAME} PRIVATE aether_examples_common) +else() + idf_build_get_property(CM_PLATFORM CM_PLATFORM) + if(CM_PLATFORM STREQUAL "ESP32") + idf_component_register( + SRCS main.cpp a_b_message_exchange.cpp + INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ../common + REQUIRES esp_wifi esp_netif nvs_flash spiffs esp_driver_uart) + add_subdirectory("../../" aether) + target_link_libraries(${COMPONENT_LIB} PRIVATE aether) + else() + message(FATAL_ERROR "Platform ${CM_PLATFORM} is not supported") + endif() +endif() diff --git a/examples/a_b_message_exchange/a_b_message_exchange.cpp b/examples/a_b_message_exchange/a_b_message_exchange.cpp new file mode 100644 index 00000000..cbaffc51 --- /dev/null +++ b/examples/a_b_message_exchange/a_b_message_exchange.cpp @@ -0,0 +1,216 @@ +#define AE_EXAMPLE_LORA_MODULE 0 +#define AE_EXAMPLE_MODEM 0 +#ifdef ESP_PLATFORM +# define AE_EXAMPLE_ESP_WIFI 1 +#else +# define AE_EXAMPLE_ETHERNET 1 +#endif + +#include +#include +#include +#include + +#include "aether-miscpp/format/format.h" +#include "aether-miscpp/misc/override.h" +#include "aether/all.h" + +// IWYU pragma: begin_keeps +#include "../common/aether_construct_esp_wifi.h" +#include "../common/aether_construct_ethernet.h" +#include "../common/aether_construct_lora_module.h" +#include "../common/aether_construct_modem.h" +// IWYU pragma: end_keeps + +namespace ae::examples { + +static constexpr auto kParentUid = + ae::Uid::FromString("3ac93165-3d37-4970-87a6-fa4ee27744e4"); + +static constexpr auto kStageTimeout = std::chrono::seconds{5}; + +template +void Log(ae::FormatScheme const& format, Args&&... args) { + ae::Format(std::cout, ">>> [{:time}] ", ae::Now()); + ae::Format(std::cout, format, std::forward(args)...); + std::cout << '\n'; +} + +static std::string MakeMessage(std::string_view client_name, int message_num) { + return ae::Format("Message from {} num {} {:time}", client_name, message_num, + ae::Now()); +} + +static ae::DataBuffer ToDataBuffer(std::string_view text) { + return {text.begin(), text.end()}; +} + +static std::string_view ToString(ae::DataBuffer const& data) { + return {reinterpret_cast(data.data()), data.size()}; +} + +struct State { + explicit State(ae::RcPtr const& app) : aether{app->aether()} {} + + ae::Aether::ptr aether; + ae::Client::ptr client_a; + ae::Client::ptr client_b; + std::shared_ptr a_stream; + std::shared_ptr b_stream; + Event a_received_event; + Event b_received_event; +}; + +static auto SelectClientSender(State* state, std::string_view name, + ae::Client::ptr State::* out_client) { + return ae::ex::let_value([=]() noexcept { + Log("client.select.start name={}", name); + return ae::ex::action_wait( + state->aether->SelectClient(kParentUid, std::string{name})); + }) | + ex::then([=](Client::ptr const& client) noexcept { + state->*out_client = client; + Log("client.select.done name={} uid={}", name, client->uid()); + }) | + ex::let_error([=](auto&&...) noexcept { + Log("client.select.error name={}", name); + return ex::just_error(1); + }) | + ae::ex::with_timeout(ae::AeContext{*state->aether}, kStageTimeout); +} + +static void OpenAStream(State* state) { + state->a_stream = std::make_shared( + *state->aether, state->client_a.Load(), state->client_b->uid(), + state->client_a->message_stream_manager().CreatePort( + state->client_b->uid())); + // subscribe to data receive + state->a_stream->out_data_event().Subscribe([state](DataBuffer const& data) { + state->a_received_event.Emit(ToString(data)); + }); + Log("stream.open.done side=A"); + + // subscribe to open new message stream + state->client_b->message_stream_manager().new_port_event().Subscribe( + [state](ae::P2pPortHandle handle) { + state->b_stream = std::make_shared( + *state->aether, state->client_b.Load(), handle.destination(), + std::move(handle)); + + // subscribe to data receive + state->b_stream->out_data_event().Subscribe( + [state](DataBuffer const& data) { + state->b_received_event.Emit(ToString(data)); + }); + Log("stream.open.done side=B"); + }); +} + +static auto SendMessageAtoB(State* state, int message_num) { + return ae::ex::let_value([=]() noexcept { + return ae::ex::create( + [=, test_sub_ = Subscription{}](auto& ctx) mutable noexcept { + auto text = MakeMessage("A", message_num); + Log("message.A_to_B.send.start num={} text=[{}]", message_num, + text); + + // Expect B receive the message + test_sub_ = + ae::EventSubscriber{state->b_received_event}.Subscribe( + [&](std::string_view message) noexcept { + Log("message.A_to_B.receive.done num={} text=[{}]", + message_num, message); + return ae::ex::set_value(std::move(ctx.receiver)); + }); + + state->a_stream->Write(ToDataBuffer(text)) + .status_event() + .Subscribe([&, state](auto status) { + if (status == ae::WriteAction::Status::kFail) { + return ae::ex::set_error(std::move(ctx.receiver), 2); + } + }); + }) | + ae::ex::with_timeout(ae::AeContext{*state->aether}, kStageTimeout); + }); +} + +static auto SendMessageBtoA(State* state, int message_num) { + return ae::ex::let_value([=]() noexcept { + return ae::ex::create( + [=, test_sub_ = Subscription{}](auto& ctx) mutable noexcept { + auto text = MakeMessage("B", message_num); + Log("message.B_to_A.send.start num={} text=[{}]", message_num, + text); + + // Expect A receive the message + test_sub_ = + ae::EventSubscriber{state->a_received_event}.Subscribe( + [&](std::string_view message) noexcept { + Log("message.B_to_A.receive.done num={} text=[{}]", + message_num, message); + return ae::ex::set_value(std::move(ctx.receiver)); + }); + + if (!state->b_stream) { + Log("message.B_to_A.send.failed B has no stream to A"); + return ae::ex::set_error(std::move(ctx.receiver), 3); + } + + state->b_stream->Write(ToDataBuffer(text)) + .status_event() + .Subscribe([&, state](auto status) { + if (status == ae::WriteAction::Status::kFail) { + return ae::ex::set_error(std::move(ctx.receiver), 2); + } + }); + }) | + ae::ex::with_timeout(ae::AeContext{*state->aether}, kStageTimeout); + }); +} + +} // namespace ae::examples + +int AetherABMessageExchangeExample() { + using namespace ae::examples; // NOLINT + Log("app.create.start"); + auto aether_app = ae::examples::construct_aether_app(); + Log("app.create.done"); + + State state{aether_app}; + + auto pipeline = ae::ex::just() | + SelectClientSender(&state, "A", &State::client_a) | + SelectClientSender(&state, "B", &State::client_b) | + ae::ex::then([&state]() noexcept { OpenAStream(&state); }) | + SendMessageAtoB(&state, 1) | SendMessageBtoA(&state, 1) | + SendMessageAtoB(&state, 2) | SendMessageBtoA(&state, 2); + // asynchronously wait till pipeline is over + auto waiter = ae::ex::AsyncWaiter{ + ae::AeContext{*aether_app}, std::move(pipeline), + [&](std::optional const& res) noexcept { + if (!res) { + Log("exchange.stopped"); + aether_app->Exit(2); + return; + } + if (res->IsOk()) { + Log("exchange.done"); + aether_app->Exit(0); + } else { // error + std::visit( + ae::Override{ + [](ae::ex::TimeoutError) { Log("exchange.fail timeout"); }, + [](auto&& e) { Log("exchange.fail code={}", e); }}, + res->error()); + aether_app->Exit(1); + } + }}; + + while (!aether_app->IsExited()) { + auto next_time = aether_app->Update(ae::Now()); + aether_app->WaitUntil(next_time); + } + + return aether_app->ExitCode(); +} diff --git a/examples/a_b_message_exchange/config/cloud_config.ini b/examples/a_b_message_exchange/config/cloud_config.ini new file mode 100644 index 00000000..f4278ed7 --- /dev/null +++ b/examples/a_b_message_exchange/config/cloud_config.ini @@ -0,0 +1,2 @@ +[cloud] +name=ab-message-exchange diff --git a/examples/a_b_message_exchange/main.cpp b/examples/a_b_message_exchange/main.cpp new file mode 100644 index 00000000..82383b3e --- /dev/null +++ b/examples/a_b_message_exchange/main.cpp @@ -0,0 +1,27 @@ +/* Copyright 2024 Aethernet Inc. */ + +#include "aether/config.h" +#include "aether/tele.h" + +#if (defined(CM_ESP32)) +# include +# include +#endif + +extern "C" void app_main(); +extern int AetherABMessageExchangeExample(); + +int test(void) { return AetherABMessageExchangeExample(); } + +#if (defined(ESP_PLATFORM)) +void app_main(void) { + esp_task_wdt_config_t config_wdt = {.timeout_ms = 60000, .idle_core_mask = 0, .trigger_panic = true}; + auto err = esp_task_wdt_reconfigure(&config_wdt); + if (err != 0) { std::cerr << "Reconfigure WDT is failed!\n"; } + test(); +} +#endif + +#if (defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(_WIN64) || defined(_WIN32)) +int main() { return test(); } +#endif diff --git a/examples/cloud/CMakeLists.txt b/examples/cloud/CMakeLists.txt index 6bc45a3f..31999c9f 100644 --- a/examples/cloud/CMakeLists.txt +++ b/examples/cloud/CMakeLists.txt @@ -27,13 +27,13 @@ if(NOT CM_PLATFORM) add_executable(${PROJECT_NAME} ${src_list}) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(${PROJECT_NAME} PRIVATE aether) + target_link_libraries(${PROJECT_NAME} PRIVATE aether_examples_common) else() idf_build_get_property(CM_PLATFORM CM_PLATFORM) if(CM_PLATFORM STREQUAL "ESP32") #ESP32 CMake idf_component_register(SRCS ${src_list} - INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} + INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ../common REQUIRES esp_wifi esp_netif diff --git a/examples/cloud/aether_construct_lora_module.h b/examples/cloud/aether_construct_lora_module.h deleted file mode 100644 index bf6a68a7..00000000 --- a/examples/cloud/aether_construct_lora_module.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CLOUD_AETHER_CONSTRUCT_LORA_MODULE_H_ -#define CLOUD_AETHER_CONSTRUCT_LORA_MODULE_H_ - -#include "aether_construct.h" - -#include "aether/config.h" - -#if CLOUD_TEST_LORA_MODULE -# if !AE_SUPPORT_LORA -# error "Lora module is not supported" -# else - -namespace ae::cloud_test { -static constexpr std::string_view kSerialPortLoraModule = - "COM2"; // Lora module serial port -SerialInit serial_init_lora_module = {std::string(kSerialPortLoraModule), - kBaudRate::kBaudRate9600}; - -LoraPowerSaveParam psp{ - kLoraModuleMode::kTransparentTransmission, // kLoraModuleMode - kLoraModuleLevel::kLevel0, // kLoraModuleLevel - kLoraModulePower::kPower22, // kLoraModulePower - kLoraModuleBandWidth::kBandWidth125K, // kLoraModuleBandWidth - kLoraModuleCodingRate::kCR4_6, // kLoraModuleCodingRate - kLoraModuleSpreadingFactor::kSF12 // kLoraModuleSpreadingFactor -}; - -ae::LoraModuleInit const lora_module_init{ - serial_init_lora_module, // Serial port - psp, // Power Save Parameters - kLoraModuleFreqRange::kFREUndef // Frequency range - 0, // Lora module address - 0, // Lora module BS address - 0, // Channel - kLoraModuleCRCCheck::kCRCOff, // CRC check - kLoraModuleIQSignalInversion::kIQoff // Signal inversion -}; - -static RcPtr construct_aether_app() { - return AetherApp::Construct( - AetherAppContext{} -# if defined AE_DISTILLATION - .AdaptersFactory([](AetherAppContext const& context) { - auto adapter_registry = - context.domain().CreateObj(); - adapter_registry->Add( - context.domain().CreateObj( - ae::GlobalId::kLoraModuleAdapter, context.aether(), - context.poller(), lora_module_init)); - return adapter_registry; - }) -# endif - ); -} -} // namespace ae::cloud_test - -# endif -#endif -#endif // CLOUD_AETHER_CONSTRUCT_LORA_MODULE_H_ diff --git a/examples/cloud/aether_construct_modem.h b/examples/cloud/aether_construct_modem.h deleted file mode 100644 index b8f6e54f..00000000 --- a/examples/cloud/aether_construct_modem.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef CLOUD_AETHER_CONSTRUCT_MODEM_H_ -#define CLOUD_AETHER_CONSTRUCT_MODEM_H_ - -#include "aether_construct.h" - -#if CLOUD_TEST_MODEM -# if !AE_SUPPORT_MODEMS -# error "Modem support is required for cloud test modem" -# else - -namespace ae::cloud_test { -static constexpr std::string_view kSerialPortModem = - "COM1"; // Modem serial port -SerialInit serial_init_modem = {std::string(kSerialPortModem), - kBaudRate::kBaudRate115200}; - -static ae::ModemInit const modem_init{ - serial_init_modem, // Serial port - {}, // Power save parameters - {}, // Base station - 1111, // Pin code - false, // Use pin - ae::kModemMode::kModeNbIot, // Modem mode - "00001", // Operator code - "", // Operator long name - "internet", // APN - "user", // APN user - "password", // APN pass - ae::kAuthType::kAuthTypeNone, // Auth type - false, // Use auth - "", // Auth user - "", // Auth pass - "", // SSL cert - false // Use SSL -}; - -static RcPtr construct_aether_app() { - return AetherApp::Construct( - AetherAppContext{} -# if defined AE_DISTILLATION - .AddAdapterFactory([](AetherAppContext const& context) { - return ModemAdapter::ptr::Create(CreateWith{context.domain()}.with_id( - ae::GlobalId::kModemAdapter), context.aether(), context.poller(), - modem_init); - }) -# endif - ); -} -} // namespace ae::cloud_test - -# endif -#endif -#endif // CLOUD_AETHER_CONSTRUCT_MODEM_H_ diff --git a/examples/cloud/cloud_test.cpp b/examples/cloud/cloud_test.cpp index 809c6763..dac88f34 100644 --- a/examples/cloud/cloud_test.cpp +++ b/examples/cloud/cloud_test.cpp @@ -20,23 +20,23 @@ #include "aether/all.h" #include "aether/client_messages/p2p_message_stream.h" -#define CLOUD_TEST_LORA_MODULE 0 -#define CLOUD_TEST_MODEM 0 +#define AE_EXAMPLE_LORA_MODULE 0 +#define AE_EXAMPLE_MODEM 0 #if defined ESP_PLATFORM -# define CLOUD_TEST_ESP_WIFI 1 +# define AE_EXAMPLE_ESP_WIFI 1 #else -# define CLOUD_TEST_ETHERNET 1 +# define AE_EXAMPLE_ETHERNET 1 #endif // IWYU pragma: begin_keeps -#include "aether_construct_esp_wifi.h" -#include "aether_construct_ethernet.h" -#include "aether_construct_lora_module.h" -#include "aether_construct_modem.h" +#include "../common/aether_construct_esp_wifi.h" +#include "../common/aether_construct_ethernet.h" +#include "../common/aether_construct_lora_module.h" +#include "../common/aether_construct_modem.h" // IWYU pragma: end_keeps -namespace ae::cloud_test { +namespace ae::examples { static constexpr inline auto kParentUid = ae::Uid::FromString("3ac93165-3d37-4970-87a6-fa4ee27744e4"); @@ -49,7 +49,7 @@ constexpr SafeStreamConfig kSafeStreamConfig{ .send_ack_timeout = std::chrono::seconds{0}, .send_repeat_timeout = std::chrono::seconds{2}, }; -} // namespace ae::cloud_test +} // namespace ae::examples int AetherCloudExample() { /** @@ -59,7 +59,7 @@ int AetherCloudExample() { * in your update loop. Also it has action context protocol implementation * \see Action. To configure its creation \see AetherAppContext. */ - auto aether_app = ae::cloud_test::construct_aether_app(); + auto aether_app = ae::examples::construct_aether_app(); /** * Start clients selection or registration. @@ -70,7 +70,7 @@ int AetherCloudExample() { ae::Client::ptr client_b; auto& select_client_a = - aether_app->aether()->SelectClient(ae::cloud_test::kParentUid, "A"); + aether_app->aether()->SelectClient(ae::examples::kParentUid, "A"); select_client_a.result_event().Subscribe([&](auto const& res) { if (res) { client_a = res.value(); @@ -80,7 +80,7 @@ int AetherCloudExample() { }); auto& select_client_b = - aether_app->aether()->SelectClient(ae::cloud_test::kParentUid, "B"); + aether_app->aether()->SelectClient(ae::examples::kParentUid, "B"); select_client_b.result_event().Subscribe([&](auto const& res) { if (res) { client_b = res.value(); @@ -134,7 +134,7 @@ int AetherCloudExample() { auto p2p_stream = std::make_shared( *aether_app, client_a.Load(), dest, std::move(handle)); receiver_stream = ae::make_unique( - *aether_app, ae::cloud_test::kSafeStreamConfig, + *aether_app, ae::examples::kSafeStreamConfig, std::move(p2p_stream)); receiver_stream->out_data_event().Subscribe([&](auto const& data) { @@ -163,7 +163,7 @@ int AetherCloudExample() { auto p2p_stream = std::make_shared( *aether_app, client_b.Load(), client_a->uid(), std::move(handle)); auto sender_stream = ae::make_unique( - *aether_app, ae::cloud_test::kSafeStreamConfig, std::move(p2p_stream)); + *aether_app, ae::examples::kSafeStreamConfig, std::move(p2p_stream)); sender_stream->out_data_event().Subscribe([&](auto const& data) { auto str_response = diff --git a/examples/common/CMakeLists.txt b/examples/common/CMakeLists.txt new file mode 100644 index 00000000..a56e11ff --- /dev/null +++ b/examples/common/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.16.0) + +add_library(aether_examples_common INTERFACE) +target_include_directories(aether_examples_common INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(aether_examples_common INTERFACE aether) diff --git a/examples/cloud/aether_construct.h b/examples/common/aether_construct.h similarity index 61% rename from examples/cloud/aether_construct.h rename to examples/common/aether_construct.h index efd7485f..47f7f2af 100644 --- a/examples/cloud/aether_construct.h +++ b/examples/common/aether_construct.h @@ -14,13 +14,26 @@ * limitations under the License. */ -#ifndef CLOUD_AETHER_CONSTRUCT_H_ -#define CLOUD_AETHER_CONSTRUCT_H_ +#ifndef EXAMPLES_COMMON_AETHER_CONSTRUCT_H_ +#define EXAMPLES_COMMON_AETHER_CONSTRUCT_H_ + +#ifndef AE_EXAMPLE_ETHERNET +# define AE_EXAMPLE_ETHERNET 0 +#endif +#ifndef AE_EXAMPLE_ESP_WIFI +# define AE_EXAMPLE_ESP_WIFI 0 +#endif +#ifndef AE_EXAMPLE_LORA_MODULE +# define AE_EXAMPLE_LORA_MODULE 0 +#endif +#ifndef AE_EXAMPLE_MODEM +# define AE_EXAMPLE_MODEM 0 +#endif #include "aether/all.h" -namespace ae::cloud_test { +namespace ae::examples { static RcPtr construct_aether_app(); } -#endif // CLOUD_AETHER_CONSTRUCT_H_ +#endif // EXAMPLES_COMMON_AETHER_CONSTRUCT_H_ diff --git a/examples/cloud/aether_construct_esp_wifi.h b/examples/common/aether_construct_esp_wifi.h similarity index 63% rename from examples/cloud/aether_construct_esp_wifi.h rename to examples/common/aether_construct_esp_wifi.h index 41f08c23..27a9ad84 100644 --- a/examples/cloud/aether_construct_esp_wifi.h +++ b/examples/common/aether_construct_esp_wifi.h @@ -14,14 +14,14 @@ * limitations under the License. */ -#ifndef CLOUD_AETHER_CONSTRUCT_ESP_WIFI_H_ -#define CLOUD_AETHER_CONSTRUCT_ESP_WIFI_H_ +#ifndef EXAMPLES_COMMON_AETHER_CONSTRUCT_ESP_WIFI_H_ +#define EXAMPLES_COMMON_AETHER_CONSTRUCT_ESP_WIFI_H_ #include "aether_construct.h" -#if CLOUD_TEST_ESP_WIFI +#if AE_EXAMPLE_ESP_WIFI -namespace ae::cloud_test { +namespace ae::examples { static const std::string kWifi1Ssid = "Test1234"; static const std::string kWifi1Pass = "Test1234"; @@ -36,14 +36,8 @@ static IpV4Addr my_dns2_ip_v4{8, 8, 4, 4}; static IpV6Addr my_static_ip_v6{0x20, 0x01, 0x0d, 0xb8, 0x85, 0xa3, 0x00, 0x00, 0x00, 0x00, 0x8a, 0x2e, 0x03, 0x70, 0x73, 0x34}; -WiFiIP wifi_ip{ - {my_static_ip_v4}, // ESP32 static IP - {my_gateway_ip_v4}, // IP Address of your network gateway (router) - {my_netmask_ip_v4}, // Subnet mask - {my_dns1_ip_v4}, // Primary DNS (optional) - {my_dns2_ip_v4}, // Secondary DNS (optional) - {my_static_ip_v6} // ESP32 static IP v6 -}; +WiFiIP wifi_ip{{my_static_ip_v4}, {my_gateway_ip_v4}, {my_netmask_ip_v4}, + {my_dns1_ip_v4}, {my_dns2_ip_v4}, {my_static_ip_v6}}; static WifiCreds my_wifi1{kWifi1Ssid, kWifi1Pass}; static WifiCreds my_wifi2{kWifi2Ssid, kWifi2Pass}; @@ -53,23 +47,13 @@ ae::WiFiAp wifi2_ap{my_wifi2, {} /*wifi_ip*/}; std::vector wifi_ap_vec{wifi1_ap, wifi2_ap}; -static WiFiPowerSaveParam wifi_psp{ - AE_WIFI_PS_MAX_MODEM, // Power save type - AE_WIFI_PROTOCOL_11B | AE_WIFI_PROTOCOL_11G | - AE_WIFI_PROTOCOL_11N, // Protocol bitmap - 3, // Listen interval - 500, // Beacon interval - 0, // Fix rate - 3, // Short retry - 3, // Long retry - 8 // Power -}; +static WiFiPowerSaveParam wifi_psp{AE_WIFI_PS_MAX_MODEM, + AE_WIFI_PROTOCOL_11B | + AE_WIFI_PROTOCOL_11G | + AE_WIFI_PROTOCOL_11N, + 3, 500, 0, 3, 3, 8}; -WiFiInit wifi_init{ - wifi_ap_vec, // Wi-Fi access points - {} - // wifi_psp, // Power save parameters -}; +WiFiInit wifi_init{wifi_ap_vec, {}}; RcPtr construct_aether_app() { return AetherApp::Construct( @@ -85,7 +69,7 @@ RcPtr construct_aether_app() { ); } -} // namespace ae::cloud_test +} // namespace ae::examples #endif -#endif // CLOUD_AETHER_CONSTRUCT_ESP_WIFI_H_ +#endif // EXAMPLES_COMMON_AETHER_CONSTRUCT_ESP_WIFI_H_ diff --git a/examples/cloud/aether_construct_ethernet.h b/examples/common/aether_construct_ethernet.h similarity index 81% rename from examples/cloud/aether_construct_ethernet.h rename to examples/common/aether_construct_ethernet.h index 37d81b8c..bb7b1603 100644 --- a/examples/cloud/aether_construct_ethernet.h +++ b/examples/common/aether_construct_ethernet.h @@ -14,13 +14,13 @@ * limitations under the License. */ -#ifndef CLOUD_AETHER_CONSTRUCT_ETHERNET_H_ -#define CLOUD_AETHER_CONSTRUCT_ETHERNET_H_ +#ifndef EXAMPLES_COMMON_AETHER_CONSTRUCT_ETHERNET_H_ +#define EXAMPLES_COMMON_AETHER_CONSTRUCT_ETHERNET_H_ #include "aether_construct.h" -#if CLOUD_TEST_ETHERNET -namespace ae::cloud_test { +#if AE_EXAMPLE_ETHERNET +namespace ae::examples { static RcPtr construct_aether_app() { return AetherApp::Construct( AetherAppContext{} @@ -34,6 +34,7 @@ static RcPtr construct_aether_app() { # endif ); } -} // namespace ae::cloud_test +} // namespace ae::examples #endif -#endif // CLOUD_AETHER_CONSTRUCT_ETHERNET_H_ + +#endif // EXAMPLES_COMMON_AETHER_CONSTRUCT_ETHERNET_H_ diff --git a/examples/common/aether_construct_lora_module.h b/examples/common/aether_construct_lora_module.h new file mode 100644 index 00000000..0917d95a --- /dev/null +++ b/examples/common/aether_construct_lora_module.h @@ -0,0 +1,62 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXAMPLES_COMMON_AETHER_CONSTRUCT_LORA_MODULE_H_ +#define EXAMPLES_COMMON_AETHER_CONSTRUCT_LORA_MODULE_H_ + +#include "aether_construct.h" +#include "aether/config.h" + +#if AE_EXAMPLE_LORA_MODULE +# if !AE_SUPPORT_LORA +# error "Lora module is not supported" +# else + +namespace ae::examples { +static constexpr std::string_view kSerialPortLoraModule = "COM2"; +SerialInit serial_init_lora_module = {std::string(kSerialPortLoraModule), + kBaudRate::kBaudRate9600}; + +LoraPowerSaveParam psp{kLoraModuleMode::kTransparentTransmission, + kLoraModuleLevel::kLevel0, kLoraModulePower::kPower22, + kLoraModuleBandWidth::kBandWidth125K, + kLoraModuleCodingRate::kCR4_6, + kLoraModuleSpreadingFactor::kSF12}; + +ae::LoraModuleInit const lora_module_init{serial_init_lora_module, psp, + kLoraModuleFreqRange::kFREUndef, + 0, 0, 0, + kLoraModuleCRCCheck::kCRCOff, + kLoraModuleIQSignalInversion::kIQoff}; + +static RcPtr construct_aether_app() { + return AetherApp::Construct( + AetherAppContext{} +# if defined AE_DISTILLATION + .AddAdapterFactory([](AetherAppContext const& context) { + return LoraModuleAdapter::ptr::Create( + CreateWith{context.domain()}.with_id(ae::GlobalId::kLoraModuleAdapter), + context.aether(), context.poller(), lora_module_init); + }) +# endif + ); +} +} // namespace ae::examples + +# endif +#endif + +#endif // EXAMPLES_COMMON_AETHER_CONSTRUCT_LORA_MODULE_H_ diff --git a/examples/common/aether_construct_modem.h b/examples/common/aether_construct_modem.h new file mode 100644 index 00000000..2b297d05 --- /dev/null +++ b/examples/common/aether_construct_modem.h @@ -0,0 +1,55 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXAMPLES_COMMON_AETHER_CONSTRUCT_MODEM_H_ +#define EXAMPLES_COMMON_AETHER_CONSTRUCT_MODEM_H_ + +#include "aether_construct.h" + +#if AE_EXAMPLE_MODEM +# if !AE_SUPPORT_MODEMS +# error "Modem support is required for cloud test modem" +# else + +namespace ae::examples { +static constexpr std::string_view kSerialPortModem = "COM1"; +SerialInit serial_init_modem = {std::string(kSerialPortModem), + kBaudRate::kBaudRate115200}; + +static ae::ModemInit const modem_init{serial_init_modem, {}, {}, 1111, false, + ae::kModemMode::kModeNbIot, "00001", "", + "internet", "user", "password", + ae::kAuthType::kAuthTypeNone, false, "", "", + "", false}; + +static RcPtr construct_aether_app() { + return AetherApp::Construct( + AetherAppContext{} +# if defined AE_DISTILLATION + .AddAdapterFactory([](AetherAppContext const& context) { + return ModemAdapter::ptr::Create( + CreateWith{context.domain()}.with_id(ae::GlobalId::kModemAdapter), + context.aether(), context.poller(), modem_init); + }) +# endif + ); +} +} // namespace ae::examples + +# endif +#endif + +#endif // EXAMPLES_COMMON_AETHER_CONSTRUCT_MODEM_H_