Compare commits

...

79 Commits

Author SHA1 Message Date
david-cermak
7a203cf085 Merge pull request #921 from david-cermak/bump/console_ping
bump(console): 1.1.0 -> 1.2.0
2025-10-29 17:04:32 +01:00
David Cermak
2e269640c6 bump(console): 1.1.0 -> 1.2.0
1.2.0
Features
- Add support for interface argument (90ddb04e)
2025-10-27 14:34:59 +01:00
david-cermak
c078c36361 Merge pull request #886 from david-cermak/feat/console_ping_interface
[console_ping]: Add support for interface argument
2025-10-24 15:17:14 +02:00
David Cermak
90ddb04e53 feat(console_ping): Add support for interface argument 2025-10-24 15:12:05 +02:00
david-cermak
cee3bdea9d Merge pull request #822 from tanyanquan/feat/support_null_value_txt
feat(mdns): support null value for boolean txt records
2025-10-24 14:05:47 +02:00
Tan Yan Quan
fa96de3bd7 feat(mdns): support null value for boolean txt records 2025-10-24 16:12:10 +08:00
bryghtlabs-richard
18f0d02806 feat(websocket): add WEBSOCKET_EVENT_HEADER_RECEIVED (#827)
Send a new event for each HTTP header-line received.

Depends on https://github.com/espressif/esp-idf/pull/16119
Closes https://github.com/espressif/esp-protocols/issues/715
2025-10-22 18:25:31 -03:00
david-cermak
bfa604b5f6 Merge pull request #904 from david-cermak/feat/add_tests_v1.9
[mdns]: Add tests for recent feats/fixes
2025-10-22 11:07:59 +02:00
David Cermak
92a31187ff fix(ci): Use python venv for mdns target tests 2025-10-21 17:16:21 +02:00
David Cermak
0197c994ee fix(mdns): Temporary fix for build issues on IDF master 2025-10-21 17:11:46 +02:00
David Cermak
487a746d14 fix(mdns): Add tests for delegated answers 2025-10-21 17:11:46 +02:00
David Cermak
af6bb1b5ee fix(mdns): Add fuzzing into mdns CI 2025-10-21 17:11:46 +02:00
Suren Gabrielyan
f5e62e83e9 Merge pull request #902 from gabsuren/docs/readme_update
[examples]: enhance example with docs, pytest setup, and standalone test server(IDFGH-16585)
2025-10-21 13:07:38 +04:00
surengab
cad527d2fc feat(examples): enhance example with docs, pytest setup, and standalone test server
- Add comprehensive README with TOC and quick start
    - Add pytest setup and certificate generation scripts
    - Add standalone WebSocket test server with TLS support
    - Add troubleshooting and multiple testing approaches
2025-10-21 12:17:00 +04:00
david-cermak
16cc2dcffb Merge pull request #914 from david-cermak/fix/modem_forwardport_patch
[modem]: Forward-port 1.4.1 patches
2025-10-21 07:14:47 +02:00
david-cermak
d622e41a54 Merge pull request #915 from david-cermak/fix/target_tests
[websockets]: Fix target tests
2025-10-21 07:14:28 +02:00
glmfe
ca6e39aac7 ci(common): Ignore ethernet warning. 2025-10-20 16:27:57 +02:00
David Cermak
e45944e143 fix(websocket): Use venv for running target tests 2025-10-20 16:27:22 +02:00
David Cermak
fe657b9737 fix(modem): Ignore more build warnings 2025-10-20 09:35:00 +02:00
David Cermak
453be4cd79 fix(modem): Fix modem console dependencies 2025-10-20 09:34:15 +02:00
David Cermak
018ba58ec5 fix(modem): Address build issues 2025-10-20 09:30:47 +02:00
David Cermak
67c682d911 fix(modem): Fix driver dependency issue on v6.0 2025-10-20 09:30:41 +02:00
Suren Gabrielyan
91915ce1c7 Merge pull request #901 from bryghtlabs-richard/fix/websocket-asprintf-ret-checks
fix(websocket): fix relying on asprintf() to NULL strp on failure (IDFGH-16595)
2025-10-15 14:27:56 +04:00
david-cermak
ae052e5507 Merge pull request #905 from david-cermak/fix/eppp_netif_rx_err
[eppp]: Fix tun netif to (optionally) return errors
2025-10-09 14:03:03 +02:00
David Cermak
44524f5de0 bump(eppp): 1.1.2 -> 1.1.3
1.1.3
Bug Fixes
- Fix test dependency issue on driver (1ace92c2)
- Fix tun netif to (optionally) return errors (7a6cf0f9)
2025-10-09 13:04:46 +02:00
David Cermak
1ace92c279 fix(eppp): Fix test dependency issue on driver 2025-10-09 13:03:06 +02:00
David Cermak
7a6cf0f9c0 fix(eppp): Fix tun netif to (optionally) return errors 2025-10-08 16:20:18 +02:00
Richard Allen
54eb002758 fix(websocket): fix relying on asprintf() to NULL strp on failure
asprintf()'s return value:
    When successful, these functions return the number of bytes
    printed, just like sprintf(3).  On error, -1 is returned, errno is
    set to indicate the error, and the contents of strp are undefined.

Fixes the following:
    error: ignoring return value of ‘asprintf’ declared with attribute ‘warn_unused_result’ [-Werror=unused-result]
2025-10-03 08:44:00 -05:00
Guilherme Alves Ferreira
318e41b3c3 fix(lws): Update websocket Echo server (#894)
* fix(lws): Update websocket Echo server

- Update Websocket Echo Server, "wss://echo.websocket.events" is no longer available.
2025-10-03 06:17:11 -03:00
david-cermak
6f6237a0cc Merge pull request #813 from tanyanquan/master
fix(mdns): put srv/txt records in additional section for ptr queries
2025-10-01 13:59:03 +02:00
Guilherme Alves Ferreira
18faeb3dfa fix(websocket): Update Remaining Websocket Echo Server (#893) 2025-10-01 07:52:42 -03:00
Guilherme Alves Ferreira
296123c14e Merge pull request #896 from glmfe/fix/ignore-ci-build-warnings
ci(common): Ignore deprecated idf-build-apps parameters
2025-09-30 10:50:22 -03:00
glmfe
4e178f06bd ci(common): Ignore deprecated idf-build-apps parameters
- Added deprecated flash, sign and extract_public_key to global warning list file
2025-09-29 08:21:50 -03:00
Guilherme Alves Ferreira
5ab7e8327e Merge pull request #729 from DazeTechnology/fix/websocket_long_stop
fix(websocket): avoid long stop time when waiting to auto-reconnect (IDFGH-14393)
2025-09-25 10:12:39 -03:00
Paolo Pasinetti
91e7e9fa08 chore(lws): fixed formatting 2025-09-19 15:42:33 +02:00
Paolo Pasinetti
ff5d6021be chore(asio): Fixed formatting 2025-09-19 15:42:33 +02:00
Paolo Pasinetti
2432e41dcb fix(websocket): avoid long stopping time when waiting to auto-reconnect
This commit fixes an issue that occurred when auto-reconnection is enabled,
and the client is disconnected from the server. In this situation, if the
esp_websocket_client_stop() method is called, the caller could remain stuck
waiting for a maximum time equal to half of wait_timeout_ms.

This fix allows the esp_websocket_client_task to be woken up when it is
waiting to reconnect, so it can be closed promptly when requested.
2025-09-19 15:42:33 +02:00
Guilherme Alves Ferreira
870ac91db7 Merge pull request #890 from glmfe/fix/update-ws-echo-server
fix(websocket): Update Websocket Echo Server
2025-09-19 08:35:01 -03:00
glmfe
94bd5b074a fix(websocket): Update Websocket Echo Server 2025-09-19 07:44:12 -03:00
david-cermak
db7baaffba Merge pull request #887 from david-cermak/fix/eppp_uart_deps
[eppp]: Fix uart driver deps with new IDF
2025-09-15 13:41:53 +02:00
David Cermak
1ea93a866b bump(eppp): 1.1.1 -> 1.1.2
1.1.2
Bug Fixes
- Update uart driver deps per IDF > v5.3 (92e14607)
2025-09-15 12:55:14 +02:00
David Cermak
92e1460721 fix(eppp): Update uart driver deps per IDF > v5.3 2025-09-15 12:55:08 +02:00
david-cermak
858d38b55f Merge pull request #885 from david-cermak/fix/eppp_uart_channels
[eppp]: Fix getting context for channel API
2025-09-09 17:03:00 +02:00
David Cermak
6428e68c8e bump(eppp): 1.1.0 -> 1.1.1
1.1.1
Bug Fixes
- Fix getting context for channel API (94563cdc)
- Cover more combinations in build tests (e0b8de8f)
2025-09-09 16:18:11 +02:00
David Cermak
94563cdc1f fix(eppp): Fix getting context for channel API 2025-09-09 13:42:18 +02:00
David Cermak
e0b8de8f38 fix(eppp): Cover more combinations in build tests 2025-09-09 13:42:18 +02:00
Guilherme Alves Ferreira
34b6681576 Merge pull request #771 from glmfe/feat/add-http-redir
feat(websocket): Add websocket HTTP redirect
2025-09-03 12:31:20 -03:00
david-cermak
1f7828f629 Merge pull request #875 from david-cermak/update/eppp_v1.1
[eppp]: Bump 1.0.1 -> 1.1.0
2025-09-03 16:03:05 +02:00
david-cermak
e74db36ebb Merge pull request #881 from david-cermak/fix/minor_ci_fixes
Fixup common CI issues with v6.0 idf-tools
2025-09-03 13:16:49 +02:00
David Cermak
f8d2ed2eed fix(tls_cxx): Remove unnnecessary warning ignore 2025-09-03 12:07:21 +02:00
David Cermak
8bba3a9734 fix(mdns): Host test to use hw_support include dir 2025-09-03 11:37:47 +02:00
David Cermak
9fbb6e6d0a fix(mosq): Fix the version check 2025-09-03 10:20:53 +02:00
David Cermak
e599cd826b fix(common): Fixup common CI issues with v6.0 2025-09-03 10:06:13 +02:00
david-cermak
e2d36b4fbd Merge pull request #879 from david-cermak/fix/modem_sim_napt
[modem_sim]: Enable LWIP-NAPT by default in modem simulator
2025-09-03 09:27:41 +02:00
david-cermak
82a784baf4 Merge pull request #880 from david-cermak/fix/modem_ci
[modem]: Fix recent CI issues
2025-09-02 18:01:05 +02:00
David Cermak
15140e04c6 fix(modem): Fix CI build issues with IDFv6.0
* Fix C++ console struct init in examples
* VFS deprecation in tests
* Add conditional defines over wakeup-cause
2025-09-02 15:47:00 +02:00
glmfe
ce1560acb1 feat(websocket): Add websocket HTTP redirect
- Handle 301 status (moved permanently) and redirect the connection to the new host.
2025-09-02 09:52:20 -03:00
David Cermak
e1be830fb7 fix(modem_sim): Enable LWIP-NAPT by default in modem simulator
As this mode is used by PPP clients
2025-09-01 18:47:47 +02:00
david-cermak
b7cfa31a0b Merge pull request #878 from david-cermak/bump/mosq_2.0.20.4
bump(mosq): 2.0.20~3 -> 2.0.20~4
2025-09-01 11:54:20 +02:00
David Cermak
845a1e2ef8 fix(mosq): Skip serverless example build on master
Due to esp-peer's long list dependencies, which are usually based
from stable IDF releases
2025-09-01 10:08:11 +02:00
David Cermak
6ae7a4d2ba bump(mosq): 2.0.20~3 -> 2.0.20~4
2.0.20~4
Features
- Update brokerless example to work with esp-peer (76e45f72)
Bug Fixes
- drop newlib from PRIV_REQUIRES (6318022c)
- Make esp-peer build optional (03df9ae9)
- Fix esp_webRTC deprecation with new FreeRTOS (78ae2559)
2025-09-01 09:55:34 +02:00
david-cermak
4d52982a69 Merge pull request #876 from Lapshin/master
fix: drop newlib from PRIV_REQUIRES
2025-09-01 09:27:09 +02:00
Alexey Lapshin
6318022cda fix: drop newlib from PRIV_REQUIRES 2025-08-28 16:18:22 +07:00
David Cermak
13591ade3d bump(eppp): 1.0.1 -> 1.1.0
1.1.0
Features
- Add support for UART flow control (cd57f1bb, #870)
Bug Fixes
- Fix SPI transport to allow already init GPIO ISR (497ee2d6, #868)
- Fix stack-overflow in ping task for TUN netif (b2568a3d, #867)
Updated
- ci(common): Update test component dir for IDFv6.0 (18418c83)
2025-08-27 15:11:15 +02:00
david-cermak
d09b302b9e Merge pull request #872 from david-cermak/fix/eppp_minor_fixes
[eppp]: Minor fixes v1.0
2025-08-27 08:57:25 +02:00
david-cermak
39e2333adb Merge pull request #871 from david-cermak/fix/modem_sim_docs
[modem_sim]: Add initial modem simulator docs
2025-08-26 11:00:46 +02:00
David Cermak
cd57f1bb13 feat(eppp): Add support for UART flow control
Closes https://github.com/espressif/esp-protocols/issues/870
2025-08-25 16:30:57 +02:00
David Cermak
497ee2d6d4 fix(eppp): Fix SPI transport to allow already init GPIO ISR
Closes https://github.com/espressif/esp-protocols/issues/868
2025-08-25 15:23:07 +02:00
David Cermak
b2568a3d83 fix(eppp): Fix stack-overflow in ping task for TUN netif
Closes https://github.com/espressif/esp-protocols/issues/867
2025-08-25 14:51:59 +02:00
david-cermak
58a21e39d0 Merge pull request #837 from david-cermak/feat/modem_tcp_client_extend
[modem]: Extend tcp-client example
2025-08-25 08:53:51 +02:00
David Cermak
9c7ee07755 feat(modem_sim): Add initial modem simulator docs 2025-08-25 08:52:48 +02:00
David Cermak
14d3cb6bd1 fix(modem): Add support for ESP-AT based tcp-client example 2025-08-25 06:38:57 +02:00
david-cermak
ccdb45ee94 Merge pull request #869 from david-cermak/fix/test_build_v6.0
fix(CI): Fix test component dir after IDFv6.0 update
2025-08-22 15:01:59 +02:00
David Cermak
18418c83ff ci(common): Update test component dir for IDFv6.0
Update websocket, mdns and eppp test apps
2025-08-22 14:23:43 +02:00
david-cermak
ecb7dae502 Merge pull request #863 from david-cermak/feat/modem_without_ppp
[modem]: Support esp-modem use without PPP
2025-08-21 12:46:29 +02:00
david-cermak
958ff6a584 Merge pull request #866 from david-cermak/fix/modem_sim
[modem_sim]: ESP-AT build failure on v5.4
2025-08-18 17:12:53 +02:00
David Cermak
5ea83be7ce fix(modem_sim): Fix build step 2025-08-18 16:35:07 +02:00
David Cermak
858f85706d feat(modem): Support esp-modem use without PPP
Closes https://github.com/espressif/esp-protocols/issues/851
2025-08-14 17:29:50 +02:00
Tan Yan Quan
b7b8c5dbd7 fix(mdns): put srv/txt records in additional section for ptr queries 2025-05-08 19:09:52 +08:00
91 changed files with 1589 additions and 255 deletions

View File

@@ -33,6 +33,9 @@ jobs:
shell: bash
working-directory: ${{matrix.test.path}}
run: |
if [[ "${{ matrix.idf_ver }}" == "release-v5.3" || "${{ matrix.idf_ver }}" == "release-v5.4" ]]; then
export EXPECTED_WARNING="unknown kconfig symbol 'LWIP_USE_ESP_GETADDRINFO'"
fi
. ${IDF_PATH}/export.sh
pip install idf-component-manager idf-build-apps --upgrade
python ../../../ci/build_apps.py ./${{ matrix.test.app }} --target ${{ matrix.idf_target }} -vv --preserve-all --pytest-app

View File

@@ -24,6 +24,11 @@ jobs:
shell: bash
run: |
. ${IDF_PATH}/export.sh
if [[ "${{ matrix.idf_ver }}" == "latest" ]]; then
export EXPECTED_WARNING="warning: unknown kconfig symbol 'EXAMPLE_ETH_PHY_IP101'"
else
export EXPECTED_WARNING="warning: unknown kconfig symbol 'EXAMPLE_ETH_PHY_GENERIC'"
fi
python -m pip install idf-build-apps
# Build default configs for all targets
python ./ci/build_apps.py components/mdns/${{ matrix.test.path }} -r default -d
@@ -71,6 +76,22 @@ jobs:
- name: Run ${{ matrix.test.app }} application on ${{ matrix.idf_target }}
working-directory: components/mdns/${{ matrix.test.path }}
run: |
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv init -)"
if ! pyenv versions --bare | grep -q '^3\.12\.6$'; then
echo "Installing Python 3.12.6..."
pyenv install -s 3.12.6
fi
if ! pyenv virtualenvs --bare | grep -q '^myenv$'; then
echo "Creating pyenv virtualenv 'myenv'..."
pyenv virtualenv 3.12.6 myenv
fi
pyenv activate myenv
python --version
pip install --prefer-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code esptool
pip install --extra-index-url https://dl.espressif.com/pypi/ -r $GITHUB_WORKSPACE/ci/requirements.txt
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do
rm -rf build sdkconfig.defaults

View File

@@ -68,3 +68,43 @@ jobs:
diff -q $file /tmp/$file || exit 1
echo "OK"
done
fuzz_test:
if: contains(github.event.pull_request.labels.*.name, 'mdns-fuzz') || github.event_name == 'push'
name: Fuzzer tests for mdns lib
strategy:
matrix:
idf_ver: ["latest"]
runs-on: ubuntu-22.04
container: aflplusplus/aflplusplus
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v4
- name: Checkout ESP-IDF
uses: actions/checkout@v4
with:
repository: espressif/esp-idf
path: idf
submodules: recursive
- name: Install Necessary Libs
run: |
apt-get update -y
apt-get install -y libbsd-dev
- name: Run AFL++
shell: bash
run: |
export IDF_PATH=$GITHUB_WORKSPACE/idf
cd components/mdns/tests/test_afl_fuzz_host/
make fuzz
- name: Upload Crash Artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: fuzz-crashes
path: components/mdns/tests/test_afl_fuzz_host/out/default/crashes.tar.gz
if-no-files-found: ignore

View File

@@ -15,11 +15,6 @@ jobs:
matrix:
idf_ver: ["latest", "release-v5.0", "release-v5.1", "release-v5.2", "release-v5.3", "release-v5.4"]
example: ["pppos_client", "modem_console", "modem_tcp_client", "ap_to_pppos", "simple_cmux_client"]
include:
- idf_ver: "release-v5.0"
example: "simple_cmux_client"
warning: "Warning: The smallest app partition is nearly full"
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
steps:
@@ -30,9 +25,9 @@ jobs:
- if: ${{ matrix.skip_config }}
run: rm -f $GITHUB_WORKSPACE/protocols/components/esp_modem/examples/${{ matrix.example }}/sdkconfig.ci.${{ matrix.skip_config }}*
- name: Build ${{ matrix.example }} with IDF-${{ matrix.idf_ver }}
env:
EXPECTED_WARNING: ${{ matrix.warning }}
shell: bash
env:
EXPECTED_WARNING: "Warning: The smallest app partition is nearly full\nwarning: unknown kconfig symbol 'ESP32_PANIC_PRINT_HALT'"
run: |
. ${IDF_PATH}/export.sh
python -m pip install idf-build-apps

View File

@@ -11,17 +11,21 @@ jobs:
build_modem_sim:
if: contains(github.event.pull_request.labels.*.name, 'modem_sim') || github.event_name == 'push'
name: Build
strategy:
matrix:
idf_ver: ["release-v5.4"]
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
runs-on: ubuntu-latest
steps:
- name: Checkout esp-protocols
uses: actions/checkout@v3
- name: Checkout idf
uses: actions/checkout@v3
with:
repository: espressif/esp-idf
ref: 8ad0d3d8f2faab752635bee36070313c47c07a13
path: idf
- name: Build ESP-AT with IDF-${{ matrix.idf_ver }}
shell: bash
run: |
export IDF_PATH=$GITHUB_WORKSPACE/idf
${IDF_PATH}/install.sh
cd common_components/modem_sim
./install.sh
source export.sh

View File

@@ -16,12 +16,11 @@ jobs:
idf_ver: ["latest", "release-v5.5", "release-v5.4", "release-v5.3", "release-v5.2", "release-v5.1"]
example: ["broker", "serverless_mqtt"]
exclude:
# serverless_mqtt is not supported on v5.1 and master (esp-peer dependency)
- idf_ver: "release-v5.1"
example: "serverless_mqtt" # serverless_mqtt is not supported due to esp-peer
include:
example: "serverless_mqtt"
- idf_ver: "latest"
example: "serverless_mqtt"
warning: "'octal_mode' is deprecated"
runs-on: ubuntu-22.04
container: espressif/idf:${{ matrix.idf_ver }}
@@ -35,8 +34,6 @@ jobs:
with:
submodules: recursive
- name: Build with IDF-${{ matrix.idf_ver }}
env:
EXPECTED_WARNING: ${{ matrix.warning }}
shell: bash
run: |
. ${IDF_PATH}/export.sh

View File

@@ -25,9 +25,6 @@ jobs:
- name: Build ${{ matrix.test.app }} with IDF-${{ matrix.idf_ver }}
shell: bash
run: |
if [[ "${{ matrix.idf_ver }}" != "latest" ]]; then
export EXPECTED_WARNING="unknown kconfig symbol 'MBEDTLS_SSL_COOKIE_C'"
fi
. ${IDF_PATH}/export.sh
pip install idf-component-manager idf-build-apps --upgrade
python ./ci/build_apps.py ./components/mbedtls_cxx/${{ matrix.test.path }} -vv --preserve-all

View File

@@ -65,14 +65,27 @@ jobs:
with:
name: websocket_bin_esp32_${{ matrix.idf_ver }}_${{ matrix.test.app }}
path: ${{ env.TEST_DIR }}/ci/
- name: Install Python packages
- name: Run Example Test on target
working-directory: ${{ env.TEST_DIR }}
env:
PIP_EXTRA_INDEX_URL: "https://www.piwheels.org/simple"
run: |
pip install --only-binary cryptography --extra-index-url https://dl.espressif.com/pypi/ -r $GITHUB_WORKSPACE/ci/requirements.txt
- name: Run Example Test on target
working-directory: ${{ env.TEST_DIR }}
run: |
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv init -)"
if ! pyenv versions --bare | grep -q '^3\.12\.6$'; then
echo "Installing Python 3.12.6..."
pyenv install -s 3.12.6
fi
if ! pyenv virtualenvs --bare | grep -q '^myenv$'; then
echo "Creating pyenv virtualenv 'myenv'..."
pyenv virtualenv 3.12.6 myenv
fi
pyenv activate myenv
python --version
pip install --prefer-binary cryptography pytest-embedded pytest-embedded-serial-esp pytest-embedded-idf pytest-custom_exit_code esptool
pip install --extra-index-url https://dl.espressif.com/pypi/ -r $GITHUB_WORKSPACE/ci/requirements.txt
unzip ci/artifacts.zip -d ci
for dir in `ls -d ci/build_*`; do
rm -rf build sdkconfig.defaults

View File

@@ -56,7 +56,7 @@ if __name__ == '__main__':
build_dir='build_@t_@w',
config_rules_str=args.rules,
build_log_filename='build_log.txt',
size_json_filename='size.json' if not args.linux else None,
size_json_filename=None,
check_warnings=True,
manifest_files=args.manifests,
default_build_targets=SUPPORTED_TARGETS,

View File

@@ -1 +1,7 @@
DeprecationWarning: pkg_resources is deprecated as an API
Warning: Deprecated: Option '--flash_size' is deprecated. Use '--flash-size' instead.
Warning: Deprecated: Option '--flash_mode' is deprecated. Use '--flash-mode' instead.
Warning: Deprecated: Option '--flash_freq' is deprecated. Use '--flash-freq' instead.
Warning: Deprecated: Command 'sign_data' is deprecated. Use 'sign-data' instead.
Warning: Deprecated: Command 'extract_public_key' is deprecated. Use 'extract-public-key' instead.
warning: unknown kconfig symbol 'EXAMPLE_ETH_PHY_IP101'

View File

@@ -6,3 +6,4 @@ dpkt
pytest
idf_build_apps
netifaces
esptool>=5.1.0

View File

@@ -0,0 +1,51 @@
# Modem Simulator Component
A Wi-Fi modem simulator that extends ESP-AT with PPP server capabilities, turning ESP32 into a fully functional Wi-Fi modem. Perfect for testing AT commands and PPP connections without real hardware dependencies.
## What it does
- Extends ESP-AT firmware with PPP server functionality
- Provides DATA mode for raw IP communication
- Enables existing communication stacks (MQTT, HTTP, custom protocols) to work over Wi-Fi
- Ideal for testing ESP-Modem library and CI reliability
## Quick Start
```bash
cd common_components/modem_sim
./install.sh
source export.sh
idf.py build
```
## Custom Platform/Module
```bash
./install.sh PLATFORM_ESP32S3 WROOM-32
```
## Configuration
The `sdkconfig.defaults` includes:
- Wi-Fi and Bluetooth enabled
- PPP server support
- AT commands for HTTP/MQTT
- 4MB flash configuration
## Project Structure
```
modem_sim/
├── install.sh # Installation script
├── export.sh # Environment setup
├── sdkconfig.defaults # Default configuration
├── pppd_cmd/ # Custom PPP commands
└── modem_sim_esp32/ # Generated ESP-AT build
```
## Use Cases
- Testing ESP-Modem library without real hardware
- Quick Wi-Fi connectivity for existing communication stacks
- CI/CD testing with reliable modem simulation
- Development and debugging of AT command implementations

View File

@@ -75,3 +75,5 @@ CONFIG_AT_HTTP_COMMAND_SUPPORT=y
CONFIG_AT_BLE_COMMAND_SUPPORT=n
CONFIG_AT_BLE_HID_COMMAND_SUPPORT=n
CONFIG_AT_BLUFI_COMMAND_SUPPORT=n
CONFIG_LWIP_IP_FORWARD=y
CONFIG_LWIP_IPV4_NAPT=y

View File

@@ -28,7 +28,7 @@ posix_event::posix_event()
}
} // namespace asio::detail
extern "C" int pause (void)
extern "C" int pause(void)
{
while (true) {
::sleep(UINT_MAX);

View File

@@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(console): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py console_cmd_ping
tag_format: console_cmd_ping-v$version
version: 1.1.0
version: 1.2.0
version_files:
- idf_component.yml

View File

@@ -1,5 +1,11 @@
# Changelog
## [1.2.0](https://github.com/espressif/esp-protocols/commits/console_cmd_ping-v1.2.0)
### Features
- Add support for interface argument ([90ddb04e](https://github.com/espressif/esp-protocols/commit/90ddb04e))
## [1.1.0](https://github.com/espressif/esp-protocols/commits/console_cmd_ping-v1.1.0)
### Features

View File

@@ -48,7 +48,7 @@ For more details refer [IDF Component Manager](https://docs.espressif.com/projec
### ping:
```
ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] [-T <n>] <host>
ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] [-T <n>] [-I <n>] <host>
send ICMP ECHO_REQUEST to network hosts
-W, --timeout=<t> Time to wait for a response, in seconds
-i, --interval=<t> Wait interval seconds between sending each packet
@@ -56,6 +56,7 @@ ping [-W <t>] [-i <t>] [-s <n>] [-c <n>] [-Q <n>] [-T <n>] <host>
-c, --count=<n> Stop after sending count packets
-Q, --tos=<n> Set Type of Service related bits in IP datagrams
-T, --ttl=<n> Set Time to Live related bits in IP datagrams
-I, --interface=<n> Set Interface number 0=no-interface selected, >0 netif number + 1 (1 is usually 'lo0')
<host> Host address
getaddrinfo [-f <AF>] [-F <FLAGS>]... [-p <port>] <hostname>
@@ -121,8 +122,8 @@ getaddrinfo -f AF_INET -F AI_PASSIVE www.example.com
ping www.example.com
```
2. To specify additional options, such as timeout, interval, packet size, etc.:
2. To specify additional options, such as timeout, interval, packet size, interface, etc.:
```
ping -W 5 -i 1 -s 64 -c 4 -Q 0x10 -T 64 www.example.com
ping -W 5 -i 1 -s 64 -c 4 -Q 0x10 -T 64 -I 0 www.example.com
```

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -99,6 +99,7 @@ static struct {
struct arg_int *count;
struct arg_int *tos;
struct arg_int *ttl;
struct arg_int *interface;
struct arg_str *host;
struct arg_end *end;
} ping_args;
@@ -137,6 +138,10 @@ static int do_ping_cmd(int argc, char **argv)
config.ttl = (uint32_t)(ping_args.ttl->ival[0]);
}
if (ping_args.interface->count > 0) {
config.interface = (uint32_t)(ping_args.interface->ival[0]);
}
// parse IP address
struct sockaddr_in6 sock_addr6;
ip_addr_t target_addr = {0};
@@ -155,12 +160,12 @@ static int do_ping_cmd(int argc, char **argv)
}
if (res->ai_family == AF_INET) {
#if CONFIG_LWIP_IPV4
struct in_addr addr4 = ((struct sockaddr_in *) (res->ai_addr))->sin_addr;
struct in_addr addr4 = ((struct sockaddr_in *)(res->ai_addr))->sin_addr;
inet_addr_to_ip4addr(ip_2_ip4(&target_addr), &addr4);
#endif
} else {
#if CONFIG_LWIP_IPV6
struct in6_addr addr6 = ((struct sockaddr_in6 *) (res->ai_addr))->sin6_addr;
struct in6_addr addr6 = ((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr;
inet6_addr_to_ip6addr(ip_2_ip6(&target_addr), &addr6);
#endif
}
@@ -204,6 +209,7 @@ esp_err_t console_cmd_ping_register(void)
ping_args.count = arg_int0("c", "count", "<n>", "Stop after sending count packets");
ping_args.tos = arg_int0("Q", "tos", "<n>", "Set Type of Service related bits in IP datagrams");
ping_args.ttl = arg_int0("T", "ttl", "<n>", "Set Time to Live related bits in IP datagrams");
ping_args.interface = arg_int0("I", "interface", "<n>", "Set Interface number");
ping_args.host = arg_str1(NULL, NULL, "<host>", "Host address");
ping_args.end = arg_end(1);
const esp_console_cmd_t ping_cmd = {

View File

@@ -1,4 +1,4 @@
version: 1.1.0
version: 1.2.0
url: https://github.com/espressif/esp-protocols/tree/master/components/console_cmd_ping
description: The component provides a console where the 'ping' command can be executed.
dependencies:

View File

@@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(eppp): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py eppp_link
tag_format: eppp-v$version
version: 1.0.1
version: 1.1.3
version_files:
- idf_component.yml

View File

@@ -1,5 +1,40 @@
# Changelog
## [1.1.3](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.3)
### Bug Fixes
- Fix test dependency issue on driver ([1ace92c2](https://github.com/espressif/esp-protocols/commit/1ace92c2))
- Fix tun netif to (optionally) return errors ([7a6cf0f9](https://github.com/espressif/esp-protocols/commit/7a6cf0f9))
## [1.1.2](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.2)
### Bug Fixes
- Update uart driver deps per IDF > v5.3 ([92e14607](https://github.com/espressif/esp-protocols/commit/92e14607))
## [1.1.1](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.1)
### Bug Fixes
- Fix getting context for channel API ([94563cdc](https://github.com/espressif/esp-protocols/commit/94563cdc))
- Cover more combinations in build tests ([e0b8de8f](https://github.com/espressif/esp-protocols/commit/e0b8de8f))
## [1.1.0](https://github.com/espressif/esp-protocols/commits/eppp-v1.1.0)
### Features
- Add support for UART flow control ([cd57f1bb](https://github.com/espressif/esp-protocols/commit/cd57f1bb), [#870](https://github.com/espressif/esp-protocols/issues/870))
### Bug Fixes
- Fix SPI transport to allow already init GPIO ISR ([497ee2d6](https://github.com/espressif/esp-protocols/commit/497ee2d6), [#868](https://github.com/espressif/esp-protocols/issues/868))
- Fix stack-overflow in ping task for TUN netif ([b2568a3d](https://github.com/espressif/esp-protocols/commit/b2568a3d), [#867](https://github.com/espressif/esp-protocols/issues/867))
### Updated
- ci(common): Update test component dir for IDFv6.0 ([18418c83](https://github.com/espressif/esp-protocols/commit/18418c83))
## [1.0.1](https://github.com/espressif/esp-protocols/commits/eppp-v1.0.1)
### Bug Fixes

View File

@@ -1,5 +1,5 @@
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3")
set(driver_deps esp_driver_gpio esp_driver_spi)
set(driver_deps esp_driver_gpio esp_driver_spi esp_driver_uart esp_driver_sdio)
else()
set(driver_deps driver)
endif()

View File

@@ -17,9 +17,17 @@
#include "esp_check.h"
#include "esp_idf_version.h"
#if defined(CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS)
typedef esp_err_t esp_netif_recv_ret_t;
#define ESP_NETIF_OPTIONAL_RETURN_CODE(expr) expr
#else
typedef void esp_netif_recv_ret_t;
#define ESP_NETIF_OPTIONAL_RETURN_CODE(expr)
#endif // CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS
static const char *TAG = "eppp_tun_netif";
static void tun_input(void *h, void *buffer, unsigned int len, void *eb)
static esp_netif_recv_ret_t tun_input(void *h, void *buffer, unsigned int len, void *eb)
{
__attribute__((unused)) esp_err_t ret = ESP_OK;
struct netif *netif = h;
@@ -31,11 +39,12 @@ static void tun_input(void *h, void *buffer, unsigned int len, void *eb)
ESP_GOTO_ON_FALSE(pbuf_remove_header(p, SIZEOF_ETH_HDR) == 0, ESP_FAIL, err, TAG, "pbuf_remove_header failed");
memcpy(p->payload, buffer, len);
ESP_GOTO_ON_FALSE(netif->input(p, netif) == ERR_OK, ESP_FAIL, err, TAG, "failed to input packet to lwip");
return;
return ESP_NETIF_OPTIONAL_RETURN_CODE(ESP_OK);
err:
if (p) {
pbuf_free(p);
}
return ESP_NETIF_OPTIONAL_RETURN_CODE(ret);
}
static err_t tun_output(struct netif *netif, struct pbuf *p)
@@ -167,6 +176,9 @@ esp_err_t eppp_check_connection(esp_netif_t *netif)
{
esp_err_t ret = ESP_OK;
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
#if CONFIG_LOG_MAXIMUM_LEVEL > 3
config.task_stack_size += 1024; // Some additional stack needed for verbose logs
#endif
config.count = 100;
ESP_LOGI(TAG, "Checking connection on EPPP interface #%" PRIu32, config.interface);
ip_addr_t target_addr = {0};

View File

@@ -208,7 +208,9 @@ static esp_err_t init_master(struct eppp_config_spi_s *config, struct eppp_spi *
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err_dev, TAG, "Failed to config interrupt GPIO");
ESP_GOTO_ON_ERROR(gpio_install_isr_service(0), err_dev, TAG, "Failed to install GPIO ISR");
ret = gpio_install_isr_service(0);
ESP_GOTO_ON_FALSE(ret == ESP_OK || ret == ESP_ERR_INVALID_STATE /* In case the GPIO ISR already installed */,
ret, err_dev, TAG, "Failed to install GPIO ISR");
ESP_GOTO_ON_ERROR(gpio_set_intr_type(config->intr, GPIO_INTR_ANYEDGE), err_dev, TAG, "Failed to set ISR type");
ESP_GOTO_ON_ERROR(gpio_isr_handler_add(config->intr, gpio_isr_handler, h), err_dev, TAG, "Failed to add ISR handler");
return ESP_OK;
@@ -468,9 +470,6 @@ err:
if (h->ready_semaphore) {
vSemaphoreDelete(h->ready_semaphore);
}
if (h->out_queue) {
vQueueDelete(h->out_queue);
}
free(h);
return NULL;
}

View File

@@ -80,12 +80,12 @@ static esp_err_t init_uart(struct eppp_uart *h, struct eppp_config_uart_s *confi
uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = UART_PARITY_DISABLE;
uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
uart_config.flow_ctrl = config->flow_control;
uart_config.source_clk = UART_SCLK_DEFAULT;
ESP_RETURN_ON_ERROR(uart_driver_install(h->uart_port, config->rx_buffer_size, 0, config->queue_size, &h->uart_event_queue, 0), TAG, "Failed to install UART");
ESP_RETURN_ON_ERROR(uart_param_config(h->uart_port, &uart_config), TAG, "Failed to set params");
ESP_RETURN_ON_ERROR(uart_set_pin(h->uart_port, config->tx_io, config->rx_io, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG, "Failed to set UART pins");
ESP_RETURN_ON_ERROR(uart_set_pin(h->uart_port, config->tx_io, config->rx_io, config->rts_io, config->cts_io), TAG, "Failed to set UART pins");
ESP_RETURN_ON_ERROR(uart_set_rx_timeout(h->uart_port, 1), TAG, "Failed to set UART Rx timeout");
return ESP_OK;
}
@@ -157,6 +157,8 @@ static void process_packet(esp_netif_t *netif, uart_port_t uart_port, size_t ava
esp_netif_receive(netif, in_buf + buf_start + sizeof(struct header), payload_size, NULL);
} else {
#ifdef CONFIG_EPPP_LINK_CHANNELS_SUPPORT
struct eppp_handle *handle = esp_netif_get_io_driver(netif);
struct eppp_uart *h = __containerof(handle, struct eppp_uart, parent);
if (h->parent.channel_rx) {
h->parent.channel_rx(netif, channel, in_buf + buf_start + sizeof(struct header), payload_size);
}
@@ -269,6 +271,7 @@ eppp_transport_handle_t eppp_uart_init(struct eppp_config_uart_s *config)
ESP_GOTO_ON_ERROR(init_uart(h, config), err, TAG, "Failed to init UART");
return &h->parent;
err:
free(h);
return NULL;
}

View File

@@ -0,0 +1,4 @@
CONFIG_IDF_TARGET="esp32s3"
CONFIG_EPPP_LINK_DEVICE_SPI=y
CONFIG_EPPP_LINK_CHANNELS_SUPPORT=y
CONFIG_EPPP_LINK_USES_PPP=y

View File

@@ -0,0 +1,3 @@
CONFIG_IDF_TARGET="esp32c3"
CONFIG_EPPP_LINK_DEVICE_UART=y
CONFIG_EPPP_LINK_CHANNELS_SUPPORT=y

View File

@@ -1,4 +1 @@
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_SERVER_SUPPORT=y
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n
CONFIG_LWIP_PPP_DEBUG_ON=y

View File

@@ -0,0 +1,4 @@
CONFIG_IDF_TARGET="esp32s2"
CONFIG_EPPP_LINK_DEVICE_SPI=y
CONFIG_EPPP_LINK_CHANNELS_SUPPORT=y
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n

View File

@@ -0,0 +1,4 @@
CONFIG_IDF_TARGET="esp32c2"
CONFIG_EPPP_LINK_DEVICE_UART=y
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n
CONFIG_EPPP_LINK_USES_PPP=y

View File

@@ -1,6 +1,3 @@
CONFIG_LWIP_IP_FORWARD=y
CONFIG_LWIP_IPV4_NAPT=y
CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=4096
CONFIG_LWIP_PPP_SUPPORT=y
CONFIG_LWIP_PPP_SERVER_SUPPORT=y
CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n

View File

@@ -1,4 +1,4 @@
version: 1.0.1
version: 1.1.3
url: https://github.com/espressif/esp-protocols/tree/master/components/eppp_link
description: The component provides a general purpose PPP connectivity, typically used as WiFi-PPP router
dependencies:

View File

@@ -34,6 +34,9 @@ extern "C" {
.rx_io = 26, \
.queue_size = 16, \
.rx_buffer_size = 1024, \
.rts_io = -1, \
.cts_io = -1, \
.flow_control = 0, \
}, \
#define EPPP_DEFAULT_SDIO_CONFIG() \
@@ -135,6 +138,9 @@ typedef struct eppp_config_t {
int rx_io;
int queue_size;
int rx_buffer_size;
int rts_io;
int cts_io;
int flow_control;
} uart;
struct eppp_config_sdio_s {

View File

@@ -1,7 +1,15 @@
# The following four lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/tools/unit-test-app/components)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0")
set(test_component_dir $ENV{IDF_PATH}/tools/test_apps/components)
else()
set(test_component_dir $ENV{IDF_PATH}/tools/unit-test-app/components)
endif()
set(EXTRA_COMPONENT_DIRS ${test_component_dir})
project(test_app)

View File

@@ -1,4 +1,10 @@
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3")
set(driver_deps esp_driver_gpio esp_driver_spi esp_driver_uart esp_driver_sdio)
else()
set(driver_deps driver)
endif()
idf_component_register(SRCS app_main.c
INCLUDE_DIRS "."
REQUIRES test_utils
PRIV_REQUIRES unity nvs_flash esp_netif driver esp_event)
PRIV_REQUIRES unity nvs_flash esp_netif esp_event ${driver_deps})

View File

@@ -11,7 +11,12 @@ else()
src/esp_modem_uart.cpp
src/esp_modem_term_uart.cpp
src/esp_modem_netif.cpp)
set(dependencies driver esp_event esp_netif)
set(dependencies esp_event esp_netif)
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "5.3")
list(APPEND dependencies esp_driver_uart)
else()
list(APPEND dependencies driver)
endif()
endif()

View File

@@ -103,4 +103,14 @@ menu "esp-modem"
Set this to 'y' if you're making changes to the actual sources of
the AT command definitions (typically in esp_modem_command_declare.inc)
config ESP_MODEM_USE_PPP_MODE
bool "Use PPP mode"
default y
select LWIP_PPP_SUPPORT
help
If enabled, the library can use PPP netif from lwip.
This is the default and most common setting.
But it's possible to disable it and use only AT commands,
in this case it's not required to enable LWIP_PPP_SUPPORT.
endmenu

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -100,10 +100,10 @@ void wifi_init_softap(void)
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
wifi_config_t wifi_config = {
.ap = {
@@ -120,7 +120,7 @@ void wifi_init_softap(void)
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",

View File

@@ -9,5 +9,4 @@ idf_component_register(SRCS "modem_console_main.cpp"
"${command_dir}/my_module_dce.cpp"
"httpget_handle.c"
"ping_handle.c"
REQUIRES console esp_http_client nvs_flash
INCLUDE_DIRS "." "${command_dir}")

View File

@@ -63,13 +63,12 @@ void ConsoleCommand::RegisterCommand(const char *command, const char *help, cons
arg_type end = { .end = arg_end(1) };
arg_table.emplace_back(end);
const esp_console_cmd_t command_def = {
.command = command,
.help = help,
.hint = nullptr,
.func = command_func_pts[last_command],
.argtable = &arg_table[0]
};
esp_console_cmd_t command_def = { };
command_def.command = command;
command_def.help = help;
command_def.hint = nullptr;
command_def.func = command_func_pts[last_command];
command_def.argtable = &arg_table[0];
ESP_ERROR_CHECK(esp_console_cmd_register(&command_def));
last_command++;
console_commands.emplace_back(this);

View File

@@ -21,6 +21,7 @@
#include "cxx_include/esp_modem_dte.hpp"
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#include "esp_idf_version.h"
#if defined(CONFIG_EXAMPLE_SERIAL_CONFIG_USB)
#include "esp_modem_usb_config.h"
#include "cxx_include/esp_modem_usb_api.hpp"
@@ -29,6 +30,12 @@
#include "console_helper.hpp"
#include "my_module_dce.hpp"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
#define GET_WAKEUP_CAUSE() esp_sleep_get_wakeup_causes()
#else
#define GET_WAKEUP_CAUSE() esp_sleep_get_wakeup_cause()
#endif
#if defined(CONFIG_EXAMPLE_FLOW_CONTROL_NONE)
#define EXAMPLE_FLOW_CONTROL ESP_MODEM_FLOW_CONTROL_NONE
#elif defined(CONFIG_EXAMPLE_FLOW_CONTROL_SW)
@@ -208,7 +215,7 @@ extern "C" void app_main(void)
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &s_repl));
switch (esp_sleep_get_wakeup_cause()) {
switch (GET_WAKEUP_CAUSE()) {
case ESP_SLEEP_WAKEUP_TIMER:
if (esp_modem::modem_mode::CMUX_MODE == mode_rtc) {
ESP_LOGI(TAG, "Deep sleep reset\n");
@@ -428,7 +435,7 @@ extern "C" void app_main(void)
const ConsoleCommand SetDeepSleep("set_deep_sleep", "Put esp32 to deep sleep", &deep_sleep_args, sizeof(deep_sleep_args), [&](ConsoleCommand * c) {
int tout = c->get_int_of(&DeepSleepArgs::timeout);
ESP_LOGI(TAG, "Entering deep sleep for %d sec", tout);
ESP_LOGI(TAG, "Wakeup Cause: %d ", esp_sleep_get_wakeup_cause());
ESP_LOGI(TAG, "Wakeup Cause: %d ", GET_WAKEUP_CAUSE());
esp_deep_sleep(tout * 1000000);
return 0;
});

View File

@@ -1,7 +1,11 @@
set(module_dir "generic_module")
if (CONFIG_EXAMPLE_MODEM_DEVICE_BG96)
set(device_srcs sock_commands_bg96.cpp)
elseif(CONFIG_EXAMPLE_MODEM_DEVICE_SIM7600)
set(device_srcs sock_commands_sim7600.cpp)
elseif(CONFIG_EXAMPLE_MODEM_DEVICE_ESPAT)
set(device_srcs sock_commands_espat.cpp)
set(module_dir "espat_module")
endif()
if(CONFIG_ESP_MODEM_ENABLE_DEVELOPMENT_MODE)
@@ -14,4 +18,4 @@ idf_component_register(SRCS "modem_client.cpp"
"${command_dir}/sock_dce.cpp"
"tcp_transport_at.cpp"
"${device_srcs}"
INCLUDE_DIRS "." "${command_dir}")
INCLUDE_DIRS "." "${command_dir}" "${module_dir}")

View File

@@ -18,8 +18,26 @@ menu "Example Configuration"
bool "SIM7600"
help
SIM7600 is Multi-Band LTE-TDD/LTE-FDD/HSPA+ and GSM/GPRS/EDGE module
config EXAMPLE_MODEM_DEVICE_ESPAT
bool "ESP-AT"
help
ESP-AT firmware for ESP32 modules with WiFi connectivity
endchoice
if EXAMPLE_MODEM_DEVICE_ESPAT
config EXAMPLE_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) to connect to.
config EXAMPLE_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2).
endif
config EXAMPLE_MODEM_APN
string "Set MODEM APN"
default "internet"

View File

@@ -8,6 +8,7 @@
#include "cxx_include/esp_modem_api.hpp"
#include <cxx_include/esp_modem_dce_factory.hpp>
#include "sock_commands.hpp"
#include "sock_module.hpp"
#include <sys/socket.h>
#pragma once
@@ -97,8 +98,8 @@ private:
std::shared_ptr<esp_modem::DTE> &dte;
};
class DCE : public ::esp_modem::GenericModule {
using esp_modem::GenericModule::GenericModule;
class DCE : public Module {
using Module::Module;
public:
/**

View File

@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#pragma once
namespace sock_dce {
class Module: public esp_modem::GenericModule {
using esp_modem::GenericModule::GenericModule;
public:
esp_modem::command_result sync() override;
esp_modem::command_result set_echo(bool on) override;
esp_modem::command_result set_pdp_context(esp_modem::PdpContext &pdp) override;
};
}

View File

@@ -8,6 +8,7 @@
#include "cxx_include/esp_modem_api.hpp"
#include <cxx_include/esp_modem_dce_factory.hpp>
#include "sock_commands.hpp"
#include "sock_module.hpp"
#include <sys/socket.h>
#pragma once
@@ -97,8 +98,8 @@ private:
std::shared_ptr<esp_modem::DTE> &dte;
};
class DCE : public ::esp_modem::GenericModule {
using esp_modem::GenericModule::GenericModule;
class DCE : public Module {
using Module::Module;
public:
// --- ESP-MODEM command module starts here ---

View File

@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#pragma once
namespace sock_dce {
using Module = esp_modem::GenericModule;
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -23,8 +23,8 @@
#include "tcp_transport_mbedtls.h"
#include "tcp_transport_at.h"
#define BROKER_URL "mqtt.eclipseprojects.io"
#define BROKER_PORT 8883
#define BROKER_URL "test.mosquitto.org"
#define BROKER_PORT 1883
static const char *TAG = "modem_client";
@@ -114,7 +114,7 @@ extern "C" void app_main(void)
mqtt_config.broker.address.port = BROKER_PORT;
mqtt_config.session.message_retransmit_timeout = 10000;
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
mqtt_config.broker.address.uri = "mqtts://127.0.0.1";
mqtt_config.broker.address.uri = "mqtt://127.0.0.1";
dce->start_listening(BROKER_PORT);
#else
mqtt_config.broker.address.uri = "mqtt://" BROKER_URL;
@@ -124,14 +124,14 @@ extern "C" void app_main(void)
mqtt_config.network.transport = ssl;
#endif
esp_mqtt_client_handle_t mqtt_client = esp_mqtt_client_init(&mqtt_config);
esp_mqtt_client_register_event(mqtt_client, static_cast<esp_mqtt_event_id_t>(ESP_EVENT_ANY_ID), mqtt_event_handler, NULL);
esp_mqtt_client_register_event(mqtt_client, static_cast<esp_mqtt_event_id_t>(ESP_EVENT_ANY_ID), mqtt_event_handler, nullptr);
esp_mqtt_client_start(mqtt_client);
#ifndef CONFIG_EXAMPLE_CUSTOM_TCP_TRANSPORT
if (!dce->connect(BROKER_URL, BROKER_PORT)) {
ESP_LOGE(TAG, "Failed to start DCE");
return;
}
while (1) {
while (true) {
while (dce->perform_sock()) {
ESP_LOGV(TAG, "...performing");
}

View File

@@ -0,0 +1,366 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <charconv>
#include <cstring>
#include "sock_commands.hpp"
#include "cxx_include/esp_modem_command_library_utils.hpp"
#include "sock_dce.hpp"
#include "sock_module.hpp"
static const char *TAG = "sock_commands_espat";
namespace sock_dce {
using namespace esp_modem;
command_result Module::sync()
{
return dce_commands::generic_command_common(dte.get(), "AT\r\n");
}
command_result Module::set_echo(bool on)
{
return dce_commands::generic_command_common(dte.get(), "ATE0\r\n");
}
command_result Module::set_pdp_context(PdpContext &pdp)
{
return command_result::OK;
}
}
namespace sock_commands {
using namespace esp_modem;
command_result net_open(CommandableIf *t)
{
ESP_LOGV(TAG, "%s", __func__);
// Set WiFi mode to station
auto ret = dce_commands::generic_command(t, "AT+CWMODE=1\r\n", "OK", "ERROR", 5000);
if (ret != command_result::OK) {
ESP_LOGE(TAG, "Failed to set WiFi mode");
return ret;
}
// Connect to WiFi network
std::string wifi_cmd = "AT+CWJAP=\"" CONFIG_EXAMPLE_WIFI_SSID "\",\"" CONFIG_EXAMPLE_WIFI_PASSWORD "\"\r\n";
ret = dce_commands::generic_command(t, wifi_cmd, "OK", "ERROR", 15000);
if (ret != command_result::OK) {
ESP_LOGE(TAG, "Failed to connect to WiFi");
return ret;
}
ESP_LOGI(TAG, "WiFi connected successfully");
// Set passive receive mode (1) for better control
ret = set_rx_mode(t, 1);
if (ret != command_result::OK) {
ESP_LOGE(TAG, "Failed to set preferred Rx mode");
return ret;
}
return command_result::OK;
}
command_result net_close(CommandableIf *t)
{
ESP_LOGV(TAG, "%s", __func__);
// Disconnect from WiFi
auto ret = dce_commands::generic_command(t, "AT+CWQAP\r\n", "OK", "ERROR", 5000);
if (ret != command_result::OK) {
ESP_LOGW(TAG, "Failed to disconnect WiFi (may already be disconnected)");
}
return command_result::OK;
}
command_result tcp_open(CommandableIf *t, const std::string &host, int port, int timeout)
{
ESP_LOGV(TAG, "%s", __func__);
// Set single connection mode (just in case)
auto ret = dce_commands::generic_command(t, "AT+CIPMUX=0\r\n", "OK", "ERROR", 1000);
if (ret != command_result::OK) {
ESP_LOGW(TAG, "Failed to set single connection mode");
}
// Establish TCP connection
std::string tcp_cmd = "AT+CIPSTART=\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n";
ret = dce_commands::generic_command(t, tcp_cmd, "CONNECT", "ERROR", timeout);
if (ret != command_result::OK) {
ESP_LOGE(TAG, "Failed to establish TCP connection to %s:%d", host.c_str(), port);
return ret;
}
ESP_LOGI(TAG, "TCP connection established to %s:%d", host.c_str(), port);
return command_result::OK;
}
command_result tcp_close(CommandableIf *t)
{
ESP_LOGV(TAG, "%s", __func__);
return dce_commands::generic_command(t, "AT+CIPCLOSE\r\n", "CLOSED", "ERROR", 5000);
}
command_result tcp_send(CommandableIf *t, uint8_t *data, size_t len)
{
ESP_LOGV(TAG, "%s", __func__);
// This function is not used in the current implementation
// Data sending is handled by the DCE responder
return command_result::FAIL;
}
command_result tcp_recv(CommandableIf *t, uint8_t *data, size_t len, size_t &out_len)
{
ESP_LOGV(TAG, "%s", __func__);
// This function is not used in the current implementation
// Data receiving is handled by the DCE responder
return command_result::FAIL;
}
command_result get_ip(CommandableIf *t, std::string &ip)
{
ESP_LOGV(TAG, "%s", __func__);
std::string out;
auto ret = dce_commands::at_raw(t, "AT+CIFSR\r\n", out, "OK", "ERROR", 5000);
if (ret != command_result::OK) {
return ret;
}
// Parse station IP from response
// Expected format: +CIFSR:STAIP,"192.168.1.100"
auto pos = out.find("+CIFSR:STAIP,\"");
if (pos != std::string::npos) {
pos += 14; // Move past "+CIFSR:STAIP,\""
auto end_pos = out.find("\"", pos);
if (end_pos != std::string::npos) {
ip = out.substr(pos, end_pos - pos);
ESP_LOGI(TAG, "Got IP address: %s", ip.c_str());
return command_result::OK;
}
}
ESP_LOGE(TAG, "Failed to parse IP address from response");
return command_result::FAIL;
}
command_result set_rx_mode(CommandableIf *t, int mode)
{
ESP_LOGE(TAG, "%s", __func__);
// Active mode (0) sends data automatically, Passive mode (1) notifies about data for reading
std::string cmd = "AT+CIPRECVTYPE=" + std::to_string(mode) + "\r\n";
return dce_commands::generic_command(t, cmd, "OK", "ERROR", 1000);
}
} // sock_commands
namespace sock_dce {
void Responder::start_sending(size_t len)
{
data_to_send = len;
send_stat = 0;
send_cmd("AT+CIPSEND=" + std::to_string(len) + "\r\n");
}
void Responder::start_receiving(size_t len)
{
send_cmd("AT+CIPRECVDATA=" + std::to_string(len) + "\r\n");
}
bool Responder::start_connecting(std::string host, int port)
{
std::string cmd = "AT+CIPSTART=\"TCP\",\"" + host + "\"," + std::to_string(port) + "\r\n";
send_cmd(cmd);
return true;
}
Responder::ret Responder::recv(uint8_t *data, size_t len)
{
const int MIN_MESSAGE = 6;
size_t actual_len = 0;
auto *recv_data = (char *)data;
if (data_to_recv == 0) {
const std::string_view head = "+CIPRECVDATA:";
// Find the response header
auto head_pos = std::search(recv_data, recv_data + len, head.data(), head.data() + head.size(), [](char a, char b) {
return a == b;
});
if (head_pos == recv_data + len) {
return ret::FAIL;
}
// Find the end of the length field
auto next_comma = (char *)memchr(head_pos + head.size(), ',', MIN_MESSAGE);
if (next_comma == nullptr) {
return ret::FAIL;
}
// Parse the actual length
if (std::from_chars(head_pos + head.size(), next_comma, actual_len).ec == std::errc::invalid_argument) {
ESP_LOGE(TAG, "Cannot convert length");
return ret::FAIL;
}
ESP_LOGD(TAG, "Received: actual len=%zu", actual_len);
if (actual_len == 0) {
ESP_LOGD(TAG, "No data received");
return ret::FAIL;
}
if (actual_len > buffer_size) {
ESP_LOGE(TAG, "Data too large: %zu > %zu", actual_len, buffer_size);
return ret::FAIL;
}
// Move to the actual data after the comma
recv_data = next_comma + 1;
auto first_data_len = len - (recv_data - (char *)data);
if (actual_len > first_data_len) {
on_read(recv_data, first_data_len);
data_to_recv = actual_len - first_data_len;
return ret::NEED_MORE_DATA;
}
on_read(recv_data, actual_len);
} else if (data_to_recv > len) { // Continue receiving
on_read(recv_data, len);
data_to_recv -= len;
return ret::NEED_MORE_DATA;
} else if (data_to_recv <= len) { // Last chunk
on_read(recv_data, data_to_recv);
actual_len = data_to_recv;
}
// Look for "OK" marker after the data
char *ok_pos = nullptr;
if (actual_len + 1 + 2 /* OK */ <= len) {
ok_pos = (char *)memchr(recv_data + actual_len + 1, 'O', MIN_MESSAGE);
if (ok_pos == nullptr || ok_pos[1] != 'K') {
data_to_recv = 0;
return ret::FAIL;
}
}
// Reset and prepare for next receive
data_to_recv = 0;
return ret::OK;
}
Responder::ret Responder::send(uint8_t *data, size_t len)
{
if (send_stat < 3) {
// Look for the '>' prompt
if (memchr(data, '>', len) == NULL) {
if (send_stat++ < 2) {
return ret::NEED_MORE_DATA;
}
ESP_LOGE(TAG, "Missed '>' prompt");
return ret::FAIL;
}
// Send the actual data
auto written = dte->write(&buffer[0], data_to_send);
if (written != data_to_send) {
ESP_LOGE(TAG, "Failed to write data: %d/%zu", written, data_to_send);
return ret::FAIL;
}
data_to_send = 0;
send_stat = 3;
}
return ret::IN_PROGRESS;
}
Responder::ret Responder::send(std::string_view response)
{
if (send_stat == 3) {
if (response.find("SEND OK") != std::string::npos) {
send_stat = 0;
return ret::OK;
} else if (response.find("SEND FAIL") != std::string::npos) {
ESP_LOGE(TAG, "Send failed");
return ret::FAIL;
} else if (response.find("ERROR") != std::string::npos) {
ESP_LOGE(TAG, "Send error");
return ret::FAIL;
}
}
return ret::IN_PROGRESS;
}
Responder::ret Responder::connect(std::string_view response)
{
if (response.find("CONNECT") != std::string::npos) {
ESP_LOGI(TAG, "TCP connected!");
return ret::OK;
}
if (response.find("ERROR") != std::string::npos) {
ESP_LOGE(TAG, "Failed to connect");
return ret::FAIL;
}
return ret::IN_PROGRESS;
}
Responder::ret Responder::check_async_replies(status state, std::string_view &response)
{
ESP_LOGD(TAG, "Response: %.*s", static_cast<int>(response.size()), response.data());
// Handle WiFi status messages
if (response.find("WIFI CONNECTED") != std::string::npos) {
ESP_LOGI(TAG, "WiFi connected");
} else if (response.find("WIFI DISCONNECTED") != std::string::npos) {
ESP_LOGW(TAG, "WiFi disconnected");
}
// Handle TCP status messages
if (response.find("CONNECT") != std::string::npos && state == status::CONNECTING) {
return connect(response);
} else if (response.find("CLOSED") != std::string::npos) {
ESP_LOGW(TAG, "TCP connection closed");
return ret::FAIL;
}
// Handle data notifications in active mode (if we switch to it later)
if (response.find("+IPD,") != std::string::npos) {
uint64_t data_ready = 1;
write(data_ready_fd, &data_ready, sizeof(data_ready));
ESP_LOGD(TAG, "Data available notification");
}
if (state == status::SENDING) {
return send(response);
} else if (state == status::CONNECTING) {
return connect(response);
}
return ret::IN_PROGRESS;
}
Responder::ret Responder::process_data(status state, uint8_t *data, size_t len)
{
if (state == status::SENDING) {
return send(data, len);
}
if (state == status::RECEIVING) {
return recv(data, len);
}
return ret::IN_PROGRESS;
}
status Responder::pending()
{
// For ESP-AT, we don't need a pending check like BG96
// Just return current status
return status::SENDING;
}
} // sock_dce

View File

@@ -0,0 +1,2 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_EXAMPLE_MODEM_DEVICE_ESPAT=y

View File

@@ -62,12 +62,16 @@ class Creator {
public:
Creator(std::shared_ptr<DTE> dte, esp_netif_t *esp_netif): dte(std::move(dte)), device(nullptr), netif(esp_netif)
{
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
ESP_MODEM_THROW_IF_FALSE(netif != nullptr, "Null netif");
#endif
}
Creator(std::shared_ptr<DTE> dte, esp_netif_t *esp_netif, std::shared_ptr<T_Module> dev): dte(std::move(dte)), device(std::move(dev)), netif(esp_netif)
{
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
ESP_MODEM_THROW_IF_FALSE(netif != nullptr, "Null netif");
#endif
}
explicit Creator(std::shared_ptr<DTE> dte): dte(std::move(dte)), device(nullptr), netif(nullptr)

View File

@@ -89,7 +89,7 @@
#define MPPE_SUPPORT 0
#define PPP_MAXIDLEFLAG 0
#define PPP_MAXIDLEFLAG 100
#define PRINTPKT_SUPPORT 1
#define PPP_PROTOCOLNAME 1

View File

@@ -104,7 +104,7 @@ static void ppp_link_status_cb(ppp_pcb *pcb, int err_code, void *ctx)
}
}
static u32_t ppp_output_cb(struct ppp_pcb_s *pcb, const void *data, u32_t len, void *ctx)
static u32_t ppp_output_cb(struct ppp_pcb_s *pcb, u8_t *data, u32_t len, void *ctx)
{
esp_netif_t *netif = (esp_netif_t *)ctx;
if (netif->transmit) {

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -14,7 +14,6 @@
#include "cxx_include/esp_modem_dte.hpp"
#include "esp_netif_ppp.h"
namespace esp_modem {
void Netif::on_ppp_changed(void *arg, esp_event_base_t event_base,
@@ -39,6 +38,7 @@ esp_err_t Netif::esp_modem_dte_transmit(void *h, void *buffer, size_t len)
return ESP_FAIL;
}
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
esp_err_t Netif::esp_modem_post_attach(esp_netif_t *esp_netif, void *args)
{
auto d = (ppp_netif_driver *) args;
@@ -62,6 +62,7 @@ esp_err_t Netif::esp_modem_post_attach(esp_netif_t *esp_netif, void *args)
return ESP_OK;
}
#endif // CONFIG_ESP_MODEM_USE_PPP_MODE
void Netif::receive(uint8_t *data, size_t len)
{
@@ -73,12 +74,16 @@ Netif::Netif(std::shared_ptr<DTE> e, esp_netif_t *ppp_netif) :
{
driver.base.netif = ppp_netif;
driver.ppp = this;
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
driver.base.post_attach = esp_modem_post_attach;
ESP_MODEM_THROW_IF_ERROR(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed, (void *) this));
#endif
ESP_MODEM_THROW_IF_ERROR(esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, ppp_netif));
ESP_MODEM_THROW_IF_ERROR(
esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected, ppp_netif));
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
ESP_MODEM_THROW_IF_ERROR(esp_netif_attach(ppp_netif, &driver));
#endif
}
void Netif::start()
@@ -120,7 +125,9 @@ Netif::~Netif()
signal.clear(PPP_STARTED);
signal.wait(PPP_EXIT, 30000);
}
#ifdef CONFIG_ESP_MODEM_USE_PPP_MODE
esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, &on_ppp_changed);
#endif
esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected);
esp_event_handler_unregister(IP_EVENT, IP_EVENT_PPP_LOST_IP, esp_netif_action_disconnected);
}

View File

@@ -14,11 +14,17 @@
#include "cxx_include/esp_modem_dte.hpp"
#include "esp_modem_config.h"
#include "cxx_include/esp_modem_api.hpp"
#include "esp_vfs_dev.h" // For optional VFS support
#include "vfs_resource/vfs_create.hpp"
#include "esp_idf_version.h"
#include "network_dce.hpp"
#include "manual_ota.hpp"
#include "mqtt_client.h"
// For optional VFS support
#include "vfs_resource/vfs_create.hpp"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
#include "driver/uart_vfs.h"
#else
#include "esp_vfs_dev.h"
#endif
using namespace esp_modem;
@@ -189,7 +195,12 @@ extern "C" void app_main(void)
assert(vfs_create_uart(&uart_config, &dte_config.vfs_config) == true);
auto dte = create_vfs_dte(&dte_config);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
uart_vfs_dev_use_driver(uart_config.uart.port_num);
#else
esp_vfs_dev_uart_use_driver(uart_config.uart.port_num);
#endif
#else
auto dte = create_uart_dte(&dte_config);
#endif // CONFIG_TEST_USE_VFS_TERM

View File

@@ -64,10 +64,17 @@ static const char *TAG = "websocket_client";
#define WS_OVER_TCP_SCHEME "ws"
#define WS_OVER_TLS_SCHEME "wss"
#define WS_HTTP_BASIC_AUTH "Basic "
#define WS_HTTP_REDIRECT(code) ((code >= 300) && (code < 400))
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
// Features supported in 5.5.0
#define WS_TRANSPORT_REDIRECT_HEADER_SUPPORT 1
#endif
const static int STOPPED_BIT = BIT0;
const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client
// and we are waiting for the server to continue with clean close
const static int REQUESTED_STOP_BIT = BIT2; // Indicates that a client stop has been requested
ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS);
@@ -471,11 +478,20 @@ static esp_err_t stop_wait_task(esp_websocket_client_handle_t client)
}
client->run = false;
xEventGroupSetBits(client->status_bits, REQUESTED_STOP_BIT);
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
client->state = WEBSOCKET_STATE_UNKNOW;
return ESP_OK;
}
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
static void websocket_header_hook(void * client, const char * line, int line_len)
{
ESP_LOGD(TAG, "%s header:%.*s", __func__, line_len, line);
esp_websocket_client_dispatch_event(client, WEBSOCKET_EVENT_HEADER_RECEIVED, line, line_len);
}
#endif
static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_handle_t client, const char *scheme)
{
esp_transport_handle_t trans = esp_transport_list_get_transport(client->transport_list, scheme);
@@ -485,6 +501,10 @@ static esp_err_t set_websocket_transport_optional_settings(esp_websocket_client_
.sub_protocol = client->config->subprotocol,
.user_agent = client->config->user_agent,
.headers = client->config->headers,
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
.header_hook = websocket_header_hook,
.header_user_context = client,
#endif
.auth = client->config->auth,
.propagate_control_frames = true
};
@@ -719,10 +739,14 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail);
if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) {
asprintf(&client->config->scheme, WS_OVER_TCP_SCHEME);
if (asprintf(&client->config->scheme, WS_OVER_TCP_SCHEME) < 0) {
client->config->scheme = NULL;
}
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
} else if (config->transport == WEBSOCKET_TRANSPORT_OVER_SSL) {
asprintf(&client->config->scheme, WS_OVER_TLS_SCHEME);
if (asprintf(&client->config->scheme, WS_OVER_TLS_SCHEME) < 0) {
client->config->scheme = NULL;
}
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
}
@@ -767,7 +791,9 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
}
if (client->config->scheme == NULL) {
asprintf(&client->config->scheme, WS_OVER_TCP_SCHEME);
if (asprintf(&client->config->scheme, WS_OVER_TCP_SCHEME) < 0) {
client->config->scheme = NULL;
}
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, goto _websocket_init_fail);
}
@@ -844,26 +870,34 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con
}
if (puri.field_data[UF_SCHEMA].len) {
free(client->config->scheme);
asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off);
if (asprintf(&client->config->scheme, "%.*s", puri.field_data[UF_SCHEMA].len, uri + puri.field_data[UF_SCHEMA].off) < 0) {
client->config->scheme = NULL;
}
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->scheme, return ESP_ERR_NO_MEM);
}
if (puri.field_data[UF_HOST].len) {
free(client->config->host);
asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off);
if (asprintf(&client->config->host, "%.*s", puri.field_data[UF_HOST].len, uri + puri.field_data[UF_HOST].off) < 0) {
client->config->host = NULL;
}
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->host, return ESP_ERR_NO_MEM);
}
if (puri.field_data[UF_PATH].len || puri.field_data[UF_QUERY].len) {
free(client->config->path);
int aret = -1;
if (puri.field_data[UF_QUERY].len == 0) {
asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off);
aret = asprintf(&client->config->path, "%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off);
} else if (puri.field_data[UF_PATH].len == 0) {
asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
aret = asprintf(&client->config->path, "/?%.*s", puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
} else {
asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off,
puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
aret = asprintf(&client->config->path, "%.*s?%.*s", puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off,
puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off);
}
if (aret < 0) {
client->config->path = NULL;
}
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config->path, return ESP_ERR_NO_MEM);
}
@@ -873,7 +907,9 @@ esp_err_t esp_websocket_client_set_uri(esp_websocket_client_handle_t client, con
if (puri.field_data[UF_USERINFO].len) {
char *user_info = NULL;
asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off);
if (asprintf(&user_info, "%.*s", puri.field_data[UF_USERINFO].len, uri + puri.field_data[UF_USERINFO].off) < 0) {
user_info = NULL;
}
if (user_info) {
char *pass = strchr(user_info, ':');
if (pass) {
@@ -1072,6 +1108,29 @@ static void esp_websocket_client_task(void *pv)
esp_websocket_client_abort_connection(client, WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT);
break;
}
#if WS_TRANSPORT_REDIRECT_HEADER_SUPPORT
else if (WS_HTTP_REDIRECT(result)) {
const char *redir = esp_transport_ws_get_redir_uri(client->transport);
if (redir) {
// Redirecting to a new URI
free(client->config->uri);
client->config->uri = strdup(redir);
client->config->port = 0;
esp_websocket_client_set_uri(client, client->config->uri);
if (client->config->port == 0) {
client->config->port = esp_transport_get_default_port(client->transport);
}
// Rerun the connection with the redir uri.
client->state = WEBSOCKET_STATE_INIT;
ESP_LOGI(TAG, "Redirecting to %s", client->config->uri);
break;
}
}
#endif
ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port);
client->state = WEBSOCKET_STATE_CONNECTED;
@@ -1170,8 +1229,8 @@ static void esp_websocket_client_task(void *pv)
}
}
} else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client->state) {
// waiting for reconnecting...
vTaskDelay(client->wait_timeout_ms / 2 / portTICK_PERIOD_MS);
// waiting for reconnection or a request to stop the client...
xEventGroupWaitBits(client->status_bits, REQUESTED_STOP_BIT, false, true, client->wait_timeout_ms / 2 / portTICK_PERIOD_MS);
} else if (WEBSOCKET_STATE_CLOSING == client->state &&
(CLOSE_FRAME_SENT_BIT & xEventGroupGetBits(client->status_bits))) {
ESP_LOGD(TAG, " Waiting for TCP connection to be closed by the server");
@@ -1233,7 +1292,7 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
ESP_LOGE(TAG, "Error create websocket task");
return ESP_FAIL;
}
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT);
xEventGroupClearBits(client->status_bits, STOPPED_BIT | CLOSE_FRAME_SENT_BIT | REQUESTED_STOP_BIT);
ESP_LOGI(TAG, "Started");
return ESP_OK;
}
@@ -1302,6 +1361,7 @@ static esp_err_t esp_websocket_client_close_with_optional_body(esp_websocket_cli
// If could not close gracefully within timeout, stop the client and disconnect
client->run = false;
xEventGroupSetBits(client->status_bits, REQUESTED_STOP_BIT);
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
client->state = WEBSOCKET_STATE_UNKNOW;
return ESP_OK;

View File

@@ -15,20 +15,28 @@ idf.py build
## Example Output
```
I (164532) websocket: [APP] Startup..
I (164532) websocket: [APP] Free memory: 4294967295 bytes
I (164532) websocket: [APP] IDF version: v5.3-dev-1353-gb3f7e2c8a4
I (164538) websocket: Connecting to ws://echo.websocket.events...
W (164538) websocket_client: `reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
W (164538) websocket_client: `network_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
I (165103) websocket: WEBSOCKET_EVENT_CONNECTED
I (165539) websocket: Sending hello 0000
I (165627) websocket: WEBSOCKET_EVENT_DATA
I (165627) websocket: Received opcode=1
W (165627) websocket: Received=hello 0000
W (165627) websocket: Total payload length=10, data_len=10, current payload offset=0
I (76826192) websocket: [APP] Startup..
I (76826193) websocket: [APP] Free memory: 4294967295 bytes
I (76826193) websocket: [APP] IDF version: v6.0-dev-2414-gab3feab1d13
I (76826195) websocket: Connecting to wss://echo.websocket.org...
W (76826195) websocket_client: `reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
W (76826195) websocket_client: `network_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds)
I (76826195) websocket: WEBSOCKET_EVENT_BEGIN
I (76826196) websocket_client: Started
I (76826294) esp-x509-crt-bundle: Certificate validated
I (76827230) websocket: WEBSOCKET_EVENT_CONNECTED
I (76827239) websocket: WEBSOCKET_EVENT_DATA
I (76827239) websocket: Received opcode=1
W (76827239) websocket: Received=Request served by 4d896d95b55478
W (76827239) websocket: Total payload length=32, data_len=32, current payload offset=0
I (166539) websocket: Sending fragmented message
I (76828198) websocket: Sending hello 0000
I (76828827) websocket: WEBSOCKET_EVENT_DATA
I (76828827) websocket: Received opcode=1
W (76828827) websocket: Received=hello 0000
W (76828827) websocket: Total payload length=10, data_len=10, current payload offset=0
I (76829207) websocket: Sending fragmented text message
```
## Coverage Reporting

View File

@@ -8,8 +8,27 @@ menu "Host-test config"
config WEBSOCKET_URI
string "Websocket endpoint URI"
default "ws://echo.websocket.events"
default "wss://echo.websocket.org"
help
URL of websocket endpoint this example connects to and sends echo
config WS_OVER_TLS_SERVER_AUTH
bool "Enable WebSocket over TLS with Server Certificate Verification Only"
default y
help
Enables WebSocket connections over TLS (WSS) with server certificate verification.
The client verifies the server certificate; the server does not require a client certificate.
config WS_OVER_TLS_MUTUAL_AUTH
bool "Enable WebSocket over TLS with Server Client Mutual Authentification"
default n
help
Enables WebSocket connections over TLS (WSS) with server and client mutual certificate verification.
config WS_OVER_TLS_SKIP_COMMON_NAME_CHECK
bool "Skip common name(CN) check during TLS authentification"
default n
help
Skip Common Name (CN) check during TLS (WSS) authentication. Use only for testing.
endmenu

View File

@@ -11,6 +11,7 @@
#include "esp_system.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_crt_bundle.h"
static const char *TAG = "websocket";
@@ -28,6 +29,11 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
case WEBSOCKET_EVENT_BEGIN:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_BEGIN");
break;
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
case WEBSOCKET_EVENT_HEADER_RECEIVED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_HEADER_RECEIVED: %.*s", data->data_len, data->data_ptr);
break;
#endif
case WEBSOCKET_EVENT_CONNECTED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
break;
@@ -75,6 +81,33 @@ static void websocket_app_start(void)
websocket_cfg.uri = CONFIG_WEBSOCKET_URI;
#if CONFIG_WS_OVER_TLS_MUTUAL_AUTH
/* Configuring client certificates for mutual authentification */
extern const char cacert_start[] asm("_binary_ca_cert_pem_start");
extern const char cert_start[] asm("_binary_client_cert_pem_start");
extern const char cert_end[] asm("_binary_client_cert_pem_end");
extern const char key_start[] asm("_binary_client_key_pem_start");
extern const char key_end[] asm("_binary_client_key_pem_end");
websocket_cfg.cert_pem = cacert_start;
websocket_cfg.client_cert = cert_start;
websocket_cfg.client_cert_len = cert_end - cert_start;
websocket_cfg.client_key = key_start;
websocket_cfg.client_key_len = key_end - key_start;
#elif CONFIG_WS_OVER_TLS_SERVER_AUTH
// Using certificate bundle as default server certificate source
websocket_cfg.crt_bundle_attach = esp_crt_bundle_attach;
// If using a custom certificate it could be added to certificate bundle,
// added to the build similar to client certificates in this examples,
// or read from NVS.
/* extern const char cacert_start[] asm("ADDED_CERTIFICATE"); */
/* websocket_cfg.cert_pem = cacert_start; */
#endif
#if CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK
websocket_cfg.skip_cert_common_name_check = true;
#endif
ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri);
esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg);

View File

@@ -3,4 +3,4 @@ CONFIG_IDF_TARGET="linux"
CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI="wss://echo.websocket.org"

View File

@@ -2,4 +2,4 @@ CONFIG_IDF_TARGET="linux"
CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI="wss://echo.websocket.org"

View File

@@ -2,4 +2,4 @@ CONFIG_IDF_TARGET="linux"
CONFIG_IDF_TARGET_LINUX=y
CONFIG_ESP_EVENT_POST_FROM_ISR=n
CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n
CONFIG_WEBSOCKET_URI="ws://echo.websocket.events"
CONFIG_WEBSOCKET_URI="wss://echo.websocket.org"

View File

@@ -1,6 +1,42 @@
# Websocket Sample application
This example will shows how to set up and communicate over a websocket.
This example shows how to set up and communicate over a websocket.
## Table of Contents
- [Hardware Required](#hardware-required)
- [Configure the project](#configure-the-project)
- [Pre-configured SDK Configurations](#pre-configured-sdk-configurations)
- [Server Certificate Verification](#server-certificate-verification)
- [Generating Self-signed Certificates](#generating-a-self-signed-certificates-with-openssl)
- [Build and Flash](#build-and-flash)
- [Testing with pytest](#testing-with-pytest)
- [Example Output](#example-output)
- [WebSocket Test Server](#websocket-test-server)
- [Python Flask Echo Server](#alternative-python-flask-echo-server)
## Quick Start
1. **Install dependencies:**
```bash
pip install -r esp-protocols/ci/requirements.txt
```
2. **Configure and build:**
```bash
idf.py menuconfig # Configure WiFi/Ethernet and WebSocket URI
idf.py build
```
3. **Flash and monitor:**
```bash
idf.py -p PORT flash monitor
```
4. **Run tests:**
```bash
pytest .
```
## How to Use Example
@@ -15,6 +51,20 @@ This example can be executed on any ESP32 board, the only required interface is
* Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing)
* To test a WebSocket client example over TLS, please enable one of the following configurations: `CONFIG_WS_OVER_TLS_MUTUAL_AUTH` or `CONFIG_WS_OVER_TLS_SERVER_AUTH`. See the sections below for more details.
### Pre-configured SDK Configurations
This example includes several pre-configured `sdkconfig.ci.*` files for different testing scenarios:
* **sdkconfig.ci** - Default configuration with WebSocket over Ethernet (IP101 PHY, ESP32, IPv6) and hardcoded URI.
* **sdkconfig.ci.plain_tcp** - WebSocket over plain TCP (no TLS, URI from stdin) using Ethernet (IP101 PHY, ESP32, IPv6).
* **sdkconfig.ci.mutual_auth** - WebSocket with mutual TLS authentication (client/server certificate verification, skips CN check) and URI from stdin.
* **sdkconfig.ci.dynamic_buffer** - WebSocket with dynamic buffer allocation, Ethernet (IP101 PHY, ESP32, IPv6), and hardcoded URI.
Example:
```
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.ci.plain_tcp" build
```
### Server Certificate Verification
* Mutual Authentication: When `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y` is enabled, it's essential to provide valid certificates for both the server and client.
@@ -26,7 +76,7 @@ This example can be executed on any ESP32 board, the only required interface is
Please note: This example represents an extremely simplified approach to generating self-signed certificates/keys with a single common CA, devoid of CN checks, lacking password protection, and featuring hardcoded key sizes and types. It is intended solely for testing purposes.
In the outlined steps, we are omitting the configuration of the CN (Common Name) field due to the context of a testing environment. However, it's important to recognize that the CN field is a critical element of SSL/TLS certificates, significantly influencing the security and efficacy of HTTPS communications. This field facilitates the verification of a website's identity, enhancing trust and security in web interactions. In practical deployments beyond testing scenarios, ensuring the CN field is accurately set is paramount for maintaining the integrity and reliability of secure communications
### Generating a self signed Certificates with OpenSSL
### Generating a self signed Certificates with OpenSSL manually
* The example below outlines the process for creating new certificates for both the server and client using OpenSSL, a widely-used command line tool for implementing TLS protocol:
```
@@ -63,6 +113,46 @@ Please see the openssl man pages (man openssl) for more details.
It is **strongly recommended** to not reuse the example certificate in your application;
it is included only for demonstration.
### Certificate Generation Options
#### Option 1: Manual OpenSSL Commands
Follow the step-by-step process in the section above to understand certificate generation.
#### Option 2: Automated Script
**Note:** Test certificates are already available in the example. If you want to regenerate them or create new ones, use the provided `generate_certs.sh` script:
```bash
# Auto-detect local IP address (recommended for network testing)
./generate_certs.sh
# Specify custom hostname or IP address
./generate_certs.sh 192.168.1.100
# Use localhost (for local-only testing)
./generate_certs.sh localhost
```
This script automatically generates all required certificates in the correct directories and cleans up temporary files.
**Important:** The server certificate's Common Name (CN) must match the hostname or IP address that ESP32 clients use to connect. If not specified, the script attempts to auto-detect your local IP address. Certificate verification will fail if there's a mismatch between the CN and the actual connection address.
**CN Mismatch Handling:**
If you encounter certificate verification failures due to CN mismatch, you have two options:
1. **Recommended (Secure):** Regenerate certificates with the correct CN:
```bash
./generate_certs.sh <actual_hostname_or_ip>
```
2. **Testing Only (Less Secure):** Skip CN verification by enabling `CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y` in `idf.py menuconfig`.
⚠️ **WARNING:** This option disables an important security check and should **NEVER** be used in production environments. It makes your application vulnerable to man-in-the-middle attacks.
#### Option 3: Online Certificate Generators
- **mkcert**: `install mkcert` then `mkcert -install` and `mkcert localhost`
- **Let's Encrypt**: For production certificates (free, automated renewal)
- **Online generators**: Search for "self-signed certificate generator" online
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
@@ -73,7 +163,36 @@ idf.py -p PORT flash monitor
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
See the [ESP-IDF Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Testing with pytest
### Install Dependencies
Before running the pytest tests, you need to install the required Python packages:
```
pip install -r esp-protocols/ci/requirements.txt
```
### Run pytest
After installing the dependencies, you can run the pytest tests:
Run all tests in current directory:
```
pytest .
```
Run specific test file:
```
pytest pytest_websocket.py
```
To specify the target device or serial port, use:
```
pytest --target esp32 --port /dev/ttyUSB0
```
## Example Output
@@ -84,7 +203,7 @@ I (4472) tcpip_adapter: eth ip: 192.168.2.137, mask: 255.255.255.0, gw: 192.168.
I (4472) example_connect: Connected to Ethernet
I (4472) example_connect: IPv4 address: 192.168.2.137
I (4472) example_connect: IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed4:a92b
I (4482) WEBSOCKET: Connecting to ws://echo.websocket.events...
I (4482) WEBSOCKET: Connecting to wss://echo.websocket.org...
I (5012) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED
I (5492) WEBSOCKET: Sending hello 0000
I (6052) WEBSOCKET: WEBSOCKET_EVENT_DATA
@@ -105,9 +224,101 @@ W (9162) WEBSOCKET: Received=hello 0003
```
## Python Flask echo server
## WebSocket Test Server
By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://<your-ip>:5000` endpoint. To do this, install Flask-sock Python package
### Standalone Test Server
This example includes a standalone WebSocket test server (`websocket_server.py`) that can be used for testing your ESP32 WebSocket client:
#### Quick Start
**Plain WebSocket (No TLS):**
```bash
# Plain WebSocket server (no encryption)
python websocket_server.py
# Custom port
python websocket_server.py --port 9000
```
**Server-Only Authentication:**
```bash
# TLS WebSocket server (ESP32 verifies server)
python websocket_server.py --tls
# Custom port with TLS
python websocket_server.py --port 9000 --tls
```
**Mutual Authentication:**
```bash
# TLS with client certificate verification (both verify each other)
python websocket_server.py --tls --client-verify
# Custom port with mutual authentication
python websocket_server.py --port 9000 --tls --client-verify
```
#### Server Features
- **Echo functionality** - Automatically echoes back received messages
- **TLS support** - Secure WebSocket (WSS) connections
- **Client certificate verification** - Mutual authentication support
- **Binary and text messages** - Handles both data types
- **Auto IP detection** - Shows connection URL with your local IP
#### Verification Modes
**Plain WebSocket (No TLS):**
- No certificate verification on either side
- Use for local testing or trusted networks
- Configuration: `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=n` and `CONFIG_WS_OVER_TLS_SERVER_AUTH=n`
**Server-Only Authentication (`--tls` without `--client-verify`):**
- ESP32 verifies the server's certificate
- Server does NOT verify the ESP32's certificate
- Use when you trust the client but want to verify the server
- Configuration: `CONFIG_WS_OVER_TLS_SERVER_AUTH=y`
**Mutual Authentication (`--tls --client-verify`):**
- ESP32 verifies the server's certificate
- Server verifies the ESP32's certificate
- Use when both parties need to authenticate each other
- Configuration: `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y`
#### Usage Examples
**Plain WebSocket (No TLS or Client Verification):**
```bash
# Basic server (port 8080)
python websocket_server.py
# Custom port
python websocket_server.py --port 9000
```
**Server-Only Authentication (ESP32 verifies server, server doesn't verify ESP32):**
```bash
# TLS server without client verification
python websocket_server.py --tls
# Custom port with TLS
python websocket_server.py --port 9000 --tls
```
**Mutual Authentication (Both ESP32 and server verify each other's certificates):**
```bash
# TLS server with client certificate verification
python websocket_server.py --tls --client-verify
# Custom port with mutual authentication
python websocket_server.py --port 9000 --tls --client-verify
```
The server will display the connection URL (e.g., `wss://192.168.1.100:8080`) that you can use in your ESP32 configuration.
### Alternative: Python Flask Echo Server
By default, the `wss://echo.websocket.org` endpoint is used. You can also setup a Python Flask websocket echo server locally and try the `ws://<your-ip>:5000` endpoint. To do this, install Flask-sock Python package
```
pip install flask-sock
@@ -135,3 +346,36 @@ if __name__ == '__main__':
# gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app
app.run(host="0.0.0.0", debug=True)
```
## Troubleshooting
### Common Issues
**Connection failed:**
- Verify WiFi/Ethernet configuration in `idf.py menuconfig`
- Check if the WebSocket server is running and accessible
- Ensure the URI is correct (use `wss://` for TLS, `ws://` for plain TCP)
**TLS certificate errors:**
- **Certificate verification failed:** The most common cause is CN mismatch. Ensure the server certificate's Common Name matches the hostname/IP you're connecting to:
- Check your connection URI (e.g., if connecting to `wss://192.168.1.100:8080`, the certificate CN must be `192.168.1.100`)
- Regenerate certificates with the correct CN: `./generate_certs.sh <your_hostname_or_ip>`
- For testing only, you can bypass CN check with `CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y` (NOT recommended for production)
- Verify certificate files are properly formatted and accessible
- Ensure the CA certificate used to sign the server certificate is loaded on the ESP32
**Build errors:**
- Clean build: `idf.py fullclean`
- Check ESP-IDF version compatibility
- Verify all dependencies are installed
**Test failures:**
- Ensure the device is connected and accessible via the specified port
- Check that the target device matches the configuration (`--target esp32`)
- Verify pytest dependencies are installed correctly
### Getting Help
- Check the [ESP-IDF documentation](https://docs.espressif.com/projects/esp-idf/)
- Review the [WebSocket client component documentation](../../README.md)
- Report issues on the [ESP Protocols repository](https://github.com/espressif/esp-protocols)

View File

@@ -0,0 +1,85 @@
#!/bin/bash
# Generate CA, Server, and Client certificates automatically
#
# Usage: ./generate_certs.sh [SERVER_CN]
# SERVER_CN: The Common Name (hostname or IP) for the server certificate.
# This should match the hostname/IP that ESP32 clients will use to connect.
# If not provided, the script will attempt to auto-detect the local IP address.
# Falls back to "localhost" if auto-detection fails.
#
# IMPORTANT: The server certificate's Common Name (CN) must match the hostname or IP address
# that ESP32 clients use to connect. If there's a mismatch, certificate verification will fail
# during the TLS handshake. For production use, always specify the correct hostname/IP.
# Get server hostname/IP from command line argument or auto-detect
if [ -n "$1" ]; then
SERVER_CN="$1"
echo "Using provided SERVER_CN: $SERVER_CN"
else
# Attempt to auto-detect local IP address
# Try multiple methods for better compatibility across different systems
if command -v hostname >/dev/null 2>&1; then
# Try to get IP from hostname command (works on most Unix systems)
SERVER_CN=$(hostname -I 2>/dev/null | awk '{print $1}')
fi
# If the above failed, try ifconfig (macOS and some Linux systems)
if [ -z "$SERVER_CN" ] && command -v ifconfig >/dev/null 2>&1; then
SERVER_CN=$(ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -n1)
fi
# If still empty, try ip command (modern Linux systems)
if [ -z "$SERVER_CN" ] && command -v ip >/dev/null 2>&1; then
SERVER_CN=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127.0.0.1 | head -n1)
fi
# Fall back to localhost if auto-detection failed
if [ -z "$SERVER_CN" ]; then
SERVER_CN="localhost"
echo "Warning: Could not auto-detect IP address. Using 'localhost' as SERVER_CN."
echo " If your server runs on a different machine or IP, re-run with: ./generate_certs.sh <hostname_or_ip>"
else
echo "Auto-detected SERVER_CN: $SERVER_CN"
fi
fi
echo "Note: ESP32 clients must connect using: $SERVER_CN"
echo ""
# Create directories if they don't exist
mkdir -p main/certs/server
echo "Generating CA certificate..."
openssl genrsa -out main/certs/ca_key.pem 2048
openssl req -new -x509 -days 3650 -key main/certs/ca_key.pem -out main/certs/ca_cert.pem -subj "/C=US/ST=State/L=City/O=Organization/CN=TestCA"
echo "Generating Server certificate with CN=$SERVER_CN..."
openssl genrsa -out main/certs/server/server_key.pem 2048
openssl req -new -key main/certs/server/server_key.pem -out server_csr.pem -subj "/C=US/ST=State/L=City/O=Organization/CN=$SERVER_CN"
openssl x509 -req -days 3650 -in server_csr.pem -CA main/certs/ca_cert.pem -CAkey main/certs/ca_key.pem -CAcreateserial -out main/certs/server/server_cert.pem
echo "Generating Client certificate..."
openssl genrsa -out main/certs/client_key.pem 2048
openssl req -new -key main/certs/client_key.pem -out client_csr.pem -subj "/C=US/ST=State/L=City/O=Organization/CN=TestClient"
openssl x509 -req -days 3650 -in client_csr.pem -CA main/certs/ca_cert.pem -CAkey main/certs/ca_key.pem -CAcreateserial -out main/certs/client_cert.pem
# Clean up CSR files
rm server_csr.pem client_csr.pem
echo "Certificates generated successfully!"
echo ""
echo "Generated files:"
echo " - main/certs/ca_cert.pem (CA certificate)"
echo " - main/certs/ca_key.pem (CA private key)"
echo " - main/certs/client_cert.pem (Client certificate)"
echo " - main/certs/client_key.pem (Client private key)"
echo " - main/certs/server/server_cert.pem (Server certificate with CN=$SERVER_CN)"
echo " - main/certs/server/server_key.pem (Server private key)"
echo ""
echo "IMPORTANT: Configure ESP32 clients to connect to: $SERVER_CN"
echo " The server certificate is valid for this hostname/IP only."
echo ""
echo "Note: If the CN doesn't match your connection hostname/IP, you have two options:"
echo " 1. Regenerate certificates with correct CN: ./generate_certs.sh <correct_hostname_or_ip>"
echo " 2. Skip CN verification (TESTING ONLY): Enable CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y"
echo " WARNING: Option 2 reduces security and should NOT be used in production!"

View File

@@ -16,7 +16,7 @@ menu "Example Configuration"
config WEBSOCKET_URI
string "Websocket endpoint URI"
depends on WEBSOCKET_URI_FROM_STRING
default "wss://echo.websocket.events"
default "wss://echo.websocket.org"
help
URL of websocket endpoint this example connects to and sends echo

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@@ -77,6 +77,11 @@ static void websocket_event_handler(void *handler_args, esp_event_base_t base, i
case WEBSOCKET_EVENT_BEGIN:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_BEGIN");
break;
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
case WEBSOCKET_EVENT_HEADER_RECEIVED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_HEADER_RECEIVED: %.*s", data->data_len, data->data_ptr);
break;
#endif
case WEBSOCKET_EVENT_CONNECTED:
ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED");
break;
@@ -142,7 +147,11 @@ static void websocket_app_start(void)
#if CONFIG_WEBSOCKET_URI_FROM_STDIN
char line[128];
ESP_LOGI(TAG, "Please enter uri of websocket endpoint");
ESP_LOGI(TAG, "Please enter WebSocket endpoint URI");
ESP_LOGI(TAG, "Examples:");
ESP_LOGI(TAG, " ws://192.168.1.100:8080 (plain WebSocket)");
ESP_LOGI(TAG, " wss://192.168.1.100:8080 (secure WebSocket)");
ESP_LOGI(TAG, " wss://echo.websocket.org (public test server)");
get_string(line, sizeof(line));
websocket_cfg.uri = line;

View File

@@ -1,84 +1,12 @@
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import json
import random
import re
import socket
import ssl
import string
import sys
from threading import Event, Thread
from SimpleWebSocketServer import (SimpleSSLWebSocketServer,
SimpleWebSocketServer, WebSocket)
def get_my_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('8.8.8.8', 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
finally:
s.close()
return IP
class WebsocketTestEcho(WebSocket):
def handleMessage(self):
if isinstance(self.data, bytes):
print(f'\n Server received binary data: {self.data.hex()}\n')
self.sendMessage(self.data, binary=True)
else:
print(f'\n Server received: {self.data}\n')
self.sendMessage(self.data)
def handleConnected(self):
print('Connection from: {}'.format(self.address))
def handleClose(self):
print('{} closed the connection'.format(self.address))
# Simple Websocket server for testing purposes
class Websocket(object):
def send_data(self, data):
for nr, conn in self.server.connections.items():
conn.sendMessage(data)
def run(self):
if self.use_tls is True:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(certfile='main/certs/server/server_cert.pem', keyfile='main/certs/server/server_key.pem')
if self.client_verify is True:
ssl_context.load_verify_locations(cafile='main/certs/ca_cert.pem')
ssl_context.verify_mode = ssl.CERT_REQUIRED
ssl_context.check_hostname = False
self.server = SimpleSSLWebSocketServer('', self.port, WebsocketTestEcho, ssl_context=ssl_context)
else:
self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho)
while not self.exit_event.is_set():
self.server.serveonce()
def __init__(self, port, use_tls, verify):
self.port = port
self.use_tls = use_tls
self.client_verify = verify
self.exit_event = Event()
self.thread = Thread(target=self.run)
self.thread.start()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.exit_event.set()
self.thread.join(10)
if self.thread.is_alive():
print('Thread cannot be joined', 'orange')
from websocket_server import WebsocketServer, get_my_ip
def test_examples_protocol_websocket(dut):
@@ -228,13 +156,13 @@ def test_examples_protocol_websocket(dut):
if uri_from_stdin:
server_port = 8080
with Websocket(server_port, use_tls, client_verify) as ws:
with WebsocketServer(server_port, use_tls, client_verify) as ws:
if use_tls is True:
uri = 'wss://{}:{}'.format(get_my_ip(), server_port)
else:
uri = 'ws://{}:{}'.format(get_my_ip(), server_port)
print('DUT connecting to {}'.format(uri))
dut.expect('Please enter uri of websocket endpoint', timeout=30)
dut.expect("Please enter WebSocket endpoint URI", timeout=30)
dut.write(uri)
test_echo(dut)
test_recv_long_msg(dut, ws, 2000, 3)

View File

@@ -0,0 +1,148 @@
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import socket
import ssl
from threading import Event, Thread
from SimpleWebSocketServer import (SimpleSSLWebSocketServer,
SimpleWebSocketServer, WebSocket)
def get_my_ip():
"""Get the local IP address of this machine."""
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# doesn't even have to be reachable
s.connect(('8.8.8.8', 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
finally:
s.close()
return IP
class WebsocketTestEcho(WebSocket):
"""WebSocket handler that echoes back received messages."""
def handleMessage(self):
if isinstance(self.data, bytes):
print(f'\n Server received binary data: {self.data.hex()}\n')
self.sendMessage(self.data, binary=True)
else:
print(f'\n Server received: {self.data}\n')
self.sendMessage(self.data)
def handleConnected(self):
print('Connection from: {}'.format(self.address))
def handleClose(self):
print('{} closed the connection'.format(self.address))
class WebsocketServer:
"""WebSocket server for testing purposes."""
def __init__(self, port, use_tls=False, client_verify=False):
self.port = port
self.use_tls = use_tls
self.client_verify = client_verify
self.exit_event = Event()
self.thread = None
self.server = None
def send_data(self, data):
"""Send data to all connected clients."""
if self.server and hasattr(self.server, 'connections'):
for nr, conn in self.server.connections.items():
conn.sendMessage(data)
def run(self):
"""Run the WebSocket server."""
if self.use_tls:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ssl_context.load_cert_chain(
certfile='main/certs/server/server_cert.pem',
keyfile='main/certs/server/server_key.pem'
)
if self.client_verify:
ssl_context.load_verify_locations(cafile='main/certs/ca_cert.pem')
ssl_context.verify_mode = ssl.CERT_REQUIRED
ssl_context.check_hostname = False
self.server = SimpleSSLWebSocketServer('', self.port, WebsocketTestEcho, ssl_context=ssl_context)
else:
self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho)
print(f"WebSocket server starting on port {self.port} (TLS: {self.use_tls}, Client verify: {self.client_verify})")
while not self.exit_event.is_set():
self.server.serveonce()
def start(self):
"""Start the server in a separate thread."""
self.thread = Thread(target=self.run)
self.thread.start()
def stop(self):
"""Stop the server."""
self.exit_event.set()
if self.thread:
self.thread.join(10)
if self.thread.is_alive():
print('Thread cannot be joined', 'orange')
def __enter__(self):
"""Context manager entry."""
self.start()
return self
def __exit__(self, exc_type, exc_value, traceback):
"""Context manager exit."""
self.stop()
def create_websocket_server(port, use_tls=False, client_verify=False):
"""Factory function to create a WebSocket server."""
return WebsocketServer(port, use_tls, client_verify)
def run_forever(port=8080, use_tls=False, client_verify=False):
"""Run WebSocket server forever (for standalone use)."""
print(f"Starting WebSocket server on port {port}")
print(f"TLS enabled: {use_tls}")
print(f"Client verification: {client_verify}")
print(f"Server IP: {get_my_ip()}")
print(f"Connect with-->{'wss' if use_tls else 'ws'}://{get_my_ip()}:{port}")
print("Press Ctrl+C to stop the server")
server = WebsocketServer(port, use_tls, client_verify)
try:
server.start()
# Wait for the server thread to complete or be interrupted
server.thread.join()
except KeyboardInterrupt:
print("\nServer stopped by user")
except Exception as e:
print(f"Server error: {e}")
finally:
server.stop()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="WebSocket Test Server")
parser.add_argument("--port", type=int, default=8080, help="Server port (default: 8080)")
parser.add_argument("--tls", action="store_true", help="Enable TLS/WSS")
parser.add_argument("--client-verify", action="store_true", help="Require client certificate verification")
args = parser.parse_args()
# Usage examples:
# python3 websocket_server.py # Plain WebSocket on port 8080
# python3 websocket_server.py --tls # TLS WebSocket on port 8080
# python3 websocket_server.py --tls --client-verify # TLS with client cert verification
# python3 websocket_server.py --port 9000 --tls # Custom port with TLS
run_forever(port=args.port, use_tls=args.tls, client_verify=args.client_verify)

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -14,6 +14,7 @@
#include "freertos/FreeRTOS.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_idf_version.h"
#include <sys/socket.h>
#include "esp_transport_ws.h"
@@ -21,6 +22,13 @@
extern "C" {
#endif
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
// Features supported in 6.0.0
#define WS_TRANSPORT_HEADER_CALLBACK_SUPPORT 1
#else
#define WS_TRANSPORT_HEADER_CALLBACK_SUPPORT 0
#endif
typedef struct esp_websocket_client *esp_websocket_client_handle_t;
ESP_EVENT_DECLARE_BASE(WEBSOCKET_EVENTS); // declaration of the task events family
@@ -31,6 +39,9 @@ ESP_EVENT_DECLARE_BASE(WEBSOCKET_EVENTS); // declaration of the task eve
typedef enum {
WEBSOCKET_EVENT_ANY = -1,
WEBSOCKET_EVENT_ERROR = 0, /*!< This event occurs when there are any errors during execution */
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
WEBSOCKET_EVENT_HEADER_RECEIVED,/*!< This event occurs for each pre-upgrade HTTP header */
#endif
WEBSOCKET_EVENT_CONNECTED, /*!< Once the Websocket has been connected to the server, no data exchange has been performed */
WEBSOCKET_EVENT_DISCONNECTED, /*!< The connection has been disconnected */
WEBSOCKET_EVENT_DATA, /*!< When receiving data from the server, possibly multiple portions of the packet */

View File

@@ -1,8 +1,15 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS ../../esp_websocket_client
"$ENV{IDF_PATH}/tools/unit-test-app/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0")
set(test_component_dir $ENV{IDF_PATH}/tools/test_apps/components)
else()
set(test_component_dir $ENV{IDF_PATH}/tools/unit-test-app/components)
endif()
set(EXTRA_COMPONENT_DIRS ../../esp_websocket_client
${test_component_dir})
project(websocket_unit_test)

View File

@@ -52,14 +52,14 @@ I (18208) lws-client: LWS minimal ws client echo
216516: __lws_lc_tag: ++ [vh|0|default||-1] (1)
I (18248) lws-client: connect_cb: connecting
210112: __lws_lc_tag: ++ [wsicli|0|WS/h1/default/echo.websocket.events] (1)
204800: [wsicli|0|WS/h1/default/echo.websocket.events]: lws_client_connect_3_connect: trying 13.248.241.119
210112: __lws_lc_tag: ++ [wsicli|0|WS/h1/default/echo.websocket.org] (1)
204800: [wsicli|0|WS/h1/default/echo.websocket.org]: lws_client_connect_3_connect: trying 13.248.241.119
180776: lws_ssl_client_bio_create: allowing selfsigned
I (19998) wifi:<ba-add>idx:0 (ifx:0, b4:89:01:63:9d:08), tid:0, ssn:321, winSize:64
I (20768) lws-client: WEBSOCKET_EVENT_CONNECTED
I (20768) lws-client: Sending hello 0000
I (20778) lws-client: WEBSOCKET_EVENT_DATA
W (20778) lws-client: Received=echo.websocket.events sponsored by Lob.com
W (20778) lws-client: Received=echo.websocket.org sponsored by Lob.com
I (20968) lws-client: WEBSOCKET_EVENT_DATA

View File

@@ -14,7 +14,7 @@ menu "Example Configuration"
config WEBSOCKET_URI
string "Websocket endpoint URI"
default "echo.websocket.events"
default "echo.websocket.org"
help
URL or IP of websocket endpoint this example connects to and sends echo
config WEBSOCKET_PORT

View File

@@ -14,7 +14,7 @@
*/
extern int __real_mbedtls_ssl_handshake_step(mbedtls_ssl_context *ssl);
int __wrap_mbedtls_ssl_handshake_step( mbedtls_ssl_context *ssl )
int __wrap_mbedtls_ssl_handshake_step(mbedtls_ssl_context *ssl)
{
int ret = 0;

View File

@@ -1,3 +1,2 @@
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
CONFIG_MBEDTLS_SSL_COOKIE_C=y
CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=8192

View File

@@ -1,4 +1,3 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
CONFIG_MBEDTLS_SSL_COOKIE_C=y
CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=8192

View File

@@ -12,6 +12,7 @@ CONFIG_EXAMPLE_CONNECT_ETHERNET=y
CONFIG_EXAMPLE_CONNECT_WIFI=n
CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y
CONFIG_EXAMPLE_ETH_PHY_IP101=y
CONFIG_EXAMPLE_ETH_PHY_GENERIC=y
CONFIG_EXAMPLE_ETH_MDC_GPIO=23
CONFIG_EXAMPLE_ETH_MDIO_GPIO=18
CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5

View File

@@ -1795,8 +1795,8 @@ static bool _mdns_create_answer_from_service(mdns_tx_packet_t *packet, mdns_serv
// According to RFC6763-section12.1, for DNS-SD, SRV, TXT and all address records
// should be included in additional records.
if (!_mdns_alloc_answer(&packet->answers, MDNS_TYPE_PTR, service, NULL, false, false) ||
!_mdns_alloc_answer(is_delegated ? &packet->additional : &packet->answers, MDNS_TYPE_SRV, service, NULL, send_flush, false) ||
!_mdns_alloc_answer(is_delegated ? &packet->additional : &packet->answers, MDNS_TYPE_TXT, service, NULL, send_flush, false) ||
!_mdns_alloc_answer(&packet->additional, MDNS_TYPE_SRV, service, NULL, send_flush, false) ||
!_mdns_alloc_answer(&packet->additional, MDNS_TYPE_TXT, service, NULL, send_flush, false) ||
!_mdns_alloc_answer((shared || is_delegated) ? &packet->additional : &packet->answers, MDNS_TYPE_A, service, host, send_flush,
false) ||
!_mdns_alloc_answer((shared || is_delegated) ? &packet->additional : &packet->answers, MDNS_TYPE_AAAA, service, host,
@@ -2656,13 +2656,18 @@ static mdns_txt_linked_item_t *_mdns_allocate_txt(size_t num_items, mdns_txt_ite
mdns_mem_free(new_item);
break;
}
new_item->value = mdns_mem_strdup(txt[i].value);
if (!new_item->value) {
mdns_mem_free((char *)new_item->key);
mdns_mem_free(new_item);
break;
if (txt[i].value) {
new_item->value = mdns_mem_strdup(txt[i].value);
if (!new_item->value) {
mdns_mem_free((char *)new_item->key);
mdns_mem_free(new_item);
break;
}
new_item->value_len = strlen(new_item->value);
} else {
new_item->value = NULL;
new_item->value_len = 0;
}
new_item->value_len = strlen(new_item->value);
new_item->next = new_txt;
new_txt = new_item;
}

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
import re
@@ -92,10 +92,58 @@ class DnsPythonWrapper:
if expect is None:
expect = name
if expected:
assert any(expect in answer for answer in answers), f"Expected record '{expect}' not found in answer section"
assert any(expect in answer for answer in answers), f"Expected record '{expect}' not in answer section"
else:
assert not any(expect in answer for answer in answers), f"Unexpected record '{expect}' found in answer section"
def parse_section(self, response, section: str, rdtype_text: str):
"""Parse a specific response section (answer, authority, additional) for given rdtype.
Returns list of textual records for that rdtype.
"""
out = []
if not response:
return out
rrsets = []
if section == 'answer':
rrsets = response.answer
elif section == 'authority':
rrsets = response.authority
elif section == 'additional':
rrsets = response.additional
else:
raise ValueError('invalid section')
for rr in rrsets:
if dns.rdatatype.to_text(rr.rdtype) != rdtype_text:
continue
for item in rr.items:
full = (
f'{rr.name} {rr.ttl} '
f'{dns.rdataclass.to_text(rr.rdclass)} '
f'{dns.rdatatype.to_text(rr.rdtype)} '
f'{item.to_text()}'
)
out.append(full)
return out
def check_additional(self, response, rdtype_text: str, owner_contains: str, expected: bool = True, expect_substr: str | None = None):
"""Check Additional section for an RR of type rdtype_text whose owner includes owner_contains.
If expect_substr is provided, also require it to appear in the textual RR.
"""
records = self.parse_section(response, 'additional', rdtype_text)
logger.info(f'additional({rdtype_text}): {records}')
def _matches(line: str) -> bool:
in_owner = owner_contains in line
has_val = (expect_substr in line) if expect_substr else True
return in_owner and has_val
found = any(_matches(r) for r in records)
if expected:
assert found, f"Expected {rdtype_text} for {owner_contains} in Additional not found"
else:
assert not found, f"Unexpected {rdtype_text} for {owner_contains} found in Additional"
if __name__ == '__main__':
if len(sys.argv) < 3:

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import logging
@@ -65,6 +65,17 @@ def test_add_service(mdns_console, dig_app):
dig_app.check_record('_http._tcp.local', query_type='PTR', expected=True)
def test_ptr_additional_records_for_service(dig_app):
# Query PTR for the service type and ensure SRV/TXT are in Additional (RFC 6763 §12.1)
resp = dig_app.run_query('_http._tcp.local', query_type='PTR')
# Answer section should have at least one PTR to the instance
answers = dig_app.parse_answer_section(resp, 'PTR')
assert any('test_service._http._tcp.local' in a for a in answers)
# Additional section should include SRV and TXT for the same instance
dig_app.check_additional(resp, 'SRV', 'test_service._http._tcp.local', expected=True)
dig_app.check_additional(resp, 'TXT', 'test_service._http._tcp.local', expected=True)
def test_remove_service(mdns_console, dig_app):
mdns_console.send_input('mdns_service_remove _http _tcp')
mdns_console.send_input('mdns_service_lookup _http _tcp')

View File

@@ -1,7 +1,9 @@
#INSTR=off
TEST_NAME=test
FUZZ=afl-fuzz
COMPONENTS_DIR=$(IDF_PATH)/components
COMPILER_ICLUDE_DIR=$(shell echo `which xtensa-esp32-elf-gcc | xargs dirname | xargs dirname`/xtensa-esp32-elf)
# Use ESP32 toolchain include path if available, otherwise fall back to system includes for host-based compilation
COMPILER_INCLUDE_DIR=$(shell if command -v xtensa-esp32-elf-gcc >/dev/null 2>&1; then echo `which xtensa-esp32-elf-gcc | xargs dirname | xargs dirname`/xtensa-esp32-elf; else echo /usr; fi)
CFLAGS=-g -Wno-unused-value -Wno-missing-declarations -Wno-pointer-bool-conversion -Wno-macro-redefined -Wno-int-to-void-pointer-cast -DHOOK_MALLOC_FAILED -DESP_EVENT_H_ -D__ESP_LOG_H__ \
-I. -I../.. -I../../include -I../../private_include -I ./build/config \
@@ -34,7 +36,8 @@ CFLAGS=-g -Wno-unused-value -Wno-missing-declarations -Wno-pointer-bool-conversi
-I$(COMPONENTS_DIR)/soc/src/esp32/include \
-I$(COMPONENTS_DIR)/xtensa/include \
-I$(COMPONENTS_DIR)/xtensa/esp32/include \
-I$(COMPILER_ICLUDE_DIR)/include
-I$(COMPONENTS_DIR)/esp_hw_support/etm/include \
-I$(COMPILER_INCLUDE_DIR)/include
MDNS_C_DEPENDENCY_INJECTION=-include mdns_di.h
@@ -76,7 +79,18 @@ $(TEST_NAME): $(OBJECTS)
@$(LD) $(OBJECTS) -o $@ $(LDLIBS)
fuzz: $(TEST_NAME)
@$(FUZZ) -i "in" -o "out" -- ./$(TEST_NAME)
# timeout returns 124 if time limit is reached, original return code otherwise
# pass only if: fuzzing was running smoothly until timeout AND no crash found
@timeout 10m $(FUZZ) -i "in" -o "out" -- ./$(TEST_NAME) || \
if [ $$? -eq 124 ]; then \
if [ -n "$$(find out/default/crashes -type f 2>/dev/null)" ]; then \
echo "Crashes found!"; \
tar -czf out/default/crashes.tar.gz -C out/default crashes; \
exit 1; \
fi \
else \
exit 1; \
fi
clean:
@rm -rf *.o *.SYM $(TEST_NAME) out

View File

@@ -55,8 +55,7 @@
#define pdMS_TO_TICKS(a) a
#define xSemaphoreTake(s,d) true
#define xTaskDelete(a)
#define vTaskDelete(a) free(a)
#define vTaskDelete(a) free(NULL)
#define xSemaphoreGive(s)
#define xQueueCreateMutex(s)
#define _mdns_pcb_init(a,b) true
@@ -66,7 +65,7 @@
#define vSemaphoreDelete(s) free(s)
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U
#define xTaskCreatePinnedToCore(a,b,c,d,e,f,g) *(f) = malloc(1)
#define xTaskCreateStaticPinnedToCore(a,b,c,d,e,f,g,h) true
#define xTaskCreateStaticPinnedToCore(a,b,c,d,e,f,g,h) ((void*)1)
#define vTaskDelay(m) usleep((m)*0)
#define esp_random() (rand()%UINT32_MAX)
@@ -139,4 +138,8 @@ TaskHandle_t xTaskGetCurrentTaskHandle(void);
void xTaskNotifyGive(TaskHandle_t task);
BaseType_t xTaskNotifyWait(uint32_t bits_entry_clear, uint32_t bits_exit_clear, uint32_t *value, TickType_t wait_time);
static inline void xTaskGetStaticBuffers(void *pvTaskBuffer, void *pvStackBuffer, void *pvTaskTCB)
{
}
#endif //_ESP32_COMPAT_H_

View File

@@ -78,30 +78,20 @@ static int mdns_test_service_txt_set(const char *service, const char *proto, ui
static int mdns_test_sub_service_add(const char *sub_name, const char *service_name, const char *proto, uint32_t port)
{
if (mdns_service_add(NULL, service_name, proto, port, NULL, 0)) {
// This is expected failure as the service thread is not running
return ESP_FAIL;
}
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
if (mdns_test_mdns_get_service_item(service_name, proto) == NULL) {
return ESP_FAIL;
}
int ret = mdns_service_subtype_add_for_host(NULL, service_name, proto, NULL, sub_name);
a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
return ret;
return mdns_service_subtype_add_for_host(NULL, service_name, proto, NULL, sub_name);
}
static int mdns_test_service_add(const char *service_name, const char *proto, uint32_t port)
{
if (mdns_service_add(NULL, service_name, proto, port, NULL, 0)) {
// This is expected failure as the service thread is not running
return ESP_FAIL;
}
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
if (mdns_test_mdns_get_service_item(service_name, proto) == NULL) {
return ESP_FAIL;
@@ -266,9 +256,6 @@ int main(int argc, char **argv)
}
#ifndef MDNS_NO_SERVICES
mdns_service_remove_all();
mdns_action_t *a = NULL;
GetLastItem(&a);
mdns_test_execute_action(a);
#endif
ForceTaskDelete();
mdns_free();

View File

@@ -1,7 +1,15 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS ../.. "$ENV{IDF_PATH}/tools/unit-test-app/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0")
set(test_component_dir $ENV{IDF_PATH}/tools/test_apps/components)
else()
set(test_component_dir $ENV{IDF_PATH}/tools/unit-test-app/components)
endif()
set(EXTRA_COMPONENT_DIRS ../..
${test_component_dir})
project(mdns_test)

View File

@@ -3,6 +3,6 @@ commitizen:
bump_message: 'bump(mosq): $current_version -> $new_version'
pre_bump_hooks: python ../../ci/changelog.py mosquitto
tag_format: mosq-v$version
version: 2.0.20~3
version: 2.0.20~4
version_files:
- idf_component.yml

View File

@@ -1,5 +1,17 @@
# Changelog
## [2.0.20~4](https://github.com/espressif/esp-protocols/commits/mosq-v2.0.20_4)
### Features
- Update brokerless example to work with esp-peer ([76e45f72](https://github.com/espressif/esp-protocols/commit/76e45f72))
### Bug Fixes
- drop newlib from PRIV_REQUIRES ([6318022c](https://github.com/espressif/esp-protocols/commit/6318022c))
- Make esp-peer build optional ([03df9ae9](https://github.com/espressif/esp-protocols/commit/03df9ae9))
- Fix esp_webRTC deprecation with new FreeRTOS ([78ae2559](https://github.com/espressif/esp-protocols/commit/78ae2559))
## [2.0.20~3](https://github.com/espressif/esp-protocols/commits/mosq-v2.0.20_3)

View File

@@ -82,7 +82,7 @@ idf_component_register(SRCS ${m_srcs}
${m_incl_dir} ${m_lib_dir} ${m_deps_dir}
INCLUDE_DIRS ${m_incl_dir} port/include
REQUIRES esp-tls
PRIV_REQUIRES newlib sock_utils esp_timer)
PRIV_REQUIRES sock_utils esp_timer)
target_compile_definitions(${COMPONENT_LIB} PRIVATE "WITH_BROKER")
if (CONFIG_MOSQ_ENABLE_SYS)

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "example_broker.c"
PRIV_REQUIRES newlib nvs_flash esp_netif esp_event mqtt
PRIV_REQUIRES nvs_flash esp_netif esp_event mqtt
EMBED_TXTFILES servercert.pem serverkey.pem cacert.pem)

View File

@@ -16,7 +16,7 @@ index d248d59..1117926 100644
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
vTaskGetSnapshot(cur_task, &snap_shot);
- snap_shot.pxTopOfStack = pxTaskGetStackStart(cur_task);;
+#if (ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 5, 0))
+#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0))
+ snap_shot.pxTopOfStack = xTaskGetStackStart(cur_task);
+#else
+ snap_shot.pxTopOfStack = pxTaskGetStackStart(cur_task);

View File

@@ -1,4 +1,4 @@
version: "2.0.20~3"
version: "2.0.20~4"
url: https://github.com/espressif/esp-protocols/tree/master/components/mosquitto
description: The component provides a simple ESP32 port of mosquitto broker
dependencies:

View File

@@ -20,4 +20,4 @@
#undef isspace
#define isspace(__c) (__ctype_lookup((int)__c)&_S)
#define VERSION "v2.0.20~3"
#define VERSION "v2.0.20~4"