Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9dc9374
Support hosted builds against Arduino-Emulator
MitchBradley Apr 4, 2026
a302812
Changed comments to address Copilot nitpicking
MitchBradley Apr 4, 2026
9f138a2
Changed cpp macros to c++ possibly-null classes, thanks willmmiles
MitchBradley Apr 5, 2026
704daee
locks in asyncsrv namespace, guarded by ASYNCSRV_USE_MUTEX
MitchBradley Apr 6, 2026
f13f005
HOST versions of logging.
MitchBradley Apr 6, 2026
4ab4d79
__unused -> __asyncws_unused
MitchBradley Apr 7, 2026
c41669f
More explicit formulation of ASYNCWEBSERVER_USE_MUTEX define
MitchBradley Apr 7, 2026
04d695e
Merge branch 'main' into HOSTmacro
mathieucarbou Apr 8, 2026
d09c360
ci(pre-commit): Apply automatic fixes
pre-commit-ci-lite[bot] Apr 8, 2026
d0c7e8d
Apply suggestion from @mathieucarbou
mathieucarbou Apr 8, 2026
5b72d9e
Fix compile sisue
mathieucarbou Apr 8, 2026
237dd64
Add CI for Arduino-Emulator
mathieucarbou Apr 8, 2026
4ccec50
Fix compilation issue where tcp_state enum type was not found
mathieucarbou Apr 8, 2026
ca7c83b
Add CI for Arduino-Emulator
mathieucarbou Apr 8, 2026
3ec0583
ci(pre-commit): Apply automatic fixes
pre-commit-ci-lite[bot] Apr 8, 2026
717925a
Fix Arduino Elumator CI
mathieucarbou Apr 8, 2026
ccdef5f
Move FPSTR in cpp
mathieucarbou Apr 8, 2026
6580dd1
Try fix CI on linux (works on mac)
mathieucarbou Apr 8, 2026
41e8e70
Project Structure Reorg: regrouping all examples under examples folder
mathieucarbou Apr 8, 2026
c610286
Merge branch 'main' into HOSTmacro
mathieucarbou Apr 8, 2026
c0619cb
Move arduino_emulator to examples folder
mathieucarbou Apr 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/build-arduino-emulator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json

name: Build (Arduino Emulator)

on:
workflow_dispatch:
push:
branches:
- main
- release/*
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"
pull_request:
paths-ignore:
- "docs/**"
- "mkdocs.yml"
- "README.md"

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
arduino-emulator:
name: Arduino Emulator (cmake, HOST)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Install build tools
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build build-essential git

- name: Clone Arduino-Emulator
run: |
git clone --depth 1 --recurse-submodules https://github.com/pschatzmann/Arduino-Emulator.git .ci/arduino-emulator

- name: Clone AsyncTCP, Arduino FS headers, lwIP and lwIP contrib
run: |
git clone --depth 1 https://github.com/ESP32Async/AsyncTCP .ci/asynctcp
git clone --depth 1 https://github.com/espressif/arduino-esp32.git .ci/arduino-esp32
git clone --depth 1 https://github.com/lwip-tcpip/lwip.git .ci/lwip
git clone --depth 1 https://git.savannah.nongnu.org/git/lwip/lwip-contrib.git .ci/lwip-contrib

- name: Build with Arduino-Emulator
run: |
cmake -S examples/arduino_emulator -B .ci/arduino-emulator-build/out -G Ninja
cmake --build .ci/arduino-emulator-build/out --target espasyncwebserver --parallel
2 changes: 1 addition & 1 deletion examples/arduino/CaptivePortal/CaptivePortal.ino
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ static AsyncWebServer server(80);

class CaptiveRequestHandler : public AsyncWebHandler {
public:
bool canHandle(__unused AsyncWebServerRequest *request) const override {
bool canHandle(__asyncws_unused AsyncWebServerRequest *request) const override {
return true;
}

Expand Down
2 changes: 1 addition & 1 deletion examples/arduino/Filters/Filters.ino
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ static AsyncWebServer server(80);

class CaptiveRequestHandler : public AsyncWebHandler {
public:
bool canHandle(__unused AsyncWebServerRequest *request) const override {
bool canHandle(__asyncws_unused AsyncWebServerRequest *request) const override {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ static WebRequestMethodComposite allowed = AsyncWebRequestMethod::HTTP_HEAD | As

class MyRequestHandler : public AsyncWebHandler {
public:
bool canHandle(__unused AsyncWebServerRequest *request) const override {
bool canHandle(__asyncws_unused AsyncWebServerRequest *request) const override {
// Test backward compatibility with previous way of checking if a method is allowed in a composite using a bit operator
return allowed & request->method();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ static WebRequestMethodComposite allowed = AsyncWebRequestMethod::HTTP_HEAD | As

class MyRequestHandler : public AsyncWebHandler {
public:
bool canHandle(__unused AsyncWebServerRequest *request) const override {
bool canHandle(__asyncws_unused AsyncWebServerRequest *request) const override {
// Test backward compatibility with previous way of checking if a method is allowed in a composite using a bit operator
return allowed & request->method();
}
Expand Down
17 changes: 17 additions & 0 deletions examples/arduino_emulator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.11)
project(espasyncwebserver_host_compile LANGUAGES C CXX)

add_subdirectory(${CMAKE_SOURCE_DIR}/../../.ci/arduino-emulator ${CMAKE_BINARY_DIR}/arduino-emulator)
file(GLOB WEB_SRC "${CMAKE_SOURCE_DIR}/../../src/*.cpp")
add_library(espasyncwebserver STATIC ${WEB_SRC})

target_compile_definitions(espasyncwebserver PUBLIC HOST ARDUINO=10813)
target_include_directories(espasyncwebserver PUBLIC
${CMAKE_SOURCE_DIR}/../../src
${CMAKE_SOURCE_DIR}/../../.ci/asynctcp/src
${CMAKE_SOURCE_DIR}/../../.ci/arduino-esp32/libraries/FS/src
${CMAKE_SOURCE_DIR}/../../.ci/lwip/src/include
${CMAKE_SOURCE_DIR}/../../.ci/lwip-contrib/ports/unix/port/include
${CMAKE_SOURCE_DIR}/host_config
)
target_link_libraries(espasyncwebserver PUBLIC arduino_emulator)
3 changes: 3 additions & 0 deletions examples/arduino_emulator/host_config/freertos/semphr.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

typedef void *SemaphoreHandle_t;
11 changes: 11 additions & 0 deletions examples/arduino_emulator/host_config/lwipopts.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#define NO_SYS 1
#define LWIP_SOCKET 0
#define LWIP_NETCONN 0
#define LWIP_TCP 1
#define LWIP_IPV6 1

#if defined(__linux__) && !defined(LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS)
#define LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS 1
#endif
3 changes: 3 additions & 0 deletions examples/arduino_emulator/host_config/sdkconfig.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

#define LWIP_IPV6 1
45 changes: 11 additions & 34 deletions src/AsyncEventSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,19 +191,15 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A
}

AsyncEventSourceClient::~AsyncEventSourceClient() {
#ifdef ESP32
// Protect message queue access (size checks and modifications) which is not thread-safe.
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
asyncsrv::lock_guard_type lock(_lockmq);
_messageQueue.clear();
close();
}

bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
#ifdef ESP32
// Protect message queue access (size checks and modifications) which is not thread-safe.
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
asyncsrv::lock_guard_type lock(_lockmq);

if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
async_ws_log_w("Event message queue overflow: discard message");
Expand Down Expand Up @@ -231,10 +227,8 @@ bool AsyncEventSourceClient::_queueMessage(const char *message, size_t len) {
}

bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
#ifdef ESP32
// Protect message queue access (size checks and modifications) which is not thread-safe.
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
asyncsrv::lock_guard_type lock(_lockmq);

if (_messageQueue.size() >= SSE_MAX_QUEUED_MESSAGES) {
async_ws_log_w("Event message queue overflow: discard message");
Expand All @@ -261,10 +255,8 @@ bool AsyncEventSourceClient::_queueMessage(AsyncEvent_SharedData_t &&msg) {
}

void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t time __attribute__((unused))) {
#ifdef ESP32
// Protect message queue access (size checks and modifications) which is not thread-safe.
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
asyncsrv::lock_guard_type lock(_lockmq);

// adjust in-flight len
if (len < _inflight) {
Expand All @@ -289,10 +281,8 @@ void AsyncEventSourceClient::_onAck(size_t len __attribute__((unused)), uint32_t
}

void AsyncEventSourceClient::_onPoll() {
#ifdef ESP32
// Protect message queue access (size checks and modifications) which is not thread-safe.
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
asyncsrv::lock_guard_type lock(_lockmq);
if (_messageQueue.size()) {
_runQueue();
}
Expand Down Expand Up @@ -373,10 +363,7 @@ void AsyncEventSource::_addClient(AsyncEventSourceClient *client) {
_connectcb(client);
}

#ifdef ESP32
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif

asyncsrv::lock_guard_type lock(_client_queue_lock);
_clients.emplace_back(client);

_adjust_inflight_window();
Expand All @@ -386,9 +373,7 @@ void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient *client) {
if (_disconnectcb) {
_disconnectcb(client);
}
#ifdef ESP32
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
asyncsrv::lock_guard_type lock(_client_queue_lock);
for (auto i = _clients.begin(); i != _clients.end(); ++i) {
if (i->get() == client) {
_clients.erase(i);
Expand All @@ -402,9 +387,7 @@ void AsyncEventSource::close() {
// While the whole loop is not done, the linked list is locked and so the
// iterator should remain valid even when AsyncEventSource::_handleDisconnect()
// is called very early
#ifdef ESP32
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
asyncsrv::lock_guard_type lock(_client_queue_lock);
for (const auto &c : _clients) {
if (c->connected()) {
/**
Expand All @@ -421,9 +404,7 @@ void AsyncEventSource::close() {
size_t AsyncEventSource::avgPacketsWaiting() const {
size_t aql = 0;
uint32_t nConnectedClients = 0;
#ifdef ESP32
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
asyncsrv::lock_guard_type lock(_client_queue_lock);
for (const auto &c : _clients) {
if (c->connected()) {
aql += c->packetsWaiting();
Expand All @@ -435,9 +416,7 @@ size_t AsyncEventSource::avgPacketsWaiting() const {

AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) {
AsyncEvent_SharedData_t shared_msg = std::make_shared<String>(generateEventMessage(message, event, id, reconnect));
#ifdef ESP32
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
asyncsrv::lock_guard_type lock(_client_queue_lock);
size_t hits = 0;
size_t miss = 0;
for (const auto &c : _clients) {
Expand All @@ -453,9 +432,7 @@ AsyncEventSource::SendStatus AsyncEventSource::send(const char *message, const c
}

size_t AsyncEventSource::count() const {
#ifdef ESP32
std::lock_guard<std::recursive_mutex> lock(_client_queue_lock);
#endif
asyncsrv::lock_guard_type lock(_client_queue_lock);
size_t n_clients{0};
for (const auto &i : _clients) {
if (i->connected()) {
Expand Down
18 changes: 6 additions & 12 deletions src/AsyncEventSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include <Arduino.h>

#if defined(ESP32) || defined(LIBRETINY)
#if defined(ESP32) || defined(LIBRETINY) || defined(HOST)
#include <AsyncTCP.h>
#ifdef LIBRETINY
#ifdef round
Expand Down Expand Up @@ -136,9 +136,7 @@ class AsyncEventSourceClient {
size_t _inflight{0}; // num of unacknowledged bytes that has been written to socket buffer
size_t _max_inflight{SSE_MAX_INFLIGH}; // max num of unacknowledged bytes that could be written to socket buffer
std::list<AsyncEventSourceMessage> _messageQueue;
#ifdef ESP32
mutable std::recursive_mutex _lockmq;
#endif
mutable asyncsrv::mutex_type _lockmq;
bool _queueMessage(const char *message, size_t len);
bool _queueMessage(AsyncEvent_SharedData_t &&msg);
void _runQueue();
Expand Down Expand Up @@ -205,9 +203,7 @@ class AsyncEventSourceClient {
return _lastId;
}
size_t packetsWaiting() const {
#ifdef ESP32
std::lock_guard<std::recursive_mutex> lock(_lockmq);
#endif
asyncsrv::lock_guard_type lock(_lockmq);
return _messageQueue.size();
};

Expand Down Expand Up @@ -245,11 +241,9 @@ class AsyncEventSource : public AsyncWebHandler {
private:
String _url;
std::list<std::unique_ptr<AsyncEventSourceClient>> _clients;
#ifdef ESP32
// Same as for individual messages, protect mutations of _clients list
// since simultaneous access from different tasks is possible
mutable std::recursive_mutex _client_queue_lock;
#endif
mutable asyncsrv::mutex_type _client_queue_lock;
ArEventHandlerFunction _connectcb = nullptr;
ArEventHandlerFunction _disconnectcb = nullptr;

Expand Down Expand Up @@ -331,11 +325,11 @@ class AsyncEventSourceResponse : public AsyncWebServerResponse {

public:
AsyncEventSourceResponse(AsyncEventSource *server);
void _respond(AsyncWebServerRequest *request);
void _respond(AsyncWebServerRequest *request) override;
size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time) override {
return 0;
};
bool _sourceValid() const {
bool _sourceValid() const override {
return true;
}
};
4 changes: 2 additions & 2 deletions src/AsyncJson.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ class AsyncCallbackJsonWebHandler : public AsyncWebHandler {
bool canHandle(AsyncWebServerRequest *request) const final;
void handleRequest(AsyncWebServerRequest *request) final;
void handleUpload(
__unused AsyncWebServerRequest *request, __unused const String &filename, __unused size_t index, __unused uint8_t *data, __unused size_t len,
__unused bool final
__asyncws_unused AsyncWebServerRequest *request, __asyncws_unused const String &filename, __asyncws_unused size_t index, __asyncws_unused uint8_t *data,
__asyncws_unused size_t len, __asyncws_unused bool final
) final {}
void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) final;
bool isRequestHandlerTrivial() const final {
Expand Down
58 changes: 58 additions & 0 deletions src/AsyncWebServerLogging.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,64 @@
#define async_ws_log_d(format, ...) log_d(format, ##__VA_ARGS__)
#define async_ws_log_v(format, ...) log_v(format, ##__VA_ARGS__)

#elif defined(HOST)
Comment thread
mathieucarbou marked this conversation as resolved.
#include <cstdio>
// Arduino-Emulator has Serial, but it does not have Serial.printf, which
// is not a member of the Arduino Core API Print class.

// define log levels
#define ASYNC_WS_LOG_NONE 0 /*!< No log output */
#define ASYNC_WS_LOG_ERROR 1 /*!< Critical errors, software module can not recover on its own */
#define ASYNC_WS_LOG_WARN 2 /*!< Error conditions from which recovery measures have been taken */
#define ASYNC_WS_LOG_INFO 3 /*!< Information messages which describe normal flow of events */
#define ASYNC_WS_LOG_DEBUG 4 /*!< Extra information which is not necessary for normal use (values, pointers, sizes, etc). */
#define ASYNC_WS_LOG_VERBOSE 5 /*!< Verbose information for debugging purposes */
#define ASYNC_WS_LOG_MAX 6 /*!< Number of levels supported */
// set default log level
#ifndef ASYNCWEBSERVER_LOG_LEVEL
#define ASYNCWEBSERVER_LOG_LEVEL ASYNC_WS_LOG_INFO
#endif
// error
#if ASYNCWEBSERVER_LOG_LEVEL >= ASYNC_WS_LOG_ERROR
#define async_ws_log_e(format, ...) \
::printf("E async_ws %s() %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
::putchar('\n');
#else
#define async_ws_log_e(format, ...)
#endif
// warn
#if ASYNCWEBSERVER_LOG_LEVEL >= ASYNC_WS_LOG_WARN
#define async_ws_log_w(format, ...) \
::printf("W async_ws %s() %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
::putchar('\n');
#else
#define async_ws_log_w(format, ...)
#endif
// info
#if ASYNCWEBSERVER_LOG_LEVEL >= ASYNC_WS_LOG_INFO
#define async_ws_log_i(format, ...) \
::printf("I async_ws %s() %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
::putchar('\n');
#else
#define async_ws_log_i(format, ...)
#endif
// debug
#if ASYNCWEBSERVER_LOG_LEVEL >= ASYNC_WS_LOG_DEBUG
#define async_ws_log_d(format, ...) \
::printf("D async_ws %s() %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
::putchar('\n');
#else
#define async_ws_log_d(format, ...)
#endif
// verbose
#if ASYNCWEBSERVER_LOG_LEVEL >= ASYNC_WS_LOG_VERBOSE
#define async_ws_log_v(format, ...) \
::printf("V async_ws %s() %d: " format, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
::putchar('\n');
#else
#define async_ws_log_v(format, ...)
Comment thread
mathieucarbou marked this conversation as resolved.
#endif

/**
* Raspberry Pi Pico specific configurations
*/
Expand Down
Loading
Loading